mirror of
https://github.com/JHDev2006/Super-Mario-Bros.-Remastered-Public.git
synced 2025-10-21 15:08:12 +00:00

* Some state machine functions to make code tidier in the future * Added a function to Global to check if we're playtesting, for convenience * Only allow noclip w/o debug in leveltesting, toggling added, kept jump key to exit
938 lines
32 KiB
GDScript
938 lines
32 KiB
GDScript
class_name Player
|
|
extends CharacterBody2D
|
|
|
|
#region Physics properies, these can be changed within a custom character's CharacterInfo.json
|
|
var JUMP_GRAVITY := 11.0 # The player's gravity while jumping, measured in px/frame
|
|
var JUMP_HEIGHT := 300.0 # The strength of the player's jump, measured in px/sec
|
|
var JUMP_INCR := 8.0 # How much the player's X velocity affects their jump speed
|
|
var JUMP_CANCEL_DIVIDE := 1.5 # When the player cancels their jump, their Y velocity gets divided by this value
|
|
var JUMP_HOLD_SPEED_THRESHOLD := 0.0 # When the player's Y velocity goes past this value while jumping, their gravity switches to FALL_GRAVITY
|
|
|
|
var BOUNCE_HEIGHT := 200.0 # The strength at which the player bounces off enemies, measured in px/sec
|
|
var BOUNCE_JUMP_HEIGHT := 300.0 # The strength at which the player bounces off enemies while holding jump, measured in px/sec
|
|
|
|
var FALL_GRAVITY := 25.0 # The player's gravity while falling, measured in px/frame
|
|
var MAX_FALL_SPEED := 280.0 # The player's maximum fall speed, measured in px/sec
|
|
var CEILING_BUMP_SPEED := 45.0 # The speed at which the player falls after hitting a ceiling, measured in px/sec
|
|
|
|
var WALK_SPEED := 96.0 # The player's speed while walking, measured in px/sec
|
|
var GROUND_WALK_ACCEL := 4.0 # The player's acceleration while walking, measured in px/frame
|
|
var WALK_SKID := 8.0 # The player's turning deceleration while running, measured in px/frame
|
|
|
|
var RUN_SPEED := 160.0 # The player's speed while running, measured in px/sec
|
|
var GROUND_RUN_ACCEL := 1.25 # The player's acceleration while running, measured in px/frame
|
|
var RUN_SKID := 8.0 # The player's turning deceleration while running, measured in px/frame
|
|
|
|
var SKID_THRESHOLD := 100.0 # The horizontal speed required, to be able to start skidding.
|
|
|
|
var DECEL := 3.0 # The player's deceleration while no buttons are pressed, measured in px/frame
|
|
var AIR_ACCEL := 3.0 # The player's acceleration while in midair, measured in px/frame
|
|
var AIR_SKID := 1.5 # The player's turning deceleration while in midair, measured in px/frame
|
|
|
|
var SWIM_SPEED := 95.0 # The player's horizontal speed while swimming, measured in px/sec
|
|
var SWIM_GROUND_SPEED := 45.0 # The player's horizontal speed while grounded underwater, measured in px/sec
|
|
var SWIM_HEIGHT := 100.0 # The strength of the player's swim, measured in px/sec
|
|
var SWIM_GRAVITY := 2.5 # The player's gravity while swimming, measured in px/frame
|
|
var MAX_SWIM_FALL_SPEED := 200.0 # The player's maximum fall speed while swimming, measured in px/sec
|
|
|
|
var DEATH_JUMP_HEIGHT := 300.0 # The strength of the player's "jump" during the death animation, measured in px/sec
|
|
#endregion
|
|
|
|
@onready var camera_center_joint: Node2D = $CameraCenterJoint
|
|
|
|
@onready var sprite: AnimatedSprite2D = %Sprite
|
|
@onready var camera: Camera2D = $Camera
|
|
@onready var score_note_spawner: ScoreNoteSpawner = $ScoreNoteSpawner
|
|
|
|
var has_jumped := false
|
|
|
|
var direction := 1
|
|
var input_direction := 0
|
|
|
|
var flight_meter := 0.0
|
|
|
|
var velocity_direction := 1
|
|
var velocity_x_jump_stored := 0
|
|
|
|
var total_keys := 0
|
|
|
|
@export var power_state: PowerUpState = null:
|
|
set(value):
|
|
power_state = value
|
|
set_power_state_frame()
|
|
var character := "Mario"
|
|
|
|
var crouching := false:
|
|
get(): # You can't crouch if the animation somehow doesn't exist.
|
|
if not sprite.sprite_frames.has_animation("Crouch"): return false
|
|
return crouching
|
|
var skidding := false
|
|
|
|
var bumping := false
|
|
var can_bump_sfx := true
|
|
var can_bump_jump = false
|
|
var can_bump_crouch = false
|
|
var can_bump_swim = false
|
|
var can_bump_fly = false
|
|
|
|
var kicking = false
|
|
var can_kick_anim = false
|
|
|
|
@export var player_id := 0
|
|
const ONE_UP_NOTE = preload("uid://dopxwjj37gu0l")
|
|
var gravity := FALL_GRAVITY
|
|
|
|
var attacking := false
|
|
var pipe_enter_direction := Vector2.ZERO#
|
|
var pipe_move_direction := 1
|
|
var stomp_combo := 0
|
|
|
|
var is_invincible := false
|
|
var can_pose := false
|
|
var is_posing := false
|
|
|
|
const COMBO_VALS := [100, 200, 400, 500, 800, 1000, 2000, 4000, 5000, 8000, null]
|
|
|
|
@export_enum("Small", "Big", "Fire") var starting_power_state := 0
|
|
@onready var state_machine: StateMachine = $States
|
|
@onready var normal_state: Node = $States/Normal
|
|
@export var auto_death_pit := true
|
|
|
|
var can_hurt := true
|
|
|
|
var in_water := false
|
|
|
|
var has_hammer := false
|
|
|
|
var spring_bouncing := false
|
|
|
|
var low_gravity := false
|
|
|
|
var gravity_vector := Vector2.DOWN
|
|
|
|
var jump_cancelled := false
|
|
|
|
var camera_pan_amount := 24
|
|
|
|
var animating_camera := false
|
|
|
|
var can_uncrouch := false
|
|
|
|
var can_air_turn := false
|
|
|
|
static var CHARACTERS := ["Mario", "Luigi", "Toad", "Toadette"]
|
|
const POWER_STATES := ["Small", "Big", "Fire"]
|
|
|
|
signal moved
|
|
signal dead
|
|
|
|
var is_dead := false
|
|
|
|
static var CHARACTER_NAMES := ["CHAR_MARIO", "CHAR_LUIGI", "CHAR_TOAD", "CHAR_TOADETTE"]
|
|
|
|
static var CHARACTER_COLOURS := [preload("res://Assets/Sprites/Players/Mario/CharacterColour.json"), preload("res://Assets/Sprites/Players/Luigi/CharacterColour.json"), preload("res://Assets/Sprites/Players/Toad/CharacterColour.json"), preload("res://Assets/Sprites/Players/Toadette/CharacterColour.json")]
|
|
|
|
var can_timer_warn := true
|
|
|
|
var colour_palette_texture: Texture = null
|
|
|
|
static var CHARACTER_PALETTES := [
|
|
preload("res://Assets/Sprites/Players/Mario/ColourPalette.json"),
|
|
preload("res://Assets/Sprites/Players/Luigi/ColourPalette.json"),
|
|
preload("res://Assets/Sprites/Players/Toad/ColourPalette.json"),
|
|
preload("res://Assets/Sprites/Players/Toadette/ColourPalette.json")
|
|
]
|
|
|
|
const ANIMATION_FALLBACKS := {
|
|
"JumpFall": "Jump",
|
|
"JumpBump": "Bump",
|
|
"Fall": "Move",
|
|
"Pipe": "Idle",
|
|
"Walk": "Move",
|
|
"Run": "Move",
|
|
"PipeWalk": "Walk",
|
|
"LookUp": "Idle",
|
|
"WaterLookUp": "LookUp",
|
|
"WingLookUp": "WaterLookUp",
|
|
"Crouch": "Idle",
|
|
"WaterCrouch": "Crouch",
|
|
"WingCrouch": "WaterCrouch",
|
|
"CrouchFall": "Crouch",
|
|
"CrouchJump": "Crouch",
|
|
"CrouchBump": "Bump",
|
|
"CrouchMove": "Crouch",
|
|
"IdleAttack": "MoveAttack",
|
|
"CrouchAttack": "IdleAttack",
|
|
"MoveAttack": "Attack",
|
|
"WalkAttack": "MoveAttack",
|
|
"RunAttack": "MoveAttack",
|
|
"SkidAttack": "MoveAttack",
|
|
"WingIdle": "WaterIdle",
|
|
"FlyUp": "SwimUp",
|
|
"WingMove": "WaterMove",
|
|
"FlyAttack": "SwimAttack",
|
|
"FlyBump": "SwimBump",
|
|
"FlagSlide": "Climb",
|
|
"WaterMove": "Move",
|
|
"WaterIdle": "Idle",
|
|
"FlyIdle": "SwimIdle",
|
|
"SwimBump": "Bump",
|
|
"DieFreeze": "Die",
|
|
"RunJump": "Jump",
|
|
"RunJumpFall": "JumpFall",
|
|
"RunJumpBump": "JumpBump",
|
|
"StarJump": "Jump",
|
|
"StarFall": "JumpFall"
|
|
}
|
|
|
|
var palette_transform := true
|
|
var transforming := false
|
|
|
|
static var camera_right_limit := 999999
|
|
|
|
static var times_hit := 0
|
|
|
|
var can_run := true
|
|
|
|
var air_frames := 0
|
|
|
|
static var classic_physics := false
|
|
|
|
var swim_stroke := false
|
|
|
|
var skid_frames := 0
|
|
|
|
var simulated_velocity := Vector2.ZERO
|
|
|
|
func _ready() -> void:
|
|
if classic_physics:
|
|
apply_classic_physics()
|
|
get_viewport().size_changed.connect(recenter_camera)
|
|
show()
|
|
$Checkpoint/Label.text = str(player_id + 1)
|
|
$Checkpoint/Label.modulate = [Color("5050FF"), Color("F73910"), Color("1A912E"), Color("FFB762")][player_id]
|
|
$Checkpoint/Label.visible = Global.connected_players > 1
|
|
Global.can_pause = true
|
|
character = CHARACTERS[int(Global.player_characters[player_id])]
|
|
Global.can_time_tick = true
|
|
if [Global.GameMode.BOO_RACE, Global.GameMode.MARATHON, Global.GameMode.MARATHON_PRACTICE].has(Global.current_game_mode) == false:
|
|
apply_character_physics()
|
|
apply_character_sfx_map()
|
|
Global.level_theme_changed.connect(apply_character_sfx_map)
|
|
Global.level_theme_changed.connect(apply_character_physics)
|
|
Global.level_theme_changed.connect(set_power_state_frame)
|
|
if Global.current_level.first_load and Global.current_game_mode == Global.GameMode.MARATHON_PRACTICE:
|
|
Global.player_power_states[player_id] = "0"
|
|
power_state = $PowerStates.get_node(POWER_STATES[int(Global.player_power_states[player_id])])
|
|
if Global.current_game_mode == Global.GameMode.LEVEL_EDITOR:
|
|
camera.enabled = false
|
|
handle_power_up_states(0)
|
|
set_power_state_frame()
|
|
handle_invincible_palette()
|
|
if Global.level_editor == null:
|
|
recenter_camera()
|
|
|
|
func apply_character_physics() -> void:
|
|
var path = "res://Assets/Sprites/Players/" + character + "/CharacterInfo.json"
|
|
if int(Global.player_characters[player_id]) > 3:
|
|
path = path.replace("res://Assets/Sprites/Players", Global.config_path.path_join("custom_characters/"))
|
|
path = ResourceSetter.get_pure_resource_path(path)
|
|
var json = JSON.parse_string(FileAccess.open(path, FileAccess.READ).get_as_text())
|
|
for i in json.physics:
|
|
set(i, json.physics[i])
|
|
|
|
for i in get_tree().get_nodes_in_group("SmallCollisions"):
|
|
var hitbox_scale = json.get("small_hitbox_scale", [1, 1])
|
|
i.hitbox = Vector3(hitbox_scale[0], hitbox_scale[1] if i.get_meta("scalable", true) else 1, json.get("small_crouch_scale", 0.75))
|
|
i._physics_process(0)
|
|
for i in get_tree().get_nodes_in_group("BigCollisions"):
|
|
var hitbox_scale = json.get("big_hitbox_scale", [1, 1])
|
|
i.hitbox = Vector3(hitbox_scale[0], hitbox_scale[1] if i.get_meta("scalable", true) else 1, json.get("big_crouch_scale", 0.5))
|
|
i._physics_process(0)
|
|
|
|
func apply_classic_physics() -> void:
|
|
var json = JSON.parse_string(FileAccess.open("res://Resources/ClassicPhysics.json", FileAccess.READ).get_as_text())
|
|
for i in json:
|
|
set(i, json[i])
|
|
|
|
func recenter_camera() -> void:
|
|
%CameraHandler.recenter_camera()
|
|
%CameraHandler.update_camera_barriers()
|
|
|
|
func reparent_camera() -> void:
|
|
return
|
|
|
|
func editor_level_start() -> void:
|
|
if PipeArea.exiting_pipe_id == -1:
|
|
power_state = get_node("PowerStates").get_child(starting_power_state)
|
|
handle_power_up_states(0)
|
|
set_power_state_frame()
|
|
camera_make_current()
|
|
recenter_camera()
|
|
state_machine.transition_to("Normal")
|
|
if camera_right_limit <= global_position.x:
|
|
camera_right_limit = 99999999
|
|
await get_tree().create_timer(0.1, false).timeout
|
|
if camera_right_limit <= global_position.x:
|
|
camera_right_limit = 99999999
|
|
|
|
|
|
func _physics_process(delta: float) -> void:
|
|
if Input.is_action_just_pressed("debug_reload"):
|
|
set_power_state_frame()
|
|
|
|
# guzlad: noclip without dev only works while playtesting.
|
|
if (Input.is_action_just_pressed("debug_noclip") or Input.is_action_just_pressed("jump_0")) and ((Global.debug_mode) or (Global.level_editor_is_playtesting())):
|
|
if state_machine.is_state("NoClip"):
|
|
state_machine.transition_to("Normal")
|
|
Global.log_comment("NOCLIP Disabled")
|
|
elif !Input.is_action_just_pressed("jump_0") and !state_machine.is_state("NoClip"):
|
|
state_machine.transition_to("NoClip")
|
|
Global.log_comment("NOCLIP Enabled")
|
|
|
|
up_direction = -gravity_vector
|
|
handle_directions()
|
|
handle_block_collision_detection()
|
|
handle_wing_flight(delta)
|
|
air_frames = (air_frames + 1 if is_on_floor() == false else 0)
|
|
for i in get_tree().get_nodes_in_group("StepCollision"):
|
|
var on_wall := false
|
|
for x in [$StepWallChecks/LWall, $StepWallChecks/RWall]:
|
|
if x.is_colliding():
|
|
on_wall = true
|
|
var step_enabled = (not on_wall and air_frames < 4 and velocity.y >= 0)
|
|
i.set_deferred("disabled", not step_enabled)
|
|
if is_actually_on_ceiling() and can_bump_sfx:
|
|
bump_ceiling()
|
|
elif is_actually_on_floor() and not is_invincible:
|
|
stomp_combo = 0
|
|
elif velocity.y > 15:
|
|
can_bump_sfx = true
|
|
handle_water_detection()
|
|
%SkidParticles.visible = Settings.file.visuals.extra_particles == 1
|
|
%SkidParticles.emitting = ((skidding and skid_frames > 2) or crouching) and is_on_floor() and abs(velocity.x) > 25 and Settings.file.visuals.extra_particles == 1
|
|
if $SkidSFX.playing:
|
|
if (is_actually_on_floor() and skidding) == false:
|
|
$SkidSFX.stop()
|
|
elif is_actually_on_floor() and skidding and Settings.file.audio.skid_sfx == 1:
|
|
$SkidSFX.play()
|
|
|
|
const BUBBLE_PARTICLE = preload("uid://bwjae1h1airtr")
|
|
|
|
func handle_water_detection() -> void:
|
|
var old_water = in_water
|
|
if $Hitbox.monitoring:
|
|
in_water = $Hitbox.get_overlapping_areas().any(func(area: Area2D): return area is WaterArea) or $WaterDetect.get_overlapping_bodies().is_empty() == false
|
|
if old_water != in_water and in_water == false and flight_meter <= 0:
|
|
water_exited()
|
|
|
|
func summon_bubble() -> void:
|
|
var bubble = BUBBLE_PARTICLE.instantiate()
|
|
bubble.global_position = global_position + Vector2(0, -16 if power_state.hitbox_size == "Small" else -32)
|
|
add_sibling(bubble)
|
|
|
|
func _process(delta: float) -> void:
|
|
handle_power_up_states(delta)
|
|
handle_invincible_palette()
|
|
if is_invincible:
|
|
DiscoLevel.combo_meter = 100
|
|
%Hammer.visible = has_hammer
|
|
%HammerHitbox.collision_layer = has_hammer
|
|
|
|
func apply_gravity(delta: float) -> void:
|
|
if in_water or flight_meter > 0:
|
|
gravity = SWIM_GRAVITY
|
|
else:
|
|
if sign(gravity_vector.y) * velocity.y + JUMP_HOLD_SPEED_THRESHOLD > 0.0:
|
|
gravity = FALL_GRAVITY
|
|
velocity += (gravity_vector * ((gravity / (1.5 if low_gravity else 1.0)) / delta)) * delta
|
|
var target_fall: float = MAX_FALL_SPEED
|
|
if in_water:
|
|
target_fall = MAX_SWIM_FALL_SPEED
|
|
if gravity_vector.y > 0:
|
|
velocity.y = clamp(velocity.y, -INF, (target_fall / (1.2 if low_gravity else 1.0)))
|
|
else:
|
|
velocity.y = clamp(velocity.y, -(target_fall / (1.2 if low_gravity else 1.0)), INF)
|
|
|
|
func camera_make_current() -> void:
|
|
camera.enabled = true
|
|
camera.make_current()
|
|
|
|
func play_animation(animation_name := "") -> void:
|
|
if sprite.sprite_frames == null: return
|
|
animation_name = get_fallback_animation(animation_name)
|
|
if sprite.animation != animation_name:
|
|
sprite.play(animation_name)
|
|
|
|
func get_fallback_animation(animation_name := "") -> String:
|
|
if sprite.sprite_frames.has_animation(animation_name) == false and ANIMATION_FALLBACKS.has(animation_name):
|
|
return get_fallback_animation(ANIMATION_FALLBACKS.get(animation_name))
|
|
else:
|
|
return animation_name
|
|
|
|
func apply_character_sfx_map() -> void:
|
|
var path = "res://Assets/Sprites/Players/" + character + "/SFX.json"
|
|
var custom_character := false
|
|
if int(Global.player_characters[player_id]) > 3:
|
|
custom_character = true
|
|
path = path.replace("res://Assets/Sprites/Players", Global.config_path.path_join("custom_characters/"))
|
|
path = ResourceSetter.get_pure_resource_path(path)
|
|
var json = JSON.parse_string(FileAccess.open(path, FileAccess.READ).get_as_text())
|
|
|
|
for i in json:
|
|
var res_path = "res://Assets/Audio/SFX/" + json[i]
|
|
res_path = ResourceSetter.get_pure_resource_path(res_path)
|
|
if FileAccess.file_exists(res_path) == false or custom_character:
|
|
var directory = "res://Assets/Sprites/Players/" + character + "/" + json[i]
|
|
if int(Global.player_characters[player_id]) > 3:
|
|
directory = directory.replace("res://Assets/Sprites/Players", Global.config_path.path_join("custom_characters/"))
|
|
directory = ResourceSetter.get_pure_resource_path(directory)
|
|
if FileAccess.file_exists(directory):
|
|
json[i] = directory
|
|
else:
|
|
json[i] = res_path
|
|
else:
|
|
json[i] = res_path
|
|
|
|
AudioManager.load_sfx_map(json)
|
|
|
|
func refresh_hitbox() -> void:
|
|
$Hitbox.set_deferred("monitoring", false)
|
|
$Hitbox.set_deferred("monitorable", false)
|
|
await get_tree().physics_frame
|
|
$Hitbox.set_deferred("monitoring", true)
|
|
$Hitbox.set_deferred("monitorable", true)
|
|
|
|
func is_actually_on_floor() -> bool:
|
|
if is_on_floor():
|
|
return true
|
|
else:
|
|
for i in get_tree().get_nodes_in_group("CollisionRays"):
|
|
if i.is_on_floor():
|
|
return true
|
|
return false
|
|
|
|
func is_actually_on_wall() -> bool:
|
|
if is_on_wall():
|
|
return true
|
|
else:
|
|
for i in get_tree().get_nodes_in_group("CollisionRays"):
|
|
if i.is_on_wall():
|
|
return true
|
|
return false
|
|
|
|
func is_actually_on_ceiling() -> bool:
|
|
if is_on_ceiling():
|
|
return true
|
|
else:
|
|
for i in get_tree().get_nodes_in_group("CollisionRays"):
|
|
if i.is_on_ceiling():
|
|
return true
|
|
return false
|
|
|
|
func enemy_bounce_off(add_combo := true, award_score := true) -> void:
|
|
if add_combo:
|
|
add_stomp_combo(award_score)
|
|
jump_cancelled = not Global.player_action_pressed("jump", player_id)
|
|
await get_tree().physics_frame
|
|
if Global.player_action_pressed("jump", player_id):
|
|
velocity.y = sign(gravity_vector.y) * -BOUNCE_JUMP_HEIGHT
|
|
gravity = JUMP_GRAVITY
|
|
has_jumped = true
|
|
else:
|
|
velocity.y = sign(gravity_vector.y) * -BOUNCE_HEIGHT
|
|
|
|
func add_stomp_combo(award_score := true) -> void:
|
|
if stomp_combo >= 10:
|
|
if award_score:
|
|
if Global.current_game_mode == Global.GameMode.CHALLENGE or Settings.file.difficulty.inf_lives:
|
|
Global.score += 10000
|
|
score_note_spawner.spawn_note(10000)
|
|
else:
|
|
Global.lives += 1
|
|
AudioManager.play_global_sfx("1_up")
|
|
score_note_spawner.spawn_one_up_note()
|
|
else:
|
|
if award_score:
|
|
Global.score += COMBO_VALS[stomp_combo]
|
|
score_note_spawner.spawn_note(COMBO_VALS[stomp_combo])
|
|
stomp_combo += 1
|
|
|
|
func bump_ceiling() -> void:
|
|
AudioManager.play_sfx("bump", global_position)
|
|
velocity.y = CEILING_BUMP_SPEED
|
|
can_bump_sfx = false
|
|
bumping = true
|
|
await get_tree().create_timer(0.1).timeout
|
|
AudioManager.kill_sfx("small_jump")
|
|
AudioManager.kill_sfx("big_jump")
|
|
await get_tree().create_timer(0.1).timeout
|
|
bumping = false
|
|
|
|
func kick_anim() -> void:
|
|
kicking = true
|
|
await get_tree().create_timer(0.2).timeout
|
|
kicking = false
|
|
|
|
func super_star() -> void:
|
|
DiscoLevel.combo_meter += 1
|
|
is_invincible = true
|
|
$StarTimer.start()
|
|
|
|
var colour_palette: Texture = null
|
|
|
|
func stop_all_timers() -> void:
|
|
flight_meter = -1
|
|
for i in [$StarTimer, $HammerTimer]:
|
|
i.stop()
|
|
|
|
func handle_invincible_palette() -> void:
|
|
sprite.material.set_shader_parameter("mode", !Settings.file.visuals.rainbow_style)
|
|
sprite.material.set_shader_parameter("player_palette", $PlayerPalette.texture)
|
|
sprite.material.set_shader_parameter("palette_size", colour_palette.get_width())
|
|
sprite.material.set_shader_parameter("invincible_palette", $InvinciblePalette.texture)
|
|
sprite.material.set_shader_parameter("palette_idx", POWER_STATES.find(power_state.state_name))
|
|
sprite.material.set_shader_parameter("enabled", (is_invincible or (palette_transform and transforming)))
|
|
|
|
func handle_block_collision_detection() -> void:
|
|
if ["Pipe"].has(state_machine.state.name): return
|
|
match power_state.hitbox_size:
|
|
"Small":
|
|
var points: Array = $SmallCollision.polygon
|
|
points.sort_custom(func(a, b): return a.y < b.y)
|
|
$BlockCollision.position.y = points.front().y * $SmallCollision.scale.y
|
|
"Big":
|
|
var points: Array = $BigCollision.polygon
|
|
points.sort_custom(func(a, b): return a.y < b.y)
|
|
$BlockCollision.position.y = points.front().y * $BigCollision.scale.y
|
|
if velocity.y <= FALL_GRAVITY:
|
|
for i in $BlockCollision.get_overlapping_bodies():
|
|
if i is Block:
|
|
if is_on_ceiling():
|
|
i.player_block_hit.emit(self)
|
|
func handle_directions() -> void:
|
|
input_direction = 0
|
|
if Global.player_action_pressed("move_right", player_id):
|
|
input_direction = 1
|
|
elif Global.player_action_pressed("move_left", player_id):
|
|
input_direction = -1
|
|
velocity_direction = sign(velocity.x)
|
|
|
|
var use_big_collision := false
|
|
|
|
func handle_power_up_states(delta) -> void:
|
|
for i in get_tree().get_nodes_in_group("SmallCollisions"):
|
|
i.disabled = power_state.hitbox_size != "Small"
|
|
i.visible = not i.disabled
|
|
i.crouching = crouching
|
|
for i in get_tree().get_nodes_in_group("BigCollisions"):
|
|
i.disabled = power_state.hitbox_size != "Big"
|
|
i.visible = not i.disabled
|
|
i.crouching = crouching
|
|
$Checkpoint.position.y = -24 if power_state.hitbox_size == "Small" else -40
|
|
power_state.update(delta)
|
|
|
|
func handle_wing_flight(delta: float) -> void:
|
|
flight_meter -= delta
|
|
if flight_meter <= 0 && %Wings.visible:
|
|
AudioManager.stop_music_override(AudioManager.MUSIC_OVERRIDES.WING)
|
|
gravity = FALL_GRAVITY
|
|
%Wings.visible = flight_meter >= 0
|
|
if flight_meter < 0:
|
|
return
|
|
%BigWing.visible = power_state.hitbox_size == "Big"
|
|
%SmallWing.visible = power_state.hitbox_size == "Small"
|
|
for i in [%SmallWing, %BigWing]:
|
|
if velocity.y < 0:
|
|
i.play("Flap")
|
|
else:
|
|
i.play("Idle")
|
|
if flight_meter <= 3:
|
|
%Wings.get_node("AnimationPlayer").play("Flash")
|
|
else:
|
|
%Wings.get_node("AnimationPlayer").play("RESET")
|
|
|
|
func damage() -> void:
|
|
if can_hurt == false or is_invincible:
|
|
return
|
|
times_hit += 1
|
|
var damage_state = power_state.damage_state
|
|
if damage_state != null:
|
|
if Settings.file.difficulty.damage_style == 0:
|
|
damage_state = get_node("PowerStates/Small")
|
|
DiscoLevel.combo_meter -= 50
|
|
AudioManager.play_sfx("damage", global_position)
|
|
await power_up_animation(damage_state.state_name)
|
|
power_state = get_node("PowerStates/" + damage_state.state_name)
|
|
Global.player_power_states[player_id] = str(power_state.get_index())
|
|
do_i_frames()
|
|
else:
|
|
die()
|
|
|
|
var cam_direction := 1
|
|
@onready var last_position := global_position
|
|
|
|
@onready var camera_position = camera.global_position
|
|
var camera_offset = Vector2.ZERO
|
|
|
|
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)
|
|
|
|
func passed_checkpoint() -> void:
|
|
if Settings.file.difficulty.checkpoint_style == 0:
|
|
$Checkpoint/Animation.play("Show")
|
|
AudioManager.play_sfx("checkpoint", global_position)
|
|
|
|
func do_i_frames() -> void:
|
|
can_hurt = false
|
|
for i in 25:
|
|
sprite.hide()
|
|
if get_tree() == null:
|
|
return
|
|
await get_tree().create_timer(0.04, false).timeout
|
|
sprite.show()
|
|
if get_tree() == null:
|
|
return
|
|
await get_tree().create_timer(0.04, false).timeout
|
|
can_hurt = true
|
|
refresh_hitbox()
|
|
|
|
func die(pit := false) -> void:
|
|
if ["Dead", "Pipe", "LevelExit"].has(state_machine.state.name):
|
|
return
|
|
is_dead = true
|
|
visible = not pit
|
|
flight_meter = 0
|
|
dead.emit()
|
|
Global.p_switch_active = false
|
|
Global.p_switch_timer = 0
|
|
stop_all_timers()
|
|
Global.total_deaths += 1
|
|
sprite.process_mode = Node.PROCESS_MODE_ALWAYS
|
|
state_machine.transition_to("Dead", {"Pit": pit})
|
|
process_mode = Node.PROCESS_MODE_ALWAYS
|
|
get_tree().paused = true
|
|
Level.can_set_time = true
|
|
Level.first_load = true
|
|
if Global.current_game_mode != Global.GameMode.BOO_RACE:
|
|
AudioManager.set_music_override(AudioManager.MUSIC_OVERRIDES.DEATH, 999, false)
|
|
await get_tree().create_timer(3).timeout
|
|
else:
|
|
AudioManager.set_music_override(AudioManager.MUSIC_OVERRIDES.RACE_LOSE, 999, false)
|
|
await get_tree().create_timer(5).timeout
|
|
|
|
death_load()
|
|
|
|
func death_load() -> void:
|
|
power_state = get_node("PowerStates/Small")
|
|
Global.player_power_states = "0000"
|
|
|
|
if Global.death_load:
|
|
return
|
|
Global.death_load = true
|
|
|
|
# Handle lives decrement for CAMPAIGN and MARATHON
|
|
if [Global.GameMode.CAMPAIGN, Global.GameMode.MARATHON].has(Global.current_game_mode):
|
|
if Settings.file.difficulty.inf_lives == 0:
|
|
Global.lives -= 1
|
|
|
|
# Full dispatch table for death handling
|
|
var death_actions = {
|
|
Global.GameMode.CUSTOM_LEVEL: func():
|
|
LevelTransition.level_to_transition_to = "res://Scenes/Levels/LevelEditor.tscn"
|
|
Global.transition_to_scene("res://Scenes/Levels/LevelTransition.tscn"),
|
|
|
|
Global.GameMode.LEVEL_EDITOR: func():
|
|
owner.stop_testing(),
|
|
|
|
|
|
Global.GameMode.CHALLENGE: func():
|
|
Global.transition_to_scene("res://Scenes/Levels/ChallengeMiss.tscn"),
|
|
|
|
Global.GameMode.BOO_RACE: func():
|
|
Global.reset_values()
|
|
Global.clear_saved_values()
|
|
Global.death_load = false
|
|
Level.start_level_path = Global.current_level.scene_file_path
|
|
Global.current_level.reload_level(),
|
|
|
|
"time_up": func():
|
|
Global.transition_to_scene("res://Scenes/Levels/TimeUp.tscn"),
|
|
|
|
"game_over": func():
|
|
Global.death_load = false
|
|
Global.transition_to_scene("res://Scenes/Levels/GameOver.tscn"),
|
|
|
|
"default_reload": func():
|
|
LevelPersistance.reset_states()
|
|
Global.current_level.reload_level()
|
|
}
|
|
|
|
# Determine which action to take
|
|
if death_actions.has(Global.current_game_mode):
|
|
death_actions[Global.current_game_mode].call()
|
|
elif Global.lives <= 0 and Settings.file.difficulty.inf_lives == 0:
|
|
death_actions["game_over"].call()
|
|
elif Global.time <= 0:
|
|
death_actions["time_up"].call()
|
|
else:
|
|
death_actions["default_reload"].call()
|
|
|
|
func time_up() -> void:
|
|
die()
|
|
|
|
func set_power_state_frame() -> void:
|
|
colour_palette = ResourceSetter.get_resource(preload("uid://b0quveyqh25dn"))
|
|
$PlayerPalette/ResourceSetterNew.resource_json = (CHARACTER_PALETTES[int(Global.player_characters[player_id])])
|
|
if power_state != null:
|
|
$ResourceSetterNew.resource_json = load(get_character_sprite_path())
|
|
$ResourceSetterNew.update_resource()
|
|
if %Sprite.sprite_frames != null:
|
|
can_pose = %Sprite.sprite_frames.has_animation("PoseDoor")
|
|
can_bump_jump = %Sprite.sprite_frames.has_animation("JumpBump")
|
|
can_bump_crouch = %Sprite.sprite_frames.has_animation("CrouchBump")
|
|
can_bump_swim = %Sprite.sprite_frames.has_animation("SwimBump")
|
|
can_bump_fly = %Sprite.sprite_frames.has_animation("FlyBump")
|
|
can_kick_anim = %Sprite.sprite_frames.has_animation("Kick")
|
|
|
|
func get_power_up(power_name := "") -> void:
|
|
if is_dead:
|
|
return
|
|
Global.score += 1000
|
|
DiscoLevel.combo_amount += 1
|
|
score_note_spawner.spawn_note(1000)
|
|
AudioManager.play_sfx("power_up", global_position)
|
|
if Settings.file.difficulty.damage_style == 0 and power_state.state_name != power_name:
|
|
if power_name != "Big" and power_state.state_name != "Big":
|
|
power_name = "Big"
|
|
var new_power_state = get_node("PowerStates/" + power_name)
|
|
if new_power_state.power_tier >= power_state.power_tier and new_power_state != power_state:
|
|
can_hurt = false
|
|
await power_up_animation(power_name)
|
|
else:
|
|
return
|
|
power_state = new_power_state
|
|
Global.player_power_states[player_id] = str(power_state.get_index())
|
|
handle_power_up_states(0)
|
|
can_hurt = true
|
|
refresh_hitbox()
|
|
await get_tree().physics_frame
|
|
check_for_block()
|
|
|
|
func check_for_block() -> void:
|
|
if test_move(global_transform, (Vector2.UP * gravity_vector) * 4):
|
|
crouching = true
|
|
|
|
func power_up_animation(new_power_state := "") -> void:
|
|
if normal_state.jump_buffer > 0:
|
|
normal_state.jump_buffer += 10
|
|
var old_frames = sprite.sprite_frames
|
|
var new_frames = $ResourceSetterNew.get_resource(load(get_character_sprite_path(new_power_state)))
|
|
sprite.process_mode = Node.PROCESS_MODE_ALWAYS
|
|
sprite.show()
|
|
get_tree().paused = true
|
|
if get_node("PowerStates/" + new_power_state).hitbox_size != power_state.hitbox_size:
|
|
if Settings.file.visuals.transform_style == 0:
|
|
sprite.speed_scale = 3
|
|
sprite.play("Grow")
|
|
var rainbow = new_power_state != "Big" and (power_state.state_name != "Big" and new_power_state != "Small")
|
|
if rainbow:
|
|
transforming = true
|
|
sprite.material.set_shader_parameter("enabled", true)
|
|
await get_tree().create_timer(0.4, true).timeout
|
|
power_state = get_node("PowerStates/" + new_power_state)
|
|
sprite.sprite_frames = new_frames
|
|
handle_invincible_palette()
|
|
sprite.play("Grow")
|
|
await get_tree().create_timer(0.4, true).timeout
|
|
if rainbow:
|
|
sprite.material.set_shader_parameter("enabled", false)
|
|
transforming = false
|
|
else:
|
|
sprite.speed_scale = 0
|
|
if new_power_state == "Small":
|
|
%GrowAnimation.play("Shrink")
|
|
else:
|
|
sprite.sprite_frames = new_frames
|
|
%GrowAnimation.play("Grow")
|
|
await get_tree().create_timer(0.8, true).timeout
|
|
sprite.sprite_frames = new_frames
|
|
transforming = false
|
|
else:
|
|
if Settings.file.visuals.transform_style == 1:
|
|
for i in 6:
|
|
sprite.sprite_frames = new_frames
|
|
await get_tree().create_timer(0.05).timeout
|
|
sprite.sprite_frames = old_frames
|
|
await get_tree().create_timer(0.05).timeout
|
|
else:
|
|
handle_invincible_palette()
|
|
sprite.stop()
|
|
sprite.material.set_shader_parameter("enabled", true)
|
|
transforming = true
|
|
await get_tree().create_timer(0.6).timeout
|
|
transforming = false
|
|
get_tree().paused = false
|
|
sprite.process_mode = Node.PROCESS_MODE_INHERIT
|
|
if Global.player_action_just_pressed("jump", player_id):
|
|
jump()
|
|
return
|
|
|
|
const RESERVE_ITEM = preload("res://Scenes/Prefabs/Entities/Items/ReserveItem.tscn")
|
|
|
|
func dispense_stored_item() -> void:
|
|
add_sibling(RESERVE_ITEM.instantiate())
|
|
|
|
func get_character_sprite_path(power_stateto_use := power_state.state_name) -> String:
|
|
var path = "res://Assets/Sprites/Players/" + character + "/" + power_stateto_use + ".json"
|
|
if int(Global.player_characters[player_id]) > 3:
|
|
path = path.replace("res://Assets/Sprites/Players", Global.config_path.path_join("custom_characters/"))
|
|
return path
|
|
|
|
func enter_pipe(pipe: PipeArea, warp_to_level := true) -> void:
|
|
z_index = -10
|
|
can_bump_sfx = false
|
|
Global.can_pause = false
|
|
Global.can_time_tick = false
|
|
pipe_enter_direction = pipe.get_vector(pipe.enter_direction)
|
|
if pipe_enter_direction.x != 0:
|
|
global_position.y = pipe.global_position.y + 14
|
|
AudioManager.play_sfx("pipe", global_position)
|
|
state_machine.transition_to("Pipe")
|
|
PipeArea.exiting_pipe_id = pipe.pipe_id
|
|
hide_pipe_animation()
|
|
if warp_to_level:
|
|
await get_tree().create_timer(1, false).timeout
|
|
if Global.current_game_mode == Global.GameMode.LEVEL_EDITOR or Global.current_game_mode == Global.GameMode.CUSTOM_LEVEL:
|
|
LevelEditor.play_pipe_transition = true
|
|
owner.transition_to_sublevel(pipe.target_sub_level)
|
|
else:
|
|
Global.transition_to_scene(pipe.target_level)
|
|
|
|
func hide_pipe_animation() -> void:
|
|
if pipe_enter_direction.x != 0:
|
|
await get_tree().create_timer(0.3, false).timeout
|
|
hide()
|
|
else:
|
|
await get_tree().create_timer(0.6, false).timeout
|
|
hide()
|
|
|
|
func go_to_exit_pipe(pipe: PipeArea) -> void:
|
|
Global.can_time_tick = false
|
|
pipe_enter_direction = Vector2.ZERO
|
|
state_machine.transition_to("Freeze")
|
|
global_position = pipe.global_position + (pipe.get_vector(pipe.enter_direction) * 32)
|
|
if pipe.enter_direction == 1:
|
|
global_position = pipe.global_position + Vector2(0, -8)
|
|
recenter_camera()
|
|
if pipe.get_vector(pipe.enter_direction).y == 0:
|
|
global_position.y += 16
|
|
global_position.x -= 8 * pipe.get_vector(pipe.enter_direction).x
|
|
reset_physics_interpolation()
|
|
hide()
|
|
|
|
func exit_pipe(pipe: PipeArea) -> void:
|
|
show()
|
|
pipe_enter_direction = -pipe.get_vector(pipe.enter_direction)
|
|
AudioManager.play_sfx("pipe", global_position)
|
|
state_machine.transition_to("Pipe")
|
|
await get_tree().create_timer(0.65, false).timeout
|
|
Global.can_pause = true
|
|
state_machine.transition_to("Normal")
|
|
Global.can_time_tick = true
|
|
|
|
func jump() -> void:
|
|
if spring_bouncing:
|
|
return
|
|
velocity.y = calculate_jump_height() * gravity_vector.y
|
|
velocity_x_jump_stored = velocity.x
|
|
gravity = JUMP_GRAVITY
|
|
AudioManager.play_sfx("small_jump" if power_state.hitbox_size == "Small" else "big_jump", global_position)
|
|
has_jumped = true
|
|
await get_tree().physics_frame
|
|
has_jumped = true
|
|
|
|
func calculate_jump_height() -> float: # Thanks wye love you xxx
|
|
return -(JUMP_HEIGHT + JUMP_INCR * int(abs(velocity.x) / 25))
|
|
|
|
const SMOKE_PARTICLE = preload("res://Scenes/Prefabs/Particles/SmokeParticle.tscn")
|
|
|
|
func teleport_player(new_position := Vector2.ZERO) -> void:
|
|
hide()
|
|
do_smoke_effect()
|
|
var old_state = state_machine.state.name
|
|
state_machine.transition_to("Freeze")
|
|
await get_tree().create_timer(0.5, false).timeout
|
|
global_position = new_position
|
|
recenter_camera()
|
|
await get_tree().create_timer(0.5, false).timeout
|
|
state_machine.transition_to(old_state)
|
|
show()
|
|
velocity.y = 0
|
|
do_smoke_effect()
|
|
|
|
func do_smoke_effect() -> void:
|
|
for i in 2:
|
|
var node = SMOKE_PARTICLE.instantiate()
|
|
node.global_position = global_position - Vector2(0, 16 * i)
|
|
add_sibling(node)
|
|
if power_state.hitbox_size == "Small":
|
|
break
|
|
AudioManager.play_sfx("magic", global_position)
|
|
|
|
func on_timeout() -> void:
|
|
AudioManager.stop_music_override(AudioManager.MUSIC_OVERRIDES.STAR)
|
|
await get_tree().create_timer(1, false).timeout
|
|
is_invincible = false
|
|
|
|
|
|
func on_area_entered(area: Area2D) -> void:
|
|
if area.owner is Player and area.owner != self:
|
|
if area.owner.velocity.y > 0 and area.owner.is_actually_on_floor() == false:
|
|
area.owner.enemy_bounce_off(false)
|
|
velocity.y = 50
|
|
AudioManager.play_sfx("bump", global_position)
|
|
|
|
func hammer_get() -> void:
|
|
has_hammer = true
|
|
$HammerTimer.start()
|
|
AudioManager.set_music_override(AudioManager.MUSIC_OVERRIDES.HAMMER, 0, false)
|
|
|
|
func wing_get() -> void:
|
|
AudioManager.set_music_override(AudioManager.MUSIC_OVERRIDES.WING, 0, false, false)
|
|
flight_meter = 10
|
|
|
|
func on_hammer_timeout() -> void:
|
|
has_hammer = false
|
|
AudioManager.stop_music_override(AudioManager.MUSIC_OVERRIDES.HAMMER)
|
|
|
|
func water_exited() -> void:
|
|
await get_tree().physics_frame
|
|
if in_water: return
|
|
normal_state.swim_up_meter = 0
|
|
if velocity.y < 0:
|
|
velocity.y = -250.0 if velocity.y < -50.0 or Global.player_action_pressed("move_up", player_id) else velocity.y
|
|
has_jumped = true
|
|
if Global.player_action_pressed("move_up", player_id):
|
|
gravity = JUMP_GRAVITY
|
|
else:
|
|
gravity = FALL_GRAVITY
|
|
|
|
func reset_camera_to_center() -> void:
|
|
animating_camera = true
|
|
var old_position = camera.position
|
|
camera.global_position = get_viewport().get_camera_2d().get_screen_center_position()
|
|
camera.reset_physics_interpolation()
|
|
var tween = create_tween()
|
|
tween.tween_property(camera, "position", old_position, 0.5)
|
|
await tween.finished
|
|
camera.position = old_position
|
|
animating_camera = false
|
|
|
|
func on_area_exited(area: Area2D) -> void:
|
|
if area is WaterArea:
|
|
water_exited()
|