From 00c9e85527c55aeba406aea941a830697036eb38 Mon Sep 17 00:00:00 2001 From: adtpdn Date: Thu, 9 Jul 2026 16:38:29 +0800 Subject: [PATCH] Release 2.4.6: Add SafeNakamaMultiplayerBridge subclass to fix dictionary lookup crashes --- CHANGELOG_DRAFT.md | 3 +++ assets/data/version.json | 11 ++++++++- export_presets.cfg | 8 +++---- project.godot | 2 +- scripts/managers/auth_manager.gd | 4 +++- scripts/nakama_manager.gd | 4 +++- .../safe_nakama_multiplayer_bridge.gd | 23 +++++++++++++++++++ 7 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 scripts/services/safe_nakama_multiplayer_bridge.gd diff --git a/CHANGELOG_DRAFT.md b/CHANGELOG_DRAFT.md index a4e0fdf..c3e0027 100644 --- a/CHANGELOG_DRAFT.md +++ b/CHANGELOG_DRAFT.md @@ -1,5 +1,8 @@ ## [NEXT] +## [2.4.6] — 2026-07-09 +- Fixed NakamaMultiplayerBridge dictionary lookup crash when receiving match state before user presence is populated by creating a `SafeNakamaMultiplayerBridge` subclass. + ## [2.4.5] — 2026-07-09 - Fixed missing `player_goals` dictionary declaration in GoalsCycleManager. - Improved Candy Survival manager sync and LobbyManager initialization. diff --git a/assets/data/version.json b/assets/data/version.json index cd61029..712b729 100644 --- a/assets/data/version.json +++ b/assets/data/version.json @@ -1,7 +1,16 @@ { - "latest_version": "2.4.5", + "latest_version": "2.4.6", "minimum_app_version": "2.1.0", "releases": [ + { + "version": "2.4.6", + "date": "2026-07-09", + "pck_url": "https://git.klud.top/danchie/tekton/raw/branch/patches/patch.pck", + "pck_size": 0, + "changelog": [ + "Fixed NakamaMultiplayerBridge dictionary lookup crash when receiving match state before user presence is populated by creating a `SafeNakamaMultiplayerBridge` subclass." + ] + }, { "version": "2.4.5", "date": "2026-07-09", diff --git a/export_presets.cfg b/export_presets.cfg index ce40c46..a9f2381 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.5.exe" +export_path="build/windows/tekton_armageddon_v2.4.6.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.5.apk" +export_path="build/tekton-dash-armageddon-v.2.4.6.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.5.zip" +export_path="build/tekton_armageddon_v2.4.6.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.5.x86_64" +export_path="build/linux/tekton_armageddon_v2.4.6.x86_64" patches=PackedStringArray() patch_delta_encoding=false patch_delta_compression_level_zstd=19 diff --git a/project.godot b/project.godot index cf28783..380f281 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.5" +config/version="2.4.6" 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/scripts/managers/auth_manager.gd b/scripts/managers/auth_manager.gd index 1be36c4..c277440 100644 --- a/scripts/managers/auth_manager.gd +++ b/scripts/managers/auth_manager.gd @@ -2,6 +2,8 @@ extends Node ## AuthManager - Centralized authentication handling for Nakama ## Supports: Guest (device ID), Email/Password, Social (Google, Apple, Facebook) +const SafeNakamaMultiplayerBridge = preload("res://scripts/services/safe_nakama_multiplayer_bridge.gd") + # Signals signal auth_started signal auth_completed(success: bool, user_data: Dictionary) @@ -472,7 +474,7 @@ func _connect_socket() -> bool: return false # Initialize multiplayer bridge - NakamaManager.bridge = NakamaMultiplayerBridge.new(NakamaManager.socket) + NakamaManager.bridge = SafeNakamaMultiplayerBridge.new(NakamaManager.socket) NakamaManager.bridge.match_joined.connect(NakamaManager._on_bridge_match_joined) NakamaManager.bridge.match_join_error.connect(NakamaManager._on_bridge_match_join_error) multiplayer.set_multiplayer_peer(NakamaManager.bridge.multiplayer_peer) diff --git a/scripts/nakama_manager.gd b/scripts/nakama_manager.gd index 5a25f62..7b33c0d 100644 --- a/scripts/nakama_manager.gd +++ b/scripts/nakama_manager.gd @@ -1,5 +1,7 @@ extends Node +const SafeNakamaMultiplayerBridge = preload("res://scripts/services/safe_nakama_multiplayer_bridge.gd") + # Standard Nakama Configuration var nakama_server_key = OS.get_environment("NAKAMA_SERVER_KEY") if OS.has_environment("NAKAMA_SERVER_KEY") else ProjectSettings.get_setting("network/nakama/server_key", "defaultkey") var nakama_host = OS.get_environment("NAKAMA_HOST") if OS.has_environment("NAKAMA_HOST") else ProjectSettings.get_setting("network/nakama/host", "tektondash.vps.webdock.cloud") @@ -141,7 +143,7 @@ func connect_to_nakama_async(email: String = "", password: String = "") -> bool: bridge.leave() bridge = null - bridge = NakamaMultiplayerBridge.new(socket) + bridge = SafeNakamaMultiplayerBridge.new(socket) # Connect bridge signals bridge.match_joined.connect(_on_bridge_match_joined) diff --git a/scripts/services/safe_nakama_multiplayer_bridge.gd b/scripts/services/safe_nakama_multiplayer_bridge.gd new file mode 100644 index 0000000..374b4a2 --- /dev/null +++ b/scripts/services/safe_nakama_multiplayer_bridge.gd @@ -0,0 +1,23 @@ +class_name SafeNakamaMultiplayerBridge +extends NakamaMultiplayerBridge + +func _init(p_nakama_socket: NakamaSocket) -> void: + super._init(p_nakama_socket) + +func _map_id_to_session(peer_id: int, session_id: String) -> void: + _id_map[peer_id] = session_id + if not _users.has(session_id): + _users[session_id] = NakamaMultiplayerBridge.User.new(null) + _users[session_id].peer_id = peer_id + +func _on_multiplayer_peer_packet_generated(peer_id: int, buffer: PackedByteArray) -> void: + if match_state == MatchState.CONNECTED: + var target_presences = null + if peer_id > 0: + if not _id_map.has(peer_id) or not _users.has(_id_map[peer_id]) or _users[_id_map[peer_id]].presence == null: + push_error("Attempting to send RPC to unknown or uninitialized peer id: %s" % peer_id) + return + target_presences = [ _users[_id_map[peer_id]].presence ] + _nakama_socket.send_match_state_raw_async(_match_id, rpc_op_code, buffer, target_presences) + else: + push_error("RPC sent while the NakamaMultiplayerBridge isn't connected!")