933 lines
34 KiB
GDScript
933 lines
34 KiB
GDScript
extends CharacterBody2D
|
|
|
|
var tileParticleScene = preload("res://scripts/components/TileParticle.tscn")
|
|
|
|
var liftable = true
|
|
var thrown_by = null
|
|
var entity_id = ""
|
|
var object_name = "bomb"
|
|
|
|
@export var is_being_thrown = false
|
|
@export var is_being_lifted = false:
|
|
set(value):
|
|
is_being_lifted = value
|
|
@export var is_being_put_down = false:
|
|
set(value):
|
|
is_being_put_down = value
|
|
@export var is_being_grabbed = false
|
|
@export var is_moving = false
|
|
@export var is_spawning = false
|
|
@export var is_fused = false
|
|
|
|
var throw_speed = 200
|
|
var throw_height = 30
|
|
var current_height = 0
|
|
var gravity = 800
|
|
var holder = null
|
|
var flipFromWall = false
|
|
|
|
# Add Z-axis variables similar to loot.gd
|
|
@export var positionZ = 0.0
|
|
var velocityZ = 0.0
|
|
var accelerationZ = -330.0 # Gravity
|
|
var bounceRestitution = 0.3
|
|
var minBounceVelocity = 60.0
|
|
|
|
var destroy_initiated = false
|
|
@export var is_destroyed = false
|
|
|
|
# Smooth lifting variables
|
|
var target_position = Vector2.ZERO
|
|
var lift_height = 12.0 # Height above player's head
|
|
var lift_speed = 10.0 # Speed of smooth movement
|
|
var put_down_start_pos = Vector2.ZERO
|
|
var put_down_target_pos = Vector2.ZERO
|
|
@export var lift_progress = 0.0
|
|
@export var re_enable_collision_after_time = 0.0
|
|
var re_enable_time = 0.08
|
|
var previousFrameVel = Vector2.ZERO
|
|
var hasShownSmokePuffs = false
|
|
var grab_follow_speed = 15.0 # Speed at which pot follows holder when grabbed
|
|
var previous_holder_position = Vector2.ZERO # Track previous holder position to calculate movement delta
|
|
var previous_client_position = Vector2.ZERO # Track previous position on client for movement detection
|
|
var push_lead_factor = 1.1 # When pushing, pot moves slightly ahead (110% of player movement)
|
|
var min_push_distance = 12.0 # Minimum distance pot should maintain from player when pushing to avoid blocking
|
|
var _throw_collision_initialized = false # Track if collision state has been initialized for throw on client
|
|
|
|
@export var holder_peer_id: int = 0:
|
|
set(value):
|
|
holder_peer_id = value
|
|
# Clear state when holder is cleared
|
|
if value == 0:
|
|
holder = null
|
|
# Always clear grabbed and lifted states when holder is cleared
|
|
is_being_grabbed = false
|
|
is_being_lifted = false
|
|
elif value != 0:
|
|
# Find the holder by peer ID
|
|
var spawn_root = get_tree().get_current_scene().get_node("SpawnRoot")
|
|
if spawn_root:
|
|
holder = spawn_root.get_node_or_null(str(value))
|
|
|
|
func _ready() -> void:
|
|
if is_spawning:
|
|
liftable = false
|
|
$Area2DCollision.set_deferred("monitoring", false)
|
|
self.set_collision_layer_value(8, false)
|
|
self.set_collision_mask_value(9, false)
|
|
self.set_collision_mask_value(10, false)
|
|
self.set_collision_mask_value(8, false)
|
|
update_sprite_scale()
|
|
pass
|
|
indicate(false)
|
|
pass
|
|
|
|
func _physics_process(delta: float) -> void:
|
|
# Update holder based on holder_peer_id for network sync
|
|
# CRITICAL: holder_peer_id is the source of truth - if it's 0, there's no holder
|
|
if holder_peer_id != 0:
|
|
var player = get_tree().get_current_scene().get_node("SpawnRoot").get_node_or_null(str(holder_peer_id))
|
|
if player and holder != player:
|
|
holder = player
|
|
elif holder_peer_id == 0:
|
|
# No holder - clear everything
|
|
# BUT: Don't clear velocity if pot is being thrown (it needs velocity to fly)
|
|
if holder != null:
|
|
holder = null
|
|
if is_being_grabbed and not is_being_thrown:
|
|
is_being_grabbed = false
|
|
if is_being_lifted and not is_being_thrown:
|
|
is_being_lifted = false
|
|
if velocity != Vector2.ZERO and not is_being_thrown:
|
|
velocity = Vector2.ZERO
|
|
|
|
# Handle lifted pot position on ALL clients for smooth following
|
|
if is_being_lifted and holder:
|
|
$GPUParticles2D.emitting = false
|
|
if lift_progress < 1.0:
|
|
lift_progress += delta * lift_speed
|
|
lift_progress = min(lift_progress, 1.0)
|
|
|
|
# Smoothly interpolate from current position to above holder during lifting
|
|
# Use the same calculation on both server and client to ensure consistent Y position
|
|
var target_pos = holder.global_position + Vector2(0, 0)
|
|
if lift_progress < 1.0:
|
|
global_position = global_position.lerp(target_pos, lift_progress)
|
|
positionZ = lift_height * lift_progress
|
|
else:
|
|
# When fully lifted, maintain exact position above holder
|
|
global_position = target_pos
|
|
positionZ = lift_height
|
|
update_sprite_scale() # Ensure sprite scale/offset is updated consistently
|
|
else:
|
|
# Debug: Check why pot is not following
|
|
if is_being_lifted and !holder:
|
|
# Fix inconsistent state
|
|
if holder_peer_id == 0:
|
|
is_being_lifted = false
|
|
elif !is_being_lifted and holder:
|
|
# Pot has holder but is_being_lifted is false - this is normal during transitions
|
|
pass
|
|
update_sprite_scale()
|
|
|
|
if multiplayer.is_server():
|
|
# CRITICAL: If holder_peer_id is 0, ALWAYS clear grab state immediately
|
|
# This must happen before ANY logic runs to prevent movement after release
|
|
# BUT: Don't clear velocity if pot is being thrown (it needs velocity to fly)
|
|
if holder_peer_id == 0 and not is_being_thrown:
|
|
is_being_grabbed = false
|
|
if holder != null:
|
|
holder = null
|
|
if velocity != Vector2.ZERO:
|
|
velocity = Vector2.ZERO
|
|
if is_moving:
|
|
is_moving = false
|
|
if $SfxDrag2.playing:
|
|
$SfxDrag2.stop()
|
|
# Skip all grab logic if holder_peer_id is 0
|
|
pass
|
|
if is_being_thrown:
|
|
re_enable_collision_after_time -= delta
|
|
if re_enable_collision_after_time <= 0.0:
|
|
# enable collisions with players again after the delay
|
|
self.set_collision_layer_value(8, true)
|
|
# Collision layer is already enabled, just re-enable collision mask with players
|
|
self.set_collision_mask_value(9, true) # Re-enable collision with players
|
|
self.set_collision_mask_value(10, true) # Re-enable collision with players (if using both)
|
|
re_enable_collision_after_time = 0
|
|
|
|
# Apply gravity to vertical movement
|
|
velocityZ += accelerationZ * delta
|
|
positionZ += velocityZ * delta
|
|
|
|
if positionZ <= 0:
|
|
# Pot has hit the ground
|
|
positionZ = 0
|
|
|
|
$SfxLand.play()
|
|
$GPUParticles2D.emitting = true
|
|
$GPUParticles2D/TimerSmokeParticles.start()
|
|
if abs(velocityZ) > minBounceVelocity:
|
|
velocityZ = - velocityZ * bounceRestitution
|
|
else:
|
|
velocityZ = 0
|
|
is_being_thrown = false
|
|
velocity = velocity.lerp(Vector2.ZERO, 0.5)
|
|
if velocity.x == 0 and velocity.y == 0:
|
|
thrown_by = null
|
|
|
|
# Move horizontally
|
|
if self.get_collision_layer_value(8) == false:
|
|
move_and_slide()
|
|
else:
|
|
var collision = move_and_collide(velocity * delta)
|
|
if collision:
|
|
if positionZ == 0:
|
|
is_being_thrown = false
|
|
update_sprite_scale()
|
|
elif is_being_put_down:
|
|
lift_progress -= delta * lift_speed
|
|
if lift_progress <= 0.0:
|
|
$SfxLand.play()
|
|
$GPUParticles2D.emitting = true
|
|
$GPUParticles2D/TimerSmokeParticles.start()
|
|
lift_progress = 0
|
|
is_being_put_down = false
|
|
is_being_lifted = false
|
|
holder = null
|
|
positionZ = 0
|
|
# enable collisions again
|
|
self.set_collision_layer_value(8, true)
|
|
self.set_collision_mask_value(9, true)
|
|
self.set_collision_mask_value(10, true)
|
|
self.set_collision_mask_value(7, true)
|
|
self.set_collision_mask_value(8, true)
|
|
$Area2DCollision.set_deferred("monitoring", true)
|
|
else:
|
|
global_position = put_down_start_pos.lerp(put_down_target_pos, 1.0 - lift_progress)
|
|
positionZ = lift_height * lift_progress
|
|
update_sprite_scale()
|
|
|
|
# CRITICAL: Only follow if holder_peer_id is NOT 0 (this is the source of truth)
|
|
# Check holder_peer_id FIRST before is_being_grabbed to ensure we never follow when released
|
|
# ONLY run this on server (pot has authority 1 = server)
|
|
# DOUBLE CHECK: holder_peer_id must be non-zero AND holder must exist AND match
|
|
# BUT: Don't run grab logic if pot is being thrown (throw logic handles movement)
|
|
if holder_peer_id != 0 and is_being_grabbed and holder != null and not is_being_thrown:
|
|
# Only follow if holder's authority matches holder_peer_id
|
|
if holder.get_multiplayer_authority() == holder_peer_id:
|
|
# Calculate how much the player has moved since last frame
|
|
var player_movement = holder.global_position - previous_holder_position
|
|
|
|
# Only move pot if player has moved
|
|
if player_movement.length() > 0.01:
|
|
# Get the locked grab direction to determine push/pull axis
|
|
var locked_dir = Vector2.ZERO
|
|
if "locked_grab_direction" in holder and holder.locked_grab_direction != Vector2.ZERO:
|
|
locked_dir = holder.locked_grab_direction.normalized()
|
|
|
|
if locked_dir != Vector2.ZERO:
|
|
# Project player's movement onto the push/pull direction
|
|
var movement_along_axis = player_movement.project(locked_dir)
|
|
|
|
# Determine if pushing (moving in locked_dir direction) or pulling (moving opposite)
|
|
var is_pushing = movement_along_axis.dot(locked_dir) > 0
|
|
|
|
if is_pushing:
|
|
# PUSHING: Pot moves first, ahead of player
|
|
# Move pot by slightly more than player movement so it leads (moves first)
|
|
var push_movement = movement_along_axis * push_lead_factor
|
|
global_position += push_movement
|
|
velocity = push_movement / delta if delta > 0 else Vector2.ZERO
|
|
else:
|
|
# PULLING: Player moves first, pot follows behind
|
|
# Move pot by the exact same amount as player (pot follows immediately)
|
|
global_position += movement_along_axis
|
|
velocity = movement_along_axis / delta if delta > 0 else Vector2.ZERO
|
|
else:
|
|
# No locked direction, just move pot by player's movement
|
|
global_position += player_movement
|
|
velocity = player_movement / delta if delta > 0 else Vector2.ZERO
|
|
|
|
# Update previous position for next frame
|
|
previous_holder_position = holder.global_position
|
|
|
|
if velocity.length() > 1.0:
|
|
is_moving = true
|
|
$GPUParticles2D.emitting = true
|
|
$GPUParticles2D/TimerSmokeParticles.start()
|
|
if !$SfxDrag2.playing:
|
|
$SfxDrag2.play()
|
|
else:
|
|
is_moving = false
|
|
if $SfxDrag2.playing:
|
|
$SfxDrag2.stop()
|
|
previousFrameVel = velocity
|
|
else:
|
|
# Player hasn't moved - keep pot at current position
|
|
velocity = Vector2.ZERO
|
|
is_moving = false
|
|
if $SfxDrag2.playing:
|
|
$SfxDrag2.stop()
|
|
# Still update previous position even if not moving
|
|
previous_holder_position = holder.global_position
|
|
else:
|
|
# No valid holder, clear grab state and stop movement immediately
|
|
is_being_grabbed = false
|
|
is_moving = false
|
|
if $SfxDrag2.playing:
|
|
$SfxDrag2.stop()
|
|
velocity = Vector2.ZERO
|
|
holder = null
|
|
holder_peer_id = 0 # Force clear to ensure no following
|
|
pass
|
|
|
|
# CRITICAL FINAL CHECK: If holder_peer_id is 0, STOP ALL MOVEMENT immediately
|
|
# This must run AFTER all grab logic to catch any cases where holder_peer_id was set to 0
|
|
# but the pot is still trying to move
|
|
# BUT: Don't clear velocity if pot is being thrown (it needs velocity to fly)
|
|
if holder_peer_id == 0 and not is_being_thrown:
|
|
if is_being_grabbed:
|
|
is_being_grabbed = false
|
|
if holder != null:
|
|
holder = null
|
|
if velocity != Vector2.ZERO:
|
|
velocity = Vector2.ZERO
|
|
if is_moving:
|
|
is_moving = false
|
|
if $SfxDrag2.playing:
|
|
$SfxDrag2.stop()
|
|
|
|
# Only handle free-falling if we're not being held (holder_peer_id == 0 is the source of truth)
|
|
# BUT: Don't run free-falling logic if pot is being thrown (throw logic handles movement)
|
|
if holder_peer_id == 0 and !is_being_lifted and !is_being_thrown: # it just spawned or is free-falling:
|
|
# Apply gravity to vertical movement
|
|
velocityZ += accelerationZ * delta
|
|
positionZ += velocityZ * delta
|
|
|
|
if positionZ <= 0:
|
|
if is_spawning:
|
|
is_spawning = false
|
|
liftable = true
|
|
$Area2DCollision.set_deferred("monitoring", true)
|
|
self.set_collision_layer_value(8, true)
|
|
self.set_collision_mask_value(9, true)
|
|
self.set_collision_mask_value(10, true)
|
|
self.set_collision_mask_value(8, true)
|
|
# Pot has hit the ground
|
|
positionZ = 0
|
|
if abs(velocityZ) > 30:
|
|
$SfxLand.play()
|
|
$GPUParticles2D.emitting = true
|
|
$GPUParticles2D/TimerSmokeParticles.start()
|
|
if abs(velocityZ) > minBounceVelocity:
|
|
velocityZ = - velocityZ * bounceRestitution
|
|
else:
|
|
velocityZ = 0
|
|
velocity = velocity.lerp(Vector2.ZERO, 0.5)
|
|
move_and_collide(velocity * delta)
|
|
update_sprite_scale()
|
|
pass
|
|
else:
|
|
# CRITICAL: If holder_peer_id is 0, ALWAYS clear grab state immediately (client side)
|
|
# This must happen before any grab logic runs
|
|
if holder_peer_id == 0:
|
|
is_being_grabbed = false
|
|
if holder != null:
|
|
holder = null
|
|
|
|
if is_fused and $AnimationPlayer.current_animation == "idle":
|
|
if $SfxBombFuse.playing:
|
|
$SfxBombFuse.play()
|
|
$AnimationPlayer.play("fused")
|
|
# for client:'
|
|
if is_being_thrown:
|
|
if $SfxDrag2.playing:
|
|
$SfxDrag2.stop()
|
|
|
|
# Timer is synced from server via sync_throw_timer RPC
|
|
# If not initialized yet, wait for RPC (fallback initialization)
|
|
if not _throw_collision_initialized:
|
|
# Fallback: initialize if RPC hasn't arrived yet
|
|
self.set_collision_layer_value(8, false)
|
|
self.set_collision_mask_value(7, true)
|
|
self.set_collision_mask_value(9, false)
|
|
self.set_collision_mask_value(10, false)
|
|
# CRITICAL: Set timer to a positive value to prevent immediate expiration
|
|
re_enable_collision_after_time = re_enable_time
|
|
_throw_collision_initialized = true
|
|
|
|
# Apply the same throw movement logic on client for smooth movement
|
|
# Handle collision re-enable timer (same as server)
|
|
# CRITICAL: Only decrement and check timer if it's been initialized AND is positive
|
|
if _throw_collision_initialized and re_enable_collision_after_time > 0.0:
|
|
re_enable_collision_after_time -= delta
|
|
if re_enable_collision_after_time <= 0.0:
|
|
# enable collisions with players again after the delay
|
|
self.set_collision_layer_value(8, true)
|
|
# Collision layer is already enabled, just re-enable collision mask with players
|
|
self.set_collision_mask_value(9, true) # Re-enable collision with players
|
|
self.set_collision_mask_value(10, true) # Re-enable collision with players (if using both)
|
|
re_enable_collision_after_time = 0
|
|
|
|
# Apply gravity to vertical movement
|
|
velocityZ += accelerationZ * delta
|
|
positionZ += velocityZ * delta
|
|
|
|
if positionZ <= 0:
|
|
# Pot has hit the ground
|
|
positionZ = 0
|
|
if !$SfxLand.playing:
|
|
$SfxLand.play()
|
|
$GPUParticles2D.emitting = true
|
|
$GPUParticles2D/TimerSmokeParticles.start()
|
|
if abs(velocityZ) > minBounceVelocity:
|
|
velocityZ = - velocityZ * bounceRestitution
|
|
else:
|
|
velocityZ = 0
|
|
is_being_thrown = false
|
|
velocity = velocity.lerp(Vector2.ZERO, 0.5)
|
|
if velocity.x == 0 and velocity.y == 0:
|
|
thrown_by = null
|
|
|
|
# Move horizontally using the same logic as server
|
|
if self.get_collision_layer_value(8) == false:
|
|
move_and_slide()
|
|
else:
|
|
var collision = move_and_collide(velocity * delta)
|
|
if collision:
|
|
if positionZ == 0:
|
|
is_being_thrown = false
|
|
update_sprite_scale()
|
|
else:
|
|
# Pot is no longer being thrown - reset initialization flag
|
|
if _throw_collision_initialized:
|
|
_throw_collision_initialized = false
|
|
if is_being_put_down:
|
|
if $SfxDrag2.playing:
|
|
$SfxDrag2.stop()
|
|
if !$SfxLand.playing:
|
|
$SfxLand.play()
|
|
$GPUParticles2D.emitting = true
|
|
$GPUParticles2D/TimerSmokeParticles.start()
|
|
elif is_being_lifted:
|
|
if $SfxDrag2.playing:
|
|
$SfxDrag2.stop()
|
|
$GPUParticles2D.emitting = false
|
|
# Update position on client to follow holder
|
|
if holder:
|
|
target_position = holder.global_position + Vector2(0, 0)
|
|
if lift_progress < 1.0:
|
|
lift_progress += delta * lift_speed
|
|
lift_progress = min(lift_progress, 1.0)
|
|
global_position = global_position.lerp(target_position, lift_progress)
|
|
positionZ = lift_height * lift_progress
|
|
else:
|
|
# When fully lifted, maintain exact position above holder
|
|
global_position = target_position
|
|
positionZ = lift_height
|
|
update_sprite_scale()
|
|
# CRITICAL: On client, do NOT move the pot - only the server should move it
|
|
# The pot's position is synced via replication from the server
|
|
# We only handle visual/audio effects here
|
|
elif holder_peer_id == 0:
|
|
# No holder - ensure we're not in grabbed state
|
|
if is_being_grabbed:
|
|
is_being_grabbed = false
|
|
is_moving = false
|
|
if $SfxDrag2.playing:
|
|
$SfxDrag2.stop()
|
|
elif is_being_grabbed and holder_peer_id != 0:
|
|
# Only handle visual/audio effects on client, don't move the pot
|
|
# The server handles the actual movement and syncs position via replication
|
|
if holder != null and holder.get_multiplayer_authority() == holder_peer_id:
|
|
# Check if pot appears to be moving (based on position changes from server)
|
|
var pot_movement = global_position - previous_client_position
|
|
|
|
# If pot has moved, it's being pushed/pulled
|
|
if pot_movement.length() > 0.5:
|
|
is_moving = true
|
|
$GPUParticles2D.emitting = true
|
|
$GPUParticles2D/TimerSmokeParticles.start()
|
|
if !$SfxDrag2.playing:
|
|
$SfxDrag2.play()
|
|
else:
|
|
is_moving = false
|
|
if $SfxDrag2.playing:
|
|
$SfxDrag2.stop()
|
|
|
|
# Update previous position for next frame
|
|
previous_client_position = global_position
|
|
else:
|
|
# No valid holder, clear state
|
|
is_being_grabbed = false
|
|
is_moving = false
|
|
if $SfxDrag2.playing:
|
|
$SfxDrag2.stop()
|
|
elif !is_being_grabbed:
|
|
if is_spawning:
|
|
if positionZ <= 0:
|
|
$SfxLand.play()
|
|
$GPUParticles2D.emitting = true
|
|
$GPUParticles2D/TimerSmokeParticles.start()
|
|
else:
|
|
if $SfxLand.playing:
|
|
$SfxLand.stop()
|
|
if $SfxDrag2.playing:
|
|
$SfxDrag2.stop()
|
|
pass
|
|
# Fix stuck put_down state
|
|
if is_being_put_down and lift_progress <= 0:
|
|
is_being_put_down = false
|
|
update_sprite_scale()
|
|
if is_destroyed and !destroy_initiated:
|
|
destroy_initiated = true
|
|
show_destroy_effect()
|
|
|
|
# Update debug label
|
|
if has_node("LabelPotStateNDirectionNSpeed"):
|
|
var collision_layer_8 = self.get_collision_layer_value(8)
|
|
var collision_str = "Col 8: %s" % ("TRUE" if collision_layer_8 else "FALSE")
|
|
collision_str += "\nthrown? %s" % ("TRUE" if is_being_thrown else "FALSE")
|
|
|
|
$LabelPotStateNDirectionNSpeed.text = collision_str
|
|
|
|
pass
|
|
pass
|
|
func update_sprite_scale() -> void:
|
|
# Calculate scale based on height
|
|
# Maximum height will have scale 1.3, ground will have scale 1.0
|
|
var height_factor = positionZ / 50.0 # Assuming 50 is max height
|
|
var posY = positionZ # Direct mapping of Z to Y offset
|
|
var sc = 1.0 + (0.1 * height_factor) # Slightly less scale change than loot (0.3 instead of 0.8)
|
|
$Sprite2D.scale = Vector2(sc, sc)
|
|
$Sprite2D.offset.y = - posY
|
|
# Also update shadow position and scale
|
|
if is_being_lifted:
|
|
$Sprite2D.z_as_relative = false
|
|
$Sprite2D.z_index = 12
|
|
$Sprite2DShadow.offset.y = 0 # Base shadow position
|
|
else:
|
|
$Sprite2D.z_as_relative = true
|
|
$Sprite2D.z_index = 0
|
|
$Sprite2DShadow.offset.y = 0
|
|
#$Sprite2DShadow.scale = Vector2(1.125 * sc, 0.5 * sc) # Scale shadow with height
|
|
$Sprite2DShadow.scale = Vector2(1, 1)
|
|
#$Sprite2DShadow.modulate.
|
|
|
|
func throw(direction: Vector2, initial_velocity: float = 200):
|
|
# When thrown, enable collision layer so pot can use move_and_collide
|
|
# But disable collision mask with players temporarily (re-enabled after re_enable_collision_after_time)
|
|
# Enable collision with walls so pot can bounce off walls
|
|
self.set_collision_layer_value(8, false) # Enable pot's collision layer (needed for move_and_collide to work)
|
|
self.set_collision_mask_value(7, true) # Enable collision with walls
|
|
self.set_collision_mask_value(9, false) # Disable collision with players initially
|
|
self.set_collision_mask_value(10, false) # Disable collision with players initially (if using both)
|
|
$Area2DCollision.set_deferred("monitoring", true)
|
|
$SfxThrow.play()
|
|
|
|
is_being_lifted = false
|
|
is_being_thrown = true
|
|
is_being_put_down = false
|
|
is_being_grabbed = false # Clear grab state
|
|
thrown_by = holder
|
|
holder = null
|
|
holder_peer_id = 0 # Clear the network holder reference
|
|
velocity = direction * initial_velocity
|
|
velocityZ = throw_height
|
|
positionZ = lift_height
|
|
current_height = 0
|
|
re_enable_collision_after_time = re_enable_time
|
|
# Sync timer to clients so they know when to re-enable collisions
|
|
sync_throw_timer.rpc(re_enable_time)
|
|
|
|
@rpc("any_peer", "reliable")
|
|
func sync_throw_timer(timer_value: float):
|
|
# Client receives timer value from server
|
|
if not multiplayer.is_server():
|
|
# CRITICAL: Always set collision layer to false FIRST when receiving timer sync
|
|
# This ensures it's false even if something else tried to set it to true
|
|
self.set_collision_layer_value(8, false)
|
|
self.set_collision_mask_value(7, true) # Enable collision with walls
|
|
self.set_collision_mask_value(9, false) # Disable collision with players initially
|
|
self.set_collision_mask_value(10, false) # Disable collision with players initially
|
|
# Set timer value from server (ensures it's positive)
|
|
re_enable_collision_after_time = timer_value
|
|
_throw_collision_initialized = true
|
|
|
|
@rpc("any_peer", "reliable")
|
|
func throw_rpc(direction: Vector2, initial_velocity: float = 200):
|
|
# Only execute on server to avoid conflicts
|
|
if multiplayer.is_server():
|
|
throw(direction, initial_velocity)
|
|
|
|
func grab(new_holder: CharacterBody2D) -> bool:
|
|
if positionZ <= 0 and holder == null: # only allow grab if no previous owner and position is 0
|
|
$GPUParticles2D/TimerSmokeParticles.stop() # reset...
|
|
holder = new_holder
|
|
holder_peer_id = new_holder.get_multiplayer_authority()
|
|
is_being_grabbed = true
|
|
indicate(false)
|
|
# Initialize previous position to current position so we can track movement
|
|
previous_holder_position = new_holder.global_position
|
|
previous_client_position = global_position # Initialize client position tracking
|
|
# Disable pot's collision layer so grabber can't collide with it (prevents blocking when pushing)
|
|
# Note: This also means other players can't collide with pot while it's grabbed
|
|
# The pot uses direct position updates (not move_and_collide) so collisions aren't needed for movement
|
|
self.set_collision_layer_value(8, false) # Disable pot's collision layer
|
|
# Don't change pot's position - it should stay where it is and only move when player pushes/pulls
|
|
return true
|
|
return false
|
|
|
|
func release():
|
|
# Clear all grab-related state
|
|
# CRITICAL: Don't re-enable collision layer if pot is being thrown (throw logic handles it)
|
|
if is_being_thrown:
|
|
# Pot is being thrown - don't change collision layer, throw logic will handle it
|
|
holder = null
|
|
holder_peer_id = 0
|
|
is_being_grabbed = false
|
|
hasShownSmokePuffs = false
|
|
#velocity = Vector2.ZERO
|
|
indicate(true)
|
|
if $SfxDrag2.playing:
|
|
$SfxDrag2.stop()
|
|
return
|
|
|
|
# CRITICAL: If we're not the server, we need to notify the server to release
|
|
# The pot has authority 1 (server), so the server must be the one to clear holder_peer_id
|
|
if not multiplayer.is_server():
|
|
# Client requests server to release - use holder_peer_id directly
|
|
if holder_peer_id != 0:
|
|
request_release_pot.rpc_id(1, get_path(), holder_peer_id)
|
|
# Also clear locally for immediate visual feedback
|
|
holder = null
|
|
holder_peer_id = 0
|
|
is_being_grabbed = false
|
|
hasShownSmokePuffs = false
|
|
#velocity = Vector2.ZERO
|
|
# Re-enable pot's collision layer when released
|
|
#self.set_collision_layer_value(8, true) # Re-enable pot's collision layer
|
|
indicate(true)
|
|
if $SfxDrag2.playing:
|
|
$SfxDrag2.stop()
|
|
else:
|
|
# Server can release directly
|
|
holder = null
|
|
holder_peer_id = 0
|
|
is_being_grabbed = false
|
|
hasShownSmokePuffs = false
|
|
#velocity = Vector2.ZERO
|
|
# Re-enable pot's collision layer when released
|
|
#self.set_collision_layer_value(8, true) # Re-enable pot's collision layer
|
|
indicate(true)
|
|
if $SfxDrag2.playing:
|
|
$SfxDrag2.stop()
|
|
pass
|
|
|
|
@rpc("any_peer", "reliable")
|
|
func request_release_pot(pot_path: NodePath, peer_id: int):
|
|
if multiplayer.is_server():
|
|
var pot = get_node_or_null(pot_path)
|
|
var player = get_tree().get_current_scene().get_node("SpawnRoot").get_node_or_null(str(peer_id))
|
|
if pot and player:
|
|
# Check if the pot is being held by this player
|
|
if pot.holder_peer_id == peer_id or (pot.holder != null and pot.holder.get_multiplayer_authority() == peer_id):
|
|
pot.holder = null
|
|
pot.holder_peer_id = 0
|
|
pot.is_being_grabbed = false
|
|
pot.velocity = Vector2.ZERO
|
|
# Re-enable pot's collision layer when released
|
|
pot.set_collision_layer_value(8, true) # Re-enable pot's collision layer
|
|
pot.indicate(true)
|
|
if $SfxDrag2.playing:
|
|
$SfxDrag2.stop()
|
|
pass
|
|
|
|
func lift(new_holder: CharacterBody2D):
|
|
if (new_holder != holder and holder != null) and "lose_held_entity" in holder:
|
|
# steal from holder
|
|
holder.lose_held_entity(self)
|
|
indicate(false)
|
|
$Area2DCollision.set_deferred("monitoring", false)
|
|
thrown_by = null
|
|
holder = new_holder
|
|
holder_peer_id = new_holder.get_multiplayer_authority()
|
|
# disable collisions with walls and players when lifted
|
|
self.set_collision_layer_value(8, false) # Disable pot's collision layer (so players can't collide with it)
|
|
self.set_collision_mask_value(7, false) # Disable collision with walls
|
|
self.set_collision_mask_value(8, false) # Disable collision with other pots
|
|
self.set_collision_mask_value(9, false) # Disable collision with players
|
|
self.set_collision_mask_value(10, false) # Disable collision with players (if using both)
|
|
is_being_lifted = true
|
|
is_being_grabbed = false
|
|
is_being_thrown = false
|
|
is_being_put_down = false
|
|
lift_progress = 0.0
|
|
velocityZ = 0
|
|
# Store initial position for smooth lifting - don't change current position yet
|
|
# The pot will smoothly glide from its current position to above the holder
|
|
$SfxLand.play()
|
|
|
|
@rpc("any_peer", "reliable")
|
|
func lift_rpc(holder_path: NodePath):
|
|
# Only execute on server to avoid conflicts
|
|
if multiplayer.is_server():
|
|
# Find the holder by path
|
|
var holder_node = get_node_or_null(holder_path)
|
|
if holder_node and holder_node is CharacterBody2D:
|
|
lift(holder_node)
|
|
|
|
func put_down() -> bool:
|
|
if not is_being_lifted or is_being_put_down:
|
|
return false
|
|
|
|
var dropDir = holder.last_direction
|
|
dropDir.x *= 12
|
|
if dropDir.y > 0:
|
|
dropDir.y *= 10
|
|
else:
|
|
dropDir.y *= 10
|
|
put_down_target_pos = holder.global_position + dropDir
|
|
|
|
# First check: Direct space state query for walls
|
|
var space_state = get_world_2d().direct_space_state
|
|
var params = PhysicsPointQueryParameters2D.new()
|
|
params.position = put_down_target_pos
|
|
params.collision_mask = 64 # Layer for walls (usually layer 7)
|
|
params.collide_with_areas = true
|
|
params.collide_with_bodies = true
|
|
|
|
var results = space_state.intersect_point(params)
|
|
if results.size() > 0:
|
|
# Found overlapping walls at target position
|
|
return false
|
|
|
|
# Second check: Line of sight between player and target position
|
|
var query = PhysicsRayQueryParameters2D.create(
|
|
holder.global_position,
|
|
put_down_target_pos,
|
|
64 # Wall collision mask
|
|
)
|
|
query.collide_with_areas = true
|
|
query.collide_with_bodies = true
|
|
|
|
var result = space_state.intersect_ray(query)
|
|
if result:
|
|
# Hit something between player and target position
|
|
return false
|
|
|
|
# Third check: Make sure we're not placing on top of another pot or object
|
|
params.collision_mask = 128 # Layer for pots/objects
|
|
results = space_state.intersect_point(params)
|
|
if results.size() > 0:
|
|
# Found overlapping objects at target position
|
|
return false
|
|
$Area2DCollision.set_deferred("monitoring", false)
|
|
|
|
# Position is valid, proceed with putting down
|
|
self.set_collision_mask_value(7, true) # instantly reenenable collision with wall
|
|
is_being_put_down = true
|
|
is_being_lifted = false
|
|
put_down_start_pos = global_position
|
|
thrown_by = null
|
|
holder = null
|
|
holder_peer_id = 0
|
|
|
|
indicate(true)
|
|
|
|
return true
|
|
|
|
func remove():
|
|
var fade_tween = create_tween()
|
|
fade_tween.set_trans(Tween.TRANS_CUBIC)
|
|
fade_tween.set_ease(Tween.EASE_OUT)
|
|
fade_tween.tween_property($Sprite2D, "modulate:a", 0.0, 0.6)
|
|
#await fade_tween.finished
|
|
await $SfxShatter.finished
|
|
if $SfxLand.playing:
|
|
$SfxLand.stop()
|
|
if $SfxDrag2.playing:
|
|
$SfxDrag2.stop()
|
|
$GPUParticles2D.emitting = false
|
|
#$SfxShatter.stop()
|
|
if multiplayer.is_server():
|
|
call_deferred("queue_free")
|
|
pass
|
|
|
|
|
|
func create4TileParticles():
|
|
var sprite_texture = $Sprite2D.texture
|
|
var frame_width = sprite_texture.get_width() / $Sprite2D.hframes
|
|
var frame_height = sprite_texture.get_height() / $Sprite2D.vframes
|
|
var frame_x = ($Sprite2D.frame % $Sprite2D.hframes) * frame_width
|
|
var frame_y = ($Sprite2D.frame / $Sprite2D.hframes) * frame_height
|
|
|
|
# Create 4 particles with different directions and different parts of the texture
|
|
var directions = [
|
|
Vector2(-1, -1).normalized(), # Top-left
|
|
Vector2(1, -1).normalized(), # Top-right
|
|
Vector2(-1, 1).normalized(), # Bottom-left
|
|
Vector2(1, 1).normalized() # Bottom-right
|
|
]
|
|
|
|
var regions = [
|
|
Rect2(frame_x, frame_y, frame_width / 2, frame_height / 2), # Top-left
|
|
Rect2(frame_x + frame_width / 2, frame_y, frame_width / 2, frame_height / 2), # Top-right
|
|
Rect2(frame_x, frame_y + frame_height / 2, frame_width / 2, frame_height / 2), # Bottom-left
|
|
Rect2(frame_x + frame_width / 2, frame_y + frame_height / 2, frame_width / 2, frame_height / 2) # Bottom-right
|
|
]
|
|
|
|
for i in range(4):
|
|
var tp = tileParticleScene.instantiate() as CharacterBody2D
|
|
var spr2D = tp.get_node("Sprite2D") as Sprite2D
|
|
tp.global_position = global_position
|
|
|
|
# Set up the sprite's texture and region
|
|
spr2D.texture = sprite_texture
|
|
spr2D.region_enabled = true
|
|
spr2D.region_rect = regions[i]
|
|
|
|
# Add some randomness to the velocity
|
|
var speed = randf_range(170, 200)
|
|
var dir = directions[i] + Vector2(randf_range(-0.2, 0.2), randf_range(-0.2, 0.2))
|
|
tp.velocity = dir * speed
|
|
|
|
# Add some rotation
|
|
tp.angular_velocity = randf_range(-7, 7)
|
|
|
|
get_parent().call_deferred("add_child", tp)
|
|
|
|
func indicate(iIndicate: bool):
|
|
$Indicator.visible = iIndicate
|
|
if !liftable or is_being_lifted:
|
|
$Indicator.visible = false
|
|
pass
|
|
|
|
|
|
func _on_area_2d_collision_body_entered(body: Node2D) -> void:
|
|
if is_being_thrown == false or body == self or body == thrown_by:
|
|
return
|
|
if multiplayer.is_server():
|
|
var collision_shape = $Area2DCollision.get_overlapping_bodies()
|
|
|
|
if collision_shape.size() > 0:
|
|
var collider = collision_shape[0]
|
|
var normal = (global_position - collider.global_position).normalized()
|
|
if abs(velocity.x) > 0.05 or abs(velocity.y) > 0.05:
|
|
if "take_damage" in body or body is TileMapLayer or collider is TileMapLayer:
|
|
if "take_damage" in body:
|
|
body.take_damage(self, thrown_by)
|
|
elif collider != self and "breakPot" in collider:
|
|
collider.take_damage(self, thrown_by)
|
|
# create particles from pot:
|
|
|
|
take_damage.rpc(null, null)
|
|
pass
|
|
normal = velocity.normalized()
|
|
velocity = velocity.bounce(normal) * 0.4 # slow down
|
|
pass # Replace with function body.
|
|
|
|
func show_destroy_effect():
|
|
$GPUParticles2D.emitting = true
|
|
$Sprite2D.frame = 13 + 19 + 19
|
|
$Sprite2DShadow.visible = false
|
|
liftable = false
|
|
indicate(false)
|
|
create4TileParticles()
|
|
is_being_thrown = false
|
|
$Sprite2DShadow.visible = false
|
|
# Play shatter sound
|
|
$SfxShatter.play()
|
|
self.call_deferred("remove")
|
|
pass
|
|
|
|
@rpc("any_peer", "reliable")
|
|
func request_grab_pot(pot_path: NodePath, peer_id: int):
|
|
if multiplayer.is_server():
|
|
var pot = get_node_or_null(pot_path)
|
|
var player = get_tree().get_current_scene().get_node("SpawnRoot").get_node_or_null(str(peer_id))
|
|
if pot and "grab" in pot and player:
|
|
if pot.grab(player):
|
|
# grab() function already disables collisions, but ensure it's done
|
|
player.grabbed_entity = pot
|
|
player.grabbed_entity_path = str(pot.get_path())
|
|
player.current_animation = "IDLE_PUSH"
|
|
# Lock direction to current last_direction when grabbing
|
|
player.locked_grab_direction = player.last_direction
|
|
# Sync to all clients (including the joiner who requested it)
|
|
player.set_grabbed_entity_path_rpc.rpc(str(pot.get_path()))
|
|
player.sync_animation.rpc("IDLE_PUSH")
|
|
|
|
@rpc("any_peer", "reliable")
|
|
func request_lift_pot(_pot_path: NodePath, _peer_id: int):
|
|
# This function is now handled by MultiplayerManager
|
|
# Keeping it for backward compatibility but it should not be called
|
|
pass
|
|
|
|
@rpc("any_peer", "reliable")
|
|
func request_throw_pot(pot_path: NodePath, peer_id: int, direction: Vector2):
|
|
if multiplayer.is_server():
|
|
var pot = get_node_or_null(pot_path)
|
|
var player = get_tree().get_current_scene().get_node("SpawnRoot").get_node_or_null(str(peer_id))
|
|
if pot and player:
|
|
# Check if the pot is being held by this player (either by holder_peer_id or by checking the holder directly)
|
|
if pot.holder_peer_id == peer_id or (pot.holder != null and pot.holder.get_multiplayer_authority() == peer_id):
|
|
pot.throw(direction)
|
|
player.held_entity = null
|
|
player.held_entity_path = ""
|
|
player.current_animation = "THROW"
|
|
# Sync pot state to all clients first
|
|
pot.sync_pot_state.rpc(false, 0) # Not lifted, no holder
|
|
# Sync animation and clear held entity to all clients
|
|
var all_players = get_tree().get_current_scene().get_node("SpawnRoot").get_children()
|
|
for p in all_players:
|
|
if p.has_method("sync_animation"):
|
|
p.sync_animation.rpc("THROW")
|
|
p.sync_held_entity.rpc("") # Clear held entity
|
|
|
|
@rpc("call_local")
|
|
func take_damage(_iBody: Node2D, _iByWhoOrWhat: Node2D) -> void:
|
|
is_destroyed = true # will trigger show_destroy_effect for clients...
|
|
show_destroy_effect()
|
|
# remove all kind of collision since it's broken now...
|
|
self.set_deferred("monitoring", false)
|
|
self.set_collision_layer_value(8, false)
|
|
self.set_collision_mask_value(7, false)
|
|
self.set_collision_mask_value(8, false)
|
|
self.set_collision_mask_value(9, false)
|
|
self.set_collision_mask_value(10, false)
|
|
pass
|
|
|
|
@rpc("call_local", "reliable")
|
|
func sync_pot_state(lifted: bool, holder_id: int):
|
|
is_being_lifted = lifted
|
|
holder_peer_id = holder_id
|
|
if holder_peer_id != 0:
|
|
var player = get_tree().get_current_scene().get_node("SpawnRoot").get_node_or_null(str(holder_peer_id))
|
|
if player:
|
|
holder = player
|
|
else:
|
|
holder = null
|
|
else:
|
|
holder = null
|
|
pass
|
|
|
|
func _on_area_2d_collision_body_exited(_body: Node2D) -> void:
|
|
pass # Replace with function body.
|
|
|
|
|
|
func _on_area_2d_pickup_body_entered(_body: Node2D) -> void:
|
|
indicate(true)
|
|
pass # Replace with function body.
|
|
|
|
|
|
func _on_area_2d_pickup_body_exited(_body: Node2D) -> void:
|
|
indicate(false)
|
|
pass # Replace with function body.
|
|
|
|
|
|
func _on_timer_smoke_particles_timeout() -> void:
|
|
$GPUParticles2D.emitting = false
|
|
pass # Replace with function body.
|