added some amazing changes
This commit is contained in:
@@ -59,6 +59,7 @@ var controls_disabled: bool = false # True when player has reached exit and cont
|
||||
|
||||
# Being held state
|
||||
var being_held_by: Node = null
|
||||
var grabbed_by_enemy_hand: Node = null # Set when enemy hand grabs (snatch); locks movement until release
|
||||
var struggle_time: float = 0.0
|
||||
var struggle_threshold: float = 0.8 # Seconds to break free
|
||||
var struggle_direction: Vector2 = Vector2.ZERO
|
||||
@@ -97,7 +98,8 @@ var burn_debuff_duration: float = 5.0 # Burn lasts 5 seconds
|
||||
var burn_debuff_damage_per_second: float = 1.0 # 1 HP per second
|
||||
var burn_debuff_visual: Node2D = null # Visual indicator for burn debuff
|
||||
var burn_damage_timer: float = 0.0 # Timer for burn damage ticks
|
||||
var movement_lock_timer: float = 0.0 # Lock movement when bow is released
|
||||
var movement_lock_timer: float = 0.0 # Lock movement when bow is released or after casting spell
|
||||
const SPELL_CAST_LOCK_DURATION: float = 0.28 # Lock in place briefly after casting a spell
|
||||
var direction_lock_timer: float = 0.0 # Lock facing direction when attacking
|
||||
var locked_facing_direction: Vector2 = Vector2.ZERO # Locked facing direction during attack
|
||||
var damage_direction_lock_timer: float = 0.0 # Lock facing when taking damage (enemies/players)
|
||||
@@ -124,9 +126,22 @@ var velocity_z: float = 0.0
|
||||
var gravity_z: float = 500.0 # Gravity pulling down (scaled for 1x scale)
|
||||
var is_airborne: bool = false
|
||||
|
||||
# Spawn fall-down: hidden at start, fall from high Z, land with DIE+concussion, then stand up
|
||||
const SPAWN_FALL_INITIAL_Z: float = 700.0 # High enough to start off-screen (y_offset = -350)
|
||||
const SPAWN_FALL_GRAVITY: float = 180.0 # Slower fall so descent is visible (~2.8s from 700)
|
||||
const SPAWN_LANDING_BOUNCE_GRAVITY: float = 420.0 # Heavier gravity during bounce so it's snappier
|
||||
const SPAWN_LANDING_BOUNCE_UP: float = 95.0 # Upward velocity on first impact for a noticeable bounce
|
||||
const SPAWN_LANDING_LAND_DURATION: float = 0.85 # Time in LAND (and concussion) before switching to STAND
|
||||
const SPAWN_LANDING_STAND_DURATION: float = 0.16 # STAND anim duration (40+40+40+40 ms) before allow control
|
||||
var spawn_landing: bool = false
|
||||
var spawn_landing_landed: bool = false
|
||||
var spawn_landing_bounced: bool = false
|
||||
|
||||
# Components
|
||||
# @onready var sprite = $Sprite2D # REMOVED: Now using layered sprites
|
||||
@onready var shadow = $Shadow
|
||||
@onready var cone_light = $ConeLight
|
||||
@onready var point_light = $PointLight2D
|
||||
@onready var collision_shape = $CollisionShape2D
|
||||
@onready var grab_area = $GrabArea
|
||||
@onready var interaction_indicator = $InteractionIndicator
|
||||
@@ -150,7 +165,6 @@ var is_airborne: bool = false
|
||||
@onready var sprite_shield = $Sprite2DShield
|
||||
@onready var sprite_shield_holding = $Sprite2DShieldHolding
|
||||
@onready var sprite_weapon = $Sprite2DWeapon
|
||||
@onready var cone_light = $ConeLight
|
||||
|
||||
# Player stats (legacy - now using character_stats)
|
||||
var max_health: float:
|
||||
@@ -308,6 +322,24 @@ const ANIMATIONS = {
|
||||
"frameDurations": [260, 260, 260, 260],
|
||||
"loop": true,
|
||||
"nextAnimation": null
|
||||
},
|
||||
"FALL": {
|
||||
"frames": [19, 21],
|
||||
"frameDurations": [10, 10],
|
||||
"loop": true,
|
||||
"nextAnimation": null
|
||||
},
|
||||
"LAND": {
|
||||
"frames": [23],
|
||||
"frameDurations": [10],
|
||||
"loop": true,
|
||||
"nextAnimation": null
|
||||
},
|
||||
"STAND": {
|
||||
"frames": [23,24,22,1],
|
||||
"frameDurations": [40,40,40,40],
|
||||
"loop": false,
|
||||
"nextAnimation": "IDLE"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,6 +353,20 @@ func _ready():
|
||||
# Add to player group for easy identification
|
||||
add_to_group("player")
|
||||
|
||||
# Spawn fall-down: hidden at start for everyone, then show and fall from high Z
|
||||
visible = false
|
||||
spawn_landing = true
|
||||
spawn_landing_landed = false
|
||||
spawn_landing_bounced = false
|
||||
position_z = SPAWN_FALL_INITIAL_Z
|
||||
velocity_z = 0.0
|
||||
is_airborne = true
|
||||
if cone_light:
|
||||
cone_light.visible = false
|
||||
if point_light:
|
||||
point_light.visible = false
|
||||
call_deferred("_spawn_landing_show")
|
||||
|
||||
# Set respawn point to starting position
|
||||
respawn_point = global_position
|
||||
|
||||
@@ -469,6 +515,11 @@ func _ready():
|
||||
# Emit character_changed to trigger appearance sync for any newly connected clients
|
||||
character_stats.character_changed.emit(character_stats)
|
||||
|
||||
func _spawn_landing_show():
|
||||
if not is_instance_valid(self):
|
||||
return
|
||||
visible = true
|
||||
|
||||
func _duplicate_sprite_materials():
|
||||
# Duplicate shader materials for ALL sprites that use tint parameters
|
||||
# This prevents shared material state between players
|
||||
@@ -1530,6 +1581,9 @@ func _update_facing_from_mouse(mouse_direction: Vector2):
|
||||
# Don't update facing when dead
|
||||
if is_dead:
|
||||
return
|
||||
# Don't update facing during spawn fall/land/stand (locked to DOWN until stand up)
|
||||
if spawn_landing:
|
||||
return
|
||||
|
||||
# Only update if using keyboard input (not gamepad)
|
||||
if input_device != -1:
|
||||
@@ -1702,8 +1756,11 @@ func _snap_to_8_directions(direction: Vector2) -> Vector2:
|
||||
return best_dir
|
||||
|
||||
func _update_z_physics(delta):
|
||||
# Apply gravity
|
||||
velocity_z -= gravity_z * delta
|
||||
# Apply gravity (slower spawn-fall; snappier bounce)
|
||||
var g = gravity_z
|
||||
if spawn_landing and not spawn_landing_landed:
|
||||
g = SPAWN_LANDING_BOUNCE_GRAVITY if spawn_landing_bounced else SPAWN_FALL_GRAVITY
|
||||
velocity_z -= g * delta
|
||||
|
||||
# Update Z position
|
||||
position_z += velocity_z * delta
|
||||
@@ -1711,15 +1768,28 @@ func _update_z_physics(delta):
|
||||
# Check if landed
|
||||
if position_z <= 0.0:
|
||||
position_z = 0.0
|
||||
velocity_z = 0.0
|
||||
is_airborne = false
|
||||
|
||||
# Stop horizontal movement on landing (with some friction)
|
||||
velocity = velocity * 0.3
|
||||
|
||||
# Visual effect when landing (removed - using layered sprites now)
|
||||
|
||||
print(name, " landed!")
|
||||
# Spawn landing: first impact -> bounce + LAND; second impact -> settle, then STAND
|
||||
if spawn_landing and not spawn_landing_landed and is_multiplayer_authority() and not spawn_landing_bounced:
|
||||
spawn_landing_bounced = true
|
||||
velocity_z = SPAWN_LANDING_BOUNCE_UP
|
||||
velocity = velocity * 0.3
|
||||
current_direction = Direction.RIGHT
|
||||
facing_direction_vector = Vector2.RIGHT
|
||||
_set_animation("LAND")
|
||||
if has_node("SfxFallDownLand"):
|
||||
$SfxFallDownLand.play()
|
||||
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
|
||||
_rpc_to_ready_peers("_sync_spawn_bounced", [name])
|
||||
# keep is_airborne true, continue falling after bounce
|
||||
else:
|
||||
velocity_z = 0.0
|
||||
is_airborne = false
|
||||
velocity = velocity * 0.3
|
||||
if not spawn_landing:
|
||||
print(name, " landed!")
|
||||
elif spawn_landing and not spawn_landing_landed and is_multiplayer_authority():
|
||||
spawn_landing_landed = true
|
||||
_spawn_landing_on_land()
|
||||
|
||||
# Update visual offset based on height for all sprite layers
|
||||
var y_offset = - position_z * 0.5 # Visual height is half of z for perspective
|
||||
@@ -1754,6 +1824,19 @@ func _physics_process(delta):
|
||||
# Reset teleport flag at start of frame
|
||||
teleported_this_frame = false
|
||||
|
||||
# Spawn fall: DOWN during fall; RIGHT on landing (bounce + rest). FALL until first impact, then LAND.
|
||||
if spawn_landing:
|
||||
if not spawn_landing_bounced:
|
||||
current_direction = Direction.DOWN
|
||||
facing_direction_vector = Vector2.DOWN
|
||||
else:
|
||||
current_direction = Direction.RIGHT
|
||||
facing_direction_vector = Vector2.RIGHT
|
||||
if is_airborne and not spawn_landing_landed and not spawn_landing_bounced:
|
||||
_set_animation("FALL")
|
||||
elif is_airborne and spawn_landing_bounced:
|
||||
_set_animation("LAND")
|
||||
|
||||
# Update animations
|
||||
_update_animation(delta)
|
||||
|
||||
@@ -1898,15 +1981,15 @@ func _physics_process(delta):
|
||||
burn_damage_timer = 0.0
|
||||
_remove_burn_debuff()
|
||||
|
||||
# Skip input if controls are disabled (e.g., when inventory is open)
|
||||
# Skip input if controls are disabled (e.g., when inventory is open) or spawn landing (fall → DIE → stand up)
|
||||
# But still allow knockback to continue (handled above)
|
||||
var skip_input = controls_disabled
|
||||
if controls_disabled:
|
||||
var skip_input = controls_disabled or spawn_landing
|
||||
if controls_disabled or spawn_landing:
|
||||
if not is_knocked_back:
|
||||
# Immediately stop movement when controls are disabled (e.g., inventory opened)
|
||||
velocity = Vector2.ZERO
|
||||
# Reset animation to IDLE if not in a special state
|
||||
if current_animation != "THROW" and current_animation != "DAMAGE" and current_animation != "SWORD" and current_animation != "BOW" and current_animation != "STAFF":
|
||||
# Reset animation to IDLE if not in a special state (skip when spawn_landing: we use DIE until stand up)
|
||||
if not spawn_landing and current_animation != "THROW" and current_animation != "DAMAGE" and current_animation != "SWORD" and current_animation != "BOW" and current_animation != "STAFF":
|
||||
if is_lifting:
|
||||
_set_animation("IDLE_HOLD")
|
||||
elif is_pushing:
|
||||
@@ -1914,7 +1997,7 @@ func _physics_process(delta):
|
||||
else:
|
||||
_set_animation("IDLE")
|
||||
|
||||
# Check if being held by someone
|
||||
# Check if being held by someone (another player) or grabbed by enemy hand
|
||||
var being_held_by_someone = false
|
||||
for other_player in get_tree().get_nodes_in_group("player"):
|
||||
if other_player != self and other_player.held_object == self:
|
||||
@@ -1928,6 +2011,11 @@ func _physics_process(delta):
|
||||
_update_shield_visibility()
|
||||
# Handle struggle mechanic
|
||||
_handle_struggle(delta)
|
||||
elif grabbed_by_enemy_hand:
|
||||
velocity = Vector2.ZERO
|
||||
is_shielding = false
|
||||
was_shielding_last_frame = false
|
||||
_update_shield_visibility()
|
||||
elif is_knocked_back:
|
||||
is_shielding = false
|
||||
was_shielding_last_frame = false
|
||||
@@ -2378,6 +2466,8 @@ func _handle_interactions():
|
||||
heal_target = _get_heal_target()
|
||||
|
||||
var has_valid_target = ((is_fire or is_frost) and target_pos != Vector2.ZERO) or (is_heal and heal_target != null)
|
||||
# Healing: allow charge even without target (don't disable charge when hovering enemy/wall/etc.)
|
||||
var can_start_charge = is_heal or has_valid_target
|
||||
|
||||
# Check if there's a grabbable object nearby - prioritize grabbing over spell casting
|
||||
var nearby_grabbable = null
|
||||
@@ -2399,7 +2489,7 @@ func _handle_interactions():
|
||||
nearby_grabbable = body
|
||||
break
|
||||
|
||||
if grab_just_pressed and not is_charging_spell and has_valid_target and not nearby_grabbable and not is_lifting and not held_object:
|
||||
if grab_just_pressed and not is_charging_spell and can_start_charge and not nearby_grabbable and not is_lifting and not held_object:
|
||||
is_charging_spell = true
|
||||
current_spell_element = "healing" if is_heal else ("frost" if is_frost else "fire")
|
||||
spell_charge_start_time = Time.get_ticks_msec() / 1000.0
|
||||
@@ -2448,6 +2538,7 @@ func _handle_interactions():
|
||||
else:
|
||||
_cast_heal_spell(heal_target)
|
||||
_set_animation("FINISH_SPELL")
|
||||
movement_lock_timer = SPELL_CAST_LOCK_DURATION
|
||||
is_charging_spell = false
|
||||
current_spell_element = "fire"
|
||||
spell_incantation_played = false
|
||||
@@ -2474,7 +2565,8 @@ func _handle_interactions():
|
||||
print(name, " released spell (", "healing" if is_heal else ("frost" if is_frost else "fire"), ", fully: ", is_fully_charged, ")")
|
||||
just_grabbed_this_frame = false
|
||||
return
|
||||
elif is_charging_spell and (not has_valid_target or is_lifting or held_object):
|
||||
elif is_charging_spell and (is_lifting or held_object or ((not is_heal) and not has_valid_target)):
|
||||
# Don't cancel heal charge for no target (allow hover over enemy/wall); cancel fire/frost
|
||||
is_charging_spell = false
|
||||
current_spell_element = "fire"
|
||||
spell_incantation_played = false
|
||||
@@ -2488,7 +2580,7 @@ func _handle_interactions():
|
||||
$SfxSpellIncantation.stop()
|
||||
if multiplayer.has_multiplayer_peer():
|
||||
_sync_spell_charge_end.rpc()
|
||||
print(name, " spell charge cancelled (no target)")
|
||||
print(name, " spell charge cancelled (no target / lift / held)")
|
||||
|
||||
# Check for trap disarm (Dwarf only)
|
||||
if character_stats and character_stats.race == "Dwarf":
|
||||
@@ -4068,14 +4160,31 @@ func _cast_heal_spell(target: Node):
|
||||
if is_crit:
|
||||
amount = floor(amount * 2.0)
|
||||
|
||||
var is_revive = "is_dead" in target and target.is_dead
|
||||
var display_amount = int(amount)
|
||||
|
||||
# Undead enemies take damage from healing spell
|
||||
if target.is_in_group("enemy") and "is_undead" in target and target.is_undead:
|
||||
var damage_amount = float(display_amount)
|
||||
var eid = target.get_multiplayer_authority()
|
||||
var my_id = multiplayer.get_unique_id()
|
||||
if eid == my_id:
|
||||
target.take_damage(damage_amount, global_position, is_crit)
|
||||
elif multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
|
||||
target.rpc_take_damage.rpc_id(eid, damage_amount, global_position, is_crit, false, false)
|
||||
else:
|
||||
target.rpc_take_damage.rpc(damage_amount, global_position, is_crit, false, false)
|
||||
_spawn_heal_effect_and_text(target, display_amount, is_crit, false, true)
|
||||
if gw and gw.has_method("_apply_heal_spell_sync"):
|
||||
_rpc_to_ready_peers("_sync_heal_spell_via_gw", [target.name, damage_amount, display_amount, is_crit, false, false, false, true])
|
||||
print(name, " cast heal on undead ", target.name, " for ", display_amount, " damage (crit: ", is_crit, ")")
|
||||
return
|
||||
|
||||
var is_revive = "is_dead" in target and target.is_dead
|
||||
var actual_heal = amount
|
||||
var allow_overheal = false
|
||||
var is_overheal = false
|
||||
|
||||
if is_revive:
|
||||
# Tome revive: no overheal, full amount revives
|
||||
actual_heal = amount
|
||||
else:
|
||||
var overheal_chance_pct = 1.0 + lck_val * 0.3
|
||||
@@ -4099,12 +4208,12 @@ func _cast_heal_spell(target: Node):
|
||||
else:
|
||||
if me == tid and actual_heal > 0:
|
||||
target.heal(actual_heal, allow_overheal)
|
||||
_spawn_heal_effect_and_text(target, display_amount, is_crit, is_overheal)
|
||||
_spawn_heal_effect_and_text(target, display_amount, is_crit, is_overheal, false)
|
||||
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree() and gw and gw.has_method("_apply_heal_spell_sync"):
|
||||
_rpc_to_ready_peers("_sync_heal_spell_via_gw", [target.name, actual_heal, display_amount, is_crit, is_overheal, allow_overheal, is_revive])
|
||||
_rpc_to_ready_peers("_sync_heal_spell_via_gw", [target.name, actual_heal, display_amount, is_crit, is_overheal, allow_overheal, is_revive, false])
|
||||
print(name, " cast heal on ", target.name, " for ", display_amount, " HP", " (actual: ", actual_heal, ", crit: ", is_crit, ", overheal: ", is_overheal, ", revive: ", is_revive, ")")
|
||||
|
||||
func _spawn_heal_effect_and_text(target: Node, display_amount: int, is_crit: bool, is_overheal: bool):
|
||||
func _spawn_heal_effect_and_text(target: Node, display_amount: int, is_crit: bool, is_overheal: bool, is_damage_to_enemy: bool = false):
|
||||
if not target or not is_instance_valid(target):
|
||||
return
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
@@ -4118,6 +4227,9 @@ func _spawn_heal_effect_and_text(target: Node, display_amount: int, is_crit: boo
|
||||
eff.global_position = target.global_position
|
||||
if eff.has_method("setup"):
|
||||
eff.setup(target)
|
||||
if is_damage_to_enemy:
|
||||
# Undead: enemy's take_damage already shows damage number; we only spawn effect
|
||||
return
|
||||
var prefix = ""
|
||||
if is_crit and is_overheal:
|
||||
prefix = "CRIT OVERHEAL! "
|
||||
@@ -4134,12 +4246,12 @@ func _spawn_heal_effect_and_text(target: Node, display_amount: int, is_crit: boo
|
||||
ft.setup(heal_text, Color.GREEN, 0.5, 0.5, null, 1, 1, 0)
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _sync_heal_spell_via_gw(target_name: String, amount_to_apply: float, display_amount: int, is_crit: bool, is_overheal: bool, allow_overheal: bool, is_revive: bool = false):
|
||||
func _sync_heal_spell_via_gw(target_name: String, amount_to_apply: float, display_amount: int, is_crit: bool, is_overheal: bool, allow_overheal: bool, is_revive: bool = false, is_damage_to_enemy: bool = false):
|
||||
if is_multiplayer_authority():
|
||||
return
|
||||
var gw = get_tree().get_first_node_in_group("game_world")
|
||||
if gw and gw.has_method("_apply_heal_spell_sync"):
|
||||
gw._apply_heal_spell_sync(target_name, amount_to_apply, display_amount, is_crit, is_overheal, allow_overheal, is_revive)
|
||||
gw._apply_heal_spell_sync(target_name, amount_to_apply, display_amount, is_crit, is_overheal, allow_overheal, is_revive, is_damage_to_enemy)
|
||||
|
||||
func _is_healing_spell() -> bool:
|
||||
if not character_stats or not character_stats.equipment.has("offhand"):
|
||||
@@ -4169,6 +4281,15 @@ func _get_heal_target() -> Node:
|
||||
if d < best_d:
|
||||
best_d = d
|
||||
best = p
|
||||
for e in get_tree().get_nodes_in_group("enemy"):
|
||||
if not is_instance_valid(e) or ("is_dead" in e and e.is_dead):
|
||||
continue
|
||||
if not ("is_undead" in e and e.is_undead):
|
||||
continue
|
||||
var d = e.global_position.distance_to(mouse_world)
|
||||
if d < best_d:
|
||||
best_d = d
|
||||
best = e
|
||||
return best
|
||||
|
||||
func _can_cast_spell_at(target_position: Vector2) -> bool:
|
||||
@@ -5580,6 +5701,34 @@ func set_being_held(held: bool):
|
||||
struggle_direction = Vector2.ZERO
|
||||
being_held_by = null
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func rpc_grabbed_by_enemy_hand(enemy_name: String) -> void:
|
||||
var en: Node = null
|
||||
var gw = get_tree().get_first_node_in_group("game_world")
|
||||
if gw:
|
||||
var entities = gw.get_node_or_null("Entities")
|
||||
if entities:
|
||||
en = entities.get_node_or_null(enemy_name)
|
||||
if not en:
|
||||
en = _find_node_by_name(gw, enemy_name)
|
||||
grabbed_by_enemy_hand = en if en and is_instance_valid(en) else null
|
||||
velocity = Vector2.ZERO
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func rpc_released_from_enemy_hand() -> void:
|
||||
grabbed_by_enemy_hand = null
|
||||
|
||||
func _find_node_by_name(node: Node, n: String) -> Node:
|
||||
if not node:
|
||||
return null
|
||||
if node.name == n:
|
||||
return node
|
||||
for c in node.get_children():
|
||||
var found = _find_node_by_name(c, n)
|
||||
if found:
|
||||
return found
|
||||
return null
|
||||
|
||||
# RPC function called by attacker to deal damage to this player
|
||||
@rpc("any_peer", "reliable")
|
||||
func rpc_take_damage(amount: float, attacker_position: Vector2, is_burn_damage: bool = false, apply_burn_debuff: bool = false):
|
||||
@@ -5903,6 +6052,64 @@ func _are_all_players_dead() -> bool:
|
||||
return false
|
||||
return true
|
||||
|
||||
func _spawn_landing_on_land():
|
||||
if has_node("SfxFallDownLand"):
|
||||
$SfxFallDownLand.play()
|
||||
_set_animation("LAND")
|
||||
var status_anim = get_node_or_null("Sprite2DStatus/AnimationPlayerStatus")
|
||||
if status_anim and status_anim.has_animation("concussion"):
|
||||
status_anim.play("concussion")
|
||||
var gw = get_tree().get_first_node_in_group("game_world")
|
||||
if gw and gw.has_method("add_screenshake"):
|
||||
gw.add_screenshake(7.0, 0.28)
|
||||
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
|
||||
_rpc_to_ready_peers("_sync_spawn_landed", [name])
|
||||
get_tree().create_timer(SPAWN_LANDING_LAND_DURATION).timeout.connect(_spawn_landing_to_stand)
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _sync_spawn_bounced(_player_name: String):
|
||||
spawn_landing_bounced = true
|
||||
current_direction = Direction.RIGHT
|
||||
facing_direction_vector = Vector2.RIGHT
|
||||
_set_animation("LAND")
|
||||
if has_node("SfxFallDownLand"):
|
||||
$SfxFallDownLand.play()
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _sync_spawn_landed(_player_name: String):
|
||||
# Received on remote copies when authority lands from spawn fall
|
||||
spawn_landing_landed = true
|
||||
position_z = 0.0
|
||||
velocity_z = 0.0
|
||||
is_airborne = false
|
||||
if has_node("SfxFallDownLand"):
|
||||
$SfxFallDownLand.play()
|
||||
_set_animation("LAND")
|
||||
var status_anim = get_node_or_null("Sprite2DStatus/AnimationPlayerStatus")
|
||||
if status_anim and status_anim.has_animation("concussion"):
|
||||
status_anim.play("concussion")
|
||||
get_tree().create_timer(SPAWN_LANDING_LAND_DURATION).timeout.connect(_spawn_landing_to_stand)
|
||||
|
||||
func _spawn_landing_to_stand():
|
||||
if not is_instance_valid(self):
|
||||
return
|
||||
_set_animation("STAND")
|
||||
get_tree().create_timer(SPAWN_LANDING_STAND_DURATION).timeout.connect(_spawn_landing_stand_up)
|
||||
|
||||
func _spawn_landing_stand_up():
|
||||
if not is_instance_valid(self):
|
||||
return
|
||||
# Clear concussion status (was showing during LAND)
|
||||
var status_anim = get_node_or_null("Sprite2DStatus/AnimationPlayerStatus")
|
||||
if status_anim and status_anim.has_animation("idle"):
|
||||
status_anim.play("idle")
|
||||
# STAND's nextAnimation -> IDLE, so we're already IDLE or about to be
|
||||
spawn_landing = false
|
||||
if cone_light:
|
||||
cone_light.visible = true
|
||||
if point_light:
|
||||
point_light.visible = true
|
||||
|
||||
func _respawn():
|
||||
print(name, " respawning!")
|
||||
was_revived = false
|
||||
@@ -6542,6 +6749,24 @@ func _show_revive_cost_number(amount: int):
|
||||
get_tree().current_scene.add_child(damage_label)
|
||||
damage_label.global_position = global_position + Vector2(0, -16)
|
||||
|
||||
func show_floating_status(text: String, col: Color = Color.WHITE) -> void:
|
||||
"""Show a damage-number-style floating text above player (e.g. 'Encumbered!')."""
|
||||
var damage_number_scene = preload("res://scenes/damage_number.tscn")
|
||||
if not damage_number_scene:
|
||||
return
|
||||
var lbl = damage_number_scene.instantiate()
|
||||
if not lbl:
|
||||
return
|
||||
lbl.label = text
|
||||
lbl.color = col
|
||||
lbl.z_index = 5
|
||||
lbl.direction = Vector2(0, -1)
|
||||
var game_world = get_tree().get_first_node_in_group("game_world")
|
||||
var parent = game_world.get_node_or_null("Entities") if game_world else get_tree().current_scene
|
||||
if parent:
|
||||
parent.add_child(lbl)
|
||||
lbl.global_position = global_position + Vector2(0, -20)
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _sync_revive_cost(amount: int):
|
||||
if is_multiplayer_authority():
|
||||
@@ -6589,12 +6814,16 @@ func _on_level_up_stats(stats_increased: Array):
|
||||
var base_y_offset = -32.0 # Start above player head
|
||||
var y_spacing = 12.0 # Space between each text
|
||||
|
||||
# Show "LEVEL UP!" first (in white)
|
||||
# Show "LEVEL UP +1!" prominently (gold, larger, longer on screen)
|
||||
var level_up_text = damage_number_scene.instantiate()
|
||||
if level_up_text:
|
||||
level_up_text.label = "LEVEL UP!"
|
||||
level_up_text.color = Color.WHITE
|
||||
level_up_text.direction = Vector2(0, -1) # Straight up
|
||||
level_up_text.label = "LEVEL UP +1!"
|
||||
level_up_text.color = Color(1.0, 0.88, 0.2) # Gold
|
||||
level_up_text.direction = Vector2(0, -1)
|
||||
level_up_text.rise_distance = 48.0
|
||||
level_up_text.fade_delay = 1.4
|
||||
level_up_text.fade_duration = 0.6
|
||||
level_up_text.add_theme_font_size_override("font_size", 20)
|
||||
entities_node.add_child(level_up_text)
|
||||
level_up_text.global_position = global_position + Vector2(0, base_y_offset)
|
||||
base_y_offset -= y_spacing
|
||||
|
||||
Reference in New Issue
Block a user