mirror of
https://github.com/JHDev2006/Super-Mario-Bros.-Remastered-Public.git
synced 2025-10-22 15:38:14 +00:00
added the game
This commit is contained in:
14
Scripts/Parts/BetterAnimatedSprite.gd
Normal file
14
Scripts/Parts/BetterAnimatedSprite.gd
Normal file
@@ -0,0 +1,14 @@
|
||||
class_name BetterAnimatedSprite2D
|
||||
extends AnimatedSprite2D
|
||||
|
||||
@export var do_offset := true
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if do_offset:
|
||||
on_frame_changed()
|
||||
|
||||
func on_frame_changed() -> void:
|
||||
if sprite_frames == null: return
|
||||
var texture = sprite_frames.get_frame_texture(animation, frame)
|
||||
if texture != null:
|
||||
position.y = -(texture.get_height() / 2.0)
|
1
Scripts/Parts/BetterAnimatedSprite.gd.uid
Executable file
1
Scripts/Parts/BetterAnimatedSprite.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://caq1qiwmy0mox
|
21
Scripts/Parts/BlockAnimations.gd
Normal file
21
Scripts/Parts/BlockAnimations.gd
Normal file
@@ -0,0 +1,21 @@
|
||||
extends Node2D
|
||||
|
||||
func _ready() -> void:
|
||||
set_process(false)
|
||||
if owner is Block:
|
||||
$Joint.remote_path = $Joint.get_path_to(owner.visuals)
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
owner.visuals.z_index = z_index
|
||||
|
||||
func bounce_block() -> void:
|
||||
set_process(true)
|
||||
owner.visuals.show()
|
||||
owner.visuals.z_index = 3
|
||||
owner.get_parent().move_child(owner, -1)
|
||||
owner.bouncing = true
|
||||
$Animations.play("BlockHit")
|
||||
await $Animations.animation_finished
|
||||
owner.visuals.z_index = 0
|
||||
owner.bouncing = false
|
||||
set_process(false)
|
1
Scripts/Parts/BlockAnimations.gd.uid
Executable file
1
Scripts/Parts/BlockAnimations.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://bna1qn33m01d5
|
19
Scripts/Parts/BlockDestructionParticles.gd
Normal file
19
Scripts/Parts/BlockDestructionParticles.gd
Normal file
@@ -0,0 +1,19 @@
|
||||
extends Node2D
|
||||
|
||||
@onready var particles := [$TL, $TR, $BL, $BR]
|
||||
|
||||
var particle_directions := [Vector2(-1, -3), Vector2(1, -3), Vector2(-1, -1), Vector2(1, -1)]
|
||||
var particle_velocities := [Vector2.ZERO, Vector2.ZERO, Vector2.ZERO, Vector2.ZERO]
|
||||
|
||||
var particle_rotations := [0.0, 0.0, 0.0, 0.0]
|
||||
|
||||
func _ready() -> void:
|
||||
for i in 4:
|
||||
particle_velocities[i] = 70 * particle_directions[i]
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
for i in 4:
|
||||
particles[i].global_position += particle_velocities[i] * delta
|
||||
particle_velocities[i] += Vector2(0, 15 / delta) * delta
|
||||
particle_rotations[i] += (1080 * particle_directions[i].x) * delta
|
||||
particles[i].global_rotation_degrees = snapped(particle_rotations[i], 90)
|
1
Scripts/Parts/BlockDestructionParticles.gd.uid
Executable file
1
Scripts/Parts/BlockDestructionParticles.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://f50767fxajwh
|
14
Scripts/Parts/BubbleParticle.gd
Normal file
14
Scripts/Parts/BubbleParticle.gd
Normal file
@@ -0,0 +1,14 @@
|
||||
extends Node2D
|
||||
|
||||
var can_kill := false
|
||||
|
||||
func _ready() -> void:
|
||||
await get_tree().create_timer(0.5, false).timeout
|
||||
can_kill = true
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
global_position.y -= 32 * delta
|
||||
if global_position.y < -176:
|
||||
queue_free()
|
||||
elif $WaterDetection.get_overlapping_bodies().is_empty() and can_kill:
|
||||
queue_free()
|
1
Scripts/Parts/BubbleParticle.gd.uid
Executable file
1
Scripts/Parts/BubbleParticle.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://denfttv4kn7f6
|
42
Scripts/Parts/BulletBillCannon.gd
Normal file
42
Scripts/Parts/BulletBillCannon.gd
Normal file
@@ -0,0 +1,42 @@
|
||||
extends Node2D
|
||||
|
||||
@export var item: PackedScene = preload("res://Scenes/Prefabs/Entities/Enemies/BulletBill.tscn")
|
||||
|
||||
var timer := 15
|
||||
|
||||
const MAX_TIME := 15
|
||||
const HARD_TIME := 7
|
||||
|
||||
func _physics_process(_delta: float) -> void:
|
||||
if randi_range(0, 8) == 8:
|
||||
timer -= 1
|
||||
if timer <= 0:
|
||||
if Global.second_quest:
|
||||
timer = HARD_TIME
|
||||
else:
|
||||
timer = MAX_TIME
|
||||
fire()
|
||||
|
||||
func fire() -> void:
|
||||
if BulletBill.amount >= 3 or $PlayerDetect.get_overlapping_areas().any(func(area: Area2D): return area.owner is Player) or is_inside_tree() == false:
|
||||
return
|
||||
var player: Player = get_tree().get_first_node_in_group("Players")
|
||||
var direction = sign(player.global_position.x - global_position.x)
|
||||
$BlockCheck.scale.x = direction
|
||||
$BlockCheck/RayCast2D.force_raycast_update()
|
||||
if $BlockCheck/RayCast2D.is_colliding():
|
||||
return
|
||||
var node = item.instantiate()
|
||||
node.global_position = global_position + Vector2(0, 8)
|
||||
node.set("direction", direction)
|
||||
if node is CharacterBody2D:
|
||||
node.position.x += 8 * direction
|
||||
node.set("velocity", Vector2(100 * direction, 0))
|
||||
if node is not BulletBill:
|
||||
AudioManager.play_sfx("cannon", global_position)
|
||||
else:
|
||||
node.cannon = true
|
||||
add_sibling(node)
|
||||
|
||||
func flag_die() -> void:
|
||||
queue_free()
|
1
Scripts/Parts/BulletBillCannon.gd.uid
Executable file
1
Scripts/Parts/BulletBillCannon.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://0btfo4kjnnrg
|
148
Scripts/Parts/CameraHandler.gd
Normal file
148
Scripts/Parts/CameraHandler.gd
Normal file
@@ -0,0 +1,148 @@
|
||||
class_name CameraHandler
|
||||
extends Node2D
|
||||
|
||||
@onready var last_position = global_position
|
||||
@export var camera: Camera2D = null
|
||||
|
||||
@export var camera_center_joint: Node2D = null
|
||||
|
||||
var camera_position := Vector2.ZERO
|
||||
var camera_offset := Vector2(8, 0)
|
||||
|
||||
var camera_right_limit := 9999999
|
||||
|
||||
var player_offset := 0.0
|
||||
|
||||
var can_scroll_left := true
|
||||
var can_scroll_right := true
|
||||
|
||||
static var cam_locked := false
|
||||
|
||||
var scrolling := false
|
||||
var cam_direction := 1
|
||||
|
||||
func _exit_tree() -> void:
|
||||
cam_locked = false
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
handle_camera(delta)
|
||||
last_position = global_position
|
||||
|
||||
func handle_camera(delta: float) -> void:
|
||||
|
||||
can_scroll_left = camera_position.x + camera_offset.x > -255
|
||||
can_scroll_right = camera_position.x + camera_offset.x < camera_right_limit - 1
|
||||
|
||||
if ["Pipe", "Climb", "FlagPole"].has(owner.state_machine.state.name):
|
||||
handle_vertical_scrolling(delta)
|
||||
do_limits()
|
||||
camera.global_position = camera_position + camera_offset
|
||||
return
|
||||
|
||||
if not cam_locked:
|
||||
handle_horizontal_scrolling(delta)
|
||||
handle_vertical_scrolling(delta)
|
||||
handle_offsets(delta)
|
||||
|
||||
do_limits()
|
||||
camera.global_position = camera_position + camera_offset
|
||||
update_camera_barriers()
|
||||
|
||||
func update_camera_barriers() -> void:
|
||||
if get_viewport() != null:
|
||||
camera_center_joint.global_position = get_viewport().get_camera_2d().get_screen_center_position()
|
||||
camera_center_joint.get_node("LeftWall").position.x = -(get_viewport_rect().size.x / 2)
|
||||
camera_center_joint.get_node("RightWall").position.x = (get_viewport_rect().size.x / 2)
|
||||
|
||||
func handle_horizontal_scrolling(delta: float) -> void:
|
||||
scrolling = false
|
||||
var true_velocity = (global_position - last_position) / delta
|
||||
var true_vel_dir = sign(true_velocity.x)
|
||||
if (owner.is_on_wall() and owner.direction == -owner.get_wall_normal().x):
|
||||
true_vel_dir = 0
|
||||
true_velocity.x = 0
|
||||
## RIGHT MOVEMENT
|
||||
if true_vel_dir == 1 and can_scroll_right:
|
||||
cam_direction = 1
|
||||
if global_position.x >= camera_position.x:
|
||||
var offset = 0
|
||||
scrolling = true
|
||||
if camera_position.x <= global_position.x - 4:
|
||||
offset = camera_position.x - global_position.x + abs(true_velocity.x * delta)
|
||||
camera_position.x = global_position.x + offset
|
||||
elif global_position.x >= camera_position.x - get_viewport_rect().size.x / 8:
|
||||
if true_velocity.x > 75:
|
||||
camera_position.x += true_velocity.x * delta / 2
|
||||
else:
|
||||
camera_position.x += true_velocity.x * delta
|
||||
|
||||
## LEFT MOVEMENT
|
||||
elif true_vel_dir == -1 and can_scroll_left and Global.current_level.can_backscroll:
|
||||
cam_direction = -1
|
||||
if global_position.x <= camera_position.x:
|
||||
scrolling = true
|
||||
var offset = 0
|
||||
if camera_position.x >= global_position.x + 4:
|
||||
offset = camera_position.x - global_position.x - abs(true_velocity.x * delta)
|
||||
camera_position.x = global_position.x + offset
|
||||
elif global_position.x <= camera_position.x + get_viewport_rect().size.x / 4:
|
||||
if true_velocity.x < -75:
|
||||
camera_position.x += true_velocity.x * delta / 2
|
||||
else:
|
||||
camera_position.x += true_velocity.x * delta
|
||||
|
||||
|
||||
func handle_vertical_scrolling(_delta: float) -> void:
|
||||
## VERTICAL MOVEMENT
|
||||
if global_position.y < camera_position.y and owner.is_on_floor():
|
||||
camera_position.y = move_toward(camera_position.y, global_position.y, 3)
|
||||
elif global_position.y < camera_position.y - 64:
|
||||
camera_position.y = global_position.y + 64
|
||||
elif global_position.y > camera_position.y + 32:
|
||||
camera_position.y = global_position.y - 32
|
||||
|
||||
func tween_ahead() -> void:
|
||||
if scrolling == false: return
|
||||
await get_tree().create_timer(0.25).timeout
|
||||
var tween = create_tween()
|
||||
tween.tween_property(self, "camera_position:x", camera_position.x + (32 * cam_direction), 0.25)
|
||||
|
||||
func recenter_camera() -> void:
|
||||
camera_position = global_position
|
||||
last_position = camera_position
|
||||
camera_position += camera_offset
|
||||
do_limits()
|
||||
camera.global_position = camera_position
|
||||
|
||||
func handle_offsets(delta: float) -> void:
|
||||
var true_velocity = (global_position - last_position) / delta
|
||||
var true_vel_dir = sign(true_velocity.x)
|
||||
if owner.velocity.x == 0 or (owner.is_on_wall() and owner.direction == -owner.get_wall_normal().x):
|
||||
true_vel_dir = 0
|
||||
true_velocity.x = 0
|
||||
if Global.current_level.can_backscroll:
|
||||
if true_vel_dir != 0 and abs(true_velocity.x) > 80:
|
||||
if abs(camera_position.x - global_position.x) <= 64:
|
||||
camera_offset.x = move_toward(camera_offset.x, 8 * true_vel_dir, abs(true_velocity.x) / 200)
|
||||
else:
|
||||
camera_offset.x = 8
|
||||
|
||||
func do_limits() -> void:
|
||||
camera_right_limit = clamp(Player.camera_right_limit, -256 + (get_viewport().get_visible_rect().size.x), INF)
|
||||
camera_position.x = clamp(camera_position.x, point_to_camera_limit(-256 - camera_offset.x, -1), point_to_camera_limit(camera_right_limit - camera_offset.x, 1))
|
||||
camera_position.y = clamp(camera_position.y, point_to_camera_limit_y(Global.current_level.vertical_height, -1), point_to_camera_limit_y(32, 1))
|
||||
var wall_enabled := true
|
||||
if is_instance_valid(Global.level_editor):
|
||||
if Global.level_editor.playing_level == false:
|
||||
wall_enabled = false
|
||||
$"../CameraCenterJoint/LeftWall".set_collision_layer_value(1, wall_enabled)
|
||||
var level_exit = false
|
||||
if owner.state_machine != null:
|
||||
level_exit = owner.state_machine.state.name == "LevelExit"
|
||||
$"../CameraCenterJoint/RightWall".set_collision_layer_value(1, wall_enabled and level_exit == false)
|
||||
|
||||
func point_to_camera_limit(point := 0, point_dir := -1) -> float:
|
||||
return point + ((get_viewport_rect().size.x / 2.0) * -point_dir)
|
||||
|
||||
func point_to_camera_limit_y(point := 0, point_dir := -1) -> float:
|
||||
return point + ((get_viewport_rect().size.y / 2.0) * -point_dir)
|
1
Scripts/Parts/CameraHandler.gd.uid
Normal file
1
Scripts/Parts/CameraHandler.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dgmaoklmc0wfa
|
22
Scripts/Parts/CameraRightLimit.gd
Normal file
22
Scripts/Parts/CameraRightLimit.gd
Normal file
@@ -0,0 +1,22 @@
|
||||
class_name CameraRightLimit
|
||||
extends Node2D
|
||||
|
||||
@export var reset_on_delete := true
|
||||
@export var lock_camera := false
|
||||
|
||||
func _enter_tree() -> void:
|
||||
Player.camera_right_limit = int(global_position.x)
|
||||
|
||||
func _exit_tree() -> void:
|
||||
if reset_on_delete:
|
||||
Player.camera_right_limit = int(99999999)
|
||||
|
||||
func return_camera_to_normal() -> void:
|
||||
for i in get_tree().get_nodes_in_group("Players"):
|
||||
CameraHandler.cam_locked = false
|
||||
i.reset_camera_to_center()
|
||||
|
||||
|
||||
func on_screen_entered() -> void:
|
||||
if lock_camera:
|
||||
CameraHandler.cam_locked = true
|
1
Scripts/Parts/CameraRightLimit.gd.uid
Executable file
1
Scripts/Parts/CameraRightLimit.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://bdq0373j5n5o0
|
107
Scripts/Parts/CastleBridge.gd
Normal file
107
Scripts/Parts/CastleBridge.gd
Normal file
@@ -0,0 +1,107 @@
|
||||
extends Node2D
|
||||
|
||||
signal victory_begin
|
||||
const CASTLE_COMPLETE = preload("res://Assets/Audio/BGM/CastleComplete.mp3")
|
||||
|
||||
var cam_move := false
|
||||
|
||||
@export_range(8, 20, 1) var length := 13
|
||||
@export var end_timer := false
|
||||
@export var do_tally := true
|
||||
|
||||
signal axe_touched
|
||||
|
||||
var bowser_present := true
|
||||
|
||||
func _ready() -> void:
|
||||
await get_tree().physics_frame
|
||||
$Axe/CameraRightLimit._enter_tree()
|
||||
|
||||
func on_area_entered(area: Area2D) -> void:
|
||||
if area.owner is Player:
|
||||
destroy_bridge(area.owner)
|
||||
|
||||
func destroy_bridge(player: Player) -> void:
|
||||
Global.can_pause = false
|
||||
for i in get_tree().get_nodes_in_group("Enemies"):
|
||||
if i is BowserFlame:
|
||||
i.queue_free()
|
||||
elif i is Hammer:
|
||||
i.queue_free()
|
||||
if (end_timer and Global.current_game_mode == Global.GameMode.MARATHON) or Global.current_game_mode == Global.GameMode.MARATHON_PRACTICE:
|
||||
SpeedrunHandler.run_finished()
|
||||
|
||||
if end_timer:
|
||||
if Global.world_num > 8:
|
||||
Global.unlock_achievement(Global.AchievementID.SMBLL_WORLD9)
|
||||
match Global.current_campaign:
|
||||
"SMB1": Global.unlock_achievement(Global.AchievementID.SMB1_CLEAR)
|
||||
"SMBLL": Global.unlock_achievement(Global.AchievementID.SMBLL_CLEAR)
|
||||
"SMBS": Global.unlock_achievement(Global.AchievementID.SMBS_CLEAR)
|
||||
"SMBANN": Global.unlock_achievement(Global.AchievementID.SMBANN_CLEAR)
|
||||
|
||||
bowser_present = get_tree().get_first_node_in_group("Bowser") != null
|
||||
player.velocity = Vector2.ZERO
|
||||
Global.can_time_tick = false
|
||||
axe_touched.emit()
|
||||
$Axe.queue_free()
|
||||
if bowser_present:
|
||||
for i in get_tree().get_nodes_in_group("Bowser"):
|
||||
i.bridge_fall()
|
||||
get_tree().paused = true
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
for i in $Bridge.get_children():
|
||||
if i.visible:
|
||||
AudioManager.play_sfx("block_break", i.global_position)
|
||||
if Settings.file.visuals.bridge_animation == 0:
|
||||
bridge_piece_break(i)
|
||||
else:
|
||||
bridge_piece_fall(i)
|
||||
await get_tree().create_timer(0.1).timeout
|
||||
await get_tree().create_timer(1.5).timeout
|
||||
get_tree().paused = false
|
||||
victory_sequence(player)
|
||||
|
||||
func bridge_piece_fall(node: Node2D) -> void:
|
||||
var tween = create_tween()
|
||||
tween.set_ease(Tween.EASE_IN).set_trans(Tween.TRANS_SINE)
|
||||
tween.tween_property(node, "global_position:y", node.global_position.y + 128, 0.5)
|
||||
|
||||
const BRIDGE_DESTRUCTION_PARTICLE = preload("uid://cwfjdgsyh35h6")
|
||||
|
||||
func bridge_piece_break(node: Node2D) -> void:
|
||||
var particle = BRIDGE_DESTRUCTION_PARTICLE.instantiate()
|
||||
particle.global_position = node.global_position
|
||||
particle.process_mode = Node.PROCESS_MODE_ALWAYS
|
||||
add_sibling(particle)
|
||||
node.modulate.a = 0
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if cam_move and $Camera.global_position.x < Player.camera_right_limit:
|
||||
$Camera.global_position.x += 96 * delta
|
||||
$Camera.global_position.x = clamp($Camera.global_position.x, -INF, Player.camera_right_limit)
|
||||
|
||||
func victory_sequence(player: Player) -> void:
|
||||
get_tree().call_group("Enemies", "flag_die")
|
||||
Global.level_complete_begin.emit()
|
||||
victory_begin.emit()
|
||||
cam_move = true
|
||||
$Camera.limit_right = Player.camera_right_limit
|
||||
$Camera.global_position = get_viewport().get_camera_2d().get_screen_center_position()
|
||||
$Camera.reset_physics_interpolation()
|
||||
|
||||
player.state_machine.transition_to("LevelExit")
|
||||
$Camera.make_current()
|
||||
if Global.current_game_mode == Global.GameMode.BOO_RACE:
|
||||
AudioManager.set_music_override(AudioManager.MUSIC_OVERRIDES.RACE_WIN, 99, false)
|
||||
await AudioManager.music_override_player.finished
|
||||
Global.current_level.transition_to_next_level()
|
||||
else:
|
||||
AudioManager.set_music_override(AudioManager.MUSIC_OVERRIDES.CASTLE_COMPLETE, 99, false)
|
||||
await get_tree().create_timer(1, false).timeout
|
||||
if do_tally:
|
||||
Global.tally_time()
|
||||
|
||||
|
||||
func on_victory_begin() -> void:
|
||||
pass # Replace with function body.
|
1
Scripts/Parts/CastleBridge.gd.uid
Executable file
1
Scripts/Parts/CastleBridge.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://drigdwsriqtma
|
13
Scripts/Parts/CastleChallengeEnd.gd
Normal file
13
Scripts/Parts/CastleChallengeEnd.gd
Normal file
@@ -0,0 +1,13 @@
|
||||
extends Node2D
|
||||
|
||||
@export var play_end_music := false
|
||||
var can_menu := false
|
||||
const ENDING = preload("res://Assets/Audio/BGM/Ending.mp3")
|
||||
func begin() -> void:
|
||||
for player in get_tree().get_nodes_in_group("Players"):
|
||||
player.z_index = -2
|
||||
$CameraRightLimit._enter_tree()
|
||||
await get_tree().create_timer(1, false).timeout
|
||||
Global.tally_time()
|
||||
await get_tree().create_timer(6, false).timeout
|
||||
Global.transition_to_scene("res://Scenes/Levels/ChallengeModeCastleResults.tscn")
|
1
Scripts/Parts/CastleChallengeEnd.gd.uid
Executable file
1
Scripts/Parts/CastleChallengeEnd.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://v5rwovoqjpag
|
88
Scripts/Parts/CastleToad.gd
Normal file
88
Scripts/Parts/CastleToad.gd
Normal file
@@ -0,0 +1,88 @@
|
||||
extends Node2D
|
||||
|
||||
@export var play_end_music := false
|
||||
var can_menu := false
|
||||
const ENDING = preload("res://Assets/Audio/BGM/Ending.mp3")
|
||||
|
||||
func _ready() -> void:
|
||||
if $Sprite is AnimatedSprite2D and Global.current_campaign == "SMBANN":
|
||||
$Sprite.play("Idle")
|
||||
Global.level_complete_begin.connect(begin)
|
||||
for i in [$SpeedrunMSG/ThankYou, $StandardMSG/ThankYou]:
|
||||
i.text = tr(i.text).replace("{PLAYER}", tr(Player.CHARACTER_NAMES[int(Global.player_characters[0])]))
|
||||
|
||||
func begin() -> void:
|
||||
$StaticBody2D/CollisionShape2D.set_deferred("disabled", false)
|
||||
%PBMessage.modulate.a = int(SpeedrunHandler.timer < SpeedrunHandler.best_time)
|
||||
if play_end_music:
|
||||
Global.game_beaten = true
|
||||
SaveManager.write_save()
|
||||
play_music()
|
||||
%Time.text = tr(%Time.text).replace("{TIME}", SpeedrunHandler.gen_time_string(SpeedrunHandler.format_time(SpeedrunHandler.timer)))
|
||||
$CameraRightLimit._enter_tree()
|
||||
await get_tree().create_timer(3, false).timeout
|
||||
if Global.current_game_mode == Global.GameMode.MARATHON_PRACTICE or (Global.current_game_mode == Global.GameMode.MARATHON and play_end_music):
|
||||
show_message($SpeedrunMSG)
|
||||
else:
|
||||
show_message($StandardMSG)
|
||||
if not play_end_music:
|
||||
await get_tree().create_timer(7, false).timeout
|
||||
exit_level()
|
||||
|
||||
func exit_level() -> void:
|
||||
match Global.current_game_mode:
|
||||
Global.GameMode.MARATHON_PRACTICE:
|
||||
Global.open_marathon_results()
|
||||
Global.GameMode.CUSTOM_LEVEL:
|
||||
Global.transition_to_scene("res://Scenes/Levels/CustomLevelMenu.tscn")
|
||||
Global.GameMode.LEVEL_EDITOR:
|
||||
Global.level_editor.stop_testing()
|
||||
_:
|
||||
if Global.current_campaign == "SMBANN":
|
||||
Global.open_disco_results()
|
||||
return
|
||||
if Global.world_num < 1:
|
||||
Global.transition_to_scene("res://Scenes/Levels/TitleScreen.tscn")
|
||||
else:
|
||||
Global.current_level.transition_to_next_level()
|
||||
|
||||
func do_tally() -> void:
|
||||
pass
|
||||
|
||||
func play_music() -> void:
|
||||
await AudioManager.music_override_player.finished
|
||||
AudioManager.set_music_override(AudioManager.MUSIC_OVERRIDES.ENDING, 999999, false)
|
||||
if [Global.GameMode.MARATHON, Global.GameMode.MARATHON_PRACTICE].has(Global.current_game_mode) == false:
|
||||
show_message($EndingSpeech)
|
||||
await get_tree().create_timer(5, false).timeout
|
||||
can_menu = true
|
||||
else:
|
||||
can_menu = true
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if can_menu and Input.is_action_just_pressed("jump_0"):
|
||||
can_menu = false
|
||||
peach_level_exit()
|
||||
|
||||
func show_message(message_node: Node) -> void:
|
||||
for i in message_node.get_children():
|
||||
i.show()
|
||||
await get_tree().create_timer(1).timeout
|
||||
|
||||
func peach_level_exit() -> void:
|
||||
match Global.current_game_mode:
|
||||
Global.GameMode.MARATHON:
|
||||
Global.open_marathon_results()
|
||||
Global.GameMode.MARATHON_PRACTICE:
|
||||
Global.open_marathon_results()
|
||||
Global.GameMode.CUSTOM_LEVEL:
|
||||
Global.transition_to_scene("res://Scenes/Levels/CustomLevelMenu.tscn")
|
||||
Global.GameMode.LEVEL_EDITOR:
|
||||
Global.level_editor.play_toggle()
|
||||
_:
|
||||
if Global.current_campaign == "SMBLL" and Global.world_num == 8:
|
||||
Global.current_level.transition_to_next_level()
|
||||
elif Global.current_game_mode == Global.GameMode.CAMPAIGN:
|
||||
CreditsLevel.go_to_title_screen = true
|
||||
Global.transition_to_scene("res://Scenes/Levels/Credits.tscn")
|
||||
else: Global.transition_to_scene("res://Scenes/Levels/TitleScreen.tscn")
|
1
Scripts/Parts/CastleToad.gd.uid
Executable file
1
Scripts/Parts/CastleToad.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://j76u1aqhwey4
|
10
Scripts/Parts/CastleVisual.gd
Normal file
10
Scripts/Parts/CastleVisual.gd
Normal file
@@ -0,0 +1,10 @@
|
||||
class_name CastleVisual
|
||||
extends Node2D
|
||||
|
||||
@export var sprite: Sprite2D = null
|
||||
|
||||
var use_sprite := false
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
$Tiles.visible = not use_sprite
|
||||
$Sprite.visible = use_sprite
|
1
Scripts/Parts/CastleVisual.gd.uid
Executable file
1
Scripts/Parts/CastleVisual.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://cdvxqkeqa7en5
|
20
Scripts/Parts/ChallengeModeNodes.gd
Normal file
20
Scripts/Parts/ChallengeModeNodes.gd
Normal file
@@ -0,0 +1,20 @@
|
||||
class_name ChallengeNodes
|
||||
extends Node
|
||||
|
||||
@export var nodes_to_delete: Array[Node]
|
||||
@export var no_report := false
|
||||
@export var force_on := false
|
||||
|
||||
func _ready() -> void:
|
||||
if force_on and Global.current_game_mode == Global.GameMode.NONE:
|
||||
Global.current_game_mode = Global.GameMode.CHALLENGE
|
||||
if Global.current_game_mode != Global.GameMode.CHALLENGE:
|
||||
queue_free()
|
||||
else:
|
||||
ChallengeModeHandler.red_coins = 0
|
||||
for i in 5:
|
||||
if ChallengeModeHandler.is_coin_collected([0, 1, 2, 3, 4][i]):
|
||||
ChallengeModeHandler.red_coins += 1
|
||||
for i in nodes_to_delete:
|
||||
if i != null:
|
||||
i.queue_free()
|
1
Scripts/Parts/ChallengeModeNodes.gd.uid
Executable file
1
Scripts/Parts/ChallengeModeNodes.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://cgm3opb5qudc1
|
60
Scripts/Parts/Checkpoint.gd
Normal file
60
Scripts/Parts/Checkpoint.gd
Normal file
@@ -0,0 +1,60 @@
|
||||
class_name Checkpoint
|
||||
extends Node2D
|
||||
|
||||
@export var nodes_to_delete: Array[Node] = []
|
||||
|
||||
@export var optional := false
|
||||
|
||||
signal crossed(player: Player)
|
||||
signal respawned
|
||||
|
||||
static var passed := false
|
||||
static var respawn_position := Vector2.ZERO
|
||||
static var level := ""
|
||||
static var sublevel_id := 0
|
||||
|
||||
static var old_state := [[], []]
|
||||
|
||||
func _enter_tree() -> void:
|
||||
if passed:
|
||||
LevelPersistance.active_nodes = old_state.duplicate(true)
|
||||
|
||||
func _ready() -> void:
|
||||
if [Global.GameMode.CHALLENGE, Global.GameMode.MARATHON_PRACTICE].has(Global.current_game_mode):
|
||||
queue_free()
|
||||
return
|
||||
if has_meta("is_flag") == false:
|
||||
hide()
|
||||
if Settings.file.difficulty.checkpoint_style != 0:
|
||||
queue_free()
|
||||
if passed and PipeArea.exiting_pipe_id == -1 and Global.current_game_mode != Global.GameMode.LEVEL_EDITOR and Level.vine_return_level == "":
|
||||
for i in nodes_to_delete:
|
||||
i.queue_free()
|
||||
for i in get_tree().get_nodes_in_group("Players"):
|
||||
i.global_position = self.global_position
|
||||
i.reset_physics_interpolation()
|
||||
i.recenter_camera()
|
||||
respawned.emit()
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
pass
|
||||
|
||||
func on_area_entered(area: Area2D) -> void:
|
||||
if area.owner is Player and not passed:
|
||||
var player: Player = area.owner
|
||||
player.passed_checkpoint()
|
||||
passed = true
|
||||
old_state = LevelPersistance.active_nodes.duplicate(true)
|
||||
Level.start_level_path = Global.current_level.scene_file_path
|
||||
if Global.current_game_mode == Global.GameMode.LEVEL_EDITOR or Global.current_game_mode == Global.GameMode.CUSTOM_LEVEL:
|
||||
sublevel_id = Global.level_editor.sub_level_id
|
||||
if Settings.file.difficulty.checkpoint_style == 2 and has_meta("is_flag"):
|
||||
if player.power_state.state_name == "Small":
|
||||
player.get_power_up("Big")
|
||||
respawn_position = global_position
|
||||
crossed.emit(area.owner)
|
||||
|
||||
|
||||
func on_tree_exiting() -> void:
|
||||
pass # Replace with function body.
|
1
Scripts/Parts/Checkpoint.gd.uid
Executable file
1
Scripts/Parts/Checkpoint.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://dtdc4oaygllfa
|
8
Scripts/Parts/ClassicThemeNode.gd
Executable file
8
Scripts/Parts/ClassicThemeNode.gd
Executable file
@@ -0,0 +1,8 @@
|
||||
class_name ClassicThemeNode
|
||||
extends Node
|
||||
|
||||
@export_enum("Overworld", "Underground", "Desert", "Snow", "Jungle", "Beach", "Garden", "Mountain", "Skyland", "Autumn", "Pipeland", "Space", "Underwater", "Volcano", "Castle") var classic_theme := "Overworld"
|
||||
@export var nodes_to_delete: Array[Node] = []
|
||||
|
||||
func _ready() -> void:
|
||||
queue_free()
|
1
Scripts/Parts/ClassicThemeNode.gd.uid
Executable file
1
Scripts/Parts/ClassicThemeNode.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://don436dsjhc8w
|
26
Scripts/Parts/CoinHeavenAllCoinsBonus.gd
Normal file
26
Scripts/Parts/CoinHeavenAllCoinsBonus.gd
Normal file
@@ -0,0 +1,26 @@
|
||||
class_name AllCoinsCollectedCheck
|
||||
extends Node
|
||||
|
||||
signal checked
|
||||
|
||||
func check() -> void:
|
||||
if get_tree().get_nodes_in_group("Coins").is_empty() and Global.current_game_mode == Global.GameMode.CHALLENGE:
|
||||
await get_tree().create_timer(1, false).timeout
|
||||
$CanvasLayer.show()
|
||||
AudioManager.set_music_override(AudioManager.MUSIC_OVERRIDES.COIN_HEAVEN_BONUS, 99, false, false)
|
||||
await get_tree().create_timer(1, false).timeout
|
||||
await score_tween()
|
||||
await get_tree().create_timer(1, false).timeout
|
||||
await get_tree().process_frame
|
||||
checked.emit()
|
||||
|
||||
func score_tween() -> void:
|
||||
Global.tallying_score = true
|
||||
Global.get_node("ScoreTally").play()
|
||||
var tween = create_tween()
|
||||
tween.tween_property(Global, "score", Global.score + 10000, 2)
|
||||
await tween.finished
|
||||
Global.get_node("ScoreTallyEnd").play()
|
||||
Global.get_node("ScoreTally").stop()
|
||||
Global.tallying_score = false
|
||||
return
|
1
Scripts/Parts/CoinHeavenAllCoinsBonus.gd.uid
Normal file
1
Scripts/Parts/CoinHeavenAllCoinsBonus.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ds3idw2a2udai
|
13
Scripts/Parts/CoinHeavenWarpPoint.gd
Normal file
13
Scripts/Parts/CoinHeavenWarpPoint.gd
Normal file
@@ -0,0 +1,13 @@
|
||||
class_name CoinHeavenWarpPoint
|
||||
extends Node2D
|
||||
|
||||
@export_file("*.tscn") var heaven_scene := ""
|
||||
|
||||
func _ready() -> void:
|
||||
Level.vine_warp_level = heaven_scene
|
||||
if Level.in_vine_level and PipeArea.exiting_pipe_id == -1:
|
||||
Level.in_vine_level = false
|
||||
for i in get_tree().get_nodes_in_group("Players"):
|
||||
i.global_position = global_position
|
||||
i.reset_physics_interpolation()
|
||||
i.recenter_camera()
|
1
Scripts/Parts/CoinHeavenWarpPoint.gd.uid
Executable file
1
Scripts/Parts/CoinHeavenWarpPoint.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://pfwgmuchergf
|
25
Scripts/Parts/ColourPaletteSampler.gd
Normal file
25
Scripts/Parts/ColourPaletteSampler.gd
Normal file
@@ -0,0 +1,25 @@
|
||||
class_name ColourPaletteSampler
|
||||
extends Node
|
||||
|
||||
@export var texture: Texture2D = null:
|
||||
set(value):
|
||||
texture = value
|
||||
update()
|
||||
signal updated
|
||||
|
||||
@export var coords := Vector2i.ZERO
|
||||
@export var node_to_affect: Node = null
|
||||
@export var value_to_set := ""
|
||||
|
||||
func _ready() -> void:
|
||||
update()
|
||||
Global.level_theme_changed.connect(update)
|
||||
|
||||
func update() -> void:
|
||||
if node_to_affect == null or texture == null:
|
||||
return
|
||||
var colour_to_sample: Color = Color.WHITE
|
||||
var image = texture.get_image()
|
||||
colour_to_sample = image.get_pixelv(coords)
|
||||
node_to_affect.set(value_to_set, colour_to_sample)
|
||||
updated.emit()
|
1
Scripts/Parts/ColourPaletteSampler.gd.uid
Executable file
1
Scripts/Parts/ColourPaletteSampler.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://dwtrmo82e12ud
|
5
Scripts/Parts/DeathPit.gd
Executable file
5
Scripts/Parts/DeathPit.gd
Executable file
@@ -0,0 +1,5 @@
|
||||
extends Area2D
|
||||
|
||||
func area_entered(area: Area2D) -> void:
|
||||
if area.owner is Player and area.owner.state_machine.state.name != "Dead":
|
||||
area.owner.die(true)
|
1
Scripts/Parts/DeathPit.gd.uid
Executable file
1
Scripts/Parts/DeathPit.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://c50qnhtmrqlot
|
43
Scripts/Parts/DifficultySetter.gd
Normal file
43
Scripts/Parts/DifficultySetter.gd
Normal file
@@ -0,0 +1,43 @@
|
||||
extends Node
|
||||
|
||||
func damage_style_changed(new_value := 0) -> void:
|
||||
Settings.file.difficulty.damage_style = new_value
|
||||
|
||||
func checkpoint_changed(new_value := 0) -> void:
|
||||
Settings.file.difficulty.checkpoint_style = new_value
|
||||
|
||||
func inf_lives_changed(new_value := 0) -> void:
|
||||
Settings.file.difficulty.inf_lives = new_value
|
||||
|
||||
func flag_lives_changed(new_value := 0) -> void:
|
||||
Settings.file.difficulty.flagpole_lives = new_value
|
||||
|
||||
func time_limit_changed(new_value := 0) -> void:
|
||||
Settings.file.difficulty.time_limit = new_value
|
||||
|
||||
func game_over_changed(new_value := 0) -> void:
|
||||
Settings.file.difficulty.game_over_behaviour = new_value
|
||||
|
||||
func backscroll_changed(new_value := 0) -> void:
|
||||
Settings.file.difficulty.back_scroll = new_value
|
||||
|
||||
func level_design_changed(new_value := 0) -> void:
|
||||
Settings.file.difficulty.level_design = new_value
|
||||
|
||||
func extra_checkpoints_changed(new_value := 0) -> void:
|
||||
Settings.file.difficulty.extra_checkpoints = new_value
|
||||
|
||||
func lakitu_style_changed(new_value := 0) -> void:
|
||||
Settings.file.difficulty.lakitu_style = new_value
|
||||
|
||||
func set_value(value_name := "", value := 0) -> void:
|
||||
{
|
||||
"damage_style": damage_style_changed,
|
||||
"checkpoint_style": checkpoint_changed,
|
||||
"inf_lives": inf_lives_changed,
|
||||
"flagpole_lives": flag_lives_changed,
|
||||
"game_over": game_over_changed,
|
||||
"level_design": level_design_changed,
|
||||
"extra_checkpoints": extra_checkpoints_changed,
|
||||
"back_scroll": backscroll_changed
|
||||
}[value_name].call(value)
|
1
Scripts/Parts/DifficultySetter.gd.uid
Executable file
1
Scripts/Parts/DifficultySetter.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://cxkumlifwb0s6
|
32
Scripts/Parts/Disclaimer.gd
Normal file
32
Scripts/Parts/Disclaimer.gd
Normal file
@@ -0,0 +1,32 @@
|
||||
extends Node
|
||||
|
||||
var can_skip := false
|
||||
|
||||
@export var default_font: Font = null
|
||||
|
||||
func _enter_tree() -> void:
|
||||
if Settings.file.game.lang != "jp":
|
||||
for i in [$Title, $"1", $"2", $Enjoy]:
|
||||
i.remove_theme_font_override("font")
|
||||
i.uppercase = true
|
||||
|
||||
func _ready() -> void:
|
||||
Global.debugged_in = false
|
||||
Global.get_node("GameHUD").hide()
|
||||
await get_tree().create_timer(1, false).timeout
|
||||
can_skip = true
|
||||
|
||||
func _exit_tree() -> void:
|
||||
Global.get_node("GameHUD").show()
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if Input.is_action_just_pressed("jump_0") and can_skip:
|
||||
go_to_menu()
|
||||
|
||||
func go_to_menu() -> void:
|
||||
if Global.rom_path == "":
|
||||
Global.transition_to_scene("res://Scenes/Levels/RomVerifier.tscn")
|
||||
elif not Global.rom_assets_exist:
|
||||
Global.transition_to_scene("res://Scenes/Levels/RomResourceGenerator.tscn")
|
||||
else:
|
||||
Global.transition_to_scene("res://Scenes/Levels/TitleScreen.tscn")
|
1
Scripts/Parts/Disclaimer.gd.uid
Executable file
1
Scripts/Parts/Disclaimer.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://bjsdmqxwrb6sd
|
46
Scripts/Parts/DiscoBg.gd
Normal file
46
Scripts/Parts/DiscoBg.gd
Normal file
@@ -0,0 +1,46 @@
|
||||
extends Parallax2D
|
||||
|
||||
var level_bpm := 120
|
||||
|
||||
@export var level := 0.5
|
||||
|
||||
var is_star := false
|
||||
|
||||
var beats := -1
|
||||
|
||||
var tween: Tween = null
|
||||
|
||||
func _ready() -> void:
|
||||
modulate.a = 0
|
||||
AudioManager.music_beat.connect(on_timeout)
|
||||
on_timeout()
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
scroll_offset.y = lerpf(scroll_offset.y, lerpf(128, 0, level), delta * 5)
|
||||
modulate.a = lerpf(modulate.a, lerpf(0, 1, level), delta * 5)
|
||||
$TextureRect.position.y = move_toward($TextureRect.position.y, -64, delta * (level_bpm / 10))
|
||||
|
||||
|
||||
func on_timeout(idx := 0) -> void:
|
||||
beats = idx
|
||||
if is_star == false:
|
||||
tween_back()
|
||||
if beats % 4 == 0:
|
||||
$AudioStreamPlayer.pitch_scale = 2
|
||||
else:
|
||||
$AudioStreamPlayer.pitch_scale = 1
|
||||
$AudioStreamPlayer.play()
|
||||
|
||||
|
||||
|
||||
|
||||
func tween_back() -> void:
|
||||
if tween != null:
|
||||
tween.kill()
|
||||
tween = create_tween().set_trans(Tween.TRANS_CUBIC)
|
||||
tween.tween_property($TextureRect, "position:y", -160, 0.05)
|
||||
|
||||
|
||||
func on_starttimeout() -> void:
|
||||
if is_star:
|
||||
tween_back()
|
1
Scripts/Parts/DiscoBg.gd.uid
Executable file
1
Scripts/Parts/DiscoBg.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://d1noy0etws3gd
|
135
Scripts/Parts/DiscoLevel.gd
Normal file
135
Scripts/Parts/DiscoLevel.gd
Normal file
@@ -0,0 +1,135 @@
|
||||
class_name DiscoLevel
|
||||
extends Node
|
||||
|
||||
@export var combo_meter_rate := 1.0
|
||||
@export var max_combo := 30.0
|
||||
@export var disco_lighting: Parallax2D = null
|
||||
|
||||
static var combo_amount := 0:
|
||||
set(value):
|
||||
if value > combo_amount:
|
||||
combo_meter = 100
|
||||
combo_amount = value
|
||||
static var score_mult := 0
|
||||
static var combo_meter := 0.0
|
||||
|
||||
static var in_disco_level := false
|
||||
static var combo_breaks := 0
|
||||
static var giving_score := false
|
||||
|
||||
static var max_combo_amount := 0.0
|
||||
static var first_load := true
|
||||
|
||||
static var can_meter_tick := true
|
||||
|
||||
const RANK_AMOUNTS := {0: "F", 0.25: "D", 0.45: "C", 0.6: "B", 0.8: "A", 1: "S"}
|
||||
|
||||
const RANKS := "FDCBASP"
|
||||
|
||||
static var current_rank := ""
|
||||
|
||||
static var active := false
|
||||
|
||||
const S_RANK_SCORES := [
|
||||
[45000, 40000, 25000, 12000],
|
||||
[52500, 25000, 25000, 12000],
|
||||
[45000, 45000, 25000, 12000],
|
||||
[45000, 45000, 25000, 12000],
|
||||
|
||||
[45000, 40000, 30000, 12000],
|
||||
[30000, 45000, 20000, 12000],
|
||||
[45000, 25000, 30000, 12000],
|
||||
[45000, 45000, 45000, 12000]
|
||||
]
|
||||
|
||||
static var level_ranks := "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
|
||||
|
||||
const RANK_IDs := ["F", "D", "C", "B", "A", "S", "P"]
|
||||
|
||||
func _ready() -> void:
|
||||
active = true
|
||||
Global.current_campaign = "SMBANN"
|
||||
if get_parent().get_node_or_null("EndFlagpole") != null:
|
||||
get_parent().get_node("EndFlagpole").player_reached.connect(level_finished)
|
||||
if get_parent().get_node_or_null("CastleBridge") != null:
|
||||
get_parent().get_node("CastleBridge").victory_begin.connect(level_finished)
|
||||
can_meter_tick = true
|
||||
if DiscoLevel.first_load == true:
|
||||
max_combo_amount = max_combo
|
||||
reset_values()
|
||||
DiscoLevel.first_load = false
|
||||
|
||||
in_disco_level = true
|
||||
|
||||
static func reset_values() -> void:
|
||||
combo_amount = 0
|
||||
combo_meter = 0
|
||||
first_load = false
|
||||
if Global.current_campaign == "SMBANN":
|
||||
Global.score = 0
|
||||
combo_breaks = 0
|
||||
current_rank = "F"
|
||||
Player.times_hit = 0
|
||||
Global.player_power_states = "0000"
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if not active:
|
||||
return
|
||||
if can_meter_tick:
|
||||
combo_meter = clamp(combo_meter - 24 * combo_meter_rate * delta, 0, 100)
|
||||
if combo_meter <= 0 and combo_amount > 0 and not giving_score:
|
||||
if combo_amount > 2 or current_rank == "P" or current_rank == "S":
|
||||
AudioManager.play_global_sfx("combo_lost")
|
||||
give_points(combo_amount)
|
||||
combo_amount = 0
|
||||
combo_breaks += 1
|
||||
var old_rank = current_rank
|
||||
current_rank = "F"
|
||||
for i in RANK_AMOUNTS.keys():
|
||||
if (Global.score + (combo_amount * 500) + (Global.time * 50)) >= (S_RANK_SCORES[Global.world_num - 1][Global.level_num - 1] * i):
|
||||
current_rank = RANK_AMOUNTS[i]
|
||||
if current_rank == "S" and combo_breaks <= 0 and combo_amount >= 1:
|
||||
current_rank = "P"
|
||||
if RANKS.find(current_rank) > RANKS.find(old_rank):
|
||||
if current_rank == "S" or current_rank == "P":
|
||||
AudioManager.play_global_sfx("rank_up_2")
|
||||
elif combo_amount > 0:
|
||||
AudioManager.play_global_sfx("rank_up_1")
|
||||
elif RANKS.find(current_rank) < RANKS.find(old_rank):
|
||||
AudioManager.play_global_sfx("rank_down")
|
||||
|
||||
func give_points(amount := 0) -> void:
|
||||
await get_tree().create_timer(0.5, false).timeout
|
||||
for i in amount * 4:
|
||||
Global.score += 125
|
||||
AudioManager.play_global_sfx("score")
|
||||
await get_tree().physics_frame
|
||||
AudioManager.play_global_sfx("score_end")
|
||||
AudioManager.kill_sfx("score")
|
||||
|
||||
func _exit_tree() -> void:
|
||||
Global.tallying_score = false
|
||||
AudioManager.kill_sfx("score")
|
||||
|
||||
func level_finished() -> void:
|
||||
if Global.world_num != 8 && Global.level_num != 4:
|
||||
SaveManager.visited_levels[SaveManager.get_level_idx(Global.world_num, Global.level_num) + 1] = "1"
|
||||
Global.score += (combo_amount * 500)
|
||||
combo_meter = 100
|
||||
_physics_process(0)
|
||||
can_meter_tick = false
|
||||
await Global.score_tally_finished
|
||||
active = false
|
||||
write_final_rank()
|
||||
|
||||
func write_final_rank() -> void:
|
||||
if ChallengeModeHandler.top_challenge_scores[Global.world_num - 1][Global.level_num - 1] < Global.score:
|
||||
ChallengeModeHandler.top_challenge_scores[Global.world_num - 1][Global.level_num - 1] = Global.score
|
||||
if RANK_IDs.find(level_ranks[SaveManager.get_level_idx(Global.world_num, Global.level_num)]) < RANK_IDs.find(current_rank):
|
||||
level_ranks[SaveManager.get_level_idx(Global.world_num, Global.level_num)] = current_rank
|
||||
check_for_p_rank_achievement()
|
||||
SaveManager.write_save("SMBANN")
|
||||
|
||||
func check_for_p_rank_achievement() -> void:
|
||||
if level_ranks == "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP":
|
||||
Global.unlock_achievement(Global.AchievementID.ANN_PRANK)
|
1
Scripts/Parts/DiscoLevel.gd.uid
Executable file
1
Scripts/Parts/DiscoLevel.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://bjs5mlc3xrxud
|
25
Scripts/Parts/DropShadowRenderer.gd
Normal file
25
Scripts/Parts/DropShadowRenderer.gd
Normal file
@@ -0,0 +1,25 @@
|
||||
extends Node2D
|
||||
|
||||
var shadow_colour := Color.BLACK
|
||||
@export var offset := Vector2(0, 0)
|
||||
var shadow_texture: Texture2D = null
|
||||
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
hide()
|
||||
if Settings.file.video.drop_shadows == 0:
|
||||
return
|
||||
show()
|
||||
if is_instance_valid(get_tree().current_scene):
|
||||
if is_instance_valid(get_tree().current_scene.get_viewport().get_camera_2d()):
|
||||
$SubViewportContainer/SubViewport.world_2d = get_tree().current_scene.get_viewport().get_camera_2d().get_world_2d()
|
||||
else:
|
||||
return
|
||||
else:
|
||||
return#
|
||||
$SubViewportContainer.material.set_shader_parameter("shadow_colour", shadow_colour)
|
||||
$SubViewportContainer/SubViewport.size = get_viewport().get_visible_rect().size + Vector2(9, 2)
|
||||
global_position = get_viewport().get_camera_2d().get_screen_center_position() + offset
|
||||
$SubViewportContainer/SubViewport/Camera2D.global_position = get_viewport().get_camera_2d().get_screen_center_position()
|
||||
queue_redraw()
|
1
Scripts/Parts/DropShadowRenderer.gd.uid
Executable file
1
Scripts/Parts/DropShadowRenderer.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://c3krxjp4ye4ko
|
26
Scripts/Parts/DropShadowRendererSmooth.gd
Normal file
26
Scripts/Parts/DropShadowRendererSmooth.gd
Normal file
@@ -0,0 +1,26 @@
|
||||
extends Node
|
||||
|
||||
@onready var sub_viewport: SubViewport = %SubViewport
|
||||
@onready var camera: Camera2D = %Camera
|
||||
@onready var point: Node2D = %Point
|
||||
|
||||
var enabled := true
|
||||
|
||||
const day_colour := Color("000000")
|
||||
const night_colour := Color("5e5e5e")
|
||||
|
||||
func _ready() -> void:
|
||||
await get_tree().physics_frame
|
||||
sub_viewport.set_world_2d(get_viewport().get_world_2d())
|
||||
|
||||
func _physics_process(_delta: float) -> void:
|
||||
if get_viewport().get_camera_2d() != null:
|
||||
camera.global_position = get_viewport().get_camera_2d().get_screen_center_position()
|
||||
camera.zoom = Vector2i(Vector2.ONE / $"%Container".scale)
|
||||
point.global_position = camera.global_position
|
||||
var colour := day_colour
|
||||
$%Container.material.set_shader_parameter("shadow_colour", colour)
|
||||
|
||||
func _exit_tree() -> void:
|
||||
pass
|
||||
#sub_viewport.set_world_2d(null)
|
1
Scripts/Parts/DropShadowRendererSmooth.gd.uid
Normal file
1
Scripts/Parts/DropShadowRendererSmooth.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cit6racuc8c60
|
15
Scripts/Parts/EditorToggleDetection.gd
Normal file
15
Scripts/Parts/EditorToggleDetection.gd
Normal file
@@ -0,0 +1,15 @@
|
||||
class_name LevelEditorToggleDetection
|
||||
extends Node
|
||||
|
||||
signal toggled
|
||||
|
||||
signal level_start
|
||||
signal editor_start
|
||||
|
||||
func _ready() -> void:
|
||||
await get_tree().physics_frame
|
||||
if is_instance_valid(Global.level_editor):
|
||||
Global.level_editor.level_start.connect(toggled.emit)
|
||||
Global.level_editor.level_start.connect(level_start.emit)
|
||||
Global.level_editor.editor_start.connect(editor_start.emit)
|
||||
Global.level_editor.editor_start.connect(toggled.emit)
|
1
Scripts/Parts/EditorToggleDetection.gd.uid
Normal file
1
Scripts/Parts/EditorToggleDetection.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b8vmbtoaaq5nc
|
11
Scripts/Parts/EditorVisibleNode.gd
Normal file
11
Scripts/Parts/EditorVisibleNode.gd
Normal file
@@ -0,0 +1,11 @@
|
||||
class_name LevelEditorVisibleNode
|
||||
extends Node2D
|
||||
|
||||
func _ready() -> void:
|
||||
update()
|
||||
if Global.level_editor != null:
|
||||
Global.level_editor.editor_start.connect(update)
|
||||
Global.level_editor.level_start.connect(update)
|
||||
|
||||
func update() -> void:
|
||||
visible = !LevelEditor.playing_level and Global.current_game_mode == Global.GameMode.LEVEL_EDITOR
|
1
Scripts/Parts/EditorVisibleNode.gd.uid
Executable file
1
Scripts/Parts/EditorVisibleNode.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://cpwloakvp672a
|
113
Scripts/Parts/EndCastle.gd
Normal file
113
Scripts/Parts/EndCastle.gd
Normal file
@@ -0,0 +1,113 @@
|
||||
extends Node2D
|
||||
|
||||
var time_save := 0
|
||||
|
||||
signal finished_sequence
|
||||
const FIREWORK = preload("res://Scenes/Prefabs/Particles/Firework.tscn")
|
||||
|
||||
var tally_finished := false
|
||||
var music_finished := false
|
||||
var tree = null
|
||||
var show_walls := false
|
||||
var doing_sequence := false
|
||||
|
||||
var can_transition := false
|
||||
|
||||
static var is_transitioning := false
|
||||
|
||||
func _ready() -> void:
|
||||
await Global.level_complete_begin
|
||||
$Overlay.show()
|
||||
$OverlaySprite.show()
|
||||
$Overlay/PlayerDetection.set_collision_layer_value(1, true)
|
||||
Global.score_tally_finished.connect(on_tally_finished)
|
||||
if Global.current_game_mode == Global.GameMode.BOO_RACE:
|
||||
get_tree().create_timer(3.5, false).timeout.connect(on_music_finished)
|
||||
else:
|
||||
get_tree().create_timer(5.5, false).timeout.connect(on_music_finished)
|
||||
time_save = Global.time
|
||||
|
||||
func update_cam_limit() -> void:
|
||||
$CameraRightLimit._enter_tree()
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
$Overlay.modulate.a = int($SmallCastleVisual.use_sprite == false)
|
||||
if get_node_or_null("Wall") != null:
|
||||
%Wall.visible = show_walls
|
||||
|
||||
func on_music_finished() -> void:
|
||||
do_sequence()
|
||||
|
||||
func on_tally_finished() -> void:
|
||||
$FlagJoint/Flag/AnimationPlayer.play("Raise")
|
||||
|
||||
func do_sequence() -> void:
|
||||
if Global.current_game_mode != Global.GameMode.BOO_RACE:
|
||||
await get_tree().create_timer(1, false).timeout
|
||||
if Global.current_campaign == "SMBLL":
|
||||
await do_lost_levels_firework_check()
|
||||
else:
|
||||
await do_firework_check()
|
||||
await get_tree().create_timer(1, false).timeout
|
||||
if is_transitioning == false:
|
||||
is_transitioning = true
|
||||
exit_level()
|
||||
|
||||
func do_firework_check() -> void:
|
||||
var digit = time_save % 10
|
||||
if [1, 3, 6].has(digit):
|
||||
await show_fireworks(digit)
|
||||
return
|
||||
|
||||
func do_lost_levels_firework_check() -> void:
|
||||
var coin_digit = Global.coins % 10
|
||||
var time_digit = time_save % 10
|
||||
if coin_digit == time_digit:
|
||||
if coin_digit % 2 == 0:
|
||||
await show_fireworks(6)
|
||||
if coin_digit % 11 == 0:
|
||||
spawn_one_up_note()
|
||||
AudioManager.play_sfx("1_up", global_position)
|
||||
Global.lives += 1
|
||||
else:
|
||||
await show_fireworks(3)
|
||||
|
||||
const ONE_UP_NOTE = preload("uid://dopxwjj37gu0l")
|
||||
|
||||
func spawn_one_up_note() -> void:
|
||||
var note = ONE_UP_NOTE.instantiate()
|
||||
note.global_position = global_position + Vector2(0, -16)
|
||||
owner.add_sibling(note)
|
||||
|
||||
func _exit_tree() -> void:
|
||||
is_transitioning = false
|
||||
|
||||
func show_fireworks(amount := 0) -> void:
|
||||
for i in amount:
|
||||
spawn_firework()
|
||||
await get_tree().create_timer(0.5, false).timeout
|
||||
|
||||
func spawn_firework() -> void:
|
||||
var node = FIREWORK.instantiate()
|
||||
Global.score += 500
|
||||
node.position.x = randf_range(-48, 48)
|
||||
node.position.y = randf_range(-112, -150)
|
||||
add_child(node)
|
||||
AudioManager.play_sfx("firework", node.global_position)
|
||||
|
||||
|
||||
func exit_level() -> void:
|
||||
await Global.frame_rule
|
||||
match Global.current_game_mode:
|
||||
Global.GameMode.MARATHON_PRACTICE:
|
||||
Global.reset_values()
|
||||
Global.open_marathon_results()
|
||||
Global.GameMode.CUSTOM_LEVEL:
|
||||
Global.transition_to_scene("res://Scenes/Levels/CustomLevelMenu.tscn")
|
||||
Global.GameMode.LEVEL_EDITOR:
|
||||
Global.level_editor.stop_testing()
|
||||
_:
|
||||
if Global.current_campaign == "SMBANN":
|
||||
Global.open_disco_results()
|
||||
else:
|
||||
Global.current_level.transition_to_next_level()
|
1
Scripts/Parts/EndCastle.gd.uid
Executable file
1
Scripts/Parts/EndCastle.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://qq26qw7ltflb
|
58
Scripts/Parts/EndFlagpole.gd
Normal file
58
Scripts/Parts/EndFlagpole.gd
Normal file
@@ -0,0 +1,58 @@
|
||||
extends Node2D
|
||||
|
||||
const FLAG_POINTS := [100, 400, 800, 2000, 5000]
|
||||
|
||||
signal player_reached
|
||||
|
||||
signal sequence_begin
|
||||
|
||||
func _ready() -> void:
|
||||
if Settings.file.difficulty.flagpole_lives == 0:
|
||||
print(Settings.file.difficulty)
|
||||
$Top.queue_free()
|
||||
|
||||
func on_area_entered(area: Area2D) -> void:
|
||||
if area.owner is Player:
|
||||
player_touch(area.owner)
|
||||
|
||||
|
||||
|
||||
func player_touch(player: Player) -> void:
|
||||
player_reached.emit()
|
||||
if Global.current_game_mode == Global.GameMode.MARATHON_PRACTICE:
|
||||
SpeedrunHandler.is_warp_run = false
|
||||
SpeedrunHandler.run_finished()
|
||||
Global.can_pause = false
|
||||
if get_node_or_null("Top") != null:
|
||||
$Top.queue_free()
|
||||
$Hitbox.queue_free()
|
||||
get_tree().call_group("Enemies", "flag_die")
|
||||
give_points(player)
|
||||
Global.can_time_tick = false
|
||||
player.z_index = -2
|
||||
player.global_position.x = $Flag.global_position.x + 3
|
||||
$Animation.play("FlagDown")
|
||||
player.state_machine.transition_to("FlagPole")
|
||||
AudioManager.set_music_override(AudioManager.MUSIC_OVERRIDES.FLAG_POLE, 99, false)
|
||||
await get_tree().create_timer(1.5, false).timeout
|
||||
sequence_begin.emit()
|
||||
if Global.current_game_mode == Global.GameMode.BOO_RACE:
|
||||
AudioManager.set_music_override(AudioManager.MUSIC_OVERRIDES.RACE_WIN, 99, false)
|
||||
else:
|
||||
AudioManager.set_music_override(AudioManager.MUSIC_OVERRIDES.LEVEL_COMPLETE, 99, false)
|
||||
Global.level_complete_begin.emit()
|
||||
await get_tree().create_timer(1, false).timeout
|
||||
if [Global.GameMode.BOO_RACE].has(Global.current_game_mode) == false:
|
||||
Global.tally_time()
|
||||
|
||||
func give_points(player: Player) -> void:
|
||||
var value = clamp(int(lerp(0, 4, (player.global_position.y / -144))), 0, 4)
|
||||
var nearest_value = FLAG_POINTS[value]
|
||||
$Score.text = str(nearest_value)
|
||||
Global.score += nearest_value
|
||||
$Score/Animation2.play("ScoreRise")
|
||||
|
||||
func on_player_entered(player: Player) -> void:
|
||||
player_touch(player)
|
||||
Global.lives += 1
|
||||
AudioManager.play_sfx("1_up", global_position)
|
1
Scripts/Parts/EndFlagpole.gd.uid
Executable file
1
Scripts/Parts/EndFlagpole.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://dwfnvpioh2kvi
|
41
Scripts/Parts/EndlessLevelConstructor.gd
Executable file
41
Scripts/Parts/EndlessLevelConstructor.gd
Executable file
@@ -0,0 +1,41 @@
|
||||
extends Node
|
||||
|
||||
var style := "Overworld"
|
||||
|
||||
var level_seed := "8923589235890"
|
||||
|
||||
var level_length := 10
|
||||
|
||||
const PIECE_FOLDER := "res://Scenes/LevelPieces/"
|
||||
|
||||
const OVERWORLD_STYLES := ["Overworld", "Desert", "Snow", "Jungle", "Garden", "Beach", "Mountain", "Autumn"]
|
||||
|
||||
@onready var pieces: Node2D = $"../Pieces"
|
||||
|
||||
func _enter_tree() -> void:
|
||||
owner.theme = OVERWORLD_STYLES.pick_random()
|
||||
Global.level_theme = owner.theme
|
||||
print(owner.theme)
|
||||
|
||||
func _ready() -> void:
|
||||
seed(int(level_seed))
|
||||
await owner.ready
|
||||
build_level()
|
||||
|
||||
func build_level() -> void:
|
||||
var piece_spawn_point := -96
|
||||
var last_piece = self
|
||||
for i in level_length:
|
||||
var piece = get_next_piece()
|
||||
piece.position.x = piece_spawn_point
|
||||
piece_spawn_point += piece.length
|
||||
$"../Pieces".add_child(piece)
|
||||
last_piece = piece
|
||||
|
||||
func get_next_piece() -> LevelPiece:
|
||||
var piece_num := 0
|
||||
var amount_of_pieces := DirAccess.get_files_at(PIECE_FOLDER + style + "/").size()
|
||||
piece_num = randi_range(1, amount_of_pieces)
|
||||
var path = PIECE_FOLDER + style + "/" + str(piece_num) + ".tscn"
|
||||
var next_piece = load(path).instantiate()
|
||||
return next_piece
|
1
Scripts/Parts/EndlessLevelConstructor.gd.uid
Executable file
1
Scripts/Parts/EndlessLevelConstructor.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://dl2nxlgmoa5t2
|
44
Scripts/Parts/EntityGenerator.gd
Normal file
44
Scripts/Parts/EntityGenerator.gd
Normal file
@@ -0,0 +1,44 @@
|
||||
class_name EntityGenerator
|
||||
extends Node2D
|
||||
|
||||
var spawn_meter := 0.0
|
||||
@export var threshold := 2.0
|
||||
var active := false
|
||||
@export_enum("Target Player", "Random Height") var y_pos := 0
|
||||
@export_enum("Right", "Bottom") var direction := 0
|
||||
@export var entity_scene: PackedScene = null
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if active:
|
||||
spawn_meter += delta
|
||||
if spawn_meter >= threshold:
|
||||
spawn_entity()
|
||||
spawn_meter = randf_range(-2, 0)
|
||||
|
||||
func activate() -> void:
|
||||
if not active:
|
||||
active = true
|
||||
spawn_meter = 0
|
||||
spawn_entity()
|
||||
|
||||
func deactivate_all_generators() -> void:
|
||||
for i in get_tree().get_nodes_in_group("EntityGenerators"):
|
||||
i.active = false
|
||||
i.deactivate()
|
||||
|
||||
func deactivate() -> void:
|
||||
pass
|
||||
|
||||
func spawn_entity() -> void:
|
||||
if entity_scene == null: return
|
||||
var node = entity_scene.instantiate()
|
||||
if direction == 1:
|
||||
node.global_position.x = get_viewport().get_camera_2d().get_screen_center_position().x + [ -32 ,-64, -96, -128].pick_random()
|
||||
node.global_position.y = 48
|
||||
else:
|
||||
if y_pos == 0:
|
||||
node.global_position.y = get_tree().get_first_node_in_group("Players").global_position.y + randi_range(-4, 4)
|
||||
else:
|
||||
node.global_position.y = randf_range(-56, -120)
|
||||
node.global_position.x = get_viewport().get_camera_2d().get_screen_center_position().x + ((get_viewport().get_visible_rect().size.x / 2) + 4)
|
||||
add_sibling(node)
|
1
Scripts/Parts/EntityGenerator.gd.uid
Normal file
1
Scripts/Parts/EntityGenerator.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bq0pc2vhp35t2
|
42
Scripts/Parts/EntityGib.gd
Normal file
42
Scripts/Parts/EntityGib.gd
Normal file
@@ -0,0 +1,42 @@
|
||||
extends Node2D
|
||||
|
||||
@export_enum("Spin", "Drop", "Poof") var gib_type := 0
|
||||
|
||||
var visuals: Node = null
|
||||
|
||||
var velocity := Vector2(0, 0)
|
||||
|
||||
var direction := 1
|
||||
|
||||
var entity_rotation := 0.0
|
||||
|
||||
func _ready() -> void:
|
||||
if visuals == null:
|
||||
queue_free()
|
||||
return
|
||||
visuals.physics_interpolation_mode = Node.PHYSICS_INTERPOLATION_MODE_OFF
|
||||
add_child(visuals)
|
||||
visuals.process_mode = Node.PROCESS_MODE_DISABLED
|
||||
visuals.position = Vector2.ZERO
|
||||
match gib_type:
|
||||
0:
|
||||
velocity = Vector2(100 * direction, -200)
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
match gib_type:
|
||||
0:
|
||||
spin_move(delta)
|
||||
1:
|
||||
velocity.y += (15 / delta) * delta
|
||||
velocity.y = clamp(velocity.y, -INF, Global.entity_max_fall_speed)
|
||||
scale.y = -1
|
||||
|
||||
global_position += velocity * delta
|
||||
|
||||
func spin_move(delta: float) -> void:
|
||||
velocity.y += (15 / delta) * delta
|
||||
velocity.y = clamp(velocity.y, -INF, Global.entity_max_fall_speed)
|
||||
entity_rotation = (180 * direction)
|
||||
visuals.global_rotation_degrees = snapped(entity_rotation, 45)
|
||||
velocity.x = lerpf(velocity.x, 0, delta / 2)
|
||||
|
1
Scripts/Parts/EntityGib.gd.uid
Executable file
1
Scripts/Parts/EntityGib.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://sfu6fevr0yop
|
85
Scripts/Parts/EntityIdMapper.gd
Normal file
85
Scripts/Parts/EntityIdMapper.gd
Normal file
@@ -0,0 +1,85 @@
|
||||
@tool
|
||||
class_name EntityIDMapper
|
||||
extends Node
|
||||
|
||||
@export_tool_button("Update ID's") var button = update_map
|
||||
@export var auto_update := true
|
||||
static var map := {}
|
||||
|
||||
const MAP_PATH := "res://EntityIDMap.json"
|
||||
|
||||
const base64_charset := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
|
||||
var selectors_to_add := []
|
||||
|
||||
func _ready() -> void:
|
||||
map = JSON.parse_string(FileAccess.open(MAP_PATH, FileAccess.READ).get_as_text())
|
||||
if Engine.is_editor_hint() == false and OS.is_debug_build() and auto_update:
|
||||
update_map()
|
||||
|
||||
func update_map() -> void:
|
||||
map = JSON.parse_string(FileAccess.open(MAP_PATH, FileAccess.READ).get_as_text())
|
||||
get_ids()
|
||||
save_to_json()
|
||||
print("done")
|
||||
|
||||
func clear_map() -> void:
|
||||
map = {}
|
||||
save_to_json()
|
||||
|
||||
func get_ids() -> void:
|
||||
var id := 0
|
||||
for i: EditorTileSelector in get_tree().get_nodes_in_group("Selectors"):
|
||||
if i.type != 1 or i.entity_scene == null:
|
||||
continue
|
||||
var selector_id := encode_to_base64_2char(id)
|
||||
var value = get_selector_info_arr(i)
|
||||
id += 1
|
||||
if map.has(selector_id):
|
||||
if map.values().find(value) != -1:
|
||||
continue
|
||||
else:
|
||||
selector_id = encode_to_base64_2char(map.size())
|
||||
map.set(selector_id, get_selector_info_arr(i))
|
||||
|
||||
|
||||
static func get_selector_info_arr(selector: EditorTileSelector) -> Array:
|
||||
return [selector.entity_scene.resource_path, str(selector.tile_offset.x) + "," + str(selector.tile_offset.y)]
|
||||
|
||||
func save_to_json() -> void:
|
||||
var file = FileAccess.open(MAP_PATH, FileAccess.WRITE)
|
||||
file.store_string(JSON.stringify(map, "\t", false))
|
||||
file.close()
|
||||
|
||||
static func get_map_id(entity_scene := "") -> String:
|
||||
var idx := 0
|
||||
for i in map.values():
|
||||
if i[0] == entity_scene:
|
||||
return map.keys()[idx]
|
||||
idx += 1
|
||||
return ""
|
||||
|
||||
|
||||
func encode_to_base64_2char(value: int) -> String:
|
||||
if value < 0 or value >= 4096:
|
||||
push_error("Value out of range for 2-char base64 encoding.")
|
||||
return ""
|
||||
|
||||
var char1 = base64_charset[(value >> 6) & 0b111111] # Top 6 bits
|
||||
var char2 = base64_charset[value & 0b111111] # Bottom 6 bits
|
||||
|
||||
return char1 + char2
|
||||
|
||||
func decode_from_base64_2char(encoded: String) -> int:
|
||||
if encoded.length() != 2:
|
||||
push_error("Encoded string must be exactly 2 characters.")
|
||||
return -1
|
||||
|
||||
var idx1 = base64_charset.find(encoded[0])
|
||||
var idx2 = base64_charset.find(encoded[1])
|
||||
|
||||
if idx1 == -1 or idx2 == -1:
|
||||
push_error("Invalid character in base64 string.")
|
||||
return -1
|
||||
|
||||
return (idx1 << 6) | idx2
|
1
Scripts/Parts/EntityIdMapper.gd.uid
Executable file
1
Scripts/Parts/EntityIdMapper.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://c08y0c8163fec
|
4
Scripts/Parts/EntityTileContainer.gd
Executable file
4
Scripts/Parts/EntityTileContainer.gd
Executable file
@@ -0,0 +1,4 @@
|
||||
class_name EntityTileContainer
|
||||
extends Node2D
|
||||
|
||||
var entity: Node = null
|
1
Scripts/Parts/EntityTileContainer.gd.uid
Executable file
1
Scripts/Parts/EntityTileContainer.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://bg8681ssontei
|
8
Scripts/Parts/ExtraBgm.gd
Normal file
8
Scripts/Parts/ExtraBgm.gd
Normal file
@@ -0,0 +1,8 @@
|
||||
class_name ExtraBGM
|
||||
extends Node
|
||||
|
||||
@export var extra_track: JSON = null
|
||||
|
||||
func _ready() -> void:
|
||||
if Settings.file.audio.extra_bgm == 1:
|
||||
owner.music = extra_track
|
1
Scripts/Parts/ExtraBgm.gd.uid
Normal file
1
Scripts/Parts/ExtraBgm.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cusgxgerctsft
|
22
Scripts/Parts/FontUpdater.gd
Normal file
22
Scripts/Parts/FontUpdater.gd
Normal file
@@ -0,0 +1,22 @@
|
||||
class_name FontUpdater
|
||||
extends Node
|
||||
|
||||
@onready var resource_getter_smb1 := ResourceGetter.new()
|
||||
@onready var resource_getter_smbll := ResourceGetter.new()
|
||||
@onready var resource_getter_score := ResourceGetter.new()
|
||||
|
||||
@onready var FONT_LL_MAIN = load("uid://djxdgxy1iv8yv")
|
||||
@onready var FONT_MAIN = load("uid://bl7sbw4nx3l1t")
|
||||
@onready var SCORE_FONT = load("uid://cflgloiossd8a")
|
||||
|
||||
|
||||
static var current_font: Font = null
|
||||
|
||||
func _ready() -> void:
|
||||
update_fonts()
|
||||
Global.level_theme_changed.connect(update_fonts)
|
||||
|
||||
func update_fonts() -> void:
|
||||
FONT_MAIN.base_font = resource_getter_smb1.get_resource(FONT_MAIN.base_font)
|
||||
FONT_LL_MAIN.base_font = resource_getter_smbll.get_resource(FONT_LL_MAIN.base_font)
|
||||
SCORE_FONT.base_font = resource_getter_score.get_resource(SCORE_FONT.base_font)
|
1
Scripts/Parts/FontUpdater.gd.uid
Normal file
1
Scripts/Parts/FontUpdater.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ctsjagoa5t33f
|
66
Scripts/Parts/GameOver.gd
Normal file
66
Scripts/Parts/GameOver.gd
Normal file
@@ -0,0 +1,66 @@
|
||||
extends Node
|
||||
|
||||
@export var reset_level := false
|
||||
|
||||
@export var has_menu := false
|
||||
|
||||
var can_continue := false
|
||||
|
||||
func _enter_tree() -> void:
|
||||
Global.level_theme = "Underground"
|
||||
Global.level_theme_changed.emit()
|
||||
AudioManager.stop_all_music()
|
||||
|
||||
func _ready() -> void:
|
||||
get_tree().paused = false
|
||||
Global.lives = clamp(Global.lives, 0, 99)
|
||||
SpeedrunHandler.timer_active = false
|
||||
await get_tree().create_timer(0.1).timeout
|
||||
can_continue = true
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
print(can_continue)
|
||||
if Input.is_action_just_pressed("jump_0") and can_continue:
|
||||
go_back_to_title()
|
||||
can_continue = false
|
||||
|
||||
|
||||
func go_back_to_title() -> void:
|
||||
if has_menu:
|
||||
$Timer.queue_free()
|
||||
has_menu = false
|
||||
$CanvasLayer/VBoxContainer.show()
|
||||
$CanvasLayer/VBoxContainer/SelectableLabel.grab_focus()
|
||||
elif not reset_level:
|
||||
quit_to_menu()
|
||||
else:
|
||||
continue_on()
|
||||
|
||||
func continue_on() -> void:
|
||||
reset_values()
|
||||
LevelTransition.level_to_transition_to = Level.get_scene_string(Global.world_num, Global.level_num)
|
||||
Global.transition_to_scene("res://Scenes/Levels/LevelTransition.tscn")
|
||||
|
||||
func quit_to_menu() -> void:
|
||||
reset_values()
|
||||
Global.transition_to_scene("res://Scenes/Levels/TitleScreen.tscn")
|
||||
|
||||
func reset_values() -> void:
|
||||
if Global.world_num <= 8:
|
||||
ChallengeModeHandler.current_run_red_coins_collected = ChallengeModeHandler.red_coins_collected[Global.world_num - 1][Global.level_num - 1]
|
||||
Global.lives = 3
|
||||
Global.score = 0
|
||||
Global.player_power_states = "0000"
|
||||
Global.coins = 0
|
||||
if Global.current_game_mode == Global.GameMode.CHALLENGE:
|
||||
return
|
||||
match Settings.file.difficulty.game_over_behaviour:
|
||||
0:
|
||||
Global.level_num = 1
|
||||
1:
|
||||
pass
|
||||
2:
|
||||
Global.level_num = 1
|
||||
Global.world_num = 1
|
||||
Global.reset_values()
|
||||
SaveManager.write_save()
|
1
Scripts/Parts/GameOver.gd.uid
Executable file
1
Scripts/Parts/GameOver.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://b1dc01fk1eomt
|
12
Scripts/Parts/GlobalObjectNode.gd
Normal file
12
Scripts/Parts/GlobalObjectNode.gd
Normal file
@@ -0,0 +1,12 @@
|
||||
class_name SecondQuestNode
|
||||
extends Node
|
||||
|
||||
@export var enabled := true
|
||||
@export var nodes_to_delete: Array[Node] = []
|
||||
|
||||
func _ready() -> void:
|
||||
if Global.second_quest or enabled:
|
||||
for i in nodes_to_delete:
|
||||
i.queue_free()
|
||||
else:
|
||||
queue_free()
|
1
Scripts/Parts/GlobalObjectNode.gd.uid
Normal file
1
Scripts/Parts/GlobalObjectNode.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://co8w6sjp0t2dm
|
29
Scripts/Parts/GravityInverter.gd
Normal file
29
Scripts/Parts/GravityInverter.gd
Normal file
@@ -0,0 +1,29 @@
|
||||
extends EntityGenerator
|
||||
|
||||
|
||||
const new_vector = Vector2.UP
|
||||
|
||||
func activate() -> void:
|
||||
for i in get_tree().get_nodes_in_group("Players"):
|
||||
on_player_entered(i)
|
||||
|
||||
func deactivate() -> void:
|
||||
for i in get_tree().get_nodes_in_group("Players"):
|
||||
on_player_exited(i)
|
||||
|
||||
func on_player_entered(player: Player) -> void:
|
||||
if player.gravity_vector == new_vector:
|
||||
return
|
||||
player.gravity_vector = new_vector
|
||||
player.global_position.y -= 16
|
||||
player.global_rotation = -player.gravity_vector.angle() + deg_to_rad(90)
|
||||
player.reset_physics_interpolation()
|
||||
|
||||
func on_player_exited(player: Player) -> void:
|
||||
if player.gravity_vector == Vector2.DOWN:
|
||||
return
|
||||
player.gravity_vector = Vector2.DOWN
|
||||
player.global_position.y += 16
|
||||
player.velocity.y *= 1.1
|
||||
player.global_rotation = -player.gravity_vector.angle() + deg_to_rad(90)
|
||||
player.reset_physics_interpolation()
|
1
Scripts/Parts/GravityInverter.gd.uid
Normal file
1
Scripts/Parts/GravityInverter.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dgqdo5cvl41l0
|
33
Scripts/Parts/LabelFontChanger.gd
Normal file
33
Scripts/Parts/LabelFontChanger.gd
Normal file
@@ -0,0 +1,33 @@
|
||||
class_name LabelFontChanger
|
||||
extends Node
|
||||
|
||||
@export var labels: Array[Label]
|
||||
|
||||
const SMB1 = preload("uid://bl7sbw4nx3l1t")
|
||||
const SMBLL = preload("uid://djxdgxy1iv8yv")
|
||||
const SCORE_FONT = preload("uid://bk0no5p6sifgu")
|
||||
|
||||
@export var use_score_font := false
|
||||
|
||||
static var current_font: Font = null
|
||||
|
||||
func _ready() -> void:
|
||||
refresh_font()
|
||||
Global.level_theme_changed.connect(refresh_font)
|
||||
|
||||
func refresh_font() -> void:
|
||||
if Global.current_campaign == "SMBLL":
|
||||
current_font = SMBLL
|
||||
else:
|
||||
current_font = SMB1
|
||||
update_labels()
|
||||
|
||||
func update_labels() -> void:
|
||||
var font_to_use = current_font
|
||||
if use_score_font:
|
||||
font_to_use = SCORE_FONT
|
||||
for i in labels:
|
||||
if i == null:
|
||||
continue
|
||||
i.remove_theme_font_override("font")
|
||||
i.add_theme_font_override("font", font_to_use)
|
1
Scripts/Parts/LabelFontChanger.gd.uid
Executable file
1
Scripts/Parts/LabelFontChanger.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://co6tjg3w6qpd8
|
7
Scripts/Parts/LabelTextCopier.gd
Executable file
7
Scripts/Parts/LabelTextCopier.gd
Executable file
@@ -0,0 +1,7 @@
|
||||
extends Node
|
||||
|
||||
@export var labels: Dictionary[Label, Label] = {}
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
for i in labels.keys():
|
||||
labels[i].text = i.text
|
1
Scripts/Parts/LabelTextCopier.gd.uid
Executable file
1
Scripts/Parts/LabelTextCopier.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://ctbxx8n6icrui
|
18
Scripts/Parts/LeapingCheepCheepArea.gd
Executable file
18
Scripts/Parts/LeapingCheepCheepArea.gd
Executable file
@@ -0,0 +1,18 @@
|
||||
extends Area2D
|
||||
|
||||
static var meter := 0.0
|
||||
|
||||
const LEAPING_CHEEP_CHEEP = preload("res://Scenes/Prefabs/Entities/Enemies/LeapingCheepCheep.tscn")
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if get_overlapping_areas().any(func(area: Area2D): return area.owner is Player) != false:
|
||||
meter += 1 * delta
|
||||
if meter >= 1:
|
||||
meter = 0
|
||||
spawn_cheep_cheep()
|
||||
|
||||
func spawn_cheep_cheep() -> void:
|
||||
var node = LEAPING_CHEEP_CHEEP.instantiate()
|
||||
node.global_position.x = get_viewport().get_camera_2d().get_screen_center_position().x + [ -32 ,-64, -96, -128].pick_random()
|
||||
node.global_position.y = 48
|
||||
add_sibling(node)
|
1
Scripts/Parts/LeapingCheepCheepArea.gd.uid
Executable file
1
Scripts/Parts/LeapingCheepCheepArea.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://dxt4s20sjndph
|
34
Scripts/Parts/LevelBG.gd
Executable file
34
Scripts/Parts/LevelBG.gd
Executable file
@@ -0,0 +1,34 @@
|
||||
extends Node2D
|
||||
|
||||
func _enter_tree() -> void:
|
||||
setup_bg_scrolling()
|
||||
|
||||
var repeat_times := 1:
|
||||
set(value):
|
||||
if repeat_times != value:
|
||||
repeat_times = value
|
||||
update_repeats()
|
||||
|
||||
@export var scroll_scale := 0.5
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
repeat_times = ceil(get_viewport_rect().size.x / 512) + 1
|
||||
|
||||
func update_repeats() -> void:
|
||||
for i in get_children():
|
||||
if i is Parallax2D:
|
||||
i.repeat_times = repeat_times
|
||||
|
||||
func setup_bg_scrolling() -> void:
|
||||
var scr_scale = scroll_scale
|
||||
match Global.parallax_style:
|
||||
0:
|
||||
scr_scale = 1
|
||||
1:
|
||||
scr_scale = scroll_scale
|
||||
2:
|
||||
return
|
||||
for i in get_children():
|
||||
if i is Parallax2D:
|
||||
if i.scroll_scale.x < 1:
|
||||
i.scroll_scale.x = scr_scale
|
1
Scripts/Parts/LevelBG.gd.uid
Executable file
1
Scripts/Parts/LevelBG.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://dyifmk51nnr8l
|
11
Scripts/Parts/LevelGuide.gd
Normal file
11
Scripts/Parts/LevelGuide.gd
Normal file
@@ -0,0 +1,11 @@
|
||||
@tool
|
||||
class_name LevelGuide
|
||||
extends Sprite2D
|
||||
|
||||
func _ready() -> void:
|
||||
if Engine.is_editor_hint() == false:
|
||||
queue_free()
|
||||
else:
|
||||
position = Vector2(-256, -208)
|
||||
centered = false
|
||||
modulate.a = 0.5
|
1
Scripts/Parts/LevelGuide.gd.uid
Normal file
1
Scripts/Parts/LevelGuide.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b64va0yqgoixo
|
174
Scripts/Parts/LevelLoader.gd
Normal file
174
Scripts/Parts/LevelLoader.gd
Normal file
@@ -0,0 +1,174 @@
|
||||
extends Node
|
||||
|
||||
var entity_map := {}
|
||||
|
||||
@onready var editor: LevelEditor = owner
|
||||
|
||||
const base64_charset := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
@onready var level: Level = $"../Level"
|
||||
@onready var level_bg: LevelBG = $"../Level/LevelBG"
|
||||
|
||||
var sub_level_file = null
|
||||
|
||||
func _ready() -> void:
|
||||
load_entity_map()
|
||||
|
||||
func load_level(level_idx := 0) -> void:
|
||||
clear_level()
|
||||
sub_level_file = editor.level_file["Levels"][level_idx]
|
||||
build_level()
|
||||
|
||||
func clear_level() -> void:
|
||||
for layer in 5:
|
||||
for i in editor.entity_layer_nodes[layer].get_children():
|
||||
if i is Player:
|
||||
reset_player(i)
|
||||
continue
|
||||
i.queue_free()
|
||||
var connect_tiles = editor.tile_layer_nodes[layer].get_used_cells_by_id(0, Vector2i(13, 8))
|
||||
editor.tile_layer_nodes[layer].clear()
|
||||
for i in connect_tiles:
|
||||
editor.tile_layer_nodes[layer].set_cell(i, 0, Vector2i(13, 8))
|
||||
editor.entity_tiles = [{}, {}, {}, {}, {}]
|
||||
|
||||
func load_entity_map() -> void:
|
||||
entity_map = JSON.parse_string(FileAccess.open(EntityIDMapper.MAP_PATH, FileAccess.READ).get_as_text())
|
||||
|
||||
func build_level() -> void:
|
||||
if sub_level_file.is_empty():
|
||||
return
|
||||
var layer_id := 0
|
||||
for layer in sub_level_file["Layers"]:
|
||||
for chunk_id in layer:
|
||||
var chunk = layer[chunk_id]
|
||||
add_tiles(LevelSaver.decompress_string(chunk["Tiles"]), int(chunk_id), int(layer_id))
|
||||
add_entities(LevelSaver.decompress_string(chunk["Entities"]), int(chunk_id), int(layer_id))
|
||||
layer_id += 1
|
||||
apply_level_data(sub_level_file["Data"])
|
||||
apply_bg_data(sub_level_file["BG"])
|
||||
|
||||
func add_tiles(chunk := "", chunk_id := 0, layer := 0) -> void:
|
||||
for tile in chunk.split("=", false):
|
||||
var tile_position := Vector2i.ZERO
|
||||
var tile_atlas_position := Vector2i.ZERO
|
||||
var source_id := 0
|
||||
|
||||
tile_position = decode_tile_position_from_chars(tile[0], tile[1], chunk_id)
|
||||
source_id = base64_charset.find(tile[4])
|
||||
tile_atlas_position = Vector2i(base64_charset.find(tile[2]), base64_charset.find(tile[3]))
|
||||
editor.tile_layer_nodes[layer].set_cell(tile_position, source_id, tile_atlas_position)
|
||||
|
||||
func add_entities(chunk := "", chunk_id := 0, layer := 0) -> void:
|
||||
for entity in chunk.split("=", false):
|
||||
var entity_id = entity.get_slice(",", 1)
|
||||
var entity_chunk_position = entity.get_slice(",", 0)
|
||||
var entity_tile_position = decode_tile_position_from_chars(entity_chunk_position[0], entity_chunk_position[1], chunk_id)
|
||||
var entity_node: Node = null
|
||||
if entity_map.has(entity_id) == false:
|
||||
Global.log_error("MISSING ENTITY ID!!!! JOE FORGOT TO UPDATE THE MAP AGAIN :(")
|
||||
continue
|
||||
if entity_map[entity_id][0] != "res://Scenes/Prefabs/Entities/Player.tscn":
|
||||
entity_node = load(entity_map[entity_id][0]).instantiate()
|
||||
else:
|
||||
entity_node = get_tree().get_first_node_in_group("Players")
|
||||
var offset = entity_map[entity_id][1].split(",")
|
||||
entity_node.global_position = entity_tile_position * 16 + (Vector2i(8, 8) + Vector2i(int(offset[0]), int(offset[1])))
|
||||
editor.entity_layer_nodes[layer].add_child(entity_node)
|
||||
entity_node.reset_physics_interpolation()
|
||||
entity_node.owner = editor
|
||||
editor.entity_tiles[layer][entity_tile_position] = entity_node
|
||||
entity_node.set_meta("tile_position", entity_tile_position)
|
||||
entity_node.set_meta("tile_offset", Vector2(int(offset[0]), int(offset[1])))
|
||||
if entity_node.has_node("EditorPropertyExposer"):
|
||||
entity_node.get_node("EditorPropertyExposer").apply_string(entity)
|
||||
|
||||
func reset_player(player: Player) -> void: ## Function literally here to just reset the player back to default starting, if loading into a level file, that hasnt been written yet (pipes)
|
||||
player.show()
|
||||
player.state_machine.transition_to("Normal")
|
||||
player.global_position = Vector2(-232, 0)
|
||||
|
||||
func gzip_encode(text: String) -> String:
|
||||
var bytes = Marshalls.base64_to_raw(text)
|
||||
bytes.compress(FileAccess.COMPRESSION_GZIP)
|
||||
return Marshalls.raw_to_base64(bytes)
|
||||
|
||||
func gzip_decode(text: String) -> String:
|
||||
var bytes = Marshalls.base64_to_raw(text)
|
||||
bytes.decompress_dynamic(-1, FileAccess.COMPRESSION_GZIP)
|
||||
return Marshalls.raw_to_base64(bytes)
|
||||
|
||||
func apply_level_data(data := "") -> void:
|
||||
var split = data.split("=")
|
||||
var values := []
|
||||
for i in split:
|
||||
if i.length() == 2:
|
||||
values.append(decode_from_base64_2char(i))
|
||||
elif i.length() == 1:
|
||||
values.append(base64_charset.find(i))
|
||||
else:
|
||||
values.append(i)
|
||||
level.theme = Level.THEME_IDXS[values[0]]
|
||||
Global.level_theme = level.theme
|
||||
level.theme_time = ["Day", "Night"][values[1]]
|
||||
Global.theme_time = level.theme_time
|
||||
editor.bgm_id = values[2]
|
||||
level.campaign = ["SMB1", "SMBLL", "SMBS", "SMBANN"][values[3]]
|
||||
Global.current_campaign = level.campaign
|
||||
level.can_backscroll = bool(values[4])
|
||||
level.vertical_height = -int(values[5])
|
||||
level.time_limit = int(values[6])
|
||||
%ThemeTime.selected = values[1]
|
||||
%LevelMusic.selected = values[2]
|
||||
%Campaign.selected = values[3]
|
||||
%BackScroll.set_pressed_no_signal(bool(values[4]))
|
||||
%HeightLimit.value = values[5]
|
||||
%TimeLimit.value = values[6]
|
||||
%SubLevelID.selected = editor.sub_level_id
|
||||
ResourceSetterNew.cache.clear()
|
||||
Global.level_theme_changed.emit()
|
||||
|
||||
func apply_bg_data(data := "") -> void:
|
||||
var split = data.split("=", false)
|
||||
var id := 0
|
||||
|
||||
const BG_VALUES := ["primary_layer", "second_layer", "second_layer_offset", "time_of_day", "particles", "liquid_layer", "overlay_clouds"]
|
||||
var SELECTORS = [%PrimaryLayer, %SecondLayer, %SecondLayerOffset, %TimeOfDay, %Particles, %LiquidLayer, %OverlayClouds]
|
||||
for i in split:
|
||||
var value := 0
|
||||
if i.length() > 1:
|
||||
value = (decode_from_base64_2char(i))
|
||||
else:
|
||||
value = (base64_charset.find(i))
|
||||
if SELECTORS[id] is SpinBox:
|
||||
SELECTORS[id].value = value
|
||||
elif SELECTORS[id] is Button:
|
||||
SELECTORS[id].set_pressed_no_signal(bool(value))
|
||||
else:
|
||||
SELECTORS[id].selected = value
|
||||
level_bg.set_value(value, BG_VALUES[id])
|
||||
id += 1
|
||||
|
||||
|
||||
func decode_tile_position_from_chars(char_x: String, char_y: String, chunk_idx: int) -> Vector2i:
|
||||
|
||||
var local_x = base64_charset.find(char_x)
|
||||
var local_y = base64_charset.find(char_y)
|
||||
|
||||
return Vector2i(local_x + (chunk_idx * 32), local_y - 30)
|
||||
|
||||
func decode_from_base64_2char(encoded: String) -> int:
|
||||
if encoded.length() != 2:
|
||||
push_error("Encoded string must be exactly 2 characters.")
|
||||
return -1
|
||||
|
||||
var idx1 = base64_charset.find(encoded[0])
|
||||
var idx2 = base64_charset.find(encoded[1])
|
||||
|
||||
if idx1 == -1 or idx2 == -1:
|
||||
push_error("Invalid character in base64 string.")
|
||||
return -1
|
||||
|
||||
return (idx1 << 6) | idx2
|
||||
|
||||
func tile_to_chunk_idx(tile_position := Vector2i.ZERO) -> int:
|
||||
return floor(tile_position.x / 32.0)
|
1
Scripts/Parts/LevelLoader.gd.uid
Executable file
1
Scripts/Parts/LevelLoader.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://clsnunmd42u6d
|
149
Scripts/Parts/LevelSaver.gd
Normal file
149
Scripts/Parts/LevelSaver.gd
Normal file
@@ -0,0 +1,149 @@
|
||||
class_name LevelSaver
|
||||
extends Node
|
||||
|
||||
var sub_level_file := {"Layers": [{}, {}, {}, {}, {}], "Data": "", "BG": ""}
|
||||
|
||||
static var level_file := {"Info": {}, "Levels": [{}, {}, {}, {}, {}]}
|
||||
|
||||
@onready var editor: LevelEditor = owner
|
||||
|
||||
const chunk_template := {"Tiles": "", "Entities": ""}
|
||||
|
||||
const base64_charset := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
@onready var level: Level = $"../Level"
|
||||
@onready var level_bg: LevelBG = $"../Level/LevelBG"
|
||||
|
||||
var entity_map := {}
|
||||
|
||||
const tile_blacklist := []
|
||||
|
||||
func _ready() -> void:
|
||||
load_entity_map()
|
||||
|
||||
func save_level(level_name := "Unnamed Level", level_author := "You", level_desc := "No Desc", difficulty := 0) -> Dictionary:
|
||||
level_file = editor.level_file
|
||||
sub_level_file = {"Layers": [{}, {}, {}, {}, {}], "Data": "", "BG": ""}
|
||||
get_tiles()
|
||||
get_entities()
|
||||
|
||||
save_bg_data()
|
||||
save_level_data()
|
||||
level_file["Levels"][editor.sub_level_id] = sub_level_file.duplicate()
|
||||
level_file["Info"] = {"Name": level_name, "Author": level_author, "Description": level_desc, "Difficulty": difficulty}
|
||||
level_file["Version"] = Global.version_number
|
||||
return level_file
|
||||
|
||||
func write_file(json := {}, lvl_file_name := "") -> void:
|
||||
DirAccess.make_dir_absolute(LevelEditor.CUSTOM_LEVEL_DIR)
|
||||
for i in "<>:?!/":
|
||||
lvl_file_name = lvl_file_name.replace(i, "")
|
||||
var file = FileAccess.open(LevelEditor.CUSTOM_LEVEL_DIR + lvl_file_name, FileAccess.WRITE)
|
||||
file.store_string(JSON.stringify(json, "", false))
|
||||
file.close()
|
||||
print("saved")
|
||||
|
||||
func load_entity_map() -> void:
|
||||
entity_map = JSON.parse_string(FileAccess.open(EntityIDMapper.MAP_PATH, FileAccess.READ).get_as_text())
|
||||
|
||||
func get_tiles() -> void:
|
||||
for layer in 5:
|
||||
for tile in editor.tile_layer_nodes[layer].get_used_cells():
|
||||
if tile_blacklist.has(editor.tile_layer_nodes[layer].get_cell_atlas_coords(tile)) or editor.tile_layer_nodes[layer].get_cell_source_id(tile) == 6:
|
||||
continue
|
||||
var tile_string := ""
|
||||
var chunk_tile = Vector2i(wrap(tile.x, 0, 32), wrap(tile.y + 30, -1, 32))
|
||||
tile_string += base64_charset[chunk_tile.x]
|
||||
tile_string += base64_charset[chunk_tile.y]
|
||||
tile_string += ""
|
||||
tile_string += base64_charset[editor.tile_layer_nodes[layer].get_cell_atlas_coords(tile).x]
|
||||
tile_string += base64_charset[editor.tile_layer_nodes[layer].get_cell_atlas_coords(tile).y]
|
||||
tile_string += base64_charset[editor.tile_layer_nodes[layer].get_cell_source_id(tile)]
|
||||
var tile_chunk_idx = tile_to_chunk_idx(tile)
|
||||
var tile_chunk := {}
|
||||
if sub_level_file["Layers"][layer].has(tile_chunk_idx):
|
||||
tile_chunk = sub_level_file["Layers"][layer][tile_chunk_idx]
|
||||
else:
|
||||
tile_chunk = chunk_template.duplicate(true)
|
||||
tile_chunk["Tiles"] += tile_string + "="
|
||||
sub_level_file["Layers"][layer][tile_chunk_idx] = tile_chunk
|
||||
for i in sub_level_file["Layers"][layer]:
|
||||
sub_level_file["Layers"][layer][i]["Tiles"] = compress_string(sub_level_file["Layers"][layer][i]["Tiles"])
|
||||
|
||||
|
||||
static func compress_string(buffer := "") -> String:
|
||||
var bytes = buffer.to_ascii_buffer()
|
||||
var compressed_bytes = bytes.compress(FileAccess.CompressionMode.COMPRESSION_DEFLATE)
|
||||
var b64_buffer = Marshalls.raw_to_base64(compressed_bytes)
|
||||
# workaround since for some reason .replace() decided not to work today
|
||||
b64_buffer = b64_buffer.replace("=", "%")
|
||||
return b64_buffer
|
||||
|
||||
static func decompress_string(buffer := "") -> String:
|
||||
if buffer.is_empty():
|
||||
return buffer
|
||||
buffer = buffer.replace("%", "=")
|
||||
var compressed = Marshalls.base64_to_raw(buffer)
|
||||
var decompressed = compressed.decompress_dynamic(-1, FileAccess.CompressionMode.COMPRESSION_DEFLATE)
|
||||
var ret = decompressed.get_string_from_ascii()
|
||||
return ret
|
||||
|
||||
func get_entities() -> void:
|
||||
for layer in 5:
|
||||
for entity in editor.entity_layer_nodes[layer].get_children():
|
||||
if entity.has_meta("tile_position") == false:
|
||||
continue
|
||||
var entity_string := ""
|
||||
var chunk_position = Vector2i(wrap(entity.get_meta("tile_position").x, 0, 32), wrap(entity.get_meta("tile_position").y + 30, 0, 32))
|
||||
entity_string += base64_charset[chunk_position.x]
|
||||
entity_string += base64_charset[chunk_position.y]
|
||||
entity_string += ","
|
||||
|
||||
entity_string += EntityIDMapper.get_map_id(entity.scene_file_path)
|
||||
if entity.has_node("EditorPropertyExposer"):
|
||||
entity_string += entity.get_node("EditorPropertyExposer").get_string()
|
||||
var entity_chunk_idx = tile_to_chunk_idx(entity.get_meta("tile_position"))
|
||||
var tile_chunk := {}
|
||||
if sub_level_file["Layers"][layer].has(entity_chunk_idx):
|
||||
tile_chunk = sub_level_file["Layers"][layer][entity_chunk_idx]
|
||||
else:
|
||||
tile_chunk = chunk_template.duplicate(true)
|
||||
tile_chunk["Entities"] += entity_string + "="
|
||||
sub_level_file["Layers"][layer][entity_chunk_idx] = tile_chunk
|
||||
for i in sub_level_file["Layers"][layer]:
|
||||
sub_level_file["Layers"][layer][i]["Entities"] = compress_string(sub_level_file["Layers"][layer][i]["Entities"])
|
||||
|
||||
func encode_to_base64_2char(value: int) -> String:
|
||||
if value < 0 or value >= 4096:
|
||||
push_error("Value out of range for 2-char base64 encoding.")
|
||||
return ""
|
||||
|
||||
var char1 = base64_charset[(value >> 6) & 0b111111] # Top 6 bits
|
||||
var char2 = base64_charset[value & 0b111111] # Bottom 6 bits
|
||||
|
||||
return char1 + char2
|
||||
|
||||
func save_level_data() -> void:
|
||||
var string := ""
|
||||
for i in [Level.THEME_IDXS.find(level.theme), ["Day", "Night"].find(level.theme_time), editor.bgm_id, ["SMB1", "SMBLL", "SMBS", "SMBANN"].find(level.campaign), level.can_backscroll, abs(level.vertical_height), level.time_limit]:
|
||||
var key := ""
|
||||
if int(i) >= 64:
|
||||
key = encode_to_base64_2char(int(i))
|
||||
else:
|
||||
key = base64_charset[int(i)]
|
||||
string += key + "="
|
||||
sub_level_file["Data"] = string
|
||||
|
||||
func save_bg_data() -> void:
|
||||
var string := ""
|
||||
for i in [level_bg.primary_layer, level_bg.second_layer, level_bg.second_layer_offset.y, level_bg.time_of_day, level_bg.particles, level_bg.liquid_layer, level_bg.overlay_clouds]:
|
||||
var key := ""
|
||||
i = int(i)
|
||||
if abs(i) >= 64:
|
||||
key = encode_to_base64_2char(abs(i))
|
||||
else:
|
||||
key = base64_charset[abs(i)]
|
||||
string += key + "="
|
||||
sub_level_file["BG"] = string
|
||||
|
||||
func tile_to_chunk_idx(tile_position := Vector2i.ZERO) -> int:
|
||||
return floor(tile_position.x / 32.0)
|
1
Scripts/Parts/LevelSaver.gd.uid
Executable file
1
Scripts/Parts/LevelSaver.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://b77kpu65i243a
|
116
Scripts/Parts/LevelTransition.gd
Normal file
116
Scripts/Parts/LevelTransition.gd
Normal file
@@ -0,0 +1,116 @@
|
||||
class_name LevelTransition
|
||||
extends Node
|
||||
|
||||
const PIPE_CUTSCENE_LEVELS := {
|
||||
"SMB1": [[1, 2], [2, 2], [4, 2], [7, 2]],
|
||||
"SMBLL": [[1, 2], [3, 2], [5, 2], [6, 2], [10, 2], [11, 2]],
|
||||
"SMBS": [[1, 2], [2, 2], [3, 1], [7, 2], [8, 3]],
|
||||
"SMBANN": []
|
||||
}
|
||||
|
||||
const PIPE_CUTSCENE_OVERRIDE := {
|
||||
"SMB1": {[2, 2]: "res://Scenes/Levels/PipeCutsceneWater.tscn", [7, 2]: "res://Scenes/Levels/PipeCutsceneWater.tscn"},
|
||||
"SMBLL": {[3, 2]: "res://Scenes/Levels/PipeCutsceneWater.tscn", [11, 2]: "res://Scenes/Levels/PipeCutsceneWater.tscn"},
|
||||
"SMBS": {[3, 1]: "res://Scenes/Levels/SMBS/SPCastlePipeCutscene.tscn", [7, 2]: "res://Scenes/Levels/PipeCutsceneWater.tscn"},
|
||||
"SMBANN": {}
|
||||
}
|
||||
|
||||
|
||||
var can_transition := false
|
||||
var level_best_time := 0.0
|
||||
|
||||
static var level_to_transition_to := "res://Scenes/Levels/World1/1-1.tscn":
|
||||
set(value):
|
||||
level_to_transition_to = value
|
||||
pass
|
||||
|
||||
@export var text_shadows: Array[Label] = []
|
||||
|
||||
func _ready() -> void:
|
||||
WarpPipeArea.has_warped = false
|
||||
Global.level_theme = "Underground"
|
||||
$BG/Control/MarathonPB.visible = Global.current_game_mode == Global.GameMode.MARATHON_PRACTICE
|
||||
$BG/Control/LivesCount.visible = Global.current_game_mode != Global.GameMode.MARATHON_PRACTICE
|
||||
Level.can_set_time = true
|
||||
ResourceSetterNew.cache.clear()
|
||||
ResourceSetterNew.property_cache.clear()
|
||||
AudioManager.current_level_theme = ""
|
||||
Level.vine_return_level = ""
|
||||
Level.vine_warp_level = ""
|
||||
Level.in_vine_level = false
|
||||
Global.p_switch_active = false
|
||||
Lakitu.present = false
|
||||
Global.p_switch_timer = -1
|
||||
if Global.current_campaign == "SMBANN":
|
||||
DiscoLevel.reset_values()
|
||||
DiscoLevel.first_load = true
|
||||
if Global.current_game_mode == Global.GameMode.MARATHON_PRACTICE:
|
||||
Global.clear_saved_values()
|
||||
if SpeedrunHandler.ghost_enabled:
|
||||
SpeedrunHandler.load_best_marathon()
|
||||
SpeedrunHandler.ghost_active = false
|
||||
show_best_time()
|
||||
Level.first_load = true
|
||||
SpeedrunHandler.ghost_idx = -1
|
||||
SpeedrunHandler.timer_active = false
|
||||
SpeedrunHandler.timer = 0
|
||||
get_tree().call_group("PlayerGhosts", "delete")
|
||||
get_tree().paused = false
|
||||
$Timer.start()
|
||||
AudioManager.stop_music_override(AudioManager.MUSIC_OVERRIDES.NONE, true)
|
||||
AudioManager.music_player.stop()
|
||||
PipeArea.exiting_pipe_id = -1
|
||||
var world_num = str(Global.world_num)
|
||||
if world_num == "-1":
|
||||
world_num = " "
|
||||
if Global.world_num >= 10:
|
||||
world_num = ["A", "B", "C", "D"][Global.world_num % 10]
|
||||
|
||||
var lvl_idx := SaveManager.get_level_idx(Global.world_num, Global.level_num)
|
||||
SaveManager.visited_levels[lvl_idx] = "1"
|
||||
|
||||
if Global.current_game_mode == Global.GameMode.CAMPAIGN:
|
||||
SaveManager.write_save(Global.current_campaign)
|
||||
Global.set_discord_status("Playing " + Global.current_campaign + ": " + str(world_num) + "-" + str(Global.level_num))
|
||||
$BG/Control/WorldNum.text = str(world_num) +"-" + str(Global.level_num)
|
||||
if Settings.file.difficulty.inf_lives:
|
||||
$BG/Control/LivesCount.text = "* ∞"
|
||||
elif Global.lives < 100:
|
||||
$BG/Control/LivesCount.text = "* " + (str(Global.lives).lpad(2, " "))
|
||||
else:
|
||||
$BG/Control/LivesCount.text = "* ♕"
|
||||
if Global.current_game_mode == Global.GameMode.CUSTOM_LEVEL:
|
||||
$BG/Control/World.hide()
|
||||
$BG/Control/WorldNum.hide()
|
||||
%CustomLevelAuthor.show()
|
||||
%CustomLevelName.show()
|
||||
%CustomLevelAuthor.text = "By " + LevelEditor.level_author
|
||||
%CustomLevelName.text = LevelEditor.level_name
|
||||
await get_tree().create_timer(0.1, false).timeout
|
||||
can_transition = true
|
||||
|
||||
func transition() -> void:
|
||||
Global.can_time_tick = true
|
||||
if PIPE_CUTSCENE_LEVELS[Global.current_campaign].has([Global.world_num, Global.level_num]) and not PipeCutscene.seen_cutscene and Global.current_game_mode != Global.GameMode.MARATHON_PRACTICE and Global.current_game_mode !=Global.GameMode.BOO_RACE:
|
||||
if PIPE_CUTSCENE_OVERRIDE[Global.current_campaign].has([Global.world_num, Global.level_num]):
|
||||
Global.transition_to_scene(PIPE_CUTSCENE_OVERRIDE[Global.current_campaign][[Global.world_num, Global.level_num]])
|
||||
else:
|
||||
Global.transition_to_scene("res://Scenes/Levels/PipeCutscene.tscn")
|
||||
else:
|
||||
Global.transition_to_scene(level_to_transition_to)
|
||||
|
||||
func show_best_time() -> void:
|
||||
var best_time = SpeedrunHandler.best_time
|
||||
if SpeedrunHandler.best_time <= 0:
|
||||
$BG/Control/MarathonPB.text = "\nNO PB"
|
||||
return
|
||||
var string = "PB\n" + SpeedrunHandler.gen_time_string(SpeedrunHandler.format_time(SpeedrunHandler.best_time))
|
||||
$BG/Control/MarathonPB.text = string
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if can_transition:
|
||||
if Input.is_action_just_pressed("jump_0"):
|
||||
transition()
|
||||
|
||||
func _exit_tree() -> void:
|
||||
Global.death_load = false
|
1
Scripts/Parts/LevelTransition.gd.uid
Executable file
1
Scripts/Parts/LevelTransition.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://vwe7y2scu8a1
|
51
Scripts/Parts/LostLevelsEnding.gd
Normal file
51
Scripts/Parts/LostLevelsEnding.gd
Normal file
@@ -0,0 +1,51 @@
|
||||
extends Level
|
||||
|
||||
var can_exit := false
|
||||
|
||||
var seen := false
|
||||
|
||||
func _enter_tree() -> void:
|
||||
update_next_level_info()
|
||||
seen = Global.game_beaten
|
||||
Global.game_beaten = true
|
||||
Global.can_time_tick = false
|
||||
Global.current_level = self
|
||||
if Global.world_num > 8:
|
||||
Global.extra_worlds_win = true
|
||||
update_theme()
|
||||
Global.current_campaign = campaign
|
||||
|
||||
func _ready() -> void:
|
||||
AudioManager.set_music_override(AudioManager.MUSIC_OVERRIDES.ENDING, 9999999, false)
|
||||
Global.can_time_tick = false
|
||||
SaveManager.visited_levels[SaveManager.get_level_idx(9, 1)] = "1"
|
||||
$Text2/Hero.text = tr("CUTSCENE_LL_PEACH_4" if Global.player_characters[0] != "3" else "CUTSCENE_LL_PEACH_4F")
|
||||
$Text2/Hurrah.text = tr("CUTSCENE_LL_PEACH_3").replace("{PLAYER}", tr(Player.CHARACTER_NAMES[int(Global.player_characters[0])]))
|
||||
$ThankYou.text = tr("CUTSCENE_CASTLE_PEACH_1").replace("{PLAYER}", tr(Player.CHARACTER_NAMES[int(Global.player_characters[0])]))
|
||||
func _process(_delta: float) -> void:
|
||||
if can_exit and Input.is_action_just_pressed("jump_0"):
|
||||
SaveManager.write_save()
|
||||
if seen or Global.current_campaign == "SMBANN" or Global.world_num > 8 or Global.current_game_mode != Global.GameMode.CAMPAIGN:
|
||||
Global.transition_to_scene("res://Scenes/Levels/TitleScreen.tscn")
|
||||
else:
|
||||
CreditsLevel.go_to_title_screen = false
|
||||
Global.transition_to_scene("res://Scenes/Levels/Credits.tscn")
|
||||
$LevelBG.combo_progress = 1
|
||||
DiscoLevel.can_meter_tick = false
|
||||
|
||||
func tally_score() -> void:
|
||||
add_lives_to_score()
|
||||
|
||||
func add_lives_to_score() -> void:
|
||||
for i in Global.lives:
|
||||
AudioManager.play_global_sfx("1_up")
|
||||
Global.score += 100000
|
||||
await get_tree().create_timer(0.5, false).timeout
|
||||
|
||||
func show_toads() -> void:
|
||||
for i in $Toads.get_children():
|
||||
i.show()
|
||||
AudioManager.play_global_sfx("coin")
|
||||
await get_tree().create_timer(0.7, false).timeout
|
||||
await get_tree().create_timer(1, false).timeout
|
||||
can_exit = true
|
1
Scripts/Parts/LostLevelsEnding.gd.uid
Executable file
1
Scripts/Parts/LostLevelsEnding.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://caugelxhwlvl0
|
15
Scripts/Parts/LostLevelsEndingDoor.gd
Normal file
15
Scripts/Parts/LostLevelsEndingDoor.gd
Normal file
@@ -0,0 +1,15 @@
|
||||
extends Node2D
|
||||
|
||||
|
||||
@export_file("*.tscn") var scene := ""
|
||||
|
||||
func begin() -> void:
|
||||
$CameraRightLimit._enter_tree()
|
||||
await AudioManager.music_override_player.finished
|
||||
await get_tree().create_timer(1, false).timeout
|
||||
if Global.current_game_mode == Global.GameMode.MARATHON or Global.current_game_mode == Global.GameMode.MARATHON_PRACTICE:
|
||||
Global.open_marathon_results()
|
||||
return
|
||||
$StaticBody2D.queue_free()
|
||||
await get_tree().create_timer(2, false).timeout
|
||||
Global.transition_to_scene(scene)
|
1
Scripts/Parts/LostLevelsEndingDoor.gd.uid
Executable file
1
Scripts/Parts/LostLevelsEndingDoor.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://d23r1hm6jbgox
|
9
Scripts/Parts/MinusWorldBlock.gd
Executable file
9
Scripts/Parts/MinusWorldBlock.gd
Executable file
@@ -0,0 +1,9 @@
|
||||
extends StaticBody2D
|
||||
|
||||
var valid := false
|
||||
|
||||
func _physics_process(_delta: float) -> void:
|
||||
var player = get_tree().get_first_node_in_group("Players")
|
||||
if player.is_on_floor() == false and not $Area2D.get_overlapping_areas().any(func(area: Area2D): return area.owner is Player):
|
||||
valid = (player.direction == -1 and player.crouching and player.power_state.hitbox_size == "Big")
|
||||
$CollisionShape2D.set_deferred("one_way_collision", valid)
|
1
Scripts/Parts/MinusWorldBlock.gd.uid
Executable file
1
Scripts/Parts/MinusWorldBlock.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://b41sx3mmdq1pi
|
16
Scripts/Parts/MoonGravity.gd
Normal file
16
Scripts/Parts/MoonGravity.gd
Normal file
@@ -0,0 +1,16 @@
|
||||
class_name MoonGravity
|
||||
extends Node
|
||||
|
||||
|
||||
@export var new_gravity := 5
|
||||
const OLD_GRAVITY := 10
|
||||
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
Global.entity_gravity = new_gravity
|
||||
for i: Player in get_tree().get_nodes_in_group("Players"):
|
||||
i.low_gravity = true
|
||||
|
||||
func _exit_tree() -> void:
|
||||
Global.entity_gravity = OLD_GRAVITY
|
1
Scripts/Parts/MoonGravity.gd.uid
Executable file
1
Scripts/Parts/MoonGravity.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://dhtqnyrj3ndnj
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user