Files
Super-Mario-Bros.-Remastere…/Scripts/Classes/Entities/Player.gd
SkyanUltra f6a31f8508 New Optional Animation States (#420)
* new animation states

additional conditional animation states, including:
- SkidAttack
- IdleAttack
- WalkAttack
- RunAttack
- CrouchJump
- CrouchMove
... along with a new set of animations, the Bump animations (play when hitting your head on a block)
- JumpBump
- CrouchBump
- SwimBump

* added fly animations & tweaked bump animation time

new anims for FlyIdle, FlyUp, FlyMove, FlyAttack, and FlyBump, which all fallback on their Swim counterparts if not found. also changed around some animation fallbacks which made more sense, like bumps using fall animations instead if applicable.

* added bump ignore checks and added a general bump animation

you can now define Bump which will define a bump animation for all bump animations. additionally, bump animations will only play if they can detect an animation. no more fallbacks needed
2025-09-28 11:25:50 +01:00

876 lines
30 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 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 total_keys := 0
@export var power_state: PowerUpState = null:
set(value):
power_state = value
set_power_state_frame()
var character := "Mario"
var crouching := false
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
@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": "Move",
"LookUp": "Idle",
"CrouchFall": "Crouch",
"CrouchJump": "Crouch",
"CrouchBump": "Bump",
"CrouchMove": "Crouch",
"IdleAttack": "Attack",
"CrouchAttack": "IdleAttack",
"MoveAttack": "IdleAttack",
"WalkAttack": "MoveAttack",
"RunAttack": "MoveAttack",
"SkidAttack": "MoveAttack",
"FlyIdle": "SwimIdle",
"FlyUp": "SwimUp",
"FlyMove": "SwimMove",
"FlyAttack": "SwimAttack",
"FlyBump": "SwimBump",
"FlagSlide": "Climb",
"WaterMove": "Move",
"WaterIdle": "Idle",
"SwimBump": "Bump",
"DieFreeze": "Die",
"StarJump": "Jump",
"StarFall": "StarJump"
}
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 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()
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.scale = Vector2(hitbox_scale[0], hitbox_scale[1])
i.update()
for i in get_tree().get_nodes_in_group("BigCollisions"):
var hitbox_scale = json.get("big_hitbox_scale", [1, 1])
i.scale = Vector2(hitbox_scale[0], hitbox_scale[1])
i.update()
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()
if Input.is_action_just_pressed("debug_noclip") and Global.debug_mode:
state_machine.transition_to("NoClip")
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()
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
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 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
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("BigCollisions"):
if i.owner == self:
i.set_deferred("disabled", power_state.hitbox_size == "Small" or crouching)
$Checkpoint.position.y = -24 if power_state.hitbox_size == "Small" or crouching 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()
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.time <= 0:
death_actions["time_up"].call()
elif Global.lives <= 0 and Settings.file.difficulty.inf_lives == 0:
death_actions["game_over"].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")
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
if new_power_state.hitbox_size == "Big" and power_state.hitbox_size == "Small":
check_for_block()
power_state = new_power_state
Global.player_power_states[player_id] = str(power_state.get_index())
can_hurt = true
refresh_hitbox()
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")
await get_tree().create_timer(0.4, true).timeout
sprite.sprite_frames = new_frames
sprite.play("Grow")
await get_tree().create_timer(0.4, true).timeout
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:
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
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("Pipe")
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.6, 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
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 on_hammer_area_entered(area: Area2D) -> void:
pass
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()