The_Dark_Side_of_Earth/player/player.gd

242 lines
7.3 KiB
GDScript3
Raw Normal View History

2025-09-17 15:59:14 +02:00
class_name Player extends CharacterBody2D
2025-09-18 17:47:49 +02:00
@export var double_jump_animation : PackedScene
2025-09-16 23:37:45 +02:00
# allow taking away player control
var handle_input : bool = true
# Gravity
2025-09-15 19:15:43 +02:00
var earth_center = Vector2.ZERO;
2025-09-16 00:28:49 +02:00
var max_fall_speed = 700;
var gravity = 100;
# Movement
2025-09-16 14:59:40 +02:00
var facing = -1;
2025-10-11 16:37:14 +02:00
var base_hspeed = 150.0;
var ground_jump_strength = 1400;
var air_jump_strength = 1100;
2025-09-16 00:28:49 +02:00
var air_jumps_max = 1;
2025-10-12 18:26:36 +02:00
var air_jumps_current = 1:
set(air_jumps_new):
air_jumps_current = min(air_jumps_new, air_jumps_max)
2025-09-16 00:28:49 +02:00
# HP and Iframes
signal health_changed(new_health : int)
2025-09-17 14:39:22 +02:00
signal max_hp_changed(new_max_hp : int)
2025-09-16 19:34:30 +02:00
signal player_died
var hit_iframes = 0.8
var current_hp = 5:
set(new_hp):
# HP can't be increased above Max HP or reduced below 0
# When reduced to 0, the player dies.
2025-10-12 18:26:36 +02:00
new_hp = min(new_hp, max_hp)
if new_hp <= 0:
new_hp = 0
die()
if new_hp != current_hp:
current_hp = new_hp
2025-10-12 18:29:14 +02:00
health_changed.emit(current_hp)
@export var max_hp = 5:
# When Max HP is reduced below current health, current health is adjusted.
2025-09-17 14:39:22 +02:00
set(new_max_hp):
max_hp = new_max_hp
2025-10-12 18:26:36 +02:00
if max_hp <= 0:
max_hp = 0
if current_hp > max_hp:
current_hp = max_hp
2025-09-17 14:39:22 +02:00
max_hp_changed.emit(max_hp)
2025-09-16 14:59:40 +02:00
var dead = false;
2025-09-16 00:28:49 +02:00
# Received Knockback
var reset_to_velocity = Vector2.ZERO
var knockback_strength = 1500
var damage_knockup = 500
2025-10-02 15:08:59 +02:00
@export var friction = 0.5
2025-09-15 19:15:43 +02:00
2025-09-16 14:59:40 +02:00
# Attack Handling
2025-09-20 02:38:45 +02:00
var can_upslash = false
2025-09-17 18:23:42 +02:00
signal attack
2025-09-15 19:15:43 +02:00
# Active Item
2025-09-17 15:59:14 +02:00
signal active_item_changed(newitem : Item)
var active_item : ActiveItem = null:
set(new_active_item):
active_item = new_active_item
active_item_changed.emit(active_item)
func set_cooldown(cooldown):
$ActiveItemCooldown.wait_time = cooldown
func activate_cooldown():
$ActiveItemCooldown.start()
2025-09-17 14:39:22 +02:00
func _ready() -> void:
# Update the Health Bar initially
2025-09-17 14:39:22 +02:00
max_hp_changed.emit(max_hp)
health_changed.emit(current_hp)
2025-09-15 19:15:43 +02:00
func _physics_process(delta: float) -> void:
# Velocity management uses the physics framework, hence runs at fix 60FPS
2025-09-23 12:36:15 +02:00
manage_velocity(delta)
move_and_slide()
2025-10-14 14:45:39 +02:00
func _process(_delta: float) -> void:
# All non-velocity management can run without FPS cap
update_vine_statuses()
manage_movement_options()
2025-10-05 15:01:51 +02:00
manage_interaction()
manage_animation()
if handle_input:
2025-10-14 14:45:39 +02:00
manage_active()
manage_attack()
func manage_attack():
# If an attack is possible, a signal is sent which the weapon can connect to
if(Input.is_action_just_pressed("attack") and $AttackCooldown.time_left <= 0):
if Input.is_action_pressed("up") and can_upslash:
attack.emit("up")
else:
attack.emit("horizontal")
$Sprite.play("attack")
$SwordSwingAudio.play()
$AttackCooldown.start()
2025-09-16 14:59:40 +02:00
2025-10-14 14:45:39 +02:00
func manage_active():
# Activate or remove items. Cooldown + use management is handled by the item.
if(active_item != null and Input.is_action_just_pressed("item") and $ActiveItemCooldown.is_stopped()):
active_item.trigger_activation()
2025-10-05 15:01:51 +02:00
if(Input.is_action_just_pressed("drop_item") and active_item != null):
active_item.remove()
func manage_movement_options() -> void:
# Reset Air Jumps when grounded
if(is_on_floor()):
2025-09-16 00:28:49 +02:00
air_jumps_current = air_jumps_max
2025-10-05 15:01:51 +02:00
func manage_interaction():
# Interacts with all overlapping interactables on button press
2025-10-05 15:01:51 +02:00
if Input.is_action_just_pressed("interact"):
for area in $InteractBox.get_overlapping_areas():
if area.has_method("interact"): area.interact()
func manage_animation() -> void:
var walk_dir = 0
2025-09-16 23:37:45 +02:00
if(handle_input):
if(Input.is_action_pressed("move_right")):
walk_dir += 1
if(Input.is_action_pressed("move_left")):
walk_dir -= 1
# Set the direction the player faces
if walk_dir != 0:
2025-09-16 14:59:40 +02:00
facing = walk_dir
$Sprite.scale.x = - abs($Sprite.scale.x) * facing
# Play the walk or idle animation when appropriate
if(walk_dir != 0):
if(is_on_floor() and not $Sprite.is_playing()):
$Sprite.play("walk")
2025-09-19 12:08:35 +02:00
else:
if $Sprite.animation == "walk":
$Sprite.stop()
2025-09-19 12:08:35 +02:00
2025-09-16 00:28:49 +02:00
2025-09-16 14:59:40 +02:00
func manage_velocity(delta: float) -> void:
up_direction = $EarthAligner.up
# Convert the current velocity into local coordinates, then compute changes there.
# This is important for capped values such as fall speed.
var old_local_velocity = $EarthAligner.local_from_global(velocity)
# Apply friction horizontally, exponentially. Factor 1 - friction per frame at 60FPS.
2025-10-02 15:08:59 +02:00
var local_velocity = Vector2(old_local_velocity.x * pow(1 - friction,60*delta), old_local_velocity.y);
2025-09-15 19:15:43 +02:00
local_velocity += Vector2(0, gravity)
2025-09-16 00:28:49 +02:00
2025-09-16 23:37:45 +02:00
if handle_input:
# Apply Slow Status if present.
2025-10-11 16:37:14 +02:00
var hspeed = base_hspeed if not Status.affects("Slow", self) else base_hspeed * get_node("Slow").params.slow_factor
# Change the local velocity by the movement speed, or more if moving in the opposite direction,
# for more responsive turning around.
2025-09-16 23:37:45 +02:00
if(Input.is_action_pressed("move_right")):
if local_velocity.x > - 3 * hspeed:
2025-10-02 15:08:59 +02:00
local_velocity += Vector2(hspeed, 0)
else:
local_velocity += Vector2(2 * hspeed, 0)
2025-09-16 23:37:45 +02:00
if(Input.is_action_pressed("move_left")):
if local_velocity.x < 3 * hspeed:
2025-10-02 15:08:59 +02:00
local_velocity += Vector2(-hspeed, 0)
else:
local_velocity += Vector2(-2 * hspeed, 0)
2025-09-16 23:37:45 +02:00
if(Input.is_action_just_pressed("jump") and (is_on_floor() or air_jumps_current > 0)):
# If the player holds drop, just move through the platform
if Input.is_action_pressed("drop"):
self.position += 12 * $EarthAligner.down
else:
# Otherwise, either jump from the ground or perform a double jump.
if is_on_floor():
local_velocity.y = -ground_jump_strength
else:
air_jumps_current -= 1;
play_double_jump_animation()
local_velocity.y = -air_jump_strength
2025-09-16 00:28:49 +02:00
if(local_velocity.y > max_fall_speed):
local_velocity.y = max_fall_speed
# When knockback is applied, momentum is reset to the knockback value instead.
if(reset_to_velocity.x != 0):
local_velocity.x = reset_to_velocity.x
if(reset_to_velocity.y != 0):
local_velocity.y = reset_to_velocity.y
reset_to_velocity = Vector2.ZERO
# Return to world coordinates and apply the changes to velocity.
velocity = $EarthAligner.global_from_local(local_velocity)
2025-09-17 11:15:00 +02:00
2025-09-16 00:28:49 +02:00
func hurt(dmg: int, dir: Vector2 = Vector2.ZERO):
# If the player has no iframes, apply knockback and damage and start iframes.
if $IFrames.time_left <= 0:
2025-10-11 16:37:14 +02:00
if Status.affects("Vulnerable", self): dmg += 1
2025-09-19 03:33:03 +02:00
$AudioStreamPlayer2D.play()
2025-09-16 00:28:49 +02:00
current_hp -= dmg
$IFrames.start(hit_iframes)
reset_to_velocity = Vector2(-sign($EarthAligner.local_from_global(dir).x)*knockback_strength, -damage_knockup)
2025-09-18 19:35:52 +02:00
return true
return false
2025-09-16 00:28:49 +02:00
func die():
2025-09-16 14:59:40 +02:00
if not dead:
2025-09-16 19:34:30 +02:00
player_died.emit()
2025-09-16 14:59:40 +02:00
dead = true
2025-09-16 23:37:45 +02:00
# Connected to the signal marking the end of an attack.
# Returns to default animation.
2025-09-19 12:24:23 +02:00
func _on_attack_end():
if($Sprite.animation != "idle"):
$Sprite.play("idle")
$Sprite.stop()
2025-09-16 23:37:45 +02:00
# Take away player control while the Death Screen is active.
2025-09-16 23:37:45 +02:00
func _on_death_screen_visibility_changed() -> void:
handle_input = !handle_input
2025-09-18 17:47:49 +02:00
func play_double_jump_animation() -> void:
# Instantiate a sprite which plays a double jump animation, then deletes itself.
2025-09-19 03:33:03 +02:00
$AirJumpAudio.play()
2025-09-18 17:47:49 +02:00
var node = double_jump_animation.instantiate()
add_child(node)
node.position = Vector2(0, 5)
node.scale = .5 * Vector2.ONE
node.reparent(get_parent())
# If there is any vine nearby, gain the corresponding statusses.
func update_vine_statuses():
var location = Grid.get_location_from_world_pos(global_position)
for vine : Vine in Grid.vines_per_node[location.x][location.y]:
2025-10-11 18:22:15 +02:00
if vine.active_depth > 0: #TODO: Properly manage procedural activation
Status.apply(vine.status_name, self, 0.1, vine.status_params)