diff --git a/assets/models/meshes/hover.res b/assets/models/meshes/hover.res index fb44c39..79585cb 100644 Binary files a/assets/models/meshes/hover.res and b/assets/models/meshes/hover.res differ diff --git a/assets/models/meshes/normal.res b/assets/models/meshes/normal.res index 5318ae0..bf8d638 100644 Binary files a/assets/models/meshes/normal.res and b/assets/models/meshes/normal.res differ diff --git a/assets/models/meshes/tiles.res b/assets/models/meshes/tiles.res index 2809732..d710bb9 100644 Binary files a/assets/models/meshes/tiles.res and b/assets/models/meshes/tiles.res differ diff --git a/scenes/main.gd b/scenes/main.gd index 044b4ec..29d884b 100644 --- a/scenes/main.gd +++ b/scenes/main.gd @@ -633,9 +633,10 @@ func _start_game(): if obstacle_manager and LobbyManager.game_mode == "Stop n Go": obstacle_manager.spawn_random_obstacles(15) - # Spawn mission tiles BEFORE countdown but AFTER walls (Stop n Go only) + # Spawn mission and power-up tiles BEFORE countdown but AFTER walls (Stop n Go only) if LobbyManager.game_mode == "Stop n Go" and stop_n_go_manager: stop_n_go_manager.setup_mission_tiles() + stop_n_go_manager.spawn_initial_powerups() # Ensure power-ups exist before 1,2,3 Go # Spawn Static Tektons BEFORE countdown (Free Mode Only) # Exclude for Stop n Go and Tekton Doors diff --git a/scripts/managers/player_input_manager.gd b/scripts/managers/player_input_manager.gd index 4cab3f7..db2be04 100644 --- a/scripts/managers/player_input_manager.gd +++ b/scripts/managers/player_input_manager.gd @@ -84,18 +84,29 @@ func handle_unhandled_input(event): # --- Keyboard Shortcuts (Event-based) --- if event is InputEventKey and event.pressed and not event.echo: + var mode = LobbyManager.get_game_mode() + var is_sng = mode == GameMode.Mode.STOP_N_GO + match event.keycode: KEY_KP_1, KEY_1, KEY_KP_2, KEY_2, KEY_KP_3, KEY_3, KEY_KP_4, KEY_4: - # Unified Mapping for all modes: 1-Speed, 2-Wall, 3-Freeze, 4-Ghost - match event.keycode: - KEY_KP_1, KEY_1: - player.activate_powerup(0) # FASTER_SPEED - KEY_KP_2, KEY_2: - player.activate_powerup(2) # BLOCK_FLOOR - KEY_KP_3, KEY_3: - player.activate_powerup(1) # AREA_FREEZE - KEY_KP_4, KEY_4: - player.activate_powerup(3) # INVISIBLE_MODE + if is_sng: + # Stop n Go Mapping: 1-Speed, 2-Ghost + match event.keycode: + KEY_KP_1, KEY_1: + player.activate_powerup(0) # FASTER_SPEED + KEY_KP_2, KEY_2: + player.activate_powerup(3) # INVISIBLE_MODE + else: + # Unified Mapping for all modes: 1-Speed, 2-Wall, 3-Freeze, 4-Ghost + match event.keycode: + KEY_KP_1, KEY_1: + player.activate_powerup(0) # FASTER_SPEED + KEY_KP_2, KEY_2: + player.activate_powerup(2) # BLOCK_FLOOR + KEY_KP_3, KEY_3: + player.activate_powerup(1) # AREA_FREEZE + KEY_KP_4, KEY_4: + player.activate_powerup(3) # INVISIBLE_MODE # KEY_R: # player.auto_put_item() KEY_Q: @@ -117,7 +128,7 @@ func handle_unhandled_input(event): KEY_G: if not player.is_carrying_tekton and player.powerup_manager: if player.powerup_manager.can_use_special(): - player.grab_tekton() + player.grab_tekton() # Handle spawn point selection if not yet selected diff --git a/scripts/managers/playerboard_manager.gd b/scripts/managers/playerboard_manager.gd index 6c982de..179d870 100644 --- a/scripts/managers/playerboard_manager.gd +++ b/scripts/managers/playerboard_manager.gd @@ -307,8 +307,11 @@ func bot_try_grab_item() -> bool: special_tiles_manager.add_powerup_from_item(item) player.playerboard[empty_slot] = item - player.rpc("sync_grid_item", current_cell.x, current_cell.y, current_cell.z, -1) - player.rpc("sync_playerboard", player.playerboard) + + var main = player.get_tree().get_root().get_node_or_null("Main") + if main: + main.rpc("sync_grid_item", current_cell.x, current_cell.y, current_cell.z, -1) + main.rpc("sync_playerboard", player.name.to_int(), player.playerboard) player.has_performed_action = true player.action_points -= 1 _check_goal_completion() @@ -331,8 +334,11 @@ func bot_try_grab_item() -> bool: if empty_slot != -1: if player.is_multiplayer_authority(): player.playerboard[empty_slot] = item - player.rpc("sync_grid_item", cell.x, cell.y, cell.z, -1) - player.rpc("sync_playerboard", player.playerboard) + + var main = player.get_tree().get_root().get_node_or_null("Main") + if main: + main.rpc("sync_grid_item", cell.x, cell.y, cell.z, -1) + main.rpc("sync_playerboard", player.name.to_int(), player.playerboard) player.has_performed_action = true player.action_points -= 1 return true diff --git a/scripts/managers/stop_n_go_manager.gd b/scripts/managers/stop_n_go_manager.gd index 95fb87d..290fe8e 100644 --- a/scripts/managers/stop_n_go_manager.gd +++ b/scripts/managers/stop_n_go_manager.gd @@ -22,6 +22,16 @@ var safe_zone_spawned: bool = false # Power-Up Tile Spawning const POWERUP_TILES = [11, 14] # Speed (11) and Ghost (14) const POWERUP_SPAWN_COUNT: int = 5 # Number of power-up tiles to spawn +var powerups_spawned: bool = false +var stop_phase_occurred: bool = false + +const PERMANENT_POWERUP_LOCATIONS: Array[Vector2i] = [ + Vector2i(4, 3), # Area 1 + Vector2i(8, 7), # Area 2 + Vector2i(11, 4), # Area 3 + Vector2i(15, 8), # Area 4 + Vector2i(18, 5) # Area 5 +] var current_phase: Phase = Phase.GO var phase_timer: float = GO_DURATION @@ -231,6 +241,7 @@ func _start_phase(phase: Phase): rpc("sync_phase", phase_name, phase_timer) if phase == Phase.STOP: + stop_phase_occurred = true # --- DYNAMIC SAFE ZONE: Penalize players outside the zone --- if safe_zone_spawned: var all_players = get_tree().get_nodes_in_group("Players") @@ -238,7 +249,7 @@ func _start_phase(phase: Phase): if not _is_in_safe_zone(p.current_position): _scatter_player_tiles(p) - # --- POWER-UP TILES: Spawn 5 Speed & Ghost tiles --- + # Refresh power-ups every STOP phase _spawn_powerup_tiles() # If GO phase starts, clear all STOP phase freezes and safe zone @@ -323,6 +334,11 @@ func setup_mission_tiles(): if multiplayer.is_server(): _spawn_mission_tiles() +func spawn_initial_powerups(): + """Public wrapper to spawn powerups before game start.""" + if multiplayer.is_server(): + _spawn_powerup_tiles() + func _spawn_mission_tiles(): var gridmap = get_parent().get_node_or_null("EnhancedGridMap") if not gridmap: @@ -679,7 +695,7 @@ func sync_clear_safe_zone(centers_to_clear: Array): # ============================================================================= func _spawn_powerup_tiles(): - """Server: Spawn 5 Speed & Ghost power-up tiles at random walkable positions.""" + """Server: Spawn 5 permanent power-up tiles at fixed positions.""" if not multiplayer.is_server(): return var main = get_node_or_null("/root/Main") @@ -688,40 +704,24 @@ func _spawn_powerup_tiles(): var gridmap = main.get_node_or_null("EnhancedGridMap") if not gridmap: return - # Collect valid positions (walkable floor, no existing item on Floor 1) - var valid_positions: Array[Vector2i] = [] - for x in range(gridmap.columns): - for z in range(gridmap.rows): - var floor_tile = gridmap.get_cell_item(Vector3i(x, 0, z)) - # Skip void, obstacles, start, finish - if floor_tile == -1 or floor_tile == TILE_OBSTACLE: - continue - # Skip cells that already have items on Floor 1 - var existing_item = gridmap.get_cell_item(Vector3i(x, 1, z)) - if existing_item != -1: - continue - valid_positions.append(Vector2i(x, z)) + print("[StopNGo] Spawning/Refreshing 5 static power-up tiles...") - if valid_positions.is_empty(): - print("[StopNGo] WARNING: No valid positions for power-up tiles!") - return - - # Shuffle and pick up to POWERUP_SPAWN_COUNT positions - var rng = RandomNumberGenerator.new() - rng.randomize() - valid_positions.shuffle() - - var spawn_count = min(POWERUP_SPAWN_COUNT, valid_positions.size()) - - for i in range(spawn_count): - var pos = valid_positions[i] - var tile_id = POWERUP_TILES[rng.randi() % POWERUP_TILES.size()] + for i in range(PERMANENT_POWERUP_LOCATIONS.size()): + var pos = PERMANENT_POWERUP_LOCATIONS[i] + + # Set Floor 0 beneath power-up to ID 1 (Hover pattern) - Static PADS + gridmap.set_cell_item(Vector3i(pos.x, 0, pos.y), 1) + + # Cycle through the available power-up types + var tile_id = POWERUP_TILES[i % POWERUP_TILES.size()] # Place on Floor 1 gridmap.set_cell_item(Vector3i(pos.x, 1, pos.y), tile_id) - # Sync to all clients + # Sync both floor and tile to all clients and host if can_rpc(): - main.rpc("sync_grid_item", pos.x, 1, pos.y, tile_id) + main.rpc("sync_grid_item", pos.x, 0, pos.y, 1) # Sync floor change (Pad on) + main.rpc("sync_grid_item", pos.x, 1, pos.y, tile_id) # Sync power-up - print("[StopNGo] Spawned %d power-up tiles (Speed & Ghost)" % spawn_count) + powerups_spawned = true + print("[StopNGo] Static power-up refresh completed.") diff --git a/scripts/ui/powerup_inventory_ui.gd b/scripts/ui/powerup_inventory_ui.gd index 34b9c02..8fe4da0 100644 --- a/scripts/ui/powerup_inventory_ui.gd +++ b/scripts/ui/powerup_inventory_ui.gd @@ -40,9 +40,16 @@ func _ready(): _setup_btn(2, wall_btn) _setup_btn(3, ghost_btn) - # All skills available with new logic - if wall_btn: wall_btn.visible = true - if freeze_btn: freeze_btn.visible = true + # All skills available with new logic, but restricted in Stop n Go + if is_restricted: + if wall_btn: wall_btn.visible = false + if freeze_btn: freeze_btn.visible = false + # Re-setup shortcut labels for restricted mode + _update_shortcuts_for_mode(true) + else: + if wall_btn: wall_btn.visible = true + if freeze_btn: freeze_btn.visible = true + _update_shortcuts_for_mode(false) print("[PowerUpUI] UI Initialization Complete. Mapped %d buttons." % icon_containers.size()) @@ -91,39 +98,49 @@ func _setup_btn(effect_id: int, btn: Button): var sc_lbl = Label.new() sc_lbl.name = "ShortcutLabel" sc_lbl.mouse_filter = Control.MOUSE_FILTER_IGNORE - - # Position: Top Left of the button sc_lbl.horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT sc_lbl.vertical_alignment = VERTICAL_ALIGNMENT_TOP - - # Anchor to top left sc_lbl.set_anchors_preset(Control.PRESET_TOP_LEFT) - - # Offset to be close to the corner sc_lbl.offset_left = 0 - sc_lbl.offset_top = -5 # Close to the button top - + sc_lbl.offset_top = -5 sc_lbl.add_theme_font_size_override("font_size", 16) sc_lbl.add_theme_color_override("font_outline_color", Color.BLACK) sc_lbl.add_theme_constant_override("outline_size", 4) - # Add color override to make it distinct (optional, but good for visibility) sc_lbl.add_theme_color_override("font_color", Color(0.9, 0.9, 0.9)) - - # Determine Label Text based on Effect ID - var key_text = "" - # Consistent mapping: 1, 2, 3, 4 + btn.add_child(sc_lbl) + + _update_btn_shortcut(effect_id, btn) + + # Connect click + if not btn.pressed.is_connected(_on_btn_pressed): + btn.pressed.connect(_on_btn_pressed.bind(effect_id)) + +func _update_shortcuts_for_mode(is_restricted: bool): + for effect_id in icon_containers: + var btn = icon_containers[effect_id] + _update_btn_shortcut(effect_id, btn) + +func _update_btn_shortcut(effect_id: int, btn: Button): + var sc_lbl = btn.get_node_or_null("ShortcutLabel") + if not sc_lbl: return + + var mode = LobbyManager.get_game_mode() + var is_sng = mode == GameMode.Mode.STOP_N_GO + + var key_text = "" + if is_sng: + match effect_id: + 0: key_text = "1" # Speed + 3: key_text = "2" # Ghost + _: key_text = "" # Others hidden/disabled + else: match effect_id: 0: key_text = "1" # Speed 2: key_text = "2" # Wall 1: key_text = "3" # Freeze 3: key_text = "4" # Ghost - - sc_lbl.text = key_text - btn.add_child(sc_lbl) - - # Connect click - if not btn.pressed.is_connected(_on_btn_pressed): - btn.pressed.connect(_on_btn_pressed.bind(effect_id)) + + sc_lbl.text = key_text func _on_btn_pressed(effect_id: int): print("[PowerUpUI] Clicked Button %d" % effect_id)