made some adjustments to brights of rooms
This commit is contained in:
@@ -44,6 +44,7 @@ var grab_tap_threshold = 0.2 # Seconds to distinguish tap from hold
|
||||
var is_shielding: bool = false # True when holding grab with shield in offhand and nothing to grab/lift
|
||||
var is_reviving: bool = false # True when holding grab on a corpse and charging revive
|
||||
var revive_charge: float = 0.0
|
||||
var was_reviving_last_frame: bool = false
|
||||
const REVIVE_DURATION: float = 2.0 # Seconds holding grab to revive
|
||||
var last_movement_direction = Vector2.DOWN # Track last direction for placing objects
|
||||
var push_axis = Vector2.ZERO # Locked axis for pushing/pulling
|
||||
@@ -1782,165 +1783,180 @@ func _physics_process(delta):
|
||||
else:
|
||||
spell_charge_tint_pulse_time = 0.0
|
||||
|
||||
# Revive charge visuals - same as healing (healing_charging on AnimationIncantation)
|
||||
if is_reviving:
|
||||
was_reviving_last_frame = true
|
||||
if has_node("AnimationIncantation") and not is_charging_spell:
|
||||
$AnimationIncantation.play("healing_charging")
|
||||
elif was_reviving_last_frame:
|
||||
was_reviving_last_frame = false
|
||||
_stop_spell_charge_incantation()
|
||||
|
||||
if is_local_player and is_multiplayer_authority():
|
||||
# Skip all input and logic if dead
|
||||
# When dead: only corpse knockback friction + sync; no input or other logic
|
||||
if is_dead:
|
||||
return
|
||||
|
||||
# Handle knockback timer (always handle knockback, even when controls are disabled)
|
||||
if is_knocked_back:
|
||||
knockback_time += delta
|
||||
if knockback_time >= knockback_duration:
|
||||
is_knocked_back = false
|
||||
knockback_time = 0.0
|
||||
|
||||
# Update movement lock timer (for bow release)
|
||||
if movement_lock_timer > 0.0:
|
||||
movement_lock_timer -= delta
|
||||
if movement_lock_timer <= 0.0:
|
||||
movement_lock_timer = 0.0
|
||||
|
||||
# Update direction lock timer (for attacks)
|
||||
if direction_lock_timer > 0.0:
|
||||
direction_lock_timer -= delta
|
||||
if direction_lock_timer <= 0.0:
|
||||
direction_lock_timer = 0.0
|
||||
|
||||
# Update damage direction lock timer (block facing change when taking damage)
|
||||
if damage_direction_lock_timer > 0.0:
|
||||
damage_direction_lock_timer -= delta
|
||||
if damage_direction_lock_timer <= 0.0:
|
||||
damage_direction_lock_timer = 0.0
|
||||
|
||||
if shield_block_cooldown_timer > 0.0:
|
||||
shield_block_cooldown_timer -= delta
|
||||
if shield_block_cooldown_timer <= 0.0:
|
||||
shield_block_cooldown_timer = 0.0
|
||||
|
||||
# Update bow charge tint (when fully charged)
|
||||
if is_charging_bow:
|
||||
var charge_time = (Time.get_ticks_msec() / 1000.0) - bow_charge_start_time
|
||||
# Smooth curve: charge from 0.2s to 1.0s
|
||||
var charge_progress = clamp((charge_time - 0.2) / 0.8, 0.0, 1.0) # 0.0 at 0.2s, 1.0 at 1.0s
|
||||
if is_knocked_back:
|
||||
knockback_time += delta
|
||||
if knockback_time >= knockback_duration:
|
||||
is_knocked_back = false
|
||||
knockback_time = 0.0
|
||||
else:
|
||||
velocity = velocity.lerp(Vector2.ZERO, delta * 8.0)
|
||||
else:
|
||||
# Handle knockback timer (always handle knockback, even when controls are disabled)
|
||||
if is_knocked_back:
|
||||
knockback_time += delta
|
||||
if knockback_time >= knockback_duration:
|
||||
is_knocked_back = false
|
||||
knockback_time = 0.0
|
||||
|
||||
# Update tint pulse timer when fully charged
|
||||
if charge_progress >= 1.0:
|
||||
# Use fast pulse speed when fully charged
|
||||
bow_charge_tint_pulse_time += delta * bow_charge_tint_pulse_speed_charged
|
||||
_apply_bow_charge_tint()
|
||||
# Update movement lock timer (for bow release)
|
||||
if movement_lock_timer > 0.0:
|
||||
movement_lock_timer -= delta
|
||||
if movement_lock_timer <= 0.0:
|
||||
movement_lock_timer = 0.0
|
||||
|
||||
# Update direction lock timer (for attacks)
|
||||
if direction_lock_timer > 0.0:
|
||||
direction_lock_timer -= delta
|
||||
if direction_lock_timer <= 0.0:
|
||||
direction_lock_timer = 0.0
|
||||
|
||||
# Update damage direction lock timer (block facing change when taking damage)
|
||||
if damage_direction_lock_timer > 0.0:
|
||||
damage_direction_lock_timer -= delta
|
||||
if damage_direction_lock_timer <= 0.0:
|
||||
damage_direction_lock_timer = 0.0
|
||||
|
||||
if shield_block_cooldown_timer > 0.0:
|
||||
shield_block_cooldown_timer -= delta
|
||||
if shield_block_cooldown_timer <= 0.0:
|
||||
shield_block_cooldown_timer = 0.0
|
||||
|
||||
# Update bow charge tint (when fully charged)
|
||||
if is_charging_bow:
|
||||
var charge_time = (Time.get_ticks_msec() / 1000.0) - bow_charge_start_time
|
||||
# Smooth curve: charge from 0.2s to 1.0s
|
||||
var charge_progress = clamp((charge_time - 0.2) / 0.8, 0.0, 1.0) # 0.0 at 0.2s, 1.0 at 1.0s
|
||||
|
||||
# Update tint pulse timer when fully charged
|
||||
if charge_progress >= 1.0:
|
||||
# Use fast pulse speed when fully charged
|
||||
bow_charge_tint_pulse_time += delta * bow_charge_tint_pulse_speed_charged
|
||||
_apply_bow_charge_tint()
|
||||
else:
|
||||
bow_charge_tint_pulse_time = 0.0
|
||||
_clear_bow_charge_tint()
|
||||
else:
|
||||
# Reset pulse timer when not charging
|
||||
bow_charge_tint_pulse_time = 0.0
|
||||
_clear_bow_charge_tint()
|
||||
else:
|
||||
# Reset pulse timer when not charging
|
||||
bow_charge_tint_pulse_time = 0.0
|
||||
_clear_bow_charge_tint()
|
||||
|
||||
# Update burn debuff (works on both authority and clients)
|
||||
if burn_debuff_timer > 0.0:
|
||||
burn_debuff_timer -= delta
|
||||
|
||||
# Only deal damage on authority (where we have authority)
|
||||
if is_multiplayer_authority():
|
||||
burn_damage_timer += delta
|
||||
# Update burn debuff (works on both authority and clients)
|
||||
if burn_debuff_timer > 0.0:
|
||||
burn_debuff_timer -= delta
|
||||
|
||||
# Deal burn damage every second (no knockback)
|
||||
if burn_damage_timer >= 1.0:
|
||||
# Only deal damage on authority (where we have authority)
|
||||
if is_multiplayer_authority():
|
||||
burn_damage_timer += delta
|
||||
|
||||
# Deal burn damage every second (no knockback)
|
||||
if burn_damage_timer >= 1.0:
|
||||
burn_damage_timer = 0.0
|
||||
# Deal burn damage directly (no knockback, no animation)
|
||||
if character_stats:
|
||||
var old_hp = character_stats.hp
|
||||
character_stats.modify_health(-burn_debuff_damage_per_second)
|
||||
if character_stats.hp <= 0:
|
||||
character_stats.no_health.emit()
|
||||
character_stats.character_changed.emit(character_stats)
|
||||
var actual_damage = old_hp - character_stats.hp
|
||||
print(name, " takes ", actual_damage, " burn damage! Health: ", character_stats.hp, "/", character_stats.maxhp)
|
||||
# Show damage number for burn damage
|
||||
_show_damage_number(actual_damage, global_position, false, false, false) # Show burn damage number
|
||||
# Sync burn damage visual to other clients
|
||||
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
|
||||
_rpc_to_ready_peers("_sync_damage", [actual_damage, global_position])
|
||||
|
||||
# Animate burn visual if it's a sprite (works on both authority and clients)
|
||||
if burn_debuff_visual and is_instance_valid(burn_debuff_visual):
|
||||
if burn_debuff_visual is Sprite2D:
|
||||
var sprite = burn_debuff_visual as Sprite2D
|
||||
var anim_timer = sprite.get_meta("burn_animation_timer", 0.0)
|
||||
anim_timer += delta
|
||||
if anim_timer >= 0.1: # ~10 FPS
|
||||
anim_timer = 0.0
|
||||
var frame = sprite.get_meta("burn_animation_frame", 0)
|
||||
frame = (frame + 1) % 16
|
||||
sprite.frame = frame
|
||||
sprite.set_meta("burn_animation_frame", frame)
|
||||
sprite.set_meta("burn_animation_timer", anim_timer)
|
||||
|
||||
# Remove burn debuff when timer expires (works on both authority and clients)
|
||||
if burn_debuff_timer <= 0.0:
|
||||
burn_debuff_timer = 0.0
|
||||
burn_damage_timer = 0.0
|
||||
# Deal burn damage directly (no knockback, no animation)
|
||||
if character_stats:
|
||||
var old_hp = character_stats.hp
|
||||
character_stats.modify_health(-burn_debuff_damage_per_second)
|
||||
if character_stats.hp <= 0:
|
||||
character_stats.no_health.emit()
|
||||
character_stats.character_changed.emit(character_stats)
|
||||
var actual_damage = old_hp - character_stats.hp
|
||||
print(name, " takes ", actual_damage, " burn damage! Health: ", character_stats.hp, "/", character_stats.maxhp)
|
||||
# Show damage number for burn damage
|
||||
_show_damage_number(actual_damage, global_position, false, false, false) # Show burn damage number
|
||||
# Sync burn damage visual to other clients
|
||||
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
|
||||
_rpc_to_ready_peers("_sync_damage", [actual_damage, global_position])
|
||||
_remove_burn_debuff()
|
||||
|
||||
# Animate burn visual if it's a sprite (works on both authority and clients)
|
||||
if burn_debuff_visual and is_instance_valid(burn_debuff_visual):
|
||||
if burn_debuff_visual is Sprite2D:
|
||||
var sprite = burn_debuff_visual as Sprite2D
|
||||
var anim_timer = sprite.get_meta("burn_animation_timer", 0.0)
|
||||
anim_timer += delta
|
||||
if anim_timer >= 0.1: # ~10 FPS
|
||||
anim_timer = 0.0
|
||||
var frame = sprite.get_meta("burn_animation_frame", 0)
|
||||
frame = (frame + 1) % 16
|
||||
sprite.frame = frame
|
||||
sprite.set_meta("burn_animation_frame", frame)
|
||||
sprite.set_meta("burn_animation_timer", anim_timer)
|
||||
# Skip input if controls are disabled (e.g., when inventory is open)
|
||||
# But still allow knockback to continue (handled above)
|
||||
var skip_input = controls_disabled
|
||||
if controls_disabled:
|
||||
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":
|
||||
if is_lifting:
|
||||
_set_animation("IDLE_HOLD")
|
||||
elif is_pushing:
|
||||
_set_animation("IDLE_PUSH")
|
||||
else:
|
||||
_set_animation("IDLE")
|
||||
|
||||
# Remove burn debuff when timer expires (works on both authority and clients)
|
||||
if burn_debuff_timer <= 0.0:
|
||||
burn_debuff_timer = 0.0
|
||||
burn_damage_timer = 0.0
|
||||
_remove_burn_debuff()
|
||||
|
||||
# Skip input if controls are disabled (e.g., when inventory is open)
|
||||
# But still allow knockback to continue (handled above)
|
||||
var skip_input = controls_disabled
|
||||
if controls_disabled:
|
||||
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":
|
||||
if is_lifting:
|
||||
_set_animation("IDLE_HOLD")
|
||||
elif is_pushing:
|
||||
_set_animation("IDLE_PUSH")
|
||||
else:
|
||||
_set_animation("IDLE")
|
||||
|
||||
# Check if being held by someone
|
||||
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:
|
||||
being_held_by_someone = true
|
||||
being_held_by = other_player
|
||||
break
|
||||
|
||||
if being_held_by_someone:
|
||||
is_shielding = false
|
||||
was_shielding_last_frame = false
|
||||
_update_shield_visibility()
|
||||
# Handle struggle mechanic
|
||||
_handle_struggle(delta)
|
||||
elif is_knocked_back:
|
||||
is_shielding = false
|
||||
was_shielding_last_frame = false
|
||||
_update_shield_visibility()
|
||||
# During knockback, no input control - just let velocity carry the player
|
||||
# Apply friction to slow down knockback
|
||||
velocity = velocity.lerp(Vector2.ZERO, delta * 8.0)
|
||||
elif not is_airborne and not skip_input:
|
||||
# Normal input handling (only if controls are not disabled)
|
||||
struggle_time = 0.0 # Reset struggle timer
|
||||
struggle_direction = Vector2.ZERO
|
||||
_handle_input()
|
||||
_handle_movement(delta)
|
||||
_handle_interactions()
|
||||
else:
|
||||
is_shielding = false
|
||||
was_shielding_last_frame = false
|
||||
_update_shield_visibility()
|
||||
# Reset struggle when airborne
|
||||
struggle_time = 0.0
|
||||
struggle_direction = Vector2.ZERO
|
||||
|
||||
# Update held object positions
|
||||
if is_lifting:
|
||||
_update_lifted_object()
|
||||
elif is_pushing:
|
||||
_update_pushed_object()
|
||||
|
||||
# Sync position, direction, and animation to other clients (unreliable broadcast)
|
||||
# Check if being held by someone
|
||||
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:
|
||||
being_held_by_someone = true
|
||||
being_held_by = other_player
|
||||
break
|
||||
|
||||
if being_held_by_someone:
|
||||
is_shielding = false
|
||||
was_shielding_last_frame = false
|
||||
_update_shield_visibility()
|
||||
# Handle struggle mechanic
|
||||
_handle_struggle(delta)
|
||||
elif is_knocked_back:
|
||||
is_shielding = false
|
||||
was_shielding_last_frame = false
|
||||
_update_shield_visibility()
|
||||
# During knockback, no input control - just let velocity carry the player
|
||||
# Apply friction to slow down knockback
|
||||
velocity = velocity.lerp(Vector2.ZERO, delta * 8.0)
|
||||
elif not is_airborne and not skip_input:
|
||||
# Normal input handling (only if controls are not disabled)
|
||||
struggle_time = 0.0 # Reset struggle timer
|
||||
struggle_direction = Vector2.ZERO
|
||||
_handle_input()
|
||||
_handle_movement(delta)
|
||||
_handle_interactions()
|
||||
else:
|
||||
is_shielding = false
|
||||
was_shielding_last_frame = false
|
||||
_update_shield_visibility()
|
||||
# Reset struggle when airborne
|
||||
struggle_time = 0.0
|
||||
struggle_direction = Vector2.ZERO
|
||||
|
||||
# Update held object positions
|
||||
if is_lifting:
|
||||
_update_lifted_object()
|
||||
elif is_pushing:
|
||||
_update_pushed_object()
|
||||
|
||||
# Sync position, direction, and animation to other clients (unreliable broadcast)
|
||||
# Only send RPC if we're in the scene tree and ready to send RPCs (prevents errors when player hasn't spawned on all clients yet)
|
||||
# On server, also wait for all clients to be ready
|
||||
if multiplayer.has_multiplayer_peer() and is_inside_tree() and can_send_rpcs:
|
||||
@@ -4035,7 +4051,7 @@ func _cast_heal_spell(target: Node):
|
||||
var dungeon_seed: int = 0
|
||||
if gw and "dungeon_seed" in gw:
|
||||
dungeon_seed = gw.dungeon_seed
|
||||
var seed_val = dungeon_seed + hash(target.name) + int(global_position.x) * 31 + int(global_position.y) * 17 + int(Time.get_ticks_msec() / 50)
|
||||
var seed_val = dungeon_seed + hash(target.name) + int(global_position.x) * 31 + int(global_position.y) * 17 + int(Time.get_ticks_msec() / 50.0)
|
||||
var rng = RandomNumberGenerator.new()
|
||||
rng.seed = seed_val
|
||||
|
||||
@@ -4052,30 +4068,41 @@ func _cast_heal_spell(target: Node):
|
||||
if is_crit:
|
||||
amount = floor(amount * 2.0)
|
||||
|
||||
var overheal_chance_pct = 1.0 + lck_val * 0.3
|
||||
var can_overheal = target.character_stats and target.character_stats.hp <= target.character_stats.maxhp
|
||||
var is_overheal = can_overheal and rng.randf() * 100.0 < overheal_chance_pct
|
||||
|
||||
var is_revive = "is_dead" in target and target.is_dead
|
||||
var display_amount = int(amount)
|
||||
var actual_heal = amount
|
||||
var allow_overheal = false
|
||||
if is_overheal:
|
||||
allow_overheal = true
|
||||
var is_overheal = false
|
||||
|
||||
if is_revive:
|
||||
# Tome revive: no overheal, full amount revives
|
||||
actual_heal = amount
|
||||
else:
|
||||
var cap = 0.0
|
||||
if target.character_stats:
|
||||
cap = target.character_stats.maxhp - target.character_stats.hp
|
||||
actual_heal = min(amount, max(0.0, cap))
|
||||
var overheal_chance_pct = 1.0 + lck_val * 0.3
|
||||
var can_overheal = target.character_stats and target.character_stats.hp <= target.character_stats.maxhp
|
||||
is_overheal = can_overheal and rng.randf() * 100.0 < overheal_chance_pct
|
||||
if is_overheal:
|
||||
allow_overheal = true
|
||||
else:
|
||||
var cap = 0.0
|
||||
if target.character_stats:
|
||||
cap = target.character_stats.maxhp - target.character_stats.hp
|
||||
actual_heal = min(amount, max(0.0, cap))
|
||||
|
||||
var me = multiplayer.get_unique_id()
|
||||
var tid = target.get_multiplayer_authority()
|
||||
if me == tid and actual_heal > 0:
|
||||
target.heal(actual_heal, allow_overheal)
|
||||
if is_revive:
|
||||
if me == tid:
|
||||
target._revive_from_heal(display_amount)
|
||||
elif multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
|
||||
target._revive_from_heal.rpc_id(tid, display_amount)
|
||||
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)
|
||||
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
|
||||
if 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])
|
||||
print(name, " cast heal on ", target.name, " for ", display_amount, " HP", " (actual: ", actual_heal, ", crit: ", is_crit, ", overheal: ", is_overheal, ")")
|
||||
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])
|
||||
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):
|
||||
if not target or not is_instance_valid(target):
|
||||
@@ -4107,12 +4134,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):
|
||||
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):
|
||||
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)
|
||||
gw._apply_heal_spell_sync(target_name, amount_to_apply, display_amount, is_crit, is_overheal, allow_overheal, is_revive)
|
||||
|
||||
func _is_healing_spell() -> bool:
|
||||
if not character_stats or not character_stats.equipment.has("offhand"):
|
||||
@@ -4138,8 +4165,6 @@ func _get_heal_target() -> Node:
|
||||
for p in get_tree().get_nodes_in_group("player"):
|
||||
if not is_instance_valid(p):
|
||||
continue
|
||||
if "is_dead" in p and p.is_dead:
|
||||
continue
|
||||
var d = p.global_position.distance_to(mouse_world)
|
||||
if d < best_d:
|
||||
best_d = d
|
||||
@@ -5826,19 +5851,7 @@ func _die():
|
||||
# Wait for DIE animation to complete (200+200+200+800 = 1400ms = 1.4s)
|
||||
await get_tree().create_timer(1.4).timeout
|
||||
|
||||
# Fade out over 0.5 seconds (fade all sprite layers)
|
||||
var fade_tween = create_tween()
|
||||
fade_tween.set_parallel(true)
|
||||
for sprite_layer in [sprite_body, sprite_boots, sprite_armour, sprite_facial_hair,
|
||||
sprite_hair, sprite_eyes, sprite_eyelashes, sprite_addons,
|
||||
sprite_headgear, sprite_shield, sprite_shield_holding, sprite_weapon, shadow]:
|
||||
if sprite_layer:
|
||||
fade_tween.tween_property(sprite_layer, "modulate:a", 0.0, 0.5)
|
||||
|
||||
# Wait for fade to finish
|
||||
await fade_tween.finished
|
||||
|
||||
# Force holder to drop us NOW (after fade, before respawn)
|
||||
# Force holder to drop us NOW (before respawn wait)
|
||||
# Search for any player holding us (don't rely on being_held_by)
|
||||
print(name, " searching for anyone holding us...")
|
||||
var found_holder = false
|
||||
@@ -5999,7 +6012,9 @@ func _respawn():
|
||||
global_position = new_respawn_pos
|
||||
respawn_point = new_respawn_pos # Update respawn point for next time
|
||||
|
||||
# Play idle animation
|
||||
# Clear concussion and play idle animation (server)
|
||||
if status_anim and status_anim.has_animation("idle"):
|
||||
status_anim.play("idle")
|
||||
_set_animation("IDLE")
|
||||
|
||||
# Sync respawn over network (only authority sends)
|
||||
@@ -6049,7 +6064,10 @@ func _do_revive(corpse: Node):
|
||||
if character_stats:
|
||||
character_stats.hp = max(1, character_stats.hp - half_hp)
|
||||
character_stats.character_changed.emit(character_stats)
|
||||
# Show -X HP on reviver (we "took" that much to revive)
|
||||
_show_revive_cost_number(half_hp)
|
||||
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
|
||||
_rpc_to_ready_peers("_sync_revive_cost", [half_hp])
|
||||
corpse._revive_from_player.rpc_id(corpse.get_multiplayer_authority(), half_hp)
|
||||
else:
|
||||
corpse._revive_from_player(half_hp)
|
||||
@@ -6074,6 +6092,42 @@ func _revive_from_player(hp_amount: int):
|
||||
if status_anim and status_anim.has_animation("idle"):
|
||||
status_anim.play("idle")
|
||||
_set_animation("IDLE")
|
||||
# Same healing effect as Tome of Healing (green frames, pulse, +X HP)
|
||||
_spawn_heal_effect_and_text(self, hp_amount, false, false)
|
||||
# Clear concussion on all clients (authority already did above; broadcast for others)
|
||||
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
|
||||
_rpc_to_ready_peers("_sync_revived_clear_concussion", [name])
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _sync_revived_clear_concussion(_player_name: String):
|
||||
# Received on each peer's copy of the revived player; clear our concussion (server + clients)
|
||||
var status_anim = get_node_or_null("Sprite2DStatus/AnimationPlayerStatus")
|
||||
if status_anim and status_anim.has_animation("idle"):
|
||||
status_anim.play("idle")
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _revive_from_heal(hp_amount: int):
|
||||
if not is_dead:
|
||||
return
|
||||
was_revived = true
|
||||
is_dead = false
|
||||
is_processing_death = false
|
||||
if character_stats:
|
||||
character_stats.hp = float(hp_amount)
|
||||
else:
|
||||
current_health = float(hp_amount)
|
||||
for sprite_layer in [sprite_body, sprite_boots, sprite_armour, sprite_facial_hair,
|
||||
sprite_hair, sprite_eyes, sprite_eyelashes, sprite_addons,
|
||||
sprite_headgear, sprite_shield, sprite_shield_holding, sprite_weapon, shadow]:
|
||||
if sprite_layer:
|
||||
sprite_layer.modulate.a = 1.0
|
||||
var status_anim = get_node_or_null("Sprite2DStatus/AnimationPlayerStatus")
|
||||
if status_anim and status_anim.has_animation("idle"):
|
||||
status_anim.play("idle")
|
||||
_set_animation("IDLE")
|
||||
_spawn_heal_effect_and_text(self, hp_amount, false, false)
|
||||
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
|
||||
_rpc_to_ready_peers("_sync_revived_clear_concussion", [name])
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _sync_death():
|
||||
@@ -6098,11 +6152,16 @@ func _sync_respawn(spawn_pos: Vector2):
|
||||
|
||||
# Restore visibility
|
||||
for sprite_layer in [sprite_body, sprite_boots, sprite_armour, sprite_facial_hair,
|
||||
sprite_hair, sprite_eyes, sprite_eyelashes, sprite_addons,
|
||||
sprite_headgear, sprite_shield, sprite_shield_holding, sprite_weapon, shadow]:
|
||||
sprite_hair, sprite_eyes, sprite_eyelashes, sprite_addons,
|
||||
sprite_headgear, sprite_shield, sprite_shield_holding, sprite_weapon, shadow]:
|
||||
if sprite_layer:
|
||||
sprite_layer.modulate.a = 1.0
|
||||
|
||||
# Clear concussion on clients (AnimationPlayerStatus -> idle)
|
||||
var status_anim = get_node_or_null("Sprite2DStatus/AnimationPlayerStatus")
|
||||
if status_anim and status_anim.has_animation("idle"):
|
||||
status_anim.play("idle")
|
||||
|
||||
_set_animation("IDLE")
|
||||
|
||||
func add_coins(amount: int):
|
||||
@@ -6440,7 +6499,8 @@ func _show_damage_number(amount: float, from_position: Vector2, is_crit: bool =
|
||||
damage_label.color = Color(0.4, 0.65, 1.0) # Light blue
|
||||
else:
|
||||
damage_label.label = str(int(amount))
|
||||
damage_label.color = Color.ORANGE if is_crit else Color.RED
|
||||
damage_label.color = Color(1.0, 0.6, 0.2) if is_crit else Color(1.0, 0.35, 0.35) # Bright orange / bright red
|
||||
damage_label.z_index = 5
|
||||
|
||||
# Calculate direction from attacker (slight upward variation)
|
||||
var direction_from_attacker = (global_position - from_position).normalized()
|
||||
@@ -6462,6 +6522,32 @@ func _show_damage_number(amount: float, from_position: Vector2, is_crit: bool =
|
||||
get_tree().current_scene.add_child(damage_label)
|
||||
damage_label.global_position = global_position + Vector2(0, -16)
|
||||
|
||||
func _show_revive_cost_number(amount: int):
|
||||
var damage_number_scene = preload("res://scenes/damage_number.tscn")
|
||||
if not damage_number_scene:
|
||||
return
|
||||
var damage_label = damage_number_scene.instantiate()
|
||||
if not damage_label:
|
||||
return
|
||||
damage_label.label = "-" + str(amount) + " HP"
|
||||
damage_label.color = Color(1.0, 0.35, 0.35)
|
||||
damage_label.z_index = 5
|
||||
damage_label.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_parent()
|
||||
if parent:
|
||||
parent.add_child(damage_label)
|
||||
damage_label.global_position = global_position + Vector2(0, -16)
|
||||
else:
|
||||
get_tree().current_scene.add_child(damage_label)
|
||||
damage_label.global_position = global_position + Vector2(0, -16)
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _sync_revive_cost(amount: int):
|
||||
if is_multiplayer_authority():
|
||||
return
|
||||
_show_revive_cost_number(amount)
|
||||
|
||||
func _on_level_up_stats(stats_increased: Array):
|
||||
# Show floating text for level up - "LEVEL UP!" and stat increases
|
||||
# Use damage_number scene with damage_numbers font
|
||||
@@ -6526,6 +6612,20 @@ func _on_level_up_stats(stats_increased: Array):
|
||||
stat_text.global_position = global_position + Vector2(0, base_y_offset)
|
||||
base_y_offset -= y_spacing
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func rpc_apply_corpse_knockback(dir_x: float, dir_y: float, force: float):
|
||||
if not is_multiplayer_authority():
|
||||
return
|
||||
if not is_dead:
|
||||
return
|
||||
var d = Vector2(dir_x, dir_y)
|
||||
if d.length_squared() < 0.01:
|
||||
return
|
||||
d = d.normalized()
|
||||
velocity = d * force
|
||||
is_knocked_back = true
|
||||
knockback_time = 0.0
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _sync_damage(_amount: float, attacker_position: Vector2, is_crit: bool = false, is_miss: bool = false, is_dodged: bool = false, is_blocked: bool = false):
|
||||
# This RPC only syncs visual effects, not damage application
|
||||
|
||||
Reference in New Issue
Block a user