From 152992398c66bb9d656fae9920f81ef35c70b662 Mon Sep 17 00:00:00 2001 From: Elrinth Date: Tue, 20 Jan 2026 03:08:24 +0100 Subject: [PATCH] fixed most of the sheisse --- src/scripts/game_world.gd | 90 ++++++++++++++++++++++------------ src/scripts/network_manager.gd | 21 ++++++++ 2 files changed, 80 insertions(+), 31 deletions(-) diff --git a/src/scripts/game_world.gd b/src/scripts/game_world.gd index 3a2ad68..d29742d 100644 --- a/src/scripts/game_world.gd +++ b/src/scripts/game_world.gd @@ -35,9 +35,9 @@ const MAX_BUFFER_SIZE: int = 2 * 1024 * 1024 # 2MB buffer threshold var last_buffer_check_time: float = 0.0 const BUFFER_CHECK_INTERVAL: float = 0.1 # Check buffer every 100ms var client_buffer_states: Dictionary = {} # peer_id -> {buffered: int, last_check: float, skip_until: float} -const CLIENT_BUFFER_CHECK_INTERVAL: float = 0.2 # Check client buffers every 200ms -const CLIENT_BUFFER_SKIP_THRESHOLD: int = 1024 * 512 # Skip sending if buffer > 512KB -const CLIENT_BUFFER_SKIP_DURATION: float = 2.0 # Skip sending for 2 seconds if buffer is full +const CLIENT_BUFFER_CHECK_INTERVAL: float = 0.1 # Check client buffers every 100ms (more frequent) +const CLIENT_BUFFER_SKIP_THRESHOLD: int = 1024 * 256 # Skip sending if buffer > 256KB (lower threshold to catch earlier) +const CLIENT_BUFFER_SKIP_DURATION: float = 3.0 # Skip sending for 3 seconds if buffer is full (longer duration) var fog_node: Node2D = null var cached_corridor_mask: PackedInt32Array = PackedInt32Array() var cached_corridor_rooms: Array = [] @@ -193,12 +193,18 @@ func _send_gameworld_ready(): if retry_count < 50: # Try up to 50 times (10 seconds total) set_meta("gameworld_ready_retry_count", retry_count + 1) LogManager.log("GameWorld: Host peer (1) not in peers and Matchbox not connected yet, retrying (" + str(retry_count + 1) + "/50)...", LogManager.CATEGORY_NETWORK) - get_tree().create_timer(0.2).timeout.connect(func(): _send_gameworld_ready()) + get_tree().create_timer(0.2).timeout.connect(func(): + if is_instance_valid(self) and is_inside_tree(): + _send_gameworld_ready() + ) return else: # After max retries, log warning and continue retrying (but less frequently) LogManager.log("GameWorld: Max retries reached for gameworld_ready, continuing with slower retries (every 1 second)...", LogManager.CATEGORY_NETWORK) - get_tree().create_timer(1.0).timeout.connect(func(): _send_gameworld_ready()) + get_tree().create_timer(1.0).timeout.connect(func(): + if is_instance_valid(self) and is_inside_tree(): + _send_gameworld_ready() + ) return # Peer is in list OR Matchbox connection is ready - try to send RPC @@ -328,7 +334,8 @@ func _schedule_gameworld_ready_check(peer_id: int, local_count: int, attempts: i return get_tree().create_timer(0.2).timeout.connect(func(): - _schedule_gameworld_ready_check(peer_id, local_count, attempts + 1) + if is_instance_valid(self) and is_inside_tree(): + _schedule_gameworld_ready_check(peer_id, local_count, attempts + 1) ) func _send_initial_client_sync(peer_id: int, local_count: int): @@ -629,6 +636,10 @@ func _rpc_node_to_ready_peers(node: Node, method: String, args: Array = []): if matchbox_conn_state == 3 or matchbox_conn_state == 4: # DISCONNECTED or FAILED client_gameworld_ready.erase(peer_id) continue + + # Check if client's buffer is full - skip sending if so (CRITICAL to prevent buffer overflow errors) + if _should_skip_client_due_to_buffer(peer_id): + continue # Skip sending to this client - their buffer is full # All checks passed, send RPC # Note: Even with all checks, there's still a tiny race condition window, @@ -852,7 +863,10 @@ func _notify_client_ready(peer_id: int): # Wait a bit longer to ensure the client has fully loaded the game scene and spawned all players if not dungeon_data.is_empty(): # Delay positioning to ensure client scene is fully loaded - get_tree().create_timer(0.5).timeout.connect(func(): _position_new_joiner_and_sync_positions(peer_id)) + get_tree().create_timer(0.5).timeout.connect(func(): + if is_instance_valid(self) and is_inside_tree(): + _position_new_joiner_and_sync_positions(peer_id) + ) # Notify all players that a client is ready (so server players can check if all are ready) _rpc_to_ready_peers("_client_ready_status_changed", [clients_ready.duplicate()]) @@ -1479,7 +1493,7 @@ func _check_tab_visibility(): if OS.get_name() == "Web": # Use DisplayServer to check if window is focused/visible # This is a simple check - on web, when tab is inactive, window loses focus - var is_visible = DisplayServer.window_get_mode() != DisplayServer.WINDOW_MODE_MINIMIZED + var window_visible = DisplayServer.window_get_mode() != DisplayServer.WINDOW_MODE_MINIMIZED # Also check if we can poll (if process is running, tab is likely active) # On web, when tab is inactive, _process may still run but less frequently # We'll track time between _process calls as a proxy for tab activity @@ -1495,7 +1509,7 @@ func _check_tab_visibility(): if time_since_last_process > 1000: return false - return is_visible + return window_visible return true # Always visible on non-web platforms func _process(_delta): @@ -1818,12 +1832,13 @@ func _update_fog_of_war(delta: float) -> void: cached_corridor_rooms = _get_rooms_connected_to_corridor(cached_corridor_mask, player_tile) cached_corridor_player_tile = player_tile - # Build a set of allowed room IDs for fast lookup + # Build a set of allowed room IDs for fast lookup cached_corridor_allowed_room_ids = {} for room in cached_corridor_rooms: - var room_id = str(room.x) + "," + str(room.y) + "," + str(room.w) + "," + str(room.h) + var room_id = str(room.x) + "," + str(room.y) + "," + str(room.w) + "," + str(room.h) cached_corridor_allowed_room_ids[room_id] = true + # Use the rebuilt data corridor_mask = cached_corridor_mask corridor_rooms = cached_corridor_rooms allowed_room_ids = cached_corridor_allowed_room_ids @@ -1853,21 +1868,21 @@ func _update_fog_of_war(delta: float) -> void: # OPTIMIZATION: Only do expensive per-tile checks when corridor state changed or player moved significantly var needs_tile_clear = corridor_state_changed or should_rebuild_corridor if needs_tile_clear: - for y in range(map_size.y): - for x in range(map_size.x): - var idx = x + y * map_size.x - if idx < 0 or idx >= combined_seen.size(): - continue + for y in range(map_size.y): + for x in range(map_size.x): + var idx = x + y * map_size.x + if idx < 0 or idx >= combined_seen.size(): + continue var tile_in_corridor = idx < corridor_mask.size() and corridor_mask[idx] == 1 - # Check if this tile is in a room, and if so, is it an allowed room? - var tile_room = _find_room_at_tile(Vector2i(x, y)) - var in_allowed_room = false - if not tile_room.is_empty(): - var room_id = str(tile_room.x) + "," + str(tile_room.y) + "," + str(tile_room.w) + "," + str(tile_room.h) - in_allowed_room = allowed_room_ids.has(room_id) - # Clear combined_seen for any tile not in corridor or allowed rooms + # Check if this tile is in a room, and if so, is it an allowed room? + var tile_room = _find_room_at_tile(Vector2i(x, y)) + var in_allowed_room = false + if not tile_room.is_empty(): + var room_id = str(tile_room.x) + "," + str(tile_room.y) + "," + str(tile_room.w) + "," + str(tile_room.h) + in_allowed_room = allowed_room_ids.has(room_id) + # Clear combined_seen for any tile not in corridor or allowed rooms if not tile_in_corridor and not in_allowed_room: - combined_seen[idx] = 0 + combined_seen[idx] = 0 # Update last corridor fog update time last_corridor_fog_update = Time.get_ticks_msec() / 1000.0 @@ -3766,6 +3781,12 @@ func _reassemble_dungeon_blob(): _update_camera() print("GameWorld: Client - Camera updated") + # Update fog of war to reveal area around player (CRITICAL for joiner to see the map) + await get_tree().process_frame + print("GameWorld: Client - Updating fog of war after player positioning...") + _update_fog_of_war(0.0) # Pass 0.0 for delta since we're in async context + print("GameWorld: Client - Fog of war updated") + # Load HUD print("=== GameWorld: Client - About to load HUD (call_deferred) ===") call_deferred("_load_hud") @@ -4323,15 +4344,18 @@ func _spawn_interactable_objects(): # Check broken_objects after object is fully spawned if broken_objects.has(i): print("GameWorld: Object at index ", i, " (name: ", obj.name, ") is marked as broken, breaking it now") - print("GameWorld: Object state - is_broken: ", obj.is_broken if "is_broken" in obj else "N/A", ", has _sync_break: ", obj.has_method("_sync_break")) - # Use timer to break after object is fully ready (wait a bit to ensure sprite is set up) - # Use call_deferred as well, but with a timer backup to ensure it happens + print("GameWorld: Object state - is_broken: ", obj.is_broken if "is_broken" in obj else "N/A", ", has _sync_break: ", obj.has_method("_sync_break"), ", is_destroyable: ", obj.is_destroyable if "is_destroyable" in obj else "N/A") + # Use both call_deferred and timer to ensure object is fully initialized var obj_ref = obj # Capture reference var obj_index = i # Capture index for logging - # Use timer to ensure object is fully initialized (wait 2 frames worth of time) - get_tree().create_timer(0.05).timeout.connect(func(): - if is_instance_valid(obj_ref): - _break_spawned_object(obj_ref, obj_index) + # First try with call_deferred (runs at end of frame) + call_deferred("_break_spawned_object", obj_ref, obj_index) + # Also use timer as backup (wait longer to ensure sprite and all components are ready) + get_tree().create_timer(0.1).timeout.connect(func(): + if is_instance_valid(obj_ref) and not obj_ref.is_queued_for_deletion(): + if "is_broken" in obj_ref and not obj_ref.is_broken: + print("GameWorld: Timer backup - breaking object at index ", obj_index, " (name: ", obj_ref.name, ")") + _break_spawned_object(obj_ref, obj_index) ) LogManager.log("GameWorld: Spawned " + str(objects.size()) + " interactable objects", LogManager.CATEGORY_DUNGEON) @@ -4347,6 +4371,10 @@ func _break_spawned_object(obj: Node, obj_index: int): if "is_broken" in obj and obj.is_broken: print("GameWorld: Object at index ", obj_index, " (name: ", obj.name, ") is already broken") return + # Check if object is destroyable (chests and pillars are not destroyable) + if "is_destroyable" in obj and not obj.is_destroyable: + print("GameWorld: Object at index ", obj_index, " (name: ", obj.name, ") is not destroyable, skipping break") + return if obj.has_method("_sync_break"): print("GameWorld: Breaking object at index ", obj_index, " (name: ", obj.name, ")") print("GameWorld: Object state before break - is_broken: ", obj.is_broken if "is_broken" in obj else "N/A", ", is_queued_for_deletion: ", obj.is_queued_for_deletion(), ", has sprite: ", "sprite" in obj and obj.sprite != null) diff --git a/src/scripts/network_manager.gd b/src/scripts/network_manager.gd index ed49923..4620dac 100644 --- a/src/scripts/network_manager.gd +++ b/src/scripts/network_manager.gd @@ -380,6 +380,12 @@ func _on_peer_disconnected(id: int): players_info.erase(id) player_disconnected.emit(id, player_info) + # If joiner and host (peer ID 1) disconnected, trigger server disconnection logic + if not is_hosting and id == 1: + log_print("NetworkManager: Host (peer ID 1) disconnected, triggering reconnection...") + _on_server_disconnected() + return + # Update room registry if hosting (player count changed) if is_hosting and room_registry and not room_id.is_empty(): var player_count = get_all_player_ids().size() @@ -745,6 +751,21 @@ func _attempt_reconnect(): log_print("NetworkManager: Cannot reconnect - is_hosting: " + str(is_hosting) + ", reconnection_room_id: " + reconnection_room_id) return + # Check if we're already connected to Matchbox (host might have reconnected and we're waiting for peer ID) + if matchbox_client and matchbox_client.has("is_network_connected") and matchbox_client.is_network_connected: + log_print("NetworkManager: Already connected to Matchbox, waiting for host to assign peer ID...") + # Cancel reconnection attempt - we're already connected, just waiting for host + reconnection_attempting = false + reconnection_timer = 0.0 + + # Show chat message + var game_world_waiting = get_tree().get_first_node_in_group("game_world") + if game_world_waiting: + var chat_ui = game_world_waiting.get_node_or_null("ChatUI") + if chat_ui and chat_ui.has_method("add_local_message"): + chat_ui.add_local_message("System", "Waiting for host to reconnect...") + return + log_print("NetworkManager: Attempting to reconnect to room: " + reconnection_room_id) # Show chat message for reconnection attempt