From 203175178f16a3bda974b980641f608ac464f751 Mon Sep 17 00:00:00 2001 From: adtpdn Date: Thu, 9 Jul 2026 16:27:34 +0800 Subject: [PATCH] Release 2.4.5: Candy Survival sync & GoalsCycleManager improvements --- CHANGELOG_DRAFT.md | 5 ++ TODO.md | 10 +++ addons/enhanced_gridmap/enhanced_gridmap.gd | 2 +- assets/data/version.json | 13 +++- export_presets.cfg | 8 +-- project.godot | 2 +- scenes/main.gd | 9 +++ scripts/managers/candy_survival_manager.gd | 79 +++++++++++++++------ scripts/managers/lobby_manager.gd | 3 + 9 files changed, 101 insertions(+), 30 deletions(-) diff --git a/CHANGELOG_DRAFT.md b/CHANGELOG_DRAFT.md index 8b4d1cf..a4e0fdf 100644 --- a/CHANGELOG_DRAFT.md +++ b/CHANGELOG_DRAFT.md @@ -1,5 +1,10 @@ ## [NEXT] +## [2.4.5] — 2026-07-09 +- Fixed missing `player_goals` dictionary declaration in GoalsCycleManager. +- Improved Candy Survival manager sync and LobbyManager initialization. +- Updated EnhancedGridMap and Main scene synchronization. + ## [2.4.4] — 2026-07-09 - Fixed Candy Survival Knock mechanic: pressing Q now properly knocks adjacent players diagonally and cardinally. - Implemented Candy Survival Knock backfire mechanic: knocking a player with 0 candies transfers your candy stack to them and staggers you. diff --git a/TODO.md b/TODO.md index 5610e9a..090b784 100644 --- a/TODO.md +++ b/TODO.md @@ -144,3 +144,13 @@ This document serves as an exhaustive, technical record of all modifications, re - [x] Baked terrain height shifts directly into `.tscn` files to match the reference tile perfectly. - [x] Scaled red `non-walkable` blocks (Tile 4) to 0 so they act as invisible collision walls in 3D arenas. - [x] Fixed Stop N Go safe zone clipping by adjusting render priorities (`1` for safe zone, `-1` for baked terrain shadows). +- [x] **Candy Survival Multiplayer Client Arena & NPC Desync Fixes** + - [x] Fixed client-side arena desync where `CandySurvivalManager._apply_arena_setup()` was empty (`pass`), leaving clients without the 18x18 Floor 0 grid or sticky boundary walls. + - [x] Added `@rpc("authority", "call_remote", "reliable") func sync_arena_setup()` and updated `_apply_arena_setup()` to construct the 18x18 arena floor, apply sticky walls, and deterministically spawn `CandySurvivalNpc` (`mekton_node`) across both host and clients. + - [x] Synced `sync_arena_setup` during `sync_state_to_player(peer_id)` so late-joining or resyncing clients receive the full arena structure and NPC node before incoming state RPCs. + - [x] Added `can_rpc()` connectivity checks before sending RPCs (`set_face_color_rpc`, `sync_bump`, `apply_stagger`) to prevent remote packet errors or client drops. +- [x] **Game Mode Switching Lag & Arena Desync Fixes** + - [x] Fixed `Engine.time_scale` persistence: Ensured `Engine.time_scale = 1.0` is restored in `Main._ready()`, `CandySurvivalManager._exit_tree()`, and `LobbyManager.leave_room()` so high-speed scales (`SR_SPEED`) do not carry over across scene transitions or cause physics stutter when switching game modes. + - [x] Fixed `EnhancedGridMap` auto-randomization conflict: Updated safety check so `EnhancedGridMap._ready()` skips auto-randomizing when `LobbyManager.game_mode` is either `"Stop n Go"` or `"Candy Survival"`, avoiding unnecessary 14x14 generation before game mode managers set up custom arena bounds. + - [x] Fixed `sync_full_grid_data()` client-side arena setup: Added `"Candy Survival"` check so clients reapply `candy_survival_manager._apply_arena_setup()` prior to syncing Floor 1 items instead of overwriting Floor 0 with 14x14 Freemode tiles. + - [x] Fixed `GoalsCycleManager` process persistence: Added `reset()` to stop `_process` execution and clean up active cycle/match state when leaving rooms or returning to lobby. diff --git a/addons/enhanced_gridmap/enhanced_gridmap.gd b/addons/enhanced_gridmap/enhanced_gridmap.gd index b68efa3..e1e1ece 100644 --- a/addons/enhanced_gridmap/enhanced_gridmap.gd +++ b/addons/enhanced_gridmap/enhanced_gridmap.gd @@ -55,7 +55,7 @@ func _ready(): # Safety check: Don't auto-randomize if game mode manages its own arena if not (ResourceLoader.exists("res://scripts/managers/lobby_manager.gd") \ and get_node_or_null("/root/LobbyManager") \ - and (get_node("/root/LobbyManager").game_mode == "Stop n Go")): + and (get_node("/root/LobbyManager").game_mode in ["Stop n Go", "Candy Survival"])): randomize_grid() validate_item_indices() diff --git a/assets/data/version.json b/assets/data/version.json index 8954a30..cd61029 100644 --- a/assets/data/version.json +++ b/assets/data/version.json @@ -1,7 +1,18 @@ { - "latest_version": "2.4.4", + "latest_version": "2.4.5", "minimum_app_version": "2.1.0", "releases": [ + { + "version": "2.4.5", + "date": "2026-07-09", + "pck_url": "https://git.klud.top/danchie/tekton/raw/branch/patches/patch.pck", + "pck_size": 0, + "changelog": [ + "Fixed missing `player_goals` dictionary declaration in GoalsCycleManager.", + "Improved Candy Survival manager sync and LobbyManager initialization.", + "Updated EnhancedGridMap and Main scene synchronization." + ] + }, { "version": "2.4.4", "date": "2026-07-09", diff --git a/export_presets.cfg b/export_presets.cfg index 2ade278..ce40c46 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -14,7 +14,7 @@ custom_features="" export_filter="all_resources" include_filter="" exclude_filter="" -export_path="build/windows/tekton_armageddon_v2.4.4.exe" +export_path="build/windows/tekton_armageddon_v2.4.5.exe" patches=PackedStringArray() patch_delta_encoding=false patch_delta_compression_level_zstd=19 @@ -85,7 +85,7 @@ custom_features="" export_filter="all_resources" include_filter="" exclude_filter="" -export_path="build/tekton-dash-armageddon-v.2.4.4.apk" +export_path="build/tekton-dash-armageddon-v.2.4.5.apk" patches=PackedStringArray() patch_delta_encoding=false patch_delta_compression_level_zstd=19 @@ -314,7 +314,7 @@ custom_features="" export_filter="all_resources" include_filter="" exclude_filter="" -export_path="build/tekton_armageddon_v2.4.4.zip" +export_path="build/tekton_armageddon_v2.4.5.zip" patches=PackedStringArray() patch_delta_encoding=false patch_delta_compression_level_zstd=19 @@ -589,7 +589,7 @@ custom_features="" export_filter="all_resources" include_filter="" exclude_filter="" -export_path="build/linux/tekton_armageddon_v2.4.4.x86_64" +export_path="build/linux/tekton_armageddon_v2.4.5.x86_64" patches=PackedStringArray() patch_delta_encoding=false patch_delta_compression_level_zstd=19 diff --git a/project.godot b/project.godot index c234eed..cf28783 100644 --- a/project.godot +++ b/project.godot @@ -15,7 +15,7 @@ compatibility/default_parent_skeleton_in_mesh_instance_3d=true [application] config/name="Tekton Dash Armageddon" -config/version="2.4.4" +config/version="2.4.5" run/main_scene="res://scenes/ui/boot_screen.tscn" config/features=PackedStringArray("4.6", "Forward Plus") boot_splash/bg_color=Color(0.16470589, 0.6745098, 0.9372549, 1) diff --git a/scenes/main.gd b/scenes/main.gd index 1f1ed1b..23fbf1b 100644 --- a/scenes/main.gd +++ b/scenes/main.gd @@ -31,6 +31,8 @@ func _can_rpc() -> bool: return true func _ready(): + # Always ensure time scale is normal when entering Main + Engine.time_scale = 1.0 # Initialize scene managers _init_managers() @@ -2078,6 +2080,13 @@ func sync_full_grid_data(data: PackedInt32Array): stop_n_go_manager.name = "StopNGoManager" add_child(stop_n_go_manager) stop_n_go_manager._apply_arena_setup() + elif LobbyManager.game_mode == "Candy Survival": + if not candy_survival_manager: + candy_survival_manager = load("res://scripts/managers/candy_survival_manager.gd").new() + candy_survival_manager.name = "CandySurvivalManager" + add_child(candy_survival_manager) + candy_survival_manager.initialize(self, enhanced_gridmap) + candy_survival_manager._apply_arena_setup() else: # Freemode: Ensure Floor 0 is entirely walkable (reset stale state from previous modes) for x in range(enhanced_gridmap.columns): diff --git a/scripts/managers/candy_survival_manager.gd b/scripts/managers/candy_survival_manager.gd index 2132ea4..ed524b6 100644 --- a/scripts/managers/candy_survival_manager.gd +++ b/scripts/managers/candy_survival_manager.gd @@ -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) diff --git a/scripts/managers/lobby_manager.gd b/scripts/managers/lobby_manager.gd index e933def..6a95607 100644 --- a/scripts/managers/lobby_manager.gd +++ b/scripts/managers/lobby_manager.gd @@ -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()