Release 2.4.5: Candy Survival sync & GoalsCycleManager improvements
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
+4
-4
@@ -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
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user