added fallout tiles, fixed so all enemys can get affected.

This commit is contained in:
2026-02-03 02:42:51 +01:00
parent 88f51fd8d0
commit 9b8d84357f
19 changed files with 1559 additions and 279 deletions

View File

@@ -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 is_being_held: bool = false # Set by set_being_held(); reliable on all clients for fallout immunity
var grabbed_by_enemy_hand: Node = null # Set when enemy hand grabs (snatch); locks movement until release
var enemy_hand_grab_knockback_time: float = 0.0 # Timer for initial knockback when grabbed
const ENEMY_HAND_GRAB_KNOCKBACK_DURATION: float = 0.15 # Short knockback duration before moving to hand
@@ -143,6 +144,27 @@ var spawn_landing_bounced: bool = false
var spawn_landing_visible_shown: bool = false # One-shot: set true when we show right before falling
var has_seen_exit_this_level: bool = false # Track if player has seen exit notification for current level
# Fallout (quicksand) state: sink into tile, then respawn at last safe
var fallout_state: bool = false
var fallout_scale_progress: float = 1.0 # 1.0 -> 0.0 during sink
var fallout_respawn_delay_timer: float = 0.0 # After scale hits 0, wait this long before respawn
var fallout_respawn_stun_timer: float = 0.0 # After respawn from fallout, stun for this long (no control)
var on_fallout_tile_near_sink: bool = false # True when on fallout tile but not yet at center (fast walk plays)
var animation_speed_multiplier: float = 1.0 # 1.0 = normal; >1 when on fallout tile so run anim plays faster
const FALLOUT_CENTER_THRESHOLD: float = 2.0 # Player center must be almost exactly at tile center to sink (Zelda Link's Awakening style)
const FALLOUT_DRAG_STRENGTH: float = 820.0 # Base pull toward fallout center (strong enough to prevent running over)
const FALLOUT_CENTER_PULL_BOOST: float = 1.8 # Pull is stronger near center: at center (1+BOOST)x, at edge 1x
const FALLOUT_DRAG_EDGE_FACTOR: float = 0.45 # At tile edge drag is 45% strength; ramps to 100% toward center
const FALLOUT_MOVEMENT_FACTOR: float = 0.3 # Movement speed on fallout tile (30%) so player cannot run over it
const FALLOUT_TILE_HALF_SIZE: float = 8.0 # Half of tile size (16) for distance-based strength
const FALLOUT_PLAYER_BOX_HALF: float = 8.0 # Player treated as 16x16 box for quicksand (center ± 8)
const FALLOUT_TILE_ANIMATION_SPEED: float = 3.0 # Run animation plays this many times faster when on fallout tile (warning to player)
const FALLOUT_SINK_DURATION: float = 0.5 # Seconds to scale from 1 to 0 (faster sink)
const FALLOUT_RESPAWN_DELAY: float = 0.3 # Seconds after scale reaches 0 before respawning at safe tile
const FALLOUT_RESPAWN_STUN_DURATION: float = 0.3 # Seconds of stun after respawn from fallout
const FALLOUT_RESPAWN_HP_PENALTY: float = 1.0 # HP lost when respawning from fallout
const HELD_POSITION_Z: float = 12.0 # Z height when held/lifted (above ground; immune to fallout)
# Components
# @onready var sprite = $Sprite2D # REMOVED: Now using layered sprites
@onready var shadow = $Shadow
@@ -150,6 +172,7 @@ var has_seen_exit_this_level: bool = false # Track if player has seen exit notif
@onready var point_light = $PointLight2D
@onready var collision_shape = $CollisionShape2D
@onready var grab_area = $GrabArea
@onready var quicksand_area = $QuicksandArea
@onready var interaction_indicator = $InteractionIndicator
# Audio
@@ -1584,9 +1607,10 @@ func _get_collision_extent(node: Node) -> float:
return 8.0
func _update_animation(delta):
# Update animation frame timing
# Update animation frame timing (faster when on fallout tile to warn player)
var frame_duration_sec = ANIMATIONS[current_animation]["frameDurations"][current_frame] / 1000.0 / animation_speed_multiplier
time_since_last_frame += delta
if time_since_last_frame >= ANIMATIONS[current_animation]["frameDurations"][current_frame] / 1000.0:
if time_since_last_frame >= frame_duration_sec:
current_frame += 1
if current_frame >= len(ANIMATIONS[current_animation]["frames"]):
current_frame -= 1 # Prevent out of bounds
@@ -1972,7 +1996,26 @@ func _physics_process(delta):
was_reviving_last_frame = false
_stop_spell_charge_incantation()
if is_local_player and is_multiplayer_authority():
# Fallout (quicksand) sink: run for ALL players so remote see scale/rotation/FALL animation
if fallout_state:
current_direction = Direction.DOWN
facing_direction_vector = Vector2.DOWN
_set_animation("FALL")
scale = Vector2.ONE * max(0.0, fallout_scale_progress)
rotation = deg_to_rad(45.0)
velocity = Vector2.ZERO
if fallout_respawn_delay_timer > 0.0:
fallout_respawn_delay_timer -= delta
if fallout_respawn_delay_timer <= 0.0:
fallout_respawn_delay_timer = 0.0
if is_multiplayer_authority():
_respawn_from_fallout()
else:
fallout_scale_progress -= delta / FALLOUT_SINK_DURATION
if fallout_scale_progress <= 0.0:
fallout_scale_progress = 0.0
fallout_respawn_delay_timer = FALLOUT_RESPAWN_DELAY
elif is_local_player and is_multiplayer_authority():
# When dead: only corpse knockback friction + sync; no input or other logic
if is_dead:
if is_knocked_back:
@@ -1983,6 +2026,42 @@ func _physics_process(delta):
else:
velocity = velocity.lerp(Vector2.ZERO, delta * 8.0)
else:
# Reset fallout-tile animation state each frame (set when on fallout tile below)
animation_speed_multiplier = 1.0
on_fallout_tile_near_sink = false
# Held players cannot fallout (use is_being_held so it works on all clients; position can put held player over a tile)
var is_held = is_being_held or (being_held_by != null and is_instance_valid(being_held_by))
if position_z == 0.0 and not is_held:
# Quicksand: only when player CENTER is on a fallout tile (avoids vortex pull from adjacent ground)
var gw = get_tree().get_first_node_in_group("game_world")
var area_center = quicksand_area.global_position if quicksand_area else global_position
if gw and gw.has_method("_is_position_on_fallout_tile"):
if not gw._is_position_on_fallout_tile(area_center):
gw.update_last_safe_position_for_player(self, global_position)
else:
# Center is on fallout: use this tile's center for drag/sink (symmetric)
var tile_center = gw._get_tile_center_at(area_center)
var dist_to_center = area_center.distance_to(tile_center)
if dist_to_center < FALLOUT_CENTER_THRESHOLD:
# If carrying something, throw it in the direction we were looking before falling
if held_object and is_lifting:
_force_throw_held_object(facing_direction_vector)
# Snap player center exactly to fallout tile center so sink looks correct
global_position = tile_center
fallout_state = true
fallout_scale_progress = 1.0
velocity = Vector2.ZERO
current_direction = Direction.DOWN
facing_direction_vector = Vector2.DOWN
_set_animation("FALL")
if has_node("SfxFallout"):
$SfxFallout.play()
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
_rpc_to_ready_peers("_sync_fallout_start", [tile_center])
else:
on_fallout_tile_near_sink = true
animation_speed_multiplier = FALLOUT_TILE_ANIMATION_SPEED
_set_animation("RUN")
# Handle knockback timer (always handle knockback, even when controls are disabled)
if is_knocked_back:
knockback_time += delta
@@ -1994,6 +2073,12 @@ func _physics_process(delta):
if grabbed_by_enemy_hand:
enemy_hand_grab_knockback_time += delta
# Update fallout respawn stun timer (no control for 0.3s after respawn from fallout)
if fallout_respawn_stun_timer > 0.0:
fallout_respawn_stun_timer -= delta
if fallout_respawn_stun_timer <= 0.0:
fallout_respawn_stun_timer = 0.0
# Update movement lock timer (for bow release)
if movement_lock_timer > 0.0:
movement_lock_timer -= delta
@@ -2084,18 +2169,18 @@ func _physics_process(delta):
burn_damage_timer = 0.0
_remove_burn_debuff()
# Skip input if controls are disabled (e.g., when inventory is open) or spawn landing (fall → DIE → stand up)
# Skip input if controls are disabled (e.g., when inventory is open) or spawn landing (fall → DIE → stand up) or fallout (sinking) or post-fallout stun
# But still allow knockback to continue (handled above)
# CRITICAL: During entrance walk-out cut-scene, game_world sets velocity; do NOT zero it here
var entrance_walk_out = controls_disabled and has_meta("entrance_walk_target")
var skip_input = controls_disabled or spawn_landing
if controls_disabled or spawn_landing:
var skip_input = controls_disabled or spawn_landing or fallout_state or (fallout_respawn_stun_timer > 0.0)
if controls_disabled or spawn_landing or fallout_state or (fallout_respawn_stun_timer > 0.0):
if not is_knocked_back and not entrance_walk_out:
# Immediately stop movement when controls are disabled (e.g., inventory opened)
# Exception: entrance walk-out - velocity is driven by game_world for cut-scene
velocity = Vector2.ZERO
# 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" and current_animation != "AXE" and current_animation != "PUNCH":
if not spawn_landing and not fallout_state and current_animation != "THROW" and current_animation != "DAMAGE" and current_animation != "SWORD" and current_animation != "BOW" and current_animation != "STAFF" and current_animation != "AXE" and current_animation != "PUNCH":
if is_lifting:
_set_animation("IDLE_HOLD")
elif is_pushing:
@@ -2152,6 +2237,24 @@ func _physics_process(delta):
struggle_time = 0.0 # Reset struggle timer
struggle_direction = Vector2.ZERO
_handle_input()
# Apply quicksand only when player CENTER is on a fallout tile (no vortex pull from adjacent tiles)
if position_z == 0.0 and not (is_being_held or (being_held_by != null and is_instance_valid(being_held_by))):
var gw = get_tree().get_first_node_in_group("game_world")
var area_center = quicksand_area.global_position if quicksand_area else global_position
if gw and gw.has_method("_is_position_on_fallout_tile") and gw._is_position_on_fallout_tile(area_center):
# Heavy movement penalty so running over the tile is not possible
velocity *= FALLOUT_MOVEMENT_FACTOR
var tile_center = gw._get_tile_center_at(area_center)
var area_center_dist = area_center.distance_to(tile_center)
if area_center_dist >= FALLOUT_CENTER_THRESHOLD:
# Drag toward this tile's center (same strength from all directions)
var dir = (tile_center - area_center).normalized()
# Softer at edge: drag ramps from FALLOUT_DRAG_EDGE_FACTOR at tile edge to 1.0 toward center
var edge_t = clamp(area_center_dist / FALLOUT_TILE_HALF_SIZE, 0.0, 1.0)
var edge_drag_factor = lerp(1.0, FALLOUT_DRAG_EDGE_FACTOR, edge_t)
# Strength: stronger when player center is closer to fallout tile center (distance-based only, no direction bias)
var strength_mult = 1.0 + FALLOUT_CENTER_PULL_BOOST * (1.0 - clamp(area_center_dist / FALLOUT_TILE_HALF_SIZE, 0.0, 1.0))
velocity += dir * FALLOUT_DRAG_STRENGTH * strength_mult * edge_drag_factor * delta
_handle_movement(delta)
_handle_interactions()
else:
@@ -3301,10 +3404,9 @@ func _try_grab():
closest_body.set_collision_mask_value(2, false)
closest_body.set_collision_mask_value(7, true) # Keep wall collision
elif _is_player(closest_body):
# Players: remove from layer fully when lifted no collision with anything
closest_body.set_collision_layer_value(1, false)
closest_body.set_collision_mask_value(1, false)
closest_body.set_collision_mask_value(7, false)
# Players: no collision layer at all while held
closest_body.collision_layer = 0
closest_body.collision_mask = 0
# When grabbing, immediately try to lift if possible
_set_animation("IDLE")
@@ -3363,10 +3465,14 @@ func _lift_object():
if "is_frozen" in held_object:
held_object.is_frozen = true
elif _is_player(held_object):
# Player: use set_being_held
# Player: use set_being_held (also sets position_z = HELD_POSITION_Z)
if held_object.has_method("set_being_held"):
held_object.set_being_held(true)
# Any held object with position_z gets lifted above ground
if "position_z" in held_object:
held_object.position_z = HELD_POSITION_Z
if held_object.has_method("on_lifted"):
held_object.on_lifted(self)
@@ -3453,11 +3559,12 @@ func reset_grab_state():
if "held_by_player" in held_object:
held_object.held_by_player = null
elif _is_player(held_object):
held_object.set_collision_layer_value(1, true)
held_object.set_collision_mask_value(1, true)
held_object.set_collision_mask_value(7, true)
held_object.collision_layer = 1
held_object.collision_mask = 1 | 2 | 64 # players, objects, walls
if held_object.has_method("set_being_held"):
held_object.set_being_held(false)
if "position_z" in held_object:
held_object.position_z = 0.0
# Stop drag sound if playing
if held_object.has_method("stop_drag_sound"):
@@ -3515,14 +3622,16 @@ func _stop_pushing():
released_obj.set_collision_mask_value(2, true)
released_obj.set_collision_mask_value(7, true) # Re-enable wall collision!
elif _is_player(released_obj):
# Players: back on layer 1
released_obj.set_collision_layer_value(1, true)
released_obj.set_collision_mask_value(1, true)
released_obj.set_collision_mask_value(7, true) # Re-enable wall collision!
# Players: restore collision layer and mask (layer 1, mask 1|2|64 so we collide with players, objects, walls)
released_obj.collision_layer = 1
released_obj.collision_mask = 1 | 2 | 64
if released_obj is CharacterBody2D and released_obj.has_method("set_being_held"):
released_obj.set_being_held(false)
if "position_z" in released_obj:
released_obj.position_z = 0.0
# Ensure position stays exactly where it is - no movement on release!
# Do this AFTER calling on_released in case it tries to change position
if released_obj.has_method("on_released"):
@@ -3640,10 +3749,11 @@ func _throw_object():
if thrown_obj.has_method("set_being_held"):
thrown_obj.set_being_held(false)
# Re-add to layer DIRECTLY when thrown (no delay)
# Re-add to layer DIRECTLY when thrown (no delay); restore full mask 1|2|64
if thrown_obj and is_instance_valid(thrown_obj):
thrown_obj.set_collision_layer_value(1, true)
thrown_obj.set_collision_mask_value(1, true)
thrown_obj.set_collision_mask_value(2, true)
thrown_obj.set_collision_mask_value(7, true)
elif thrown_obj and thrown_obj.has_method("can_be_grabbed") and thrown_obj.can_be_grabbed():
# Bomb or other grabbable object - handle like box
@@ -3785,9 +3895,8 @@ func _force_throw_held_object(direction: Vector2):
# Re-add to layer DIRECTLY when thrown (no delay)
if thrown_obj and is_instance_valid(thrown_obj):
thrown_obj.set_collision_layer_value(1, true)
thrown_obj.set_collision_mask_value(1, true)
thrown_obj.set_collision_mask_value(7, true)
thrown_obj.collision_layer = 1
thrown_obj.collision_mask = 1 | 2 | 64 # players, objects, walls
elif thrown_obj and thrown_obj.has_method("can_be_grabbed") and thrown_obj.can_be_grabbed():
# Other grabbable object - handle like box
thrown_obj.global_position = throw_start_pos
@@ -3917,10 +4026,9 @@ func _place_down_object():
if "velocity_z" in placed_obj:
placed_obj.velocity_z = 0.0
elif _is_player(placed_obj):
# Player: back on layer 1
placed_obj.set_collision_layer_value(1, true)
placed_obj.set_collision_mask_value(1, true)
placed_obj.set_collision_mask_value(7, true) # Re-enable wall collision!
# Player: restore collision layer and mask (1|2|64 so we collide with players, objects, walls)
placed_obj.collision_layer = 1
placed_obj.collision_mask = 1 | 2 | 64
placed_obj.global_position = place_pos
placed_obj.velocity = Vector2.ZERO
if placed_obj.has_method("set_being_held"):
@@ -5223,7 +5331,7 @@ func _update_lifted_object():
held_object = null
return
# Object floats above player's head
# Object floats above player's head (XY) and at HELD_POSITION_Z (above ground, immune to fallout)
var target_pos = position + Vector2(0, -12) # Above head
# Instant follow for local player, smooth for network sync
@@ -5232,6 +5340,10 @@ func _update_lifted_object():
else:
held_object.global_position = held_object.global_position.lerp(target_pos, 0.3)
# Keep held object at Z height so it's "above" ground (no fallout under it)
if "position_z" in held_object:
held_object.position_z = HELD_POSITION_Z
# Sync held object position over network
if multiplayer.has_multiplayer_peer() and is_multiplayer_authority() and can_send_rpcs and is_inside_tree():
var obj_name = _get_object_name_for_sync(held_object)
@@ -5301,6 +5413,12 @@ func _update_pushed_object():
var results = space_state.intersect_point(query)
was_blocked = results.size() > 0
# StonePillar must NOT be pushed onto fallout - treat fallout as solid
if not was_blocked and held_object.get("object_type") == "Pillar":
var gw = get_tree().get_first_node_in_group("game_world")
if gw and gw.has_method("_is_position_on_fallout_tile") and gw._is_position_on_fallout_tile(target_pos):
was_blocked = true
# Update the flag for next frame's input handling
object_blocked_by_wall = was_blocked
@@ -5777,10 +5895,11 @@ func _sync_throw(obj_name: String, throw_pos: Vector2, force: Vector2, thrower_n
print("Player is now airborne on client!")
# Re-add to layer DIRECTLY when thrown (no delay)
# Re-add to layer DIRECTLY when thrown (no delay); full mask 1|2|64
if obj and is_instance_valid(obj):
obj.set_collision_layer_value(1, true)
obj.set_collision_mask_value(1, true)
obj.set_collision_mask_value(2, true)
obj.set_collision_mask_value(7, true)
@rpc("any_peer", "reliable")
@@ -5818,9 +5937,8 @@ func _sync_initial_grab(obj_name: String, _offset: Vector2):
obj.set_collision_mask_value(1, false)
obj.set_collision_mask_value(2, false)
elif _is_player(obj):
obj.set_collision_layer_value(1, false)
obj.set_collision_mask_value(1, false)
obj.set_collision_mask_value(7, false)
obj.collision_layer = 0
obj.collision_mask = 0
print("Synced initial grab on client: ", obj_name)
@@ -5868,9 +5986,8 @@ func _sync_grab(obj_name: String, is_lift: bool, axis: Vector2 = Vector2.ZERO):
if "throw_velocity" in obj:
obj.throw_velocity = Vector2.ZERO
elif _is_player(obj):
obj.set_collision_layer_value(1, false)
obj.set_collision_mask_value(1, false)
obj.set_collision_mask_value(7, false)
obj.collision_layer = 0
obj.collision_mask = 0
if obj.has_method("set_being_held"):
obj.set_being_held(true)
else:
@@ -5887,9 +6004,8 @@ func _sync_grab(obj_name: String, is_lift: bool, axis: Vector2 = Vector2.ZERO):
if "throw_velocity" in obj:
obj.throw_velocity = Vector2.ZERO
elif _is_player(obj):
obj.set_collision_layer_value(1, false)
obj.set_collision_mask_value(1, false)
obj.set_collision_mask_value(7, false)
obj.collision_layer = 0
obj.collision_mask = 0
if obj.has_method("set_being_held"):
obj.set_being_held(true)
@@ -5940,6 +6056,7 @@ func _sync_release(obj_name: String):
elif _is_player(obj):
obj.set_collision_layer_value(1, true)
obj.set_collision_mask_value(1, true)
obj.set_collision_mask_value(2, true)
obj.set_collision_mask_value(7, true) # Re-enable wall collision!
if obj.has_method("set_being_held"):
obj.set_being_held(false)
@@ -6000,6 +6117,7 @@ func _sync_place_down(obj_name: String, place_pos: Vector2):
elif _is_player(obj):
obj.set_collision_layer_value(1, true)
obj.set_collision_mask_value(1, true)
obj.set_collision_mask_value(2, true)
obj.set_collision_mask_value(7, true) # Re-enable wall collision!
obj.velocity = Vector2.ZERO
if obj.has_method("set_being_held"):
@@ -6126,6 +6244,7 @@ func _break_free_from_holder():
struggle_time = 0.0
struggle_direction = Vector2.ZERO
being_held_by = null
is_being_held = false
@rpc("any_peer", "reliable")
func _sync_break_free(holder_name: String, direction: Vector2):
@@ -6182,6 +6301,7 @@ func _force_place_down(direction: Vector2):
elif _is_player(placed_obj):
placed_obj.set_collision_layer_value(1, true)
placed_obj.set_collision_mask_value(1, true)
placed_obj.set_collision_mask_value(2, true)
placed_obj.set_collision_mask_value(7, true) # Re-enable wall collision!
placed_obj.velocity = Vector2.ZERO
if placed_obj.has_method("set_being_held"):
@@ -6193,19 +6313,22 @@ func _force_place_down(direction: Vector2):
print("Forced to place down ", placed_obj.name)
func set_being_held(held: bool):
# When being held by another player, disable movement
# But keep physics_process running for network sync
# When being held by another player, disable movement and collision; use HELD_POSITION_Z so we're "above" ground (immune to fallout)
is_being_held = held
if held:
# Just prevent input handling, don't disable physics
velocity = Vector2.ZERO
is_airborne = false
position_z = 0.0
position_z = HELD_POSITION_Z
velocity_z = 0.0
collision_layer = 0
collision_mask = 0
else:
# Released - reset struggle state
struggle_time = 0.0
struggle_direction = Vector2.ZERO
being_held_by = null
position_z = 0.0
collision_layer = 1
collision_mask = 1 | 2 | 64 # layer 1 players, 2 objects, 7 walls
@rpc("any_peer", "reliable")
func rpc_grabbed_by_enemy_hand(enemy_name: String) -> void:
@@ -6257,10 +6380,13 @@ func rpc_take_damage(amount: float, attacker_position: Vector2, is_burn_damage:
if is_multiplayer_authority():
take_damage(amount, attacker_position, is_burn_damage, apply_burn_debuff)
func take_damage(amount: float, attacker_position: Vector2, is_burn_damage: bool = false, apply_burn_debuff: bool = false):
func take_damage(amount: float, attacker_position: Vector2, is_burn_damage: bool = false, apply_burn_debuff: bool = false, no_knockback: bool = false):
# Don't take damage if already dead
if is_dead:
return
# Invulnerable during fallout sink (can't take damage from anything while falling)
if fallout_state:
return
# Cancel bow charging when taking damage
if is_charging_bow:
@@ -6391,8 +6517,8 @@ func take_damage(amount: float, attacker_position: Vector2, is_burn_damage: bool
# Lock facing direction briefly so player can't change it while taking damage
damage_direction_lock_timer = damage_direction_lock_duration
# Only apply knockback if not burn damage
if not is_burn_damage:
# Only apply knockback if not burn damage and not suppressed (e.g. fallout respawn)
if not is_burn_damage and not no_knockback:
# Calculate direction FROM attacker TO victim
var direction_from_attacker = (global_position - attacker_position).normalized()
@@ -6476,6 +6602,7 @@ func _die():
elif _is_player(released_obj):
released_obj.set_collision_layer_value(1, true)
released_obj.set_collision_mask_value(1, true)
released_obj.set_collision_mask_value(2, true)
released_obj.set_collision_mask_value(7, true) # Re-enable wall collision!
if released_obj.has_method("set_being_held"):
released_obj.set_being_held(false)
@@ -6539,10 +6666,9 @@ func _die():
other_player.grab_offset = Vector2.ZERO
other_player.push_axis = Vector2.ZERO
# Re-enable our collision
set_collision_layer_value(1, true)
set_collision_mask_value(1, true)
set_collision_mask_value(7, true) # Re-enable wall collision!
# Re-enable our collision (layer 1, mask 1|2|64)
collision_layer = 1
collision_mask = 1 | 2 | 64
# THEN sync to other clients
if multiplayer.has_multiplayer_peer() and can_send_rpcs and is_inside_tree():
@@ -6555,6 +6681,7 @@ func _die():
print(name, " is NOT being held by anyone")
being_held_by = null
is_being_held = false
# Replicas: no wait loop; we get _sync_respawn from authority.
if not is_multiplayer_authority():
@@ -6666,6 +6793,24 @@ func _spawn_landing_stand_up():
if game_world and game_world.has_method("_start_bg_music"):
game_world._start_bg_music()
func _respawn_from_fallout():
# Teleport to last safe tile, reset fallout state, then apply 1 HP damage via take_damage (no knockback)
var gw = get_tree().get_first_node_in_group("game_world")
if gw and gw.has_method("get_last_safe_position_for_player"):
global_position = gw.get_last_safe_position_for_player(self)
fallout_state = false
fallout_scale_progress = 1.0
fallout_respawn_delay_timer = 0.0
scale = Vector2.ONE
rotation = 0.0
velocity = Vector2.ZERO
fallout_respawn_stun_timer = FALLOUT_RESPAWN_STUN_DURATION
_set_animation("IDLE")
# Apply damage via take_damage (shows damage number, sound, etc.) but with no knockback
take_damage(FALLOUT_RESPAWN_HP_PENALTY, global_position, false, false, true)
if multiplayer.has_multiplayer_peer() and is_multiplayer_authority() and can_send_rpcs and is_inside_tree():
_rpc_to_ready_peers("_sync_respawn_from_fallout", [global_position])
func _respawn():
print(name, " respawning!")
was_revived = false
@@ -6684,6 +6829,7 @@ func _respawn():
# Re-enable collision in case it was disabled while being carried
set_collision_layer_value(1, true)
set_collision_mask_value(1, true)
set_collision_mask_value(2, true)
set_collision_mask_value(7, true) # Re-enable wall collision!
# Reset health and state
@@ -6815,9 +6961,10 @@ func _force_holder_to_drop_local(holder_name: String):
holder.grab_offset = Vector2.ZERO
holder.push_axis = Vector2.ZERO
# Re-enable collision on dropped player
# Re-enable collision on dropped player (layer 1, mask 1|2|64)
set_collision_layer_value(1, true)
set_collision_mask_value(1, true)
set_collision_mask_value(2, true)
set_collision_mask_value(7, true) # Re-enable wall collision!
else:
print(" ✗ held_object doesn't match self")
@@ -6936,14 +7083,43 @@ func _sync_death():
_apply_death_visual()
return
@rpc("any_peer", "reliable")
func _sync_fallout_start(tile_center_pos: Vector2):
# Other clients: start fallout sink visuals; ignore if this player is being held (immune to fallout)
if not is_multiplayer_authority():
if is_being_held:
return
global_position = tile_center_pos
fallout_state = true
fallout_scale_progress = 1.0
fallout_respawn_delay_timer = 0.0
velocity = Vector2.ZERO
current_direction = Direction.DOWN
facing_direction_vector = Vector2.DOWN
_set_animation("FALL")
@rpc("any_peer", "reliable")
func _sync_respawn_from_fallout(safe_pos: Vector2):
if not is_multiplayer_authority():
global_position = safe_pos
fallout_state = false
fallout_scale_progress = 1.0
fallout_respawn_delay_timer = 0.0
scale = Vector2.ONE
rotation = 0.0
velocity = Vector2.ZERO
fallout_respawn_stun_timer = FALLOUT_RESPAWN_STUN_DURATION
_set_animation("IDLE")
@rpc("any_peer", "reliable")
func _sync_respawn(spawn_pos: Vector2):
if not is_multiplayer_authority():
# being_held_by already cleared via RPC in _die()
# Holder already dropped us via _force_holder_to_drop RPC
# Re-enable collision in case it was disabled while being carried
# Re-enable collision in case it was disabled while being carried (layer 1, mask 1|2|64)
set_collision_layer_value(1, true)
set_collision_mask_value(1, true)
set_collision_mask_value(2, true)
set_collision_mask_value(7, true) # Re-enable wall collision!
# Reset health and state