From 4990ce3c892e8b8e3d645f089707d31766f8608a Mon Sep 17 00:00:00 2001 From: Yogi Wiguna Date: Wed, 25 Feb 2026 12:09:14 +0800 Subject: [PATCH] feat: Implement tile scarcity model for tile generation and power-up inventory UI for player interaction. --- scenes/main.gd | 56 +++++++++++++++++++---- scripts/managers/goals_cycle_manager.gd | 25 ++++++++-- scripts/managers/player_input_manager.gd | 26 +++++++---- scripts/managers/special_tiles_manager.gd | 7 ++- scripts/managers/touch_controls.gd | 8 ++-- scripts/models/scarcity_model.gd | 3 ++ scripts/ui/powerup_inventory_ui.gd | 27 +++++++---- 7 files changed, 116 insertions(+), 36 deletions(-) diff --git a/scenes/main.gd b/scenes/main.gd index d774206..f4c54c8 100644 --- a/scenes/main.gd +++ b/scenes/main.gd @@ -11,6 +11,7 @@ var screen_shake_manager var touch_controls var camera_context_manager var stop_n_go_manager +var stop_n_go_winner_id: int = -1 # Track who finished first in Stop n Go mode var obstacle_manager # Minimal local state @@ -598,8 +599,8 @@ func _start_game(): _assign_random_spawn_positions() # PRE-GAME COUNTDOWN (3s) - # Spawn static obstacles before countdown starts - if obstacle_manager: + # Spawn static obstacles before countdown starts (Stop n Go only) + 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) @@ -1625,8 +1626,17 @@ func _on_leaderboard_updated(sorted_scores: Array): var score = goals_cycle_manager.get_player_score(peer_id) if goals_cycle_manager else 0 sorted_players.append({"node": p, "score": score}) - # Sort by score descending - sorted_players.sort_custom(func(a, b): return a.score > b.score) + # Sort by score descending (with Stop n Go winner priority) + if LobbyManager.game_mode == "Stop n Go" and stop_n_go_winner_id != -1: + sorted_players.sort_custom(func(a, b): + var a_id = a.node.name.to_int() + var b_id = b.node.name.to_int() + if a_id == stop_n_go_winner_id: return true + if b_id == stop_n_go_winner_id: return false + return a.score > b.score + ) + else: + sorted_players.sort_custom(func(a, b): return a.score > b.score) # Assign rank for i in range(sorted_players.size()): @@ -1647,6 +1657,9 @@ func _on_global_timer_updated(time_remaining: float): @rpc("any_peer", "call_local", "reliable") func sync_game_end_stop_n_go(winner_id: int): print("[STOP n GO] Game ended! Winner: ", winner_id) + stop_n_go_winner_id = winner_id + if goals_cycle_manager: + goals_cycle_manager.stop_n_go_winner_id = winner_id var winner_name = "Player " + str(winner_id) var player_node = get_node_or_null(str(winner_id)) @@ -1736,14 +1749,23 @@ func _show_game_over_panel(): leaderboard_container.add_theme_constant_override("separation", 15) inner_vbox.add_child(leaderboard_container) - # Get final scores var player_scores = [] for p in get_tree().get_nodes_in_group("Players"): player_scores.append({ + "peer_id": p.name.to_int(), "name": p.display_name if not p.display_name.is_empty() else str(p.name), "score": goals_cycle_manager.get_player_score(p.name.to_int()) if goals_cycle_manager else 0 }) - player_scores.sort_custom(func(a, b): return a.score > b.score) + + # Custom Sort for Stop n Go: Winner always first + if LobbyManager.game_mode == "Stop n Go" and stop_n_go_winner_id != -1: + player_scores.sort_custom(func(a, b): + if a.peer_id == stop_n_go_winner_id: return true + if b.peer_id == stop_n_go_winner_id: return false + return a.score > b.score + ) + else: + player_scores.sort_custom(func(a, b): return a.score > b.score) # Display each player for i in range(min(player_scores.size(), 8)): @@ -1867,8 +1889,15 @@ func sync_leaderboard_data(player_data: Array): if not vbox: return - # Sort by score descending - player_data.sort_custom(func(a, b): return a.score > b.score) + # Sort by score descending (with Stop n Go winner priority) + if LobbyManager.game_mode == "Stop n Go" and stop_n_go_winner_id != -1: + player_data.sort_custom(func(a, b): + if a.peer_id == stop_n_go_winner_id: return true + if b.peer_id == stop_n_go_winner_id: return false + return a.score > b.score + ) + else: + player_data.sort_custom(func(a, b): return a.score > b.score) # Update entries _render_leaderboard_entries(player_data) @@ -1895,8 +1924,15 @@ func _update_leaderboard_display(): var score = goals_cycle_manager.get_player_score(peer_id) if goals_cycle_manager else 0 player_data.append({"peer_id": peer_id, "name": p.display_name if not p.display_name.is_empty() else str(p.name), "score": score}) - # Sort by score descending - player_data.sort_custom(func(a, b): return a.score > b.score) + # Sort by score descending (with Stop n Go winner priority) + if LobbyManager.game_mode == "Stop n Go" and stop_n_go_winner_id != -1: + player_data.sort_custom(func(a, b): + if a.peer_id == stop_n_go_winner_id: return true + if b.peer_id == stop_n_go_winner_id: return false + return a.score > b.score + ) + else: + player_data.sort_custom(func(a, b): return a.score > b.score) _render_leaderboard_entries(player_data) diff --git a/scripts/managers/goals_cycle_manager.gd b/scripts/managers/goals_cycle_manager.gd index 47ac422..23d05c6 100644 --- a/scripts/managers/goals_cycle_manager.gd +++ b/scripts/managers/goals_cycle_manager.gd @@ -19,6 +19,7 @@ var is_match_active: bool = false # Score tracking: peer_id -> score var player_scores: Dictionary = {} var player_goal_counts: Dictionary = {} # peer_id -> count +var stop_n_go_winner_id: int = -1 # Track winner for Stop n Go sorting # Reference to main scene var main_scene: Node = null @@ -299,8 +300,16 @@ func _update_leaderboard(): var sorted_scores = [] for peer_id in player_scores.keys(): sorted_scores.append({"peer_id": peer_id, "score": player_scores[peer_id]}) - - sorted_scores.sort_custom(func(a, b): return a.score > b.score) + # Custom Sort for Stop n Go + if stop_n_go_winner_id != -1: + sorted_scores.sort_custom(func(a, b): + if a.peer_id == stop_n_go_winner_id: return true + if b.peer_id == stop_n_go_winner_id: return false + return a.score > b.score + ) + else: + sorted_scores.sort_custom(func(a, b): return a.score > b.score) + emit_signal("leaderboard_updated", sorted_scores) # ============================================================================= @@ -432,7 +441,17 @@ func get_leaderboard() -> Array: var sorted_scores = [] for peer_id in player_scores.keys(): sorted_scores.append({"peer_id": peer_id, "score": player_scores[peer_id]}) - sorted_scores.sort_custom(func(a, b): return a.score > b.score) + + # Custom Sort for Stop n Go + if stop_n_go_winner_id != -1: + sorted_scores.sort_custom(func(a, b): + if a.peer_id == stop_n_go_winner_id: return true + if b.peer_id == stop_n_go_winner_id: return false + return a.score > b.score + ) + else: + sorted_scores.sort_custom(func(a, b): return a.score > b.score) + return sorted_scores func get_time_remaining() -> float: diff --git a/scripts/managers/player_input_manager.gd b/scripts/managers/player_input_manager.gd index 2b4497f..d80d6dd 100644 --- a/scripts/managers/player_input_manager.gd +++ b/scripts/managers/player_input_manager.gd @@ -89,14 +89,24 @@ func handle_unhandled_input(event): # --- Keyboard Shortcuts (Event-based) --- if event is InputEventKey and event.pressed and not event.echo: 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_KP_1, KEY_1, KEY_KP_2, KEY_2, KEY_KP_3, KEY_3, KEY_KP_4, KEY_4: + var is_sng = LobbyManager.game_mode == "Stop n Go" + match event.keycode: + KEY_KP_1, KEY_1: + player.activate_powerup(0) # FASTER_SPEED + KEY_KP_2, KEY_2: + if is_sng: + player.activate_powerup(1) # AREA_FREEZE (StopNGo) + else: + player.activate_powerup(2) # BLOCK_FLOOR (Free) + KEY_KP_3, KEY_3: + if is_sng: + player.activate_powerup(3) # INVISIBLE_MODE (StopNGo) + else: + player.activate_powerup(1) # AREA_FREEZE (Free) + KEY_KP_4, KEY_4: + if not is_sng: + player.activate_powerup(3) # INVISIBLE_MODE (Free) # KEY_R: # player.auto_put_item() KEY_Q: diff --git a/scripts/managers/special_tiles_manager.gd b/scripts/managers/special_tiles_manager.gd index 2f3b1bc..13b6fe6 100644 --- a/scripts/managers/special_tiles_manager.gd +++ b/scripts/managers/special_tiles_manager.gd @@ -461,8 +461,11 @@ func spawn_powerups_around(center: Vector2i, force_powerups: bool = true): if rng.randf() < 0.7: item_id = rng.randi_range(7, 10) else: - # 30% Chance for PowerUp (11-14) - item_id = rng.randi_range(11, 14) + # 30% Chance for PowerUp (Exclude Wall 13 only in Stop n Go) + if LobbyManager.game_mode == "Stop n Go": + item_id = [11, 12, 14].pick_random() + else: + item_id = rng.randi_range(11, 14) var cell = Vector3i(pos.x, 1, pos.y) diff --git a/scripts/managers/touch_controls.gd b/scripts/managers/touch_controls.gd index 8e2a512..b713d4a 100644 --- a/scripts/managers/touch_controls.gd +++ b/scripts/managers/touch_controls.gd @@ -245,11 +245,13 @@ func _ensure_shortcut_label(btn: Button, button_name: String): if btn.has_node("ShortcutLabel"): # Update Label content if it exists to match potential remapping var existing_lbl = btn.get_node("ShortcutLabel") + var is_sng = LobbyManager.game_mode == "Stop n Go" + match button_name: - "Grab": existing_lbl.text = "Space" + "Grab": existing_lbl.text = "Space" if is_sng else "" "Put": existing_lbl.text = "" - "AttackMode": existing_lbl.text = "Q" - "SpawnBoost": existing_lbl.text = "E" + "AttackMode": existing_lbl.text = "Q" if is_sng else "" + "SpawnBoost": existing_lbl.text = "E" if is_sng else "" return # Add Keyboard Shortcut Label diff --git a/scripts/models/scarcity_model.gd b/scripts/models/scarcity_model.gd index d840c0a..e2c08c1 100644 --- a/scripts/models/scarcity_model.gd +++ b/scripts/models/scarcity_model.gd @@ -50,7 +50,10 @@ static func get_tile_weights() -> Dictionary: weights[tile] = STANDARD_WEIGHT # Special tiles + var is_sng = LobbyManager.game_mode == "Stop n Go" for tile in SPECIAL_TILES: + if is_sng and tile == TILE_FREEZE: + continue # Hide Wall Block only in Stop n Go weights[tile] = special_weight return weights diff --git a/scripts/ui/powerup_inventory_ui.gd b/scripts/ui/powerup_inventory_ui.gd index ce13bff..6feaec2 100644 --- a/scripts/ui/powerup_inventory_ui.gd +++ b/scripts/ui/powerup_inventory_ui.gd @@ -29,7 +29,10 @@ func _ready(): # We use 0, 1, 2, 3 to match SpecialTilesManager.SpecialEffect enum _setup_btn(0, container.get_node_or_null("SpeedBtn")) _setup_btn(1, container.get_node_or_null("FreezeAreaBtn")) - _setup_btn(2, container.get_node_or_null("WallBtn")) + var wall_btn = container.get_node_or_null("WallBtn") + _setup_btn(2, wall_btn) + if wall_btn and LobbyManager.game_mode == "Stop n Go": + wall_btn.visible = false # Hide Wall Power-up only in Stop n Go _setup_btn(3, container.get_node_or_null("GhostBtn")) print("[PowerUpUI] UI Initialization Complete. Mapped %d buttons." % icon_containers.size()) @@ -103,16 +106,20 @@ func _setup_btn(effect_id: int, btn: Button): sc_lbl.add_theme_color_override("font_color", Color(0.9, 0.9, 0.9)) # Determine Label Text based on Effect ID - # 0: Speed -> 1 - # 2: Wall -> 2 - # 1: Freeze -> 3 - # 3: Ghost -> 4 var key_text = "" - match effect_id: - 0: key_text = "1" - 2: key_text = "2" - 1: key_text = "3" - 3: key_text = "4" + if LobbyManager.game_mode == "Stop n Go": + # Stop n Go Mapping: 1, 2, 3 (No Wall) + match effect_id: + 0: key_text = "1" + 1: key_text = "2" + 3: key_text = "3" + else: + # Free Mode Mapping: 1, 2, 3, 4 (Original) + match effect_id: + 0: key_text = "1" + 2: key_text = "2" + 1: key_text = "3" + 3: key_text = "4" sc_lbl.text = key_text btn.add_child(sc_lbl)