lots of changeru
This commit is contained in:
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user