lots of changeru

This commit is contained in:
2026-01-25 16:27:19 +01:00
parent a95e22d2fa
commit 7abadb92a9
14 changed files with 855 additions and 106 deletions

View File

@@ -42,6 +42,9 @@ var grab_released_while_lifting = false # Track if grab was released while lifti
var grab_start_time = 0.0 # Time when we first grabbed (to detect quick tap)
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
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
var push_direction_locked: int = Direction.DOWN # Locked facing direction when pushing
@@ -160,6 +163,7 @@ var current_health: float:
character_stats.hp = value
var is_dead: bool = false
var is_processing_death: bool = false # Prevent multiple death sequences
var was_revived: bool = false # Set by reviver; aborts _die() wait-for-all-dead
var respawn_point: Vector2 = Vector2.ZERO
var coins: int:
get:
@@ -299,7 +303,7 @@ const ANIMATIONS = {
"nextAnimation": null
},
"RUN_PULL": {
"frames": [32, 32, 32, 33],
"frames": [33, 32, 33, 34],
"frameDurations": [260, 260, 260, 260],
"loop": true,
"nextAnimation": null
@@ -2255,8 +2259,8 @@ func _handle_input():
if character_stats and character_stats.is_over_encumbered():
current_speed = base_speed * 0.25
# Lock movement if movement_lock_timer is active
if movement_lock_timer > 0.0:
# Lock movement if movement_lock_timer is active or reviving a corpse
if movement_lock_timer > 0.0 or is_reviving:
velocity = Vector2.ZERO
else:
velocity = input_vector * current_speed
@@ -2614,6 +2618,10 @@ func _handle_interactions():
# 1. We just grabbed this frame (prevents immediate release bug) - THIS IS THE MOST IMPORTANT CHECK
# 2. Button is still down (shouldn't happen, but safety check)
# 3. grab_just_pressed is also true (same frame tap)
if grab_just_released:
is_reviving = false
revive_charge = 0.0
if grab_just_released and held_object:
# For bombs that are already lifted, skip the "just grabbed" logic
# and go straight to the normal release handling (drop-on-second-press)
@@ -2675,7 +2683,23 @@ func _handle_interactions():
# Update object position based on mode (only if button is still held)
if held_object and grab_button_down:
if is_lifting:
_update_lifted_object()
var holding_dead_player = _is_player(held_object) and "is_dead" in held_object and held_object.is_dead
var reviver_hp = character_stats.hp if character_stats else 1.0
if holding_dead_player and reviver_hp > 1.0:
is_reviving = true
revive_charge += get_process_delta_time()
if revive_charge >= REVIVE_DURATION:
_do_revive(held_object)
_place_down_object()
is_reviving = false
revive_charge = 0.0
else:
_update_lifted_object()
else:
if holding_dead_player:
is_reviving = false
revive_charge = 0.0
_update_lifted_object()
# Clear the "released while lifting" flag if button is held again
if grab_released_while_lifting:
grab_released_while_lifting = false
@@ -4007,28 +4031,53 @@ func _cast_heal_spell(target: Node):
return
if not character_stats:
return
var gw = get_tree().get_first_node_in_group("game_world")
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 rng = RandomNumberGenerator.new()
rng.seed = seed_val
var int_val = character_stats.baseStats.int + character_stats.get_pass("int")
var base_heal = 10.0
var amount = base_heal + int_val * 0.5
var lck_val = character_stats.baseStats.lck + character_stats.get_pass("lck")
var crit_chance_pct = (character_stats.baseStats.lck + character_stats.get_pass("lck")) * 1.2
var base_heal = 10.0 + int_val * 0.5
var variance = 0.2
var amount = base_heal * (1.0 + (rng.randf() * 2.0 - 1.0) * variance)
amount = max(1.0, floor(amount))
var cap = 0.0
if target.character_stats:
cap = target.character_stats.maxhp - target.character_stats.hp
amount = min(amount, max(0.0, cap))
if amount <= 0:
return
var is_crit = rng.randf() * 100.0 < crit_chance_pct
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 display_amount = int(amount)
var actual_heal = amount
var allow_overheal = false
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:
target.heal(amount)
_spawn_heal_effect_and_text(target, amount)
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():
var gw = get_tree().get_first_node_in_group("game_world")
if gw and gw.has_method("_sync_heal_spell"):
_rpc_to_ready_peers("_sync_heal_spell_via_gw", [target.name, amount])
print(name, " cast heal on ", target.name, " for ", int(amount), " HP")
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, ")")
func _spawn_heal_effect_and_text(target: Node, amount: float):
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):
return
var game_world = get_tree().get_first_node_in_group("game_world")
@@ -4042,20 +4091,28 @@ func _spawn_heal_effect_and_text(target: Node, amount: float):
eff.global_position = target.global_position
if eff.has_method("setup"):
eff.setup(target)
var prefix = ""
if is_crit and is_overheal:
prefix = "CRIT OVERHEAL! "
elif is_crit:
prefix = "CRIT! "
elif is_overheal:
prefix = "OVERHEAL! "
var heal_text = prefix + "+" + str(display_amount) + " HP"
var floating_text_scene = preload("res://scenes/floating_text.tscn")
if floating_text_scene:
var ft = floating_text_scene.instantiate()
parent.add_child(ft)
ft.global_position = Vector2(target.global_position.x, target.global_position.y - 20)
ft.setup("+" + str(int(amount)) + " HP", Color.GREEN, 0.5, 0.5, null, 1, 1, 0)
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: float):
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):
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)
gw._apply_heal_spell_sync(target_name, amount_to_apply, display_amount, is_crit, is_overheal, allow_overheal)
func _is_healing_spell() -> bool:
if not character_stats or not character_stats.equipment.has("offhand"):
@@ -5737,6 +5794,11 @@ func _die():
print(name, " died!")
# Show concussion status effect above head
var status_anim = get_node_or_null("Sprite2DStatus/AnimationPlayerStatus")
if status_anim and status_anim.has_animation("concussion"):
status_anim.play("concussion")
# Play death sound effect
if sfx_die:
for i in 12:
@@ -5808,14 +5870,29 @@ func _die():
being_held_by = null
# Wait 0.5 seconds after fade before respawning
# If another player is alive, lie dead until ALL players are dead (or we get revived)
while not _are_all_players_dead():
await get_tree().create_timer(0.2).timeout
if was_revived:
return
# Brief delay after last death before respawning
await get_tree().create_timer(0.5).timeout
if was_revived:
return
# Respawn (this will reset is_processing_death)
_respawn()
func _are_all_players_dead() -> bool:
for p in get_tree().get_nodes_in_group("player"):
if "is_dead" in p and not p.is_dead:
return false
return true
func _respawn():
print(name, " respawning!")
was_revived = false
# being_held_by already cleared in _die() before this
# Holder already dropped us 0.2 seconds ago
@@ -5846,6 +5923,10 @@ func _respawn():
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")
# Get respawn position - use spawn room (start room) for respawning
var new_respawn_pos = respawn_point
var game_world = get_tree().get_first_node_in_group("game_world")
@@ -5958,6 +6039,42 @@ func _force_holder_to_drop_local(holder_name: String):
else:
print(" ✗ Holder not found or invalid")
func _do_revive(corpse: Node):
if not _is_player(corpse) or not "is_dead" in corpse or not corpse.is_dead:
return
var reviver_hp = character_stats.hp if character_stats else 1.0
if reviver_hp <= 1.0:
return
var half_hp = max(1, int(reviver_hp * 0.5))
if character_stats:
character_stats.hp = max(1, character_stats.hp - half_hp)
character_stats.character_changed.emit(character_stats)
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
corpse._revive_from_player.rpc_id(corpse.get_multiplayer_authority(), half_hp)
else:
corpse._revive_from_player(half_hp)
@rpc("any_peer", "reliable")
func _revive_from_player(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")
@rpc("any_peer", "reliable")
func _sync_death():
if not is_multiplayer_authority():
@@ -6258,16 +6375,17 @@ func _apply_inventory_and_equipment_from_server(inventory_data: Array, equipment
character_stats.character_changed.emit(character_stats)
print(name, " inventory+equipment applied from server: ", character_stats.inventory.size(), " items")
func heal(amount: float):
func heal(amount: float, allow_overheal: bool = false):
if is_dead:
return
if character_stats:
character_stats.heal(amount)
character_stats.heal(amount, allow_overheal)
print(name, " healed for ", amount, " HP! Health: ", character_stats.hp, "/", character_stats.maxhp)
else:
# Fallback for legacy
current_health = min(current_health + amount, max_health)
var new_hp = current_health + amount
current_health = max(0.0, new_hp) if allow_overheal else clamp(new_hp, 0.0, max_health)
print(name, " healed for ", amount, " HP! Health: ", current_health, "/", max_health)
func add_key(amount: int = 1):