diff --git a/addons/enhanced_gridmap/meshlibrary/default.tres b/addons/enhanced_gridmap/meshlibrary/default.tres index 20efe18..8f6e9d0 100644 --- a/addons/enhanced_gridmap/meshlibrary/default.tres +++ b/addons/enhanced_gridmap/meshlibrary/default.tres @@ -13,6 +13,7 @@ [ext_resource type="ArrayMesh" uid="uid://onkud44h3we1" path="res://assets/models/meshes/safe_zone.res" id="8_cg50n"] [ext_resource type="ArrayMesh" uid="uid://dx41n2x8v30r1" path="res://assets/models/meshes/crack.res" id="10_r32il"] [ext_resource type="Texture2D" uid="uid://dpkx1a780pvwv" path="res://assets/textures/tile_diamond.png" id="10_sx8rm"] +[ext_resource type="ArrayMesh" uid="uid://bj4qr20oxos13" path="res://assets/models/meshes/freeze_floor.res" id="11_pgnbl"] [sub_resource type="CompressedTexture2D" id="CompressedTexture2D_5d0gc"] load_path = "res://.godot/imported/tile_heart.png-deeef50755ca225f028608dfd16900e6.s3tc.ctex" @@ -116,7 +117,8 @@ item/4/mesh_cast_shadow = 1 item/4/shapes = [] item/4/navigation_mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0) item/4/navigation_layers = 1 -item/5/name = "disable" +item/5/name = "freeze_floor" +item/5/mesh = ExtResource("11_pgnbl") item/5/mesh_transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0) item/5/mesh_cast_shadow = 1 item/5/shapes = [] diff --git a/assets/models/meshes/freeze_floor.res b/assets/models/meshes/freeze_floor.res new file mode 100644 index 0000000..a343f04 Binary files /dev/null and b/assets/models/meshes/freeze_floor.res differ diff --git a/assets/models/tiles/tile_heart.tres b/assets/models/tiles/tile_heart.tres index 00fc1da..6cd1952 100644 --- a/assets/models/tiles/tile_heart.tres +++ b/assets/models/tiles/tile_heart.tres @@ -1,4 +1,4 @@ -[gd_resource type="ArrayMesh" load_steps=4 format=4 uid="uid://36tgon3b60db"] +[gd_resource type="ArrayMesh" format=4 uid="uid://36tgon3b60db"] [sub_resource type="CompressedTexture2D" id="CompressedTexture2D_5d0gc"] load_path = "res://.godot/imported/tile_heart.png-deeef50755ca225f028608dfd16900e6.s3tc.ctex" diff --git a/scenes/main.gd b/scenes/main.gd index 88e8125..e88bd41 100644 --- a/scenes/main.gd +++ b/scenes/main.gd @@ -57,6 +57,24 @@ func _ready(): # Ensure grid is randomized with Scarcity if server if multiplayer.is_server(): randomize_game_grid() + + # Setup global multiplayer spawners (Stands, etc.) + _setup_multiplayer_spawners() + +func _setup_multiplayer_spawners(): + # Setup MultiplayerSpawner for Static Tekton Stands + # Create a container node for strict pathing + if not has_node("Stands"): + var stands_container = Node3D.new() + stands_container.name = "Stands" + add_child(stands_container) + + if not has_node("StandSpawner"): + var stand_spawner = MultiplayerSpawner.new() + stand_spawner.name = "StandSpawner" + stand_spawner.spawn_path = NodePath("../Stands") # Relative to Spawner, finding sibling + stand_spawner.add_spawnable_scene("res://scenes/static_tekton_stand.tscn") + add_child(stand_spawner) @rpc("any_peer", "call_local", "reliable") func sync_portal_configs(configs: Array): @@ -70,18 +88,6 @@ func sync_portal_configs(configs: Array): var em = $EnhancedGridMap if em: em.cell_size = Vector3(1, 0.05, 1) - - # Setup MultiplayerSpawner for Static Tekton Stands - # Create a container node for strict pathing - var stands_container = Node3D.new() - stands_container.name = "Stands" - add_child(stands_container) - - var stand_spawner = MultiplayerSpawner.new() - stand_spawner.name = "StandSpawner" - stand_spawner.spawn_path = NodePath("../Stands") # Relative to Spawner, finding sibling - stand_spawner.add_spawnable_scene("res://scenes/static_tekton_stand.tscn") - add_child(stand_spawner) func _on_goal_count_updated(peer_id: int, count: int): # Only update for local player @@ -1588,8 +1594,30 @@ func sync_grid_item(x: int, y: int, z: int, item: int): if enhanced_gridmap.has_method("update_grid_data"): enhanced_gridmap.update_grid_data() - # Sync grid update (no need to sync whole grid if we do it at start, but if we do it late we might need to sync) - # For simplicity, we trust the grid syncs via normal mechanisms or initial state. +@rpc("any_peer", "call_local", "reliable") +func sync_grid_items_batch(data: Array): + # data is an array of dictionaries: [{x: int, y: int, z: int, item: int}, ...] + var enhanced_gridmap = $EnhancedGridMap + if not enhanced_gridmap: + return + + for entry in data: + var x = entry.get("x", 0) + var y = entry.get("y", 0) + var z = entry.get("z", 0) + var item = entry.get("item", -1) + + # WALL-SAFETY CHECK + if y == 1 and item >= 7 and item <= 20: + var f0 = enhanced_gridmap.get_cell_item(Vector3i(x, 0, z)) + if f0 == 4 or f0 == -1: + continue + + enhanced_gridmap.set_cell_item(Vector3i(x, y, z), item) + + # Force visual update ONCE after batch + if enhanced_gridmap.has_method("update_grid_data"): + enhanced_gridmap.update_grid_data() func randomize_game_grid(): if LobbyManager.game_mode == "Stop n Go" or LobbyManager.game_mode == "Tekton Doors": @@ -1762,7 +1790,7 @@ func sync_game_end_stop_n_go(winner_id: int): winner_name = player_node.display_name # Broadcast win (Validation already done in check_win_condition) - add_message_to_bar("MATCH COMPLETE", winner_name + " Wins with 3 Missions!", MessageType.GOAL) + add_message_to_bar("MATCH COMPLETE", winner_name + " Wins with 8 Missions!", MessageType.GOAL) # Stop logic if stop_n_go_manager: @@ -1800,10 +1828,42 @@ func _on_match_ended(): var local_player = GameStateManager.local_player_character if local_player: local_player.action_points = 0 + + # Signal Global Game End (Stops Bot ticks and logic) + GameStateManager.end_game() + + # Freeze all game actors (Players, Bots, Tektons) + _freeze_all_game_actors() # Show game over overlay _show_game_over_panel() +func _freeze_all_game_actors(): + """Manually stop all game entities from processing without pausing the UI.""" + print("[Main] Freezing all game actors recursively...") + + var groups = ["Players", "Bots", "Tektons", "StaticTektonStands"] + for group_name in groups: + var nodes = get_tree().get_nodes_in_group(group_name) + for node in nodes: + _freeze_node_recursive(node) + +func _freeze_node_recursive(node: Node): + """Recursively disable processing and stop tweens for a node and its children.""" + if node.has_method("set_physics_process"): + node.set_physics_process(false) + if node.has_method("set_process"): + node.set_process(false) + + # Kill movement tweens if it's a character + if "_movement_tween" in node and node._movement_tween: + node._movement_tween.kill() + node._movement_tween = null + + # Recursive call for all children + for child in node.get_children(): + _freeze_node_recursive(child) + func _show_game_over_panel(): """Create and display the game over panel with final leaderboard.""" # Check if panel already exists @@ -1818,6 +1878,9 @@ func _show_game_over_panel(): if stop_n_go_manager and stop_n_go_manager.hud_layer: stop_n_go_manager.hud_layer.hide() + + if portal_mode_manager and portal_mode_manager.hud_layer: + portal_mode_manager.hud_layer.hide() # Create game over panel var panel = PanelContainer.new() @@ -1829,7 +1892,7 @@ func _show_game_over_panel(): style.bg_color = Color(0.0, 0.0, 0.0, 0.85) panel.add_theme_stylebox_override("panel", style) - # Content container + # CONTENT VBOX var vbox = VBoxContainer.new() vbox.name = "VBox" vbox.set_anchors_preset(Control.PRESET_CENTER) @@ -2209,13 +2272,13 @@ func _toggle_pause_menu(): var pause_menu = get_node_or_null("PauseMenu") if pause_menu: pause_menu.visible = not pause_menu.visible - get_tree().paused = pause_menu.visible + # get_tree().paused = pause_menu.visible # Removed for multiplayer consistency func _on_resume_pressed(): var pause_menu = get_node_or_null("PauseMenu") if pause_menu: pause_menu.visible = false - get_tree().paused = false + # get_tree().paused = false # Removed for multiplayer consistency func _on_how_to_play_pressed(): var pause_menu = get_node_or_null("PauseMenu") @@ -2255,7 +2318,7 @@ func _on_settings_pressed(): opacity_slider.set_value_no_signal(touch_controls.button_opacity) func _on_quit_match_pressed(): - get_tree().paused = false + get_tree().paused = false # Ensure unpaused when returning to menu # Properly disconnect from Nakama match _cleanup_multiplayer() # Return to lobby or main menu diff --git a/scenes/main.tscn b/scenes/main.tscn index 589b555..cb0c8b9 100644 --- a/scenes/main.tscn +++ b/scenes/main.tscn @@ -9978,6 +9978,7 @@ horizontal_alignment = 1 [node name="TabContainer" type="TabContainer" parent="HowToPlayPanel/Panel/VBox" unique_id=123456794] layout_mode = 2 size_flags_vertical = 3 +current_tab = 0 [node name="Free Mode" type="MarginContainer" parent="HowToPlayPanel/Panel/VBox/TabContainer" unique_id=123456795] layout_mode = 2 @@ -9985,6 +9986,7 @@ theme_override_constants/margin_left = 10 theme_override_constants/margin_top = 10 theme_override_constants/margin_right = 10 theme_override_constants/margin_bottom = 10 +metadata/_tab_index = 0 [node name="RichTextLabel" type="RichTextLabel" parent="HowToPlayPanel/Panel/VBox/TabContainer/Free Mode" unique_id=123456796] layout_mode = 2 @@ -10003,6 +10005,7 @@ theme_override_constants/margin_left = 10 theme_override_constants/margin_top = 10 theme_override_constants/margin_right = 10 theme_override_constants/margin_bottom = 10 +metadata/_tab_index = 1 [node name="RichTextLabel" type="RichTextLabel" parent="HowToPlayPanel/Panel/VBox/TabContainer/Stop n Go" unique_id=123456798] layout_mode = 2 @@ -10013,7 +10016,7 @@ text = "[b]Stop n Go[/b] - Move forward when the phase is [color=green][b]GO[/b][/color] (Green). - Stop completely when the phase is [color=red][b]STOP[/b][/color] (Red). Moving during a red phase will reset you to the start! - Your objective is to reach the mission tiles at the far end of the arena and safely carry them back to your starting zone. -- The first player to complete 3 missions wins." +- The first player to complete 8 missions and reach the finish floor wins." [node name="Tekton Doors" type="MarginContainer" parent="HowToPlayPanel/Panel/VBox/TabContainer" unique_id=123456799] visible = false @@ -10022,6 +10025,7 @@ theme_override_constants/margin_left = 10 theme_override_constants/margin_top = 10 theme_override_constants/margin_right = 10 theme_override_constants/margin_bottom = 10 +metadata/_tab_index = 2 [node name="RichTextLabel" type="RichTextLabel" parent="HowToPlayPanel/Panel/VBox/TabContainer/Tekton Doors" unique_id=123456800] layout_mode = 2 @@ -10031,7 +10035,7 @@ text = "[b]Tekton Doors[/b] - Navigate a sprawling arena connected by color-coded portal doors. - Grab tiles and match goal patterns to earn mission completions. - Use doors to quickly teleport across rooms, but watch out for closures and traps. -- The first player to complete 8 missions wins." +- The first player to complete 8 missions and reach the finish room wins." [node name="Controls" type="MarginContainer" parent="HowToPlayPanel/Panel/VBox/TabContainer" unique_id=123456805] visible = false @@ -10040,6 +10044,7 @@ theme_override_constants/margin_left = 10 theme_override_constants/margin_top = 10 theme_override_constants/margin_right = 10 theme_override_constants/margin_bottom = 10 +metadata/_tab_index = 3 [node name="RichTextLabel" type="RichTextLabel" parent="HowToPlayPanel/Panel/VBox/TabContainer/Controls" unique_id=123456806] layout_mode = 2 @@ -10061,6 +10066,7 @@ theme_override_constants/margin_left = 10 theme_override_constants/margin_top = 10 theme_override_constants/margin_right = 10 theme_override_constants/margin_bottom = 10 +metadata/_tab_index = 4 [node name="RichTextLabel" type="RichTextLabel" parent="HowToPlayPanel/Panel/VBox/TabContainer/The Grid" unique_id=123456808] layout_mode = 2 @@ -10079,6 +10085,7 @@ theme_override_constants/margin_left = 10 theme_override_constants/margin_top = 10 theme_override_constants/margin_right = 10 theme_override_constants/margin_bottom = 10 +metadata/_tab_index = 5 [node name="RichTextLabel" type="RichTextLabel" parent="HowToPlayPanel/Panel/VBox/TabContainer/Tektons" unique_id=123456810] layout_mode = 2 @@ -10095,6 +10102,7 @@ theme_override_constants/margin_left = 10 theme_override_constants/margin_top = 10 theme_override_constants/margin_right = 10 theme_override_constants/margin_bottom = 10 +metadata/_tab_index = 6 [node name="RichTextLabel" type="RichTextLabel" parent="HowToPlayPanel/Panel/VBox/TabContainer/Skills" unique_id=123456812] layout_mode = 2 diff --git a/scripts/managers/portal_mode_manager.gd b/scripts/managers/portal_mode_manager.gd index 96e2039..e474102 100644 --- a/scripts/managers/portal_mode_manager.gd +++ b/scripts/managers/portal_mode_manager.gd @@ -301,7 +301,7 @@ func _spawn_portal_doors(): const PORTAL_COLORS = [ Color(0, 1, 1), # Cyan Color(1, 0, 1), # Magenta - Color(1, 1, 0), # Yellow + Color(1, 0, 0), # Red Color(0, 1, 0), # Green Color(1, 0.5, 0) # Orange ] diff --git a/scripts/managers/special_tiles_manager.gd b/scripts/managers/special_tiles_manager.gd index 5b2ee42..edeca88 100644 --- a/scripts/managers/special_tiles_manager.gd +++ b/scripts/managers/special_tiles_manager.gd @@ -328,37 +328,36 @@ func _execute_area_freeze(center_pos: Vector2i = Vector2i.ZERO): else: NotificationManager.send_message(player, "Hit %d Players!" % hit_count, NotificationManager.MessageType.GOAL) - # Visual Feedback (Turn Floor Blue - Item 12 on Layer 0) + # Visual Feedback (Overlay Layer 2) if player.is_multiplayer_authority(): - # Sync Icy Floor (Layer 0) - for x in range(-radius, radius + 1): - for y in range(-radius, radius + 1): - var pos = center_pos + Vector2i(x, y) - if enhanced_gridmap.is_position_valid(pos): - var main = player.get_tree().get_root().get_node_or_null("Main") - if main: - # CHECK: Don't overwrite Wall Block (Item 4) - var current_item = enhanced_gridmap.get_cell_item(Vector3i(pos.x, 0, pos.y)) - if current_item != 4: # 4 is Wall Block - # Use Item 12 (Blue Freeze Tile) on Layer 0 (Floor) - if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED: - main.rpc("sync_grid_item", pos.x, 0, pos.y, 12) - - # Cleanup visual timer (managed locally by author) - get_tree().create_timer(FREEZE_SLOW_DURATION).timeout.connect(func(): + var main = player.get_tree().get_root().get_node_or_null("Main") + if main and main.has_method("sync_grid_items_batch"): + var batch_data = [] for x in range(-radius, radius + 1): for y in range(-radius, radius + 1): var pos = center_pos + Vector2i(x, y) if enhanced_gridmap.is_position_valid(pos): - var main = player.get_tree().get_root().get_node_or_null("Main") - if main: - # CHECK: Only restore if it is STILL Ice (Item 12) - # This prevents removing a Wall that was placed AFTER the freeze started - var current_check = enhanced_gridmap.get_cell_item(Vector3i(pos.x, 0, pos.y)) - if current_check == 12: - # Restore to Item 0 (Standard Floor) - main.rpc("sync_grid_item", pos.x, 0, pos.y, 0) - ) + # Use Item 5 (Freeze Floor) on Layer 2 + batch_data.append({"x": pos.x, "y": 2, "z": pos.y, "item": 5}) + + if not batch_data.is_empty(): + main.rpc("sync_grid_items_batch", batch_data) + + # Cleanup visual timer (managed locally by author) + get_tree().create_timer(FREEZE_SLOW_DURATION).timeout.connect(func(): + var restore_batch = [] + for x in range(-radius, radius + 1): + for y in range(-radius, radius + 1): + var pos = center_pos + Vector2i(x, y) + if enhanced_gridmap.is_position_valid(pos): + # Check if it is STILL Freeze Overlay + var current_check = enhanced_gridmap.get_cell_item(Vector3i(pos.x, 2, pos.y)) + if current_check == 5: + restore_batch.append({"x": pos.x, "y": 2, "z": pos.y, "item": -1}) + + if not restore_batch.is_empty(): + main.rpc("sync_grid_items_batch", restore_batch) + ) func toggle_wall_orientation(): wall_orientation_horizontal = !wall_orientation_horizontal @@ -384,41 +383,34 @@ func _execute_block_floor(target_pos: Vector2i): neighbors.append({"position": Vector2i(col_x, z)}) print("Player %s activated Wall Block: VERTICAL COLUMN (X=%d)" % [player.name, col_x]) - for n in neighbors: - var pos = n.position - - # PHYSICS CHECK - if _is_position_blocked_by_stand(pos): - continue + if player.is_multiplayer_authority(): + var main = player.get_tree().get_root().get_node_or_null("Main") + if main and main.has_method("sync_grid_items_batch"): + var batch_data = [] + for n in neighbors: + var pos = n.position + if _is_position_blocked_by_stand(pos): continue + + var block_pos = Vector3i(pos.x, 0, pos.y) + var current_item = enhanced_gridmap.get_cell_item(block_pos) + + var is_immutable = false + if "immutable_items" in enhanced_gridmap: + if current_item in enhanced_gridmap.immutable_items: + is_immutable = true + if current_item == 4 or is_immutable: continue + + batch_data.append({"x": block_pos.x, "y": 0, "z": block_pos.z, "item": 4}) + + # Record for restoration + blocked_tiles.append({ + "position": block_pos, + "original_item": current_item, + "timer": BLOCK_DURATION + }) - var block_pos = Vector3i(pos.x, 0, pos.y) - - # Check current item first - var current_item = enhanced_gridmap.get_cell_item(block_pos) - - # Skip if already a wall or immutable - # We assume Item 4 is the wall/stand. - # Also check enhanced_gridmap.immutable_items if available - var is_immutable = false - if "immutable_items" in enhanced_gridmap: - if current_item in enhanced_gridmap.immutable_items: - is_immutable = true - - if current_item == 4 or is_immutable: - # Don't overwrite existing walls/stands, and don't schedule them for "restoration" (deletion) - continue - - if player.is_multiplayer_authority(): - var main = player.get_tree().get_root().get_node_or_null("Main") - if main and multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED: - main.rpc("sync_grid_item", block_pos.x, block_pos.y, block_pos.z, 4) - - # Record for restoration - blocked_tiles.append({ - "position": block_pos, - "original_item": current_item, # Restore the ACTUAL item that was there (e.g. ground 0 or maybe a dropped item?) - "timer": BLOCK_DURATION - }) + if not batch_data.is_empty(): + main.rpc("sync_grid_items_batch", batch_data) # Notify var all_players = player.get_tree().get_nodes_in_group("Players") @@ -471,6 +463,16 @@ func spawn_powerups_around(center: Vector2i, force_powerups: bool = true): var cell = Vector3i(pos.x, 1, pos.y) + # PREVENT SPAWNING ON FROZEN FLOORS (Visual/Lag Fix) + var is_frozen = false + if enhanced_gridmap: + # Check Layer 2 for Freeze Overlay (ID 5) + if enhanced_gridmap.get_cell_item(Vector3i(pos.x, 2, pos.y)) == 5: + is_frozen = true + + if is_frozen: + continue + if player.is_multiplayer_authority(): var main = player.get_tree().get_root().get_node_or_null("Main") if main and multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED: @@ -497,9 +499,9 @@ func _update_freeze_zones(delta: float): # If inside zone if dx <= zone.radius and dy <= zone.radius: - # Apply slow effect repeatedly - # We use a short duration so it expires quickly if they leave - p.rpc("apply_slow_effect", 0.5) + # Apply slow effect via RPC only IF not already slowed to prevent network flood + if "movement_manager" in p and p.movement_manager and p.movement_manager.speed_multiplier >= 1.0: + p.rpc("apply_slow_effect", 0.5) if zone.timer <= 0: zones_to_remove.append(i) @@ -523,7 +525,7 @@ func _check_for_icy_floor(): return var current_item = enhanced_gridmap.get_cell_item(Vector3i(player.current_position.x, 2, player.current_position.y)) - if current_item == 15: + if current_item == 5: # Freeze Floor _apply_slow_mo(player) elif player.movement_manager and player.movement_manager.speed_multiplier < 1.0: # Check if we should restore speed @@ -581,7 +583,7 @@ func _create_restore_speed_timer(target_player: Node3D, duration: float): var still_in_zone = false if enhanced_gridmap: var item = enhanced_gridmap.get_cell_item(Vector3i(target_player.current_position.x, 2, target_player.current_position.y)) - if item == 15: + if item == 5: # Freeze Floor still_in_zone = true if not still_in_zone: