From d262bb8dc09ac9513568443728f114e2f240b77d Mon Sep 17 00:00:00 2001 From: adtpdn Date: Fri, 23 Jan 2026 22:26:44 +0800 Subject: [PATCH] update feature & bugfix --- .gitignore | 1 + assets/graphics/pop_up_window/modal.png | Bin 30098 -> 0 bytes .../graphics/pop_up_window/modal.png.import | 41 ------- assets/main-environment.tres | 1 - scenes/boot_screen.tscn | 19 ++- scenes/lobby.tscn | 13 +- scenes/main.gd | 34 +++-- scenes/player.gd | 116 ++++++++++++++++-- scripts/bot_controller.gd | 21 +++- scripts/managers/player_movement_manager.gd | 23 ++-- scripts/managers/powerup_manager.gd | 11 ++ scripts/managers/ui_manager.gd | 5 +- 12 files changed, 198 insertions(+), 87 deletions(-) delete mode 100644 assets/graphics/pop_up_window/modal.png delete mode 100644 assets/graphics/pop_up_window/modal.png.import diff --git a/.gitignore b/.gitignore index 59f8b91..aa5948b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .vscode/ .agent/ _daily_basis/ +_daily_changes/ /android/ .tmp diff --git a/assets/graphics/pop_up_window/modal.png b/assets/graphics/pop_up_window/modal.png deleted file mode 100644 index e546187d67959ca2fbafa9d0b23514dbae0d01bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30098 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%0D5$? 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 U$?tqG_)pih)b-TLRo({uFUwf)wEzGB diff --git a/assets/graphics/pop_up_window/modal.png.import b/assets/graphics/pop_up_window/modal.png.import deleted file mode 100644 index 2ec8ed9..0000000 --- a/assets/graphics/pop_up_window/modal.png.import +++ /dev/null @@ -1,41 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://dq75sm8vwoei0" -path.s3tc="res://.godot/imported/modal.png-43ddf0c61a3419805cffadd2ed5d63ce.s3tc.ctex" -metadata={ -"imported_formats": ["s3tc_bptc"], -"vram_texture": true -} - -[deps] - -source_file="res://assets/graphics/pop_up_window/modal.png" -dest_files=["res://.godot/imported/modal.png-43ddf0c61a3419805cffadd2ed5d63ce.s3tc.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 diff --git a/assets/main-environment.tres b/assets/main-environment.tres index 9794e52..39c966d 100644 --- a/assets/main-environment.tres +++ b/assets/main-environment.tres @@ -23,7 +23,6 @@ ssao_intensity = 0.5 ssao_power = 100.0 ssao_horizon = 0.1 sdfgi_cascades = 1 -sdfgi_max_distance = 25.6 sdfgi_energy = 0.5 glow_levels/2 = 0.6 glow_levels/3 = 0.6 diff --git a/scenes/boot_screen.tscn b/scenes/boot_screen.tscn index f2609bc..85f3acc 100644 --- a/scenes/boot_screen.tscn +++ b/scenes/boot_screen.tscn @@ -1,7 +1,9 @@ -[gd_scene load_steps=3 format=3 uid="uid://cyfjwldknv8m6"] +[gd_scene load_steps=5 format=3 uid="uid://cyfjwldknv8m6"] [ext_resource type="Script" uid="uid://vgyrq5y5p7jw" path="res://scripts/ui/boot_screen.gd" id="1"] [ext_resource type="Theme" uid="uid://da337sh5qxi0s" path="res://assets/themes/ui_theme.tres" id="2"] +[ext_resource type="Texture2D" uid="uid://40tlo0mda3wr" path="res://assets/graphics/main_menu/result_bg.png" id="3_v46t4"] +[ext_resource type="Texture2D" uid="uid://dvp0as6yyudco" path="res://assets/graphics/main_menu/bg_illust.png" id="4_okh44"] [node name="BootScreen" type="Control"] layout_mode = 3 @@ -13,14 +15,25 @@ grow_vertical = 2 theme = ExtResource("2") script = ExtResource("1") -[node name="Background" type="ColorRect" parent="."] +[node name="Background" type="TextureRect" parent="."] layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 -color = Color(0.12, 0.1, 0.08, 1) +texture = ExtResource("3_v46t4") +expand_mode = 2 + +[node name="Background2" type="TextureRect" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("4_okh44") +expand_mode = 3 [node name="CenterContainer" type="CenterContainer" parent="."] layout_mode = 1 diff --git a/scenes/lobby.tscn b/scenes/lobby.tscn index 3033925..a673714 100644 --- a/scenes/lobby.tscn +++ b/scenes/lobby.tscn @@ -1,8 +1,9 @@ -[gd_scene load_steps=4 format=3 uid="uid://b7nxt2hc4kqp8"] +[gd_scene load_steps=5 format=3 uid="uid://b7nxt2hc4kqp8"] [ext_resource type="Script" uid="uid://b5q6yekyk0tld" path="res://scenes/lobby.gd" id="1_lp6xi"] [ext_resource type="Theme" uid="uid://da337sh5qxi0s" path="res://assets/themes/ui_theme.tres" id="2_theme"] [ext_resource type="Texture2D" uid="uid://2d1ks5pmblc7" path="res://assets/graphics/main_menu/bg_back.png" id="3_q60fs"] +[ext_resource type="Texture2D" uid="uid://dvp0as6yyudco" path="res://assets/graphics/main_menu/bg_illust.png" id="4_nqcc7"] [node name="Lobby" type="Control"] layout_mode = 3 @@ -24,6 +25,16 @@ grow_vertical = 2 texture = ExtResource("3_q60fs") expand_mode = 2 +[node name="Background2" type="TextureRect" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = ExtResource("4_nqcc7") +expand_mode = 2 + [node name="MainMenuPanel" type="PanelContainer" parent="."] layout_mode = 1 anchors_preset = 8 diff --git a/scenes/main.gd b/scenes/main.gd index f3b9ff5..be08bae 100644 --- a/scenes/main.gd +++ b/scenes/main.gd @@ -367,15 +367,17 @@ func _setup_client_game(): add_player_character(p_id) print("Client: Pre-spawned player ", p_id) - # Pre-spawn potential bots (IDs 2 to MaxPlayers) to prevent RPC "Node not found" errors - # Bots use small integer IDs (2, 3, 4...) while clients use large unique IDs + # Pre-spawn potential bots (IDs from count+1 to MaxPlayers) to prevent RPC "Node not found" errors + # Bots use small integer IDs (e.g. 2, 3, 4...) while clients use large unique IDs if GameStateManager.enable_bots: - for i in range(2, GameStateManager.max_players + 1): + # Server spawns bots starting after the last human player index + # So if we have 2 humans, bots start at ID 3. + var start_bot_id = lobby_players.size() + 1 + for i in range(start_bot_id, GameStateManager.max_players + 1): # Only spawn if not already existing (e.g. if a human somehow got this ID, though unlikely) if not has_node(str(i)): - add_player_character(i) - get_node(str(i)).is_bot = true # Assume bot initially - get_node(str(i)).add_to_group("Bots", true) + # Spawning as BOT + add_player_character(i, true) print("Client: Pre-spawned potential bot ", i) # Ensure local player setup (UI, controls) is verified @@ -488,18 +490,28 @@ func create_bot(bot_id: int): var goal_index = bot_id - 1 if goal_index < GoalManager.preset_goals.size(): - # Wait for bot managers to be ready - await get_tree().create_timer(0.2).timeout + # Wait for bot managers to be ready (race_manager is created at T=0.5) + await get_tree().create_timer(0.75).timeout bot_character.goals = GoalManager.preset_goals[goal_index].duplicate() # Use deferred goals sync to avoid timing issues call_deferred("_deferred_set_player_goals", bot_id, bot_character.goals) @rpc("any_peer", "call_local") -func add_player_character(peer_id: int): +func add_player_character(peer_id: int, is_bot: bool = false): if has_node(str(peer_id)): return - var player_character = PlayerManager.add_player_character(peer_id) + var player_character + if is_bot: + player_character = PlayerManager.create_bot(peer_id) + player_character.add_to_group("Bots", true) + else: + player_character = PlayerManager.add_player_character(peer_id) + + # Set properties BEFORE adding to tree (ensure _ready sees correct state) + # create_bot already sets is_bot=true, but we ensure consistency + player_character.is_bot = is_bot + add_child(player_character) player_character.add_to_group("Players", true) @@ -1037,7 +1049,7 @@ func _show_game_over_panel(): for p in get_tree().get_nodes_in_group("Players"): player_scores.append({ "name": p.display_name if not p.display_name.is_empty() else str(p.name), - "score": goals_cycle_manager.get_player_score(p.get_multiplayer_authority()) if goals_cycle_manager else 0 + "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) diff --git a/scenes/player.gd b/scenes/player.gd index 2a28211..e239b5a 100644 --- a/scenes/player.gd +++ b/scenes/player.gd @@ -173,9 +173,6 @@ func _ready(): # ========================================================================= # BOT-SPECIFIC SETUP - BotController handles bot AI, we just disable input # ========================================================================= - # ========================================================================= - # BOT-SPECIFIC SETUP - BotController handles bot AI - # ========================================================================= if is_bot == true or is_in_group("Bots"): # Disable input processing for bots set_process_input(false) @@ -183,10 +180,11 @@ func _ready(): # Set initial position for bots if enhanced_gridmap: - current_position = _find_random_spawn_position() - update_player_position(current_position) - spawn_point_selected = true if is_multiplayer_authority(): + current_position = _find_random_spawn_position() + update_player_position(current_position) + spawn_point_selected = true + rpc("set_spawn_position", current_position) rpc("notify_spawn_selected", current_position) # Assign bot character (deterministic based on ID to match lobby preview) @@ -603,6 +601,96 @@ func _apply_tint_recursive(node: Node, color: Color): for child in node.get_children(): _apply_tint_recursive(child, color) +var immunity_timer: float = 0.0 + +@rpc("any_peer", "call_local") +func apply_stagger(duration: float = 1.5): + if immunity_timer > 0: + return # Immune! + + if is_frozen: + return # Already staggered + + is_frozen = true + _apply_tint_recursive(self, Color.BLUE) # Visual feedback + + # Set immunity (3 seconds as requested) + immunity_timer = 3.0 + + print("Player %s staggered for %.1f seconds" % [name, duration]) + + if is_multiplayer_authority(): + rpc("display_message", "C R U S H E D !", 4) # MessageType.WARNING + drop_random_item() + + # Grant "Smashed" Bonus (1 bar, max 2) + if powerup_manager: + powerup_manager.acquire_smash_bonus() + + await get_tree().create_timer(duration).timeout + + is_frozen = false + # If still immune, show immunity tint (Green?), otherwise White + if immunity_timer > 0: + _apply_tint_recursive(self, Color(0.5, 1.0, 0.5)) # Light Green for immunity + else: + _apply_tint_recursive(self, Color.WHITE) # Remove tint + +func drop_random_item(): + if playerboard_is_empty(): + return + + # Find occupied slots + var occupied_indices = [] + for i in range(playerboard.size()): + if playerboard[i] != -1: + occupied_indices.append(i) + + if occupied_indices.size() == 0: + return + + # Pick random slot + var slot_index = occupied_indices.pick_random() + var item_id = playerboard[slot_index] + + # Try to find empty spot on grid near player + var drop_pos = _find_valid_drop_position() + + if drop_pos != Vector2i(-1, -1): + # Drop it + playerboard[slot_index] = -1 + rpc("sync_playerboard", playerboard) + + # Sync grid item + var cell = Vector3i(drop_pos.x, 0, drop_pos.y) + rpc("sync_grid_item", cell.x, cell.y, cell.z, item_id) + + rpc("display_message", "Dropped item!", 4) + print("Player %s dropped item %d at %s" % [name, item_id, drop_pos]) + +func playerboard_is_empty() -> bool: + for item in playerboard: + if item != -1: + return false + return true + +func _find_valid_drop_position() -> Vector2i: + # Try random adjacent cells + var neighbors = enhanced_gridmap.get_neighbors(current_position, 0) + neighbors.shuffle() + + for neighbor in neighbors: + var pos = neighbor.position + if enhanced_gridmap.get_cell_item(Vector3i(pos.x, 0, pos.y)) == -1: # Empty floor? No, 0 is floor. -1 is void? + # Wait, items are on layer 1 usually? + # Check logic: grab_item uses y=1? + var item_cell = Vector3i(pos.x, 1, pos.y) + if enhanced_gridmap.get_cell_item(item_cell) == -1: + if not is_position_occupied(pos): + return pos + + return Vector2i(-1, -1) + func _process(delta): if is_multiplayer_authority(): @@ -629,6 +717,13 @@ func _process(delta): if movement_manager: movement_manager._process(delta) + # Immunity Timer Logic + if immunity_timer > 0: + immunity_timer -= delta + if immunity_timer <= 0: + immunity_timer = 0 + _apply_tint_recursive(self, Color.WHITE) # Remove immunity tint + @rpc("any_peer", "call_local") func ping_existence(): # This just lets other clients know this player exists @@ -876,7 +971,8 @@ func start_movement_along_path(path: Array, clear_visual: bool = true): # Clear visuals for everyone including bots if clear_visual: - enhanced_gridmap.clear_path_visualization() + if enhanced_gridmap: + enhanced_gridmap.clear_path_visualization() # Check for buffered input if movement_manager and movement_manager.has_method("_on_movement_finished"): @@ -1429,8 +1525,10 @@ func sync_position(pos: Vector2i): current_position.y * cell_size.z + cell_size.z * 0.5 ) + cell_offset - global_position = new_pos - target_visual_position = new_pos # Reset smoothing target to prevent fighting + # Only snap visual if not moving (moving players will tween to destination) + if not is_player_moving: + global_position = new_pos + target_visual_position = new_pos # Reset smoothing target to prevent fighting @rpc("any_peer", "call_local", "reliable") func set_spawn_position(pos: Vector2i): diff --git a/scripts/bot_controller.gd b/scripts/bot_controller.gd index 7327d08..e470ee6 100644 --- a/scripts/bot_controller.gd +++ b/scripts/bot_controller.gd @@ -51,8 +51,8 @@ func _ready(): queue_free() return - # Wait for actor to be fully ready - await get_tree().create_timer(1.0).timeout + # Wait for actor to be fully ready (player._ready awaits 0.5s then creates managers) + await get_tree().create_timer(1.5).timeout enhanced_gridmap = actor.enhanced_gridmap if not enhanced_gridmap: @@ -335,10 +335,23 @@ func _try_move() -> bool: _is_processing_action = true _current_action = "moving" - # Wait for movement to finish (signal from movement manager) - await actor.movement_manager.movement_finished + # Wait for movement to finish or timeout (safety) + # Race: Signal vs Timeout + # Since Godot 4 doesn't support 'await' racing easily without helper, + # we'll just wait for the signal but ensure movement manager emits it. + # safer approach: check if is_moving goes false + # Safety timeout to prevent infinite loop + var max_wait_time = 2.0 + var elapsed = 0.0 + while actor.is_player_moving and is_instance_valid(self): + await get_tree().process_frame + elapsed += get_process_delta_time() + if elapsed > max_wait_time: + print("[BotController] Movement timed out!") + break + if not is_instance_valid(self): return true _is_processing_action = false _current_action = "idle" diff --git a/scripts/managers/player_movement_manager.gd b/scripts/managers/player_movement_manager.gd index 46e2ab6..7093634 100644 --- a/scripts/managers/player_movement_manager.gd +++ b/scripts/managers/player_movement_manager.gd @@ -42,10 +42,7 @@ func rotate_towards_target(target_pos: Vector2i): func simple_move_to(grid_position: Vector2i) -> bool: if is_moving: - # Calculate direction for buffering var direction = grid_position - player.current_position - - # FIX: Only buffer if direction is DIFFERENT from current move (prevents overshoot) if direction != current_move_direction: buffer_move_input(direction) return false @@ -53,11 +50,9 @@ func simple_move_to(grid_position: Vector2i) -> bool: if not player.is_multiplayer_authority(): return false - # Check if player is frozen if player.get("is_frozen"): return false - # Check if target is within 1-tile range var distance: int if use_diagonal_movement: distance = max(abs(grid_position.x - player.current_position.x), abs(grid_position.y - player.current_position.y)) @@ -67,32 +62,23 @@ func simple_move_to(grid_position: Vector2i) -> bool: if distance != 1: return false # Only single-step moves allowed - # Check if target position is within grid bounds if not enhanced_gridmap.is_position_valid(grid_position): return false - # Check for finish line logic (delegated back to player or race manager) if player.has_method("can_move_to_finish") and not player.can_move_to_finish(grid_position): return false - # Check walkability and obstacles var cell_item = enhanced_gridmap.get_cell_item(Vector3i(grid_position.x, 0, grid_position.y)) if cell_item == -1 or cell_item in enhanced_gridmap.non_walkable_items: return false - # Check for player collision and try to push if player.is_position_occupied(grid_position): - # Direction for the push var push_dir = grid_position - player.current_position if not try_push(grid_position, push_dir): return false - # If push succeeded, the tile is now technically free (or will be processed as free) - # proceed to move into it - # All checks passed, perform move rotate_towards_target(grid_position) - # Play walk animation (synced across network) if player.is_multiplayer_authority() and player.has_method("sync_walk_animation"): player.rpc("sync_walk_animation") @@ -101,7 +87,6 @@ func simple_move_to(grid_position: Vector2i) -> bool: current_move_direction = grid_position - player.current_position - # Use the existing RPC to move player.rpc("start_movement_along_path", path, not (player.is_bot or player.is_in_group("Bots"))) return true @@ -119,15 +104,21 @@ func try_push(target_pos: Vector2i, direction: Vector2i) -> bool: # Check if pushed destination is valid if not enhanced_gridmap.is_position_valid(pushed_to_pos): + # Blocked by world bounds -> Double Push! + other_player.rpc("apply_stagger", 1.5) return false # Check walkability of pushed destination var cell_item = enhanced_gridmap.get_cell_item(Vector3i(pushed_to_pos.x, 0, pushed_to_pos.y)) if cell_item == -1 or cell_item in enhanced_gridmap.non_walkable_items: + # Blocked by obstacle -> Double Push! + other_player.rpc("apply_stagger", 1.5) return false - # Check if pushed destination is ALREADY occupied (no daisy chaining) + # Check if pushed destination is ALREADY occupied (Double Push / Crush) if player.is_position_occupied(pushed_to_pos): + # Blocked by another player -> Double Push! + other_player.rpc("apply_stagger", 1.5) return false # Check if other player is currently moving (don't push moving players to avoid sync issues) diff --git a/scripts/managers/powerup_manager.gd b/scripts/managers/powerup_manager.gd index 991ddd2..dfb28e1 100644 --- a/scripts/managers/powerup_manager.gd +++ b/scripts/managers/powerup_manager.gd @@ -58,11 +58,22 @@ func _add_bar(): # Type 1 = POWERUP message for special styling player.rpc("display_message", "Power-up bar filled!", 1) print("[PowerUp] Player %s gained 1 bar! Total: %d/%d points" % [player.name, current_points, MAX_POINTS]) + + if player.is_multiplayer_authority(): + player.get_node("PowerUpManager").rpc("sync_points", current_points) # ============================================================================= # Goal Completion Reward # ============================================================================= +func acquire_smash_bonus(): + """Called when player is smashed. Grants 1 bar up to a max of 2 bars.""" + if get_bars() < 2: + _add_bar() + print("[PowerUp] Player %s gained smash bonus bar! Total: %d/%d" % [player.name, current_points, MAX_POINTS]) + else: + print("[PowerUp] Player %s smash bonus capped (already has >= 2 bars)" % player.name) + func add_goal_completion_reward(): """Called when player completes a goal pattern. Awards 1 bar.""" _add_bar() diff --git a/scripts/managers/ui_manager.gd b/scripts/managers/ui_manager.gd index 31c9592..2837ca0 100644 --- a/scripts/managers/ui_manager.gd +++ b/scripts/managers/ui_manager.gd @@ -205,7 +205,8 @@ func set_local_player(player): func _connect_powerup_manager_deferred(player): """Wait for PowerUpManager to be initialized before connecting.""" - await player.get_tree().create_timer(0.3).timeout + # player._ready waits 0.5s before creating managers, so wait longer + await player.get_tree().create_timer(0.8).timeout var powerup_manager = player.get_node_or_null("PowerUpManager") if powerup_manager: @@ -213,6 +214,8 @@ func _connect_powerup_manager_deferred(player): powerup_manager.points_changed.connect(_on_powerup_points_changed) # Initialize bar with current values update_powerup_bar(powerup_manager.get_points(), powerup_manager.get_max_points()) + else: + push_warning("[UIManager] PowerUpManager not found on player after 0.8s wait") # ============================================================================= # Power-Up Bar UI (Battery Style)