now both players can take damage

This commit is contained in:
2025-12-26 07:59:27 +01:00
parent 37b96d0518
commit 4915a1d945
3 changed files with 149 additions and 45 deletions

View File

@@ -233,42 +233,40 @@ func broadcast_chat_message(message: String):
@rpc("any_peer", "reliable")
func request_lift_pot(pot_path: NodePath, peer_id: int):
if multiplayer.is_server():
var pot = get_node_or_null(pot_path)
var entity = 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 "lift" in pot and pot.liftable and player:
# Clear grabbed entity if it's not the pot we're lifting
if player.grabbed_entity != null and player.grabbed_entity != pot and "release" in player.grabbed_entity:
player.grabbed_entity.release()
if player.grabbed_entity != pot:
player.grabbed_entity = null
# Don't clear grabbed_entity_path if we're lifting the same pot
# This prevents the pot from being released when we're trying to lift it
player.is_grabbing = false
player.is_lifting = true
# Set held_entity_path directly on server, use RPC for clients
player.held_entity_path = str(pot.get_path())
# Send RPC to all other players (excluding server)
var all_players = get_tree().get_current_scene().get_node("SpawnRoot").get_children()
for p in all_players:
if p != player and p.has_method("set_held_entity_path_rpc"):
p.set_held_entity_path_rpc.rpc(str(pot.get_path()))
pot.lift(player)
# Set animation on the player who is lifting (the joiner)
player.current_animation = "LIFT"
# Sync animation to all clients
player.sync_animation.rpc("LIFT")
# Pot state is auto-synced via @export variables, no need for manual sync
if entity and player:
# Check if entity is a pot or a player
var is_pot = "lift" in entity and "liftable" in entity and entity.liftable
var is_player_entity = "is_player" in entity and entity.is_player and not entity.is_being_lifted
# Sync animation and entity to all clients
#print("MultiplayerManager syncing LIFT animation and held_entity to all clients")
# Send RPC to all players except the server (since server already has correct state)
#var all_players = get_tree().get_current_scene().get_node("SpawnRoot").get_children()
#for p in all_players:
#if p.has_method("sync_animation") and p.get_multiplayer_authority() != 1:
#print("MultiplayerManager syncing to player: ", p.name)
#p.sync_animation.rpc("LIFT")
#p.sync_held_entity.rpc(str(pot.get_path()))
#p.sync_grabbed_entity.rpc("")
if (is_pot or is_player_entity):
# Clear grabbed entity if it's not the entity we're lifting
if player.grabbed_entity != null and player.grabbed_entity != entity and "release" in player.grabbed_entity:
player.grabbed_entity.release()
if player.grabbed_entity != entity:
player.grabbed_entity = null
player.is_grabbing = false
# DON'T clear is_lifting - it should stay true while holding the pot
# is_lifting will be cleared when player releases button or throws
# Set held_entity_path directly on server, use RPC for clients
player.held_entity_path = str(entity.get_path())
# Send RPC to the requesting player (joiner) to sync held_entity_path
player.set_held_entity_path_rpc.rpc(str(entity.get_path()))
# Send RPC to all other players (excluding server and requesting player)
var all_players = get_tree().get_current_scene().get_node("SpawnRoot").get_children()
for p in all_players:
if p != player and p.has_method("set_held_entity_path_rpc"):
p.set_held_entity_path_rpc.rpc(str(entity.get_path()))
# Call lift on the entity (works for both pot and player)
if "lift" in entity:
entity.lift(player)
# Set animation on the player who is lifting (the joiner)
player.current_animation = "LIFT"
# Sync animation to all clients
player.sync_animation.rpc("LIFT")
@rpc("any_peer", "reliable")
func request_put_down_pot(pot_path: NodePath, peer_id: int):
@@ -287,16 +285,22 @@ func request_put_down_pot(pot_path: NodePath, peer_id: int):
@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 entity = 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.throw(direction)
if entity and player:
# Check if the entity (pot or player) is being held by this player
var is_held = false
if "holder_peer_id" in entity:
is_held = entity.holder_peer_id == peer_id or (entity.holder != null and entity.holder.get_multiplayer_authority() == peer_id)
elif "is_being_lifted" in entity and entity.is_being_lifted:
is_held = entity.holder_peer_id == peer_id or (entity.holder != null and entity.holder.get_multiplayer_authority() == peer_id)
if is_held and "throw" in entity:
entity.throw(direction)
# Use RPC to clear held_entity_path on all players (including server)
player.set_held_entity_path_rpc.rpc("")
player.current_animation = "THROW"
# Pot state is auto-synced via @export variables, no need for manual sync
# Entity state is auto-synced via @export variables, no need for manual sync
# Sync animation to all clients (entity sync is handled automatically by PlayerSynchronizer)
var all_players = get_tree().get_current_scene().get_node("SpawnRoot").get_children()

View File

@@ -285,7 +285,17 @@ func _physics_process(delta: float) -> void:
if stats.hp > 0:
#print("we are below", knockback_timer, ", delta was:", delta)
stats.is_invulnerable = false
# Sync invulnerability cleared to clients
if multiplayer.is_server():
sync_invulnerability.rpc(false)
move_and_collide(velocity * delta)
else:
# Client also needs to decrement knockback_timer for visual effects
if knockback_timer > 0:
velocity = velocity.move_toward(Vector2.ZERO, 300 * delta)
knockback_timer -= delta
if knockback_timer <= 0.0:
knockback_timer = 0
if is_moving:
# this only plays on server.... must play on client also somehow...
if !$SfxWalk.playing and $SfxWalk/TimerWalk.is_stopped():
@@ -769,19 +779,40 @@ func die_sound():
pass
func take_damage(iBody: Node2D, _iByWhoOrWhat: Node2D) -> void:
# Direct call version - used when called locally
var damager_pos = iBody.global_position if iBody != null else global_position
var damager_path = _iByWhoOrWhat.get_path() if _iByWhoOrWhat != null else ""
_take_damage_internal(damager_pos, damager_path, _iByWhoOrWhat)
@rpc("any_peer", "reliable")
func take_damage_rpc(damager_position: Vector2, damager_path: String, damager_peer_id: int):
# RPC version - used when server calls on joiner's player node
# Get the actual damager node if we can
var _iByWhoOrWhat = null
if damager_path != "":
_iByWhoOrWhat = get_node_or_null(damager_path)
_take_damage_internal(damager_position, damager_path, _iByWhoOrWhat)
func _take_damage_internal(damager_position: Vector2, damager_path: String, _iByWhoOrWhat: Node2D):
# Internal function that handles the actual damage logic
# This allows both direct calls and RPC calls to work
if !stats.is_invulnerable:
take_dmg_sound.rpc()
# Apply damage (works on both server and client since each player has authority over themselves)
stats.take_damage(13.0)
stats.is_invulnerable = true
knockback_timer = knockback_duration
# Sync invulnerability state to all clients (so other players know this player is invulnerable)
sync_invulnerability.rpc(true)
if current_animation != "DAMAGE":
time_since_last_frame = 0
current_frame = 0
current_animation = "DAMAGE"
# Calculate knockback direction from the damaging body
knockback_direction = (global_position - iBody.global_position).normalized()
# Calculate knockback direction from the damaging body position
knockback_direction = (global_position - damager_position).normalized()
velocity = knockback_direction * knockback_strength
_updateHp()
if held_entity != null:
@@ -819,7 +850,7 @@ func take_damage(iBody: Node2D, _iByWhoOrWhat: Node2D) -> void:
pass
else:
_iByWhoOrWhat.stats.forceUpdate()
'
'
# give score to other player...
if current_animation != "DIE":
@@ -831,6 +862,46 @@ func take_damage(iBody: Node2D, _iByWhoOrWhat: Node2D) -> void:
#_updateScore.rpc()
call_deferred("_on_died")
return
@rpc("any_peer", "reliable")
func take_damage_effects(damager_position: Vector2, damager_path: String):
# Client receives damage effects - play visual/audio but don't modify stats
# Note: is_invulnerable is already set to true by sync_invulnerability RPC
# We just need to set knockback_timer for visual effects
take_dmg_sound.rpc()
knockback_timer = knockback_duration
if current_animation != "DAMAGE":
time_since_last_frame = 0
current_frame = 0
current_animation = "DAMAGE"
# Calculate knockback direction from the damaging body position
knockback_direction = (global_position - damager_position).normalized()
velocity = knockback_direction * knockback_strength
if held_entity != null:
if multiplayer.is_server():
held_entity.throw(knockback_direction)
else:
MultiplayerManager.request_throw_pot.rpc_id(1, held_entity.get_path(), multiplayer.get_unique_id(), knockback_direction)
held_entity = null
held_entity_path = ""
is_lifting = false
if grabbed_entity != null and "release" in grabbed_entity:
grabbed_entity.release()
grabbed_entity = null
is_grabbing = false
# Create damage number
if damage_number_scene:
var damage_number = damage_number_scene.instantiate()
get_tree().current_scene.call_deferred("add_child", damage_number)
damage_number.global_position = global_position + Vector2(0, -16)
damage_number.direction = Vector2(0, -1)
damage_number.label = "1"
@rpc("call_local", "reliable")
func sync_player_kills(iKills: int):
@@ -854,6 +925,16 @@ func sync_animation(animation_name: String):
current_animation = animation_name
pass
@rpc("any_peer", "reliable")
func sync_invulnerability(is_invul: bool):
# Sync invulnerability state from server to clients
# This ensures both server and client have the same invulnerability state
stats.is_invulnerable = is_invul
# If invulnerability is cleared, also clear knockback_timer on client
if not is_invul:
knockback_timer = 0
pass
# RPC functions removed - entity synchronization now handled by setter functions
@rpc("reliable")

View File

@@ -817,7 +817,26 @@ func _on_area_2d_collision_body_entered(body: Node2D) -> void:
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)
# Check if body is a player - if so, check if it's a joiner (needs RPC) or server (direct call)
# Otherwise, call directly (for enemies, etc.)
if "is_player" in body and body.is_player:
# Player - check if it's a joiner (needs RPC) or server (direct call)
# If the player's authority matches the server's unique ID, it's the server's own player
var player_authority = body.get_multiplayer_authority()
var is_server_player = (player_authority == 1) # Server's peer ID is 1
if is_server_player:
# Server's own player - call directly (server has authority)
body.take_damage(self, thrown_by)
else:
# Joiner player - use RPC with serializable parameters (joiner has authority)
var damager_pos = self.global_position
var damager_path = thrown_by.get_path() if thrown_by != null else ""
var damager_peer_id = thrown_by.get_multiplayer_authority() if thrown_by != null else 0
body.take_damage_rpc.rpc(damager_pos, damager_path, damager_peer_id)
else:
# Non-player (enemy, etc.) - call directly
body.take_damage(self, thrown_by)
elif collider != self and "breakPot" in collider:
collider.take_damage(self, thrown_by)
# create particles from pot: