From f060a2f4b816846c7f30d8a5cb7f639373569a36 Mon Sep 17 00:00:00 2001 From: adtpdn Date: Wed, 8 Jul 2026 17:40:01 +0800 Subject: [PATCH] fix(candy-survival): implement stagger (knockdown), lunge visual bump, and target stealing/backfire animations for Knock (Q) --- scripts/managers/candy_survival_manager.gd | 16 ++++++++++- scripts/managers/player_input_manager.gd | 31 +++++++++++++++------- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/scripts/managers/candy_survival_manager.gd b/scripts/managers/candy_survival_manager.gd index 3d3332e..6019d9d 100644 --- a/scripts/managers/candy_survival_manager.gd +++ b/scripts/managers/candy_survival_manager.gd @@ -395,11 +395,21 @@ func try_knock(attacker: int, target: int) -> bool: var target_colors = player_candy_colors.get(target, []) var target_candies = target_colors.size() + var attacker_node = main_scene.get_node_or_null(str(attacker)) + var target_node = main_scene.get_node_or_null(str(target)) + + if attacker_node and target_node: + attacker_node.rpc("sync_bump", target_node.current_position) + if target_candies == 0: # Backfire: both lose a charge player_knocks[attacker] = player_knocks.get(attacker, 0) - 1 rpc("sync_knock_charge_count", attacker, player_knocks[attacker]) - # Attacker is also knocked down briefly (visual handled by caller) + + # Attacker is also knocked down briefly (backfire) + if attacker_node: + attacker_node.rpc("apply_stagger", 2.0) + rpc("sync_knock_result", attacker, target, 0) return false @@ -416,6 +426,10 @@ func try_knock(attacker: int, target: int) -> bool: rpc("sync_knock_charge_count", attacker, player_knocks[attacker]) player_last_knocked_by[target] = attacker + # Target is knocked down / staggered on successful knock + if target_node: + target_node.rpc("apply_stagger", 2.0) + _add_score(attacker, target_candies * 100) _update_candy_badge(attacker) _update_candy_badge(target) diff --git a/scripts/managers/player_input_manager.gd b/scripts/managers/player_input_manager.gd index edc6845..7461aa0 100644 --- a/scripts/managers/player_input_manager.gd +++ b/scripts/managers/player_input_manager.gd @@ -119,22 +119,35 @@ func handle_unhandled_input(event): if LobbyManager.game_mode == "Candy Survival": var gm = player.get_node_or_null("/root/Main/CandySurvivalManager") if gm and gm.active: + var att_id = player.get("peer_id") if "peer_id" in player else player.name.to_int() + if gm.player_knocks.get(att_id, 0) <= 0: + NotificationManager.send_message(player, "No Knock charges left!", NotificationManager.MessageType.WARNING) + get_viewport().set_input_as_handled() + return + # Find a target to knock (first adjacent player) var p_pos = player.current_position var all_players = get_tree().get_nodes_in_group("Players") + var target_found = null for other in all_players: if other == player: continue var other_pos = other.current_position if abs(p_pos.x - other_pos.x) + abs(p_pos.y - other_pos.y) == 1: - # Adjacent - var att_id = player.get("peer_id") if "peer_id" in player else player.name.to_int() - var tgt_id = other.get("peer_id") if "peer_id" in other else other.name.to_int() - if multiplayer.is_server(): - gm.try_knock(att_id, tgt_id) - else: - gm.rpc_id(1, "try_knock", att_id, tgt_id) - get_viewport().set_input_as_handled() - return + target_found = other + break + + if target_found: + var tgt_id = target_found.get("peer_id") if "peer_id" in target_found else target_found.name.to_int() + if multiplayer.is_server(): + gm.try_knock(att_id, tgt_id) + else: + gm.rpc_id(1, "try_knock", att_id, tgt_id) + else: + # No target, but play the bump animation anyway in place + player.rpc("sync_bump", p_pos) + + get_viewport().set_input_as_handled() + return if player.powerup_manager: player.powerup_manager.use_special_effect()