Release 2.4.5: Candy Survival sync & GoalsCycleManager improvements
Release / Build & Release (push) Successful in 7m23s

This commit is contained in:
2026-07-09 16:27:34 +08:00
parent 8573f8a85d
commit 203175178f
9 changed files with 101 additions and 30 deletions
+56 -23
View File
@@ -64,6 +64,9 @@ func _ready():
set_process(false)
_setup_hud()
func _exit_tree() -> void:
Engine.time_scale = 1.0
func _setup_hud() -> void:
var hud_instance = _candy_survival_hud_scene.instantiate()
hud_layer = hud_instance
@@ -203,8 +206,11 @@ func _process(delta: float) -> void:
current_face = CandyColor.values()[(current_face + 1) % CandyColor.size()]
# Sync color to the Mekton NPC
if mekton_node and mekton_node.has_method("set_face_color_rpc"):
mekton_node.rpc("set_face_color_rpc", current_face)
if mekton_node and is_instance_valid(mekton_node) and mekton_node.has_method("set_face_color_rpc"):
if multiplayer.is_server() and can_rpc():
mekton_node.rpc("set_face_color_rpc", current_face)
else:
mekton_node.set_face_color_rpc(current_face)
# Update delivery indicators for all players based on new face
for pid in player_candies:
@@ -399,7 +405,8 @@ func try_knock(attacker: int, target: int) -> bool:
var target_node = _find_player_node(target)
if attacker_node and target_node:
attacker_node.rpc("sync_bump", target_node.current_position)
if attacker_node.has_method("can_rpc") and attacker_node.can_rpc():
attacker_node.rpc("sync_bump", target_node.current_position)
if target_candies == 0:
# Backfire: attacker loses a charge & gets staggered
@@ -408,7 +415,10 @@ func try_knock(attacker: int, target: int) -> bool:
# Attacker is knocked down briefly (backfire)
if attacker_node:
attacker_node.rpc("apply_stagger", 2.0)
if attacker_node.has_method("can_rpc") and attacker_node.can_rpc():
attacker_node.rpc("apply_stagger", 2.0)
elif attacker_node.has_method("apply_stagger"):
attacker_node.apply_stagger(2.0)
# Backfire penalty: Attacker's candy stack transfers to Target!
var attacker_colors = player_candy_colors.get(attacker, [])
@@ -444,7 +454,10 @@ func try_knock(attacker: int, target: int) -> bool:
# Target is knocked down / staggered on successful knock
if target_node:
target_node.rpc("apply_stagger", 2.0)
if target_node.has_method("can_rpc") and target_node.can_rpc():
target_node.rpc("apply_stagger", 2.0)
elif target_node.has_method("apply_stagger"):
target_node.apply_stagger(2.0)
_add_score(attacker, target_candies * 100)
_update_candy_badge(attacker)
@@ -485,9 +498,38 @@ func try_activate_ghost(pid: int) -> bool:
func is_ghost_active(pid: int) -> bool:
return player_ghost_active.get(pid, false)
func can_rpc() -> bool:
if not multiplayer.has_multiplayer_peer() or multiplayer.multiplayer_peer.get_connection_status() != MultiplayerPeer.CONNECTION_CONNECTED:
return false
return true
# ── Arena ──
func _setup_arena() -> void:
if not gridmap:
return
if can_rpc():
rpc("sync_arena_setup")
_apply_arena_setup()
@rpc("authority", "call_remote", "reliable")
func sync_arena_setup() -> void:
print("[CandySurvival] Client: Syncing Arena Setup (18x18)...")
_apply_arena_setup()
func _spawn_mekton_npc(center: Vector2i) -> Node:
var path = "res://scenes/candy_survival_npc.tscn"
if ResourceLoader.exists(path):
var npc = load(path).instantiate()
npc.name = "CandySurvivalNpc"
npc.position = Vector3(center.x + 1, 0, center.y + 1)
add_child(npc, true)
if npc.has_method("set_face_color_rpc"):
npc.set_face_color_rpc(current_face)
return npc
return null
func _apply_arena_setup() -> void:
if not gridmap:
return
gridmap.set("columns", ARENA_COLS)
@@ -504,24 +546,10 @@ func _setup_arena() -> void:
gridmap.set_cell_item(Vector3i(pos.x, 2, pos.y), 14)
# Spawn Mekton NPC at center
if mekton_node:
if mekton_node and is_instance_valid(mekton_node):
mekton_node.queue_free()
mekton_node = _spawn_mekton_npc(NPC_CENTER)
print("[CandySurvival] Arena setup 18x18")
func _spawn_mekton_npc(center: Vector2i) -> Node:
var path = "res://scenes/candy_survival_npc.tscn"
if ResourceLoader.exists(path):
var npc = load(path).instantiate()
npc.position = Vector3(center.x + 1, 0, center.y + 1)
add_child(npc)
if npc.has_method("set_face_color_rpc"):
npc.set_face_color_rpc(current_face)
return npc
return null
func _apply_arena_setup() -> void:
pass
print("[CandySurvival] Arena setup applied 18x18")
func setup_mission_tiles() -> void:
if not gridmap:
@@ -697,9 +725,14 @@ func sync_ghost_active(pid: int, active_on: bool) -> void:
hud_ghost_label.text = "👻 Ghosts: %d" % player_ghosts[pid]
func sync_state_to_player(peer_id: int) -> void:
# Ensure the client gets the arena setup before state
if can_rpc():
rpc_id(peer_id, "sync_arena_setup")
# Sync Mekton's current face color
if mekton_node and mekton_node.has_method("set_face_color_rpc"):
mekton_node.rpc_id(peer_id, "set_face_color_rpc", current_face)
if mekton_node and is_instance_valid(mekton_node) and mekton_node.has_method("set_face_color_rpc"):
if can_rpc():
mekton_node.rpc_id(peer_id, "set_face_color_rpc", current_face)
# Sync Ghost inventory to make the HUD button visible on player load
_update_special_inventory_for_ghosts(peer_id)
+3
View File
@@ -309,6 +309,9 @@ func leave_room() -> void:
kick_all_clients.rpc()
# Important: Reset all lobby settings and player lists first
Engine.time_scale = 1.0
if get_node_or_null("/root/GoalsCycleManager"):
get_node("/root/GoalsCycleManager").reset()
reset()
_stop_lan_broadcast()