From 6949e20a1f3ae47bd693ea02988a92ad166201d7 Mon Sep 17 00:00:00 2001 From: adtpdn Date: Wed, 28 Jan 2026 02:29:43 +0800 Subject: [PATCH] refactor --- .vscode/settings.json | 2 +- addons/enhanced_gridmap/enhanced_gridmap.gd | 33 +++--- scenes/lobby.gd | 57 +++++++++ scenes/main.gd | 109 +++++++++++++++--- scripts/bot_controller.gd | 3 +- scripts/controllers/scarcity_controller.gd | 25 ++++ .../controllers/scarcity_controller.gd.uid | 1 + .../generators/modular_map_generator.gd.uid | 1 + scripts/managers/lobby_manager.gd | 26 +++++ scripts/managers/playerboard_manager.gd | 20 ++-- scripts/managers/scarcity_manager.gd | 51 -------- scripts/models/scarcity_model.gd | 56 +++++++++ scripts/models/scarcity_model.gd.uid | 1 + 13 files changed, 285 insertions(+), 100 deletions(-) create mode 100644 scripts/controllers/scarcity_controller.gd create mode 100644 scripts/controllers/scarcity_controller.gd.uid create mode 100644 scripts/generators/modular_map_generator.gd.uid delete mode 100644 scripts/managers/scarcity_manager.gd create mode 100644 scripts/models/scarcity_model.gd create mode 100644 scripts/models/scarcity_model.gd.uid diff --git a/.vscode/settings.json b/.vscode/settings.json index 90dcaee..ac1d800 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "godotTools.editorPath.godot4": "/home/beng/Documents/GodotProjects/Godot_v4.5.1-stable_linux.x86_64" + "godotTools.editorPath.godot4": "c:\\Users\\beng\\Godot\\Editors\\4.5.1-stable\\Godot_v4.5.1-stable_win64.exe" } \ No newline at end of file diff --git a/addons/enhanced_gridmap/enhanced_gridmap.gd b/addons/enhanced_gridmap/enhanced_gridmap.gd index 1c99efe..5829a0d 100644 --- a/addons/enhanced_gridmap/enhanced_gridmap.gd +++ b/addons/enhanced_gridmap/enhanced_gridmap.gd @@ -212,20 +212,20 @@ func fill_floor(item_index: int, floor_index: int): set_cell_item(cell_pos, item_index, current_orientation) # Randomization functions -func randomize_grid(floor_index: int = -1): +func randomize_grid(floor_index: int = -1, custom_rng_callable: Callable = Callable()): if floor_index == -1: # Default auto-randomize behavior: # Preserve Floor 0 (Ground) # Shuffle Floor 1 (Objects/Items) shuffle_floor_objects(1) else: - randomize_floor(floor_index) + randomize_floor(floor_index, custom_rng_callable) update_grid_data() initialize_astar() update_astar_costs() -func randomize_floor(floor_index: int): +func randomize_floor(floor_index: int, custom_rng_callable: Callable = Callable()): if not mesh_library: print("Error: No MeshLibrary assigned to GridMap") return @@ -235,10 +235,6 @@ func randomize_floor(floor_index: int): var rng = RandomNumberGenerator.new() rng.randomize() - var scarcity_mgr = null - if floor_index == 1: - scarcity_mgr = load("res://scripts/managers/scarcity_manager.gd") - for x in range(columns): for z in range(rows): # IMPORTANT: Only place items if Floor 0 has a walkable tile @@ -249,9 +245,9 @@ func randomize_floor(floor_index: int): set_cell_item(Vector3i(x, floor_index, z), -1) # Clear item if no ground continue - # If Floor 1 and ScarcityManager exists, use it - if floor_index == 1 and scarcity_mgr: - var item_index = scarcity_mgr.get_random_tile_id() + # Use custom callable if provided + if custom_rng_callable.is_valid(): + var item_index = custom_rng_callable.call() set_cell_item(Vector3i(x, floor_index, z), item_index) else: # Default behavior @@ -505,7 +501,7 @@ func initialize_astar(): update_astar_costs() -func find_path(start: Vector2, end: Vector2, floor_index: int = 0, clear_path_visual: bool = true) -> Array: +func find_path(start: Vector2, end: Vector2, floor_index: int = 0, clear_path_visual: bool = true, visualize: bool = true) -> Array: var astar = astar_by_floor.get(floor_index) if not astar: return [] @@ -514,14 +510,15 @@ func find_path(start: Vector2, end: Vector2, floor_index: int = 0, clear_path_vi var end_point = end.y * columns + end.x path = astar.get_point_path(start_point, end_point) - if clear_path_visual: - clear_path_visualization(floor_index) + if visualize: + if clear_path_visual: + clear_path_visualization(floor_index) - set_cell_item(Vector3i(start.x, floor_index, start.y), start_item) - set_cell_item(Vector3i(end.x, floor_index, end.y), end_item) - for point in path: - if point != start and point != end: - set_cell_item(Vector3i(point.x, floor_index, point.y), hover_item) + set_cell_item(Vector3i(start.x, floor_index, start.y), start_item) + set_cell_item(Vector3i(end.x, floor_index, end.y), end_item) + for point in path: + if point != start and point != end: + set_cell_item(Vector3i(point.x, floor_index, point.y), hover_item) return path diff --git a/scenes/lobby.gd b/scenes/lobby.gd index 2ac0da1..0320c5d 100644 --- a/scenes/lobby.gd +++ b/scenes/lobby.gd @@ -64,6 +64,10 @@ var admin_panel_instance: Control # Store current match ID for copy function var current_match_id: String = "" +# Scarcity Controls (Injected via code since we can't edit scene) +var scarcity_option: OptionButton +var scarcity_label: Label + func _ready(): # Check if user is authenticated if not AuthManager.is_logged_in(): @@ -73,6 +77,9 @@ func _ready(): # Load character textures _load_character_textures() + # Inject Scarcity UI + call_deferred("_setup_scarcity_ui") + # Get player slot references _setup_player_slots() @@ -135,6 +142,7 @@ func _ready(): LobbyManager.enable_cycle_timer_changed.connect(_on_enable_cycle_timer_changed) LobbyManager.character_changed.connect(_on_character_changed) LobbyManager.area_changed.connect(_on_area_changed) + LobbyManager.scarcity_mode_changed.connect(_on_scarcity_mode_changed) LobbyManager.player_list_changed.connect(_update_player_slots) # Connect NakamaManager signals @@ -158,6 +166,35 @@ func _load_character_textures() -> void: else: print("[Lobby] Character texture not found: ", tex_path) +func _setup_scarcity_ui() -> void: + """Inject scarcity controls into SettingsSection.""" + if not duration_option: return + + var settings_section = duration_option.get_parent() + if not settings_section: return + + # Create OptionButton for Host + scarcity_option = OptionButton.new() + scarcity_option.add_item("Normal") # ID 0 + scarcity_option.add_item("Aggressive") # ID 1 + scarcity_option.add_item("Chaos") # ID 2 + scarcity_option.selected = 0 + scarcity_option.tooltip_text = "Select Scarcity Mode" + scarcity_option.name = "ScarcityOption" + scarcity_option.item_selected.connect(_on_scarcity_selected) + + # Create Label for Clients + scarcity_label = Label.new() + scarcity_label.text = "Normal" + scarcity_label.name = "ScarcityLabel" + # Copy style from duration_text_label if possible, usually fine default + + # Add to scene + settings_section.add_child(scarcity_option) + settings_section.add_child(scarcity_label) + + # Initial visibility update will handle showing correct one + func _setup_player_slots() -> void: """Get references to all player slot nodes.""" player_slots.clear() @@ -279,6 +316,21 @@ func _on_random_spawn_toggled(toggled_on): func _on_enable_timer_toggled(toggled_on): LobbyManager.set_enable_cycle_timer(toggled_on) +func _on_scarcity_selected(index: int) -> void: + if not LobbyManager.is_host: return + var mode = scarcity_option.get_item_text(index) + LobbyManager.set_scarcity_mode(mode) + +func _on_scarcity_mode_changed(mode: String) -> void: + if scarcity_option: + for i in range(scarcity_option.item_count): + if scarcity_option.get_item_text(i) == mode: + scarcity_option.selected = i + break + if scarcity_label: + scarcity_label.text = mode + + func _update_random_spawn_label(enabled: bool) -> void: if random_spawn_label: random_spawn_label.text = "Random ✓" if enabled else "Random ✗" @@ -345,6 +397,11 @@ func _on_room_joined(room_data: Dictionary) -> void: _on_randomize_spawn_changed(LobbyManager.randomize_spawn) _on_enable_cycle_timer_changed(LobbyManager.enable_cycle_timer) + # Scarcity Update + if scarcity_option: scarcity_option.visible = is_host + if scarcity_label: scarcity_label.visible = not is_host + _on_scarcity_mode_changed(LobbyManager.scarcity_mode) + # Area selector: only host can interact area_left_btn.disabled = not is_host area_right_btn.disabled = not is_host diff --git a/scenes/main.gd b/scenes/main.gd index 867b892..fa24d62 100644 --- a/scenes/main.gd +++ b/scenes/main.gd @@ -39,6 +39,61 @@ func _ready(): await get_tree().process_frame _auto_start_from_lobby() + # Ensure grid is randomized with Scarcity if server + if multiplayer.is_server(): + randomize_game_grid() + _setup_tile_respawn_timer() + +func _setup_tile_respawn_timer(): + # Configure respawn rate based on Scarcity Mode from Lobby + var mode = LobbyManager.get_scarcity_mode() + var interval = 0.0 + + match mode: + "Aggressive": interval = 5.0 + "Chaos": interval = 1.0 # Very fast! + "Normal": interval = 0.0 # No periodic respawn (only refill when empty) + _: interval = 0.0 + + if interval > 0: + var timer = Timer.new() + timer.name = "TileRespawnTimer" + timer.wait_time = interval + timer.autostart = true + timer.one_shot = false + timer.timeout.connect(_on_tile_respawn_timeout) + add_child(timer) + print("[Main] TileRespawnTimer started with interval: %.1f (Mode: %s)" % [interval, mode]) + +func _on_tile_respawn_timeout(): + if not multiplayer.is_server(): return + + var enhanced_gridmap = $EnhancedGridMap + if not enhanced_gridmap: return + + # Try to find an empty spot (max 10 retries) + for i in range(10): + var x = randi() % enhanced_gridmap.columns + var z = randi() % enhanced_gridmap.rows + var cell = Vector3i(x, 1, z) + + if enhanced_gridmap.get_cell_item(cell) == -1: + # Check floor 0 + if enhanced_gridmap.get_cell_item(Vector3i(x, 0, z)) != -1: + # Check player occupancy + var occupied = false + for player in get_tree().get_nodes_in_group("Players"): + if Vector2i(player.current_position.x, player.current_position.y) == Vector2i(x, z): + occupied = true + break + + if not occupied: + # Spawn! + var new_item = ScarcityController.get_random_tile_id() + sync_grid_item(x, 1, z, new_item) + rpc("sync_grid_item", x, 1, z, new_item) + return # Spawned one, done for this tick + func _init_managers(): # Create and attach scene managers ui_manager = load("res://scripts/managers/ui_manager.gd").new() @@ -148,10 +203,11 @@ func add_message_to_bar(player_name: String, message: String, type: int = Messag # Powerup gets extra pulse effect if type == MessageType.POWERUP: await entrance_tween.finished - var pulse_tween = create_tween() - pulse_tween.set_loops(2) - pulse_tween.tween_property(label, "scale", Vector2(1.1, 1.1), 0.15).set_trans(Tween.TRANS_SINE) - pulse_tween.tween_property(label, "scale", Vector2(1.0, 1.0), 0.15).set_trans(Tween.TRANS_SINE) + if is_instance_valid(label): + var pulse_tween = create_tween() + pulse_tween.set_loops(2) + pulse_tween.tween_property(label, "scale", Vector2(1.1, 1.1), 0.15).set_trans(Tween.TRANS_SINE) + pulse_tween.tween_property(label, "scale", Vector2(1.0, 1.0), 0.15).set_trans(Tween.TRANS_SINE) # Remove oldest messages if over limit while message_container.get_child_count() > MAX_MESSAGES: @@ -913,24 +969,28 @@ func randomize_item_at_position(grid_position: Vector2i): var current_item = enhanced_gridmap.get_cell_item(cell) # If current item exists, replace it (scarcity aware) - if current_item != -1: - var scarcity_mgr = load("res://scripts/managers/scarcity_manager.gd") + # If current item exists OR we are forcing a spawn on valid ground + var floor_0_item = enhanced_gridmap.get_cell_item(Vector3i(grid_position.x, 0, grid_position.y)) + var is_ground = (floor_0_item != -1) # Simple check, or check specific ground items + + # Prevent stacking on players + if is_ground: + for player in get_tree().get_nodes_in_group("Players"): + if Vector2i(player.current_position.x, player.current_position.y) == grid_position: + is_ground = false + break + + if is_ground: var new_item = 7 + # Use ScarcityController + new_item = ScarcityController.get_random_tile_id() - if scarcity_mgr: - new_item = scarcity_mgr.get_random_tile_id() - # Try to ensure it changes, but respect scarcity + # If we are replacing an existing item, try to ensure it changes + if current_item != -1: var max_retries = 3 while new_item == current_item and max_retries > 0: - new_item = scarcity_mgr.get_random_tile_id() + new_item = ScarcityController.get_random_tile_id() max_retries -= 1 - else: - # Fallback - var rng = RandomNumberGenerator.new() - rng.randomize() - new_item = rng.randi_range(7, 10) - while new_item == current_item: - new_item = rng.randi_range(7, 10) sync_grid_item(cell.x, cell.y, cell.z, new_item) rpc("sync_grid_item", cell.x, cell.y, cell.z, new_item) @@ -946,6 +1006,21 @@ func sync_grid_item(x: int, y: int, z: int, item: int): if enhanced_gridmap: enhanced_gridmap.set_cell_item(Vector3i(x, y, z), item) + # 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. + +func randomize_game_grid(): + var enhanced_gridmap = $EnhancedGridMap + if enhanced_gridmap: + # Randomize Floor 1 using ScarcityController + enhanced_gridmap.randomize_floor(1, ScarcityController.get_random_tile_id) + + # Sync to clients if needed (usually handled by initial state sync or explicit item syncs) + # Since Main.gd doesn't have a "Sync Floor" RPC, we rely on clients running the same seed or syncing individual cells. + # For now, let's assume server authority + sync on connect handles it, or add sync loop if critical. + pass + + # ============================================================================= # Goals Cycle & Leaderboard UI # ============================================================================= diff --git a/scripts/bot_controller.gd b/scripts/bot_controller.gd index e470ee6..2a36840 100644 --- a/scripts/bot_controller.gd +++ b/scripts/bot_controller.gd @@ -307,7 +307,8 @@ func _try_move() -> bool: Vector2(actor.current_position), Vector2(final_target), 0, - false + false, + false # Disable visualization ) var next_step = Vector2i(-1, -1) diff --git a/scripts/controllers/scarcity_controller.gd b/scripts/controllers/scarcity_controller.gd new file mode 100644 index 0000000..85e42fd --- /dev/null +++ b/scripts/controllers/scarcity_controller.gd @@ -0,0 +1,25 @@ +class_name ScarcityController +extends RefCounted + +# ScarcityController - Logic for tile generation based on ScarcityModel + +static func get_random_tile_id() -> int: + var rng = RandomNumberGenerator.new() + rng.randomize() + + var weights = ScarcityModel.get_tile_weights() + var total_weight = 0 + + # Calculate total weight + for tile in weights: + total_weight += weights[tile] + + var roll = rng.randi_range(0, total_weight - 1) + var current = 0 + + for tile in weights: + current += weights[tile] + if roll < current: + return tile + + return ScarcityModel.TILE_HEART # Fallback diff --git a/scripts/controllers/scarcity_controller.gd.uid b/scripts/controllers/scarcity_controller.gd.uid new file mode 100644 index 0000000..4856913 --- /dev/null +++ b/scripts/controllers/scarcity_controller.gd.uid @@ -0,0 +1 @@ +uid://4ocyvmq6fbmx diff --git a/scripts/generators/modular_map_generator.gd.uid b/scripts/generators/modular_map_generator.gd.uid new file mode 100644 index 0000000..181ecd7 --- /dev/null +++ b/scripts/generators/modular_map_generator.gd.uid @@ -0,0 +1 @@ +uid://cray2jslf200d diff --git a/scripts/managers/lobby_manager.gd b/scripts/managers/lobby_manager.gd index 6abb8d6..63bf189 100644 --- a/scripts/managers/lobby_manager.gd +++ b/scripts/managers/lobby_manager.gd @@ -34,6 +34,10 @@ var randomize_spawn: bool = true # Default enabled var enable_cycle_timer: bool = false # Default disabled signal enable_cycle_timer_changed(enabled: bool) +# Scarcity setting +var scarcity_mode: String = "Normal" # Normal, Aggressive, Chaos +signal scarcity_mode_changed(mode: String) + # Character and area selection var available_characters: Array[String] = ["Bob", "Gatot", "Masbro", "Oldpop"] var available_areas: Array[String] = ["Desert", "Forest", "City", "Factory"] @@ -203,6 +207,26 @@ func sync_enable_cycle_timer(enabled: bool) -> void: func get_enable_cycle_timer() -> bool: return enable_cycle_timer +# ============================================================================= +# Scarcity Mode +# ============================================================================= + +func set_scarcity_mode(mode: String) -> void: + """Host sets scarcity mode. Syncs to clients.""" + scarcity_mode = mode + if is_host: + rpc("sync_scarcity_mode", mode) + +@rpc("authority", "call_local", "reliable") +func sync_scarcity_mode(mode: String) -> void: + scarcity_mode = mode + # Update ScarcityModel immediately + ScarcityModel.set_mode(mode) + emit_signal("scarcity_mode_changed", mode) + +func get_scarcity_mode() -> String: + return scarcity_mode + # ============================================================================= # Character Selection # ============================================================================= @@ -297,6 +321,8 @@ func start_game() -> void: rpc("sync_match_duration", match_duration) # Sync timer setting rpc("sync_enable_cycle_timer", enable_cycle_timer) + # Sync scarcity mode + rpc("sync_scarcity_mode", scarcity_mode) # Notify all clients to start rpc("_on_game_starting") diff --git a/scripts/managers/playerboard_manager.gd b/scripts/managers/playerboard_manager.gd index 59d8bf8..55b1c3c 100644 --- a/scripts/managers/playerboard_manager.gd +++ b/scripts/managers/playerboard_manager.gd @@ -170,22 +170,18 @@ func _check_and_refill_grid_if_needed(server_gridmap: Node): var has_items = false var item_list = server_gridmap.mesh_library.get_item_list() if server_gridmap.mesh_library else [] - # We can't efficiently iterate all cells, but we can check usage via get_used_cells_by_item is too specific - # Iterating columns/rows is safe for this map size (10x10 usually) - for x in range(server_gridmap.columns): - for z in range(server_gridmap.rows): - var item = server_gridmap.get_cell_item(Vector3i(x, 1, z)) - if item != -1: - has_items = true - break - if has_items: + # Iterate used cells to check for ANY item on floor 1 + var used_cells = server_gridmap.get_used_cells() + for cell in used_cells: + if cell.y == 1: # Floor 1 + has_items = true break if not has_items: print("[PlayerboardManager] Floor 1 empty! Respawning tiles with Scarcity...") - # Call randomize_floor on floor 1 - # Since Main owns gridmap, we can sync this or just do it server side (which we are) - server_gridmap.randomize_floor(1) + # Call randomize_floor on floor 1 using ScarcityController + # ScarcityController is a global class, so we can pass its static function as a Callable + server_gridmap.randomize_floor(1, ScarcityController.get_random_tile_id) # We need to sync the ENTIRE floor to clients. # EnhancedGridMap doesn't have a "Sync Floor" RPC built-in to Main, only single cells or array update. diff --git a/scripts/managers/scarcity_manager.gd b/scripts/managers/scarcity_manager.gd deleted file mode 100644 index 687cd74..0000000 --- a/scripts/managers/scarcity_manager.gd +++ /dev/null @@ -1,51 +0,0 @@ -extends Node -class_name ScarcityManager - -# ScarcityManager - Handles weighted random, tile generation for gameplay balance - -const TILES = [7, 8, 9, 10] -const SPECIAL_TILES = [11, 12, 13, 14] - -# Weights (higher = more common) -# Standard tiles: Heart, Diamond, Star, Coin -const TILE_WEIGHTS = { - 7: 100, # Heart - 8: 100, # Diamond - 9: 100, # Star - 10: 100 # Coin -} - -# Special tiles: Burn, Spawn, Freeze, Block, Invisible -const SPECIAL_WEIGHTS = { - 11: 5, - 12: 5, - 13: 5, - 14: 5 -} - -static func get_random_tile_id() -> int: - var rng = RandomNumberGenerator.new() - rng.randomize() - - var total_weight = 0 - var pool = {} - - # Add standard tiles - for id in TILE_WEIGHTS: - pool[id] = TILE_WEIGHTS[id] - total_weight += TILE_WEIGHTS[id] - - # Add special tiles - for id in SPECIAL_WEIGHTS: - pool[id] = SPECIAL_WEIGHTS[id] - total_weight += SPECIAL_WEIGHTS[id] - - var roll = rng.randi_range(0, total_weight - 1) - var current = 0 - - for id in pool: - current += pool[id] - if roll < current: - return id - - return 7 # Fallback to Heart diff --git a/scripts/models/scarcity_model.gd b/scripts/models/scarcity_model.gd new file mode 100644 index 0000000..d840c0a --- /dev/null +++ b/scripts/models/scarcity_model.gd @@ -0,0 +1,56 @@ +class_name ScarcityModel +extends RefCounted + +# ScarcityModel - Data definitions for tile scarcity + +# Standard tiles +const TILE_HEART = 7 +const TILE_DIAMOND = 8 +const TILE_STAR = 9 +const TILE_COIN = 10 + +# Special tiles (Holo) +const TILE_BURN = 11 +const TILE_SPAWN = 12 +const TILE_FREEZE = 13 +const TILE_BLOCK = 14 + +const STANDARD_TILES = [ + TILE_HEART, TILE_DIAMOND, TILE_STAR, TILE_COIN +] + +const SPECIAL_TILES = [ + TILE_BURN, TILE_SPAWN, TILE_FREEZE, TILE_BLOCK +] + +# Weights (higher = more common) +const STANDARD_WEIGHT = 100 +const SPECIAL_WEIGHT = 5 + +# Configuration +static var current_mode: String = "Normal" + +# Mode Weights (Special Tile Weight) +const WEIGHTS = { + "Normal": 5, # 5% chance relative to 100 + "Aggressive": 25, # 25% chance + "Chaos": 50 # 50% chance +} + +static func set_mode(mode: String): + if mode in WEIGHTS: + current_mode = mode + +static func get_tile_weights() -> Dictionary: + var weights = {} + var special_weight = WEIGHTS.get(current_mode, 5) + + # Standard tiles + for tile in STANDARD_TILES: + weights[tile] = STANDARD_WEIGHT + + # Special tiles + for tile in SPECIAL_TILES: + weights[tile] = special_weight + + return weights diff --git a/scripts/models/scarcity_model.gd.uid b/scripts/models/scarcity_model.gd.uid new file mode 100644 index 0000000..37d7ced --- /dev/null +++ b/scripts/models/scarcity_model.gd.uid @@ -0,0 +1 @@ +uid://dh2t1v7y0e61q