added fallout tiles, fixed so all enemys can get affected.
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 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
|
||||
|
||||
Reference in New Issue
Block a user