241 lines
7.3 KiB
GDScript
241 lines
7.3 KiB
GDScript
class_name Player extends CharacterBody2D
|
|
|
|
@export var double_jump_animation : PackedScene
|
|
|
|
# allow taking away player control
|
|
var handle_input : bool = true
|
|
|
|
# Gravity
|
|
var earth_center = Vector2.ZERO;
|
|
var max_fall_speed = 700;
|
|
var gravity = 100;
|
|
|
|
# Movement
|
|
var facing = -1;
|
|
var base_hspeed = 150.0;
|
|
var ground_jump_strength = 1400;
|
|
var air_jump_strength = 1100;
|
|
var air_jumps_max = 1;
|
|
var air_jumps_current = 1:
|
|
set(air_jumps_new):
|
|
air_jumps_current = min(air_jumps_new, air_jumps_max)
|
|
|
|
# HP and Iframes
|
|
signal health_changed(new_health : int)
|
|
signal max_hp_changed(new_max_hp : int)
|
|
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.
|
|
new_hp = min(new_hp, max_hp)
|
|
if new_hp <= 0:
|
|
new_hp = 0
|
|
die()
|
|
if new_hp != current_hp:
|
|
current_hp = new_hp
|
|
health_changed.emit(current_hp)
|
|
@export var max_hp = 5:
|
|
# When Max HP is reduced below current health, current health is adjusted.
|
|
set(new_max_hp):
|
|
max_hp = new_max_hp
|
|
if max_hp <= 0:
|
|
max_hp = 0
|
|
if current_hp > max_hp:
|
|
current_hp = max_hp
|
|
max_hp_changed.emit(max_hp)
|
|
var dead = false;
|
|
|
|
# Received Knockback
|
|
var reset_to_velocity = Vector2.ZERO
|
|
var knockback_strength = 1500
|
|
var damage_knockup = 500
|
|
@export var friction = 0.5
|
|
|
|
# Attack Handling
|
|
var can_upslash = false
|
|
signal attack
|
|
|
|
# Active Item
|
|
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()
|
|
|
|
|
|
func _ready() -> void:
|
|
# Update the Health Bar initially
|
|
max_hp_changed.emit(max_hp)
|
|
health_changed.emit(current_hp)
|
|
|
|
func _physics_process(delta: float) -> void:
|
|
# Velocity management uses the physics framework, hence runs at fix 60FPS
|
|
manage_velocity(delta)
|
|
move_and_slide()
|
|
|
|
func _process(_delta: float) -> void:
|
|
# All non-velocity management can run without FPS cap
|
|
update_vine_statuses()
|
|
manage_movement_options()
|
|
manage_interaction()
|
|
manage_animation()
|
|
if handle_input:
|
|
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()
|
|
|
|
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()
|
|
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()):
|
|
air_jumps_current = air_jumps_max
|
|
|
|
func manage_interaction():
|
|
# Interacts with all overlapping interactables on button press
|
|
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
|
|
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:
|
|
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")
|
|
else:
|
|
if $Sprite.animation == "walk":
|
|
$Sprite.stop()
|
|
|
|
|
|
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.
|
|
var local_velocity = Vector2(old_local_velocity.x * pow(1 - friction,60*delta), old_local_velocity.y);
|
|
local_velocity += Vector2(0, gravity)
|
|
|
|
if handle_input:
|
|
# Apply Slow Status if present.
|
|
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.
|
|
if(Input.is_action_pressed("move_right")):
|
|
if local_velocity.x > - 3 * hspeed:
|
|
local_velocity += Vector2(hspeed, 0)
|
|
else:
|
|
local_velocity += Vector2(2 * hspeed, 0)
|
|
if(Input.is_action_pressed("move_left")):
|
|
if local_velocity.x < 3 * hspeed:
|
|
local_velocity += Vector2(-hspeed, 0)
|
|
else:
|
|
local_velocity += Vector2(-2 * hspeed, 0)
|
|
|
|
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
|
|
|
|
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)
|
|
|
|
|
|
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:
|
|
if Status.affects("Vulnerable", self): dmg += 1
|
|
$AudioStreamPlayer2D.play()
|
|
current_hp -= dmg
|
|
$IFrames.start(hit_iframes)
|
|
reset_to_velocity = Vector2(-sign($EarthAligner.local_from_global(dir).x)*knockback_strength, -damage_knockup)
|
|
return true
|
|
return false
|
|
|
|
func die():
|
|
if not dead:
|
|
player_died.emit()
|
|
dead = true
|
|
|
|
# Connected to the signal marking the end of an attack.
|
|
# Returns to default animation.
|
|
func _on_attack_end():
|
|
if($Sprite.animation != "idle"):
|
|
$Sprite.play("idle")
|
|
$Sprite.stop()
|
|
|
|
# Take away player control while the Death Screen is active.
|
|
func _on_death_screen_visibility_changed() -> void:
|
|
handle_input = !handle_input
|
|
|
|
func play_double_jump_animation() -> void:
|
|
# Instantiate a sprite which plays a double jump animation, then deletes itself.
|
|
$AirJumpAudio.play()
|
|
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]:
|
|
if vine.active_depth > 0: #TODO: Properly manage procedural activation
|
|
Status.apply(vine.status_name, self, 0.1, vine.status_params)
|