From 75eb39864940c9b59377197bf735b0fb2eba2670 Mon Sep 17 00:00:00 2001 From: adtpdn Date: Wed, 17 Dec 2025 03:51:15 +0800 Subject: [PATCH] feat: Implement core game managers for power-ups, UI, goals, and player actions, and establish initial game scene and player. --- _daily_basis/report_2025-12-17.md | 21 ++++++ scenes/main.gd | 80 +++++++++++++++++++---- scenes/main.tscn | 64 +++++++++++++----- scenes/player.gd | 4 +- scripts/managers/goals_cycle_manager.gd | 6 +- scripts/managers/player_action_manager.gd | 21 ++---- scripts/managers/powerup_manager.gd | 6 +- scripts/managers/ui_manager.gd | 70 ++++++++++++++++++-- 8 files changed, 216 insertions(+), 56 deletions(-) create mode 100644 _daily_basis/report_2025-12-17.md diff --git a/_daily_basis/report_2025-12-17.md b/_daily_basis/report_2025-12-17.md new file mode 100644 index 0000000..a8482a1 --- /dev/null +++ b/_daily_basis/report_2025-12-17.md @@ -0,0 +1,21 @@ +[ ADT's Daily Report - 2025-12-17 ] + +✅ Action Points Non-Turn-Based Mode - Removed auto-reset behavior for action points in non-turn-based mode. Action points are now unlimited in real-time fast-paced mode, meaning `player_action_manager.gd` no longer deducts points when `TurnManager.turn_based_mode` is false. + +✅ Player Goals on Playerboard - Moved the 3x3 player goals visual to the center of the playerboard. Goals now display in slots 6,7,8,11,12,13,16,17,18 of the 5x5 grid. Hidden the `AllPlayerGoals` UI in `main.tscn` since goals are now shown on the playerboard. + +✅ Goal Dimming for Uncollected Tiles - Goals that aren't collected yet appear dimmed (dark gray `Color(0.3, 0.3, 0.3, 1.0)`) in the playerboard center. Collected tiles display at full brightness. + +✅ Goal Completion Rewards - After completing goals, all tiles on the playerboard are now cleared (`player.playerboard.fill(-1)`) and converted into one powerup bar. New random goals are regenerated via `regenerate_goals_for_player()`. + +✅ Enhanced Message Bar - Upgraded `add_message_to_bar()` in `main.gd` with animations and styling. Added color-coded messages with emojis: ⚡ Powerup (green), 🎯 Goal (gold), ⏱️ Cycle (blue), ⚠️ Warning (orange), 💬 Normal (gray). Messages now have fade-in/out animations, slide-in entrance effects, and pulse animation for powerup messages. + +✅ Powerup Bar Visual Effects - Added pulse animation to `ui_manager.gd` when powerup bar segments fill. The `_pulse_segment()` function scales the segment to 1.3x with a bounce effect. + +✅ Powerup Bar Repositioning - Moved `PowerUpBar` to be directly above the playerboard in `main.tscn`. Changed label text from "POWER" to "⚡" emoji. Reduced segment size from 36x20 to 20x16 pixels for a more compact look. + +✅ Playerboard Background Panel - Added `PlayerboardPanel` as a dark semi-transparent background (rgba 0.08, 0.08, 0.12, 0.9) with 8px rounded corners and subtle border. Created `StyleBoxFlat_playerboard` subresource for consistent styling. + +✅ GUI Margin Consistency - Aligned all left-side UI elements with 10px margin from screen edge. PowerUpBar and PlayerboardUI are now vertically aligned within the background panel. + +🚧 Cycle Duration - User manually changed `CYCLE_DURATION` from 60.0 to 30.0 seconds in `goals_cycle_manager.gd`. diff --git a/scenes/main.gd b/scenes/main.gd index 185e2b1..15fab7a 100644 --- a/scenes/main.gd +++ b/scenes/main.gd @@ -62,41 +62,99 @@ func _init_managers(): # Message Bar Configuration const MAX_MESSAGES := 5 -const MESSAGE_DURATION := 3.0 +const MESSAGE_DURATION := 4.0 @onready var message_bar: PanelContainer = $MessageBar @onready var message_container: VBoxContainer = $MessageBar/MarginContainer/MessageContainer -func add_message_to_bar(player_name: String, message: String): +# Message types for different styling +enum MessageType {NORMAL, POWERUP, GOAL, CYCLE, WARNING} + +func add_message_to_bar(player_name: String, message: String, type: int = MessageType.NORMAL): if not message_container: return - # Create message label + # Create message label with rich styling var label = Label.new() - label.text = "[%s] %s" % [player_name, message] label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - label.add_theme_color_override("font_color", Color.WHITE) - label.add_theme_font_size_override("font_size", 14) + label.add_theme_font_size_override("font_size", 16) + label.modulate.a = 0.0 # Start invisible for fade-in + + # Style based on message type + var icon = "" + var color = Color.WHITE + match type: + MessageType.POWERUP: + icon = "⚡ " + color = Color(0.4, 1.0, 0.4) # Bright green + MessageType.GOAL: + icon = "🎯 " + color = Color(1.0, 0.85, 0.2) # Gold + MessageType.CYCLE: + icon = "⏱️ " + color = Color(0.4, 0.8, 1.0) # Light blue + MessageType.WARNING: + icon = "⚠️ " + color = Color(1.0, 0.5, 0.3) # Orange + _: + icon = "💬 " + color = Color(0.9, 0.9, 0.9) # Light gray + + label.text = "%s%s" % [icon, message] + label.add_theme_color_override("font_color", color) + + # Add shadow for better visibility + label.add_theme_constant_override("shadow_offset_x", 2) + label.add_theme_constant_override("shadow_offset_y", 2) + label.add_theme_color_override("font_shadow_color", Color(0, 0, 0, 0.7)) # Add to container message_container.add_child(label) - # Show the message bar - message_bar.visible = true + # Show the message bar with fade + if not message_bar.visible: + message_bar.visible = true + message_bar.modulate.a = 0.0 + var bar_tween = create_tween() + bar_tween.tween_property(message_bar, "modulate:a", 1.0, 0.2) + + # Animate label entrance (slide in + fade) + label.position.x = -50 + var entrance_tween = create_tween() + entrance_tween.set_parallel(true) + entrance_tween.tween_property(label, "modulate:a", 1.0, 0.3) + entrance_tween.tween_property(label, "position:x", 0.0, 0.3).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) + + # 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) # Remove oldest messages if over limit while message_container.get_child_count() > MAX_MESSAGES: var oldest = message_container.get_child(0) oldest.queue_free() - # Auto-remove after duration + # Auto-remove after duration with fade-out await get_tree().create_timer(MESSAGE_DURATION).timeout if is_instance_valid(label): - label.queue_free() + var exit_tween = create_tween() + exit_tween.set_parallel(true) + exit_tween.tween_property(label, "modulate:a", 0.0, 0.3) + exit_tween.tween_property(label, "position:x", 50.0, 0.3) + await exit_tween.finished + if is_instance_valid(label): + label.queue_free() - # Hide bar when empty + # Hide bar when empty with fade await get_tree().process_frame if message_container.get_child_count() == 0: + var hide_tween = create_tween() + hide_tween.tween_property(message_bar, "modulate:a", 0.0, 0.3) + await hide_tween.finished message_bar.visible = false @rpc("any_peer", "call_local") diff --git a/scenes/main.tscn b/scenes/main.tscn index d030858..1168f3d 100644 --- a/scenes/main.tscn +++ b/scenes/main.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=26 format=3 uid="uid://dxn87yj8qnfpp"] +[gd_scene load_steps=27 format=3 uid="uid://dxn87yj8qnfpp"] [ext_resource type="MeshLibrary" uid="uid://kcv6ans86ug7" path="res://addons/enhanced_gridmap/meshlibrary/default.tres" id="1_110wo"] [ext_resource type="Script" uid="uid://co1ads72by6na" path="res://scenes/main.gd" id="1_xcpe3"] @@ -30,6 +30,22 @@ texture = ExtResource("13_ahjgs") [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_s1l63"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_playerboard"] +bg_color = Color(0.08, 0.08, 0.12, 0.9) +border_color = Color(0.3, 0.3, 0.4, 0.8) +border_width_left = 1 +border_width_top = 1 +border_width_right = 1 +border_width_bottom = 1 +corner_radius_top_left = 8 +corner_radius_top_right = 8 +corner_radius_bottom_right = 8 +corner_radius_bottom_left = 8 +content_margin_left = 8.0 +content_margin_top = 8.0 +content_margin_right = 8.0 +content_margin_bottom = 8.0 + [node name="Main" type="Node3D"] script = ExtResource("1_xcpe3") @@ -88,15 +104,26 @@ text = "Unique Peer ID" horizontal_alignment = 1 vertical_alignment = 1 +[node name="PlayerboardPanel" type="PanelContainer" parent="."] +anchors_preset = 4 +anchor_top = 0.5 +anchor_bottom = 0.5 +offset_left = 10.0 +offset_top = -120.0 +offset_right = 216.0 +offset_bottom = 120.0 +grow_vertical = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_playerboard") + [node name="PlayerboardUI" type="GridContainer" parent="."] clip_contents = true anchors_preset = 4 anchor_top = 0.5 anchor_bottom = 0.5 -offset_left = 32.0 -offset_top = -104.0 -offset_right = 228.0 -offset_bottom = 92.0 +offset_left = 18.0 +offset_top = -88.0 +offset_right = 208.0 +offset_bottom = 102.0 grow_vertical = 2 size_flags_horizontal = 3 columns = 5 @@ -1065,6 +1092,7 @@ environment = ExtResource("4_ky38j") [node name="CanvasLayer" type="CanvasLayer" parent="."] [node name="AllPlayerGoals" type="HBoxContainer" parent="."] +visible = false y_sort_enabled = true clip_contents = true layout_direction = 2 @@ -9325,14 +9353,14 @@ layout_mode = 2 theme_override_constants/separation = 4 [node name="PowerUpBar" type="PanelContainer" parent="."] -anchors_preset = 5 -anchor_left = 0.5 -anchor_right = 0.5 -offset_left = -110.0 -offset_top = 125.0 +anchors_preset = 4 +anchor_top = 0.5 +anchor_bottom = 0.5 +offset_left = 18.0 +offset_top = -115.0 offset_right = 110.0 -offset_bottom = 161.0 -grow_horizontal = 2 +offset_bottom = -92.0 +grow_vertical = 2 [node name="HBox" type="HBoxContainer" parent="PowerUpBar"] layout_mode = 2 @@ -9340,23 +9368,23 @@ theme_override_constants/separation = 4 [node name="PowerLabel" type="Label" parent="PowerUpBar/HBox"] layout_mode = 2 -theme_override_font_sizes/font_size = 14 -text = "POWER " +theme_override_font_sizes/font_size = 18 +text = "⚡" [node name="Segment0" type="Panel" parent="PowerUpBar/HBox"] -custom_minimum_size = Vector2(36, 20) +custom_minimum_size = Vector2(20, 16) layout_mode = 2 [node name="Segment1" type="Panel" parent="PowerUpBar/HBox"] -custom_minimum_size = Vector2(36, 20) +custom_minimum_size = Vector2(20, 16) layout_mode = 2 [node name="Segment2" type="Panel" parent="PowerUpBar/HBox"] -custom_minimum_size = Vector2(36, 20) +custom_minimum_size = Vector2(20, 16) layout_mode = 2 [node name="Segment3" type="Panel" parent="PowerUpBar/HBox"] -custom_minimum_size = Vector2(36, 20) +custom_minimum_size = Vector2(20, 16) layout_mode = 2 [node name="LeaderboardPanel" type="PanelContainer" parent="."] diff --git a/scenes/player.gd b/scenes/player.gd index 7e4bdb0..56f354a 100644 --- a/scenes/player.gd +++ b/scenes/player.gd @@ -675,12 +675,12 @@ func remote_set_position(authority_position): global_position = authority_position @rpc("any_peer", "call_local") -func display_message(message): +func display_message(message, type: int = 0): # Send message to the main scene's message bar instead of player bubble var main = get_tree().get_root().get_node_or_null("Main") if main and main.has_method("add_message_to_bar"): var player_name = $Name.text.split("\n")[0] if $Name else str(name) - main.add_message_to_bar(player_name, message) + main.add_message_to_bar(player_name, message, type) func initialize_random_goals(_size: int, min_value: int, max_value: int, null_count: float) -> Array[int]: goals.clear() diff --git a/scripts/managers/goals_cycle_manager.gd b/scripts/managers/goals_cycle_manager.gd index 5ee249c..e1eef82 100644 --- a/scripts/managers/goals_cycle_manager.gd +++ b/scripts/managers/goals_cycle_manager.gd @@ -2,7 +2,7 @@ extends Node # GoalsCycleManager - Handles 60-second goal cycles, scoring, and goal regeneration -const CYCLE_DURATION: float = 60.0 +const CYCLE_DURATION: float = 30.0 const BASE_SCORE: int = 100 const TIME_BONUS_MULTIPLIER: float = 2.0 @@ -126,6 +126,10 @@ func on_goal_completed(player: Node, time_remaining: float): # Sync score to all clients rpc("sync_player_score", peer_id, player_scores[peer_id]) + # Clear playerboard tiles (they convert to powerup bar reward) + player.playerboard.fill(-1) + player.rpc("sync_playerboard", player.playerboard) + # Regenerate goals for this player regenerate_goals_for_player(player) diff --git a/scripts/managers/player_action_manager.gd b/scripts/managers/player_action_manager.gd index 6a631bf..3e1e611 100644 --- a/scripts/managers/player_action_manager.gd +++ b/scripts/managers/player_action_manager.gd @@ -21,22 +21,17 @@ func consume_action_points(points: int): var main = player.get_tree().get_root().get_node_or_null("Main") if not main: return - - # Don't consume points for bots in non-turn-based mode - if player.is_bot == true and not TurnManager.turn_based_mode: + + # Non-turn-based mode: unlimited action points for real-time fast-paced gameplay + if not TurnManager.turn_based_mode: after_action_completed() return + # Turn-based mode: consume action points normally player.action_points -= points if player.action_points <= 0: - if TurnManager.turn_based_mode: - main.request_next_turn() - else: - player.action_points = 2 - player.has_performed_action = false - player.has_moved_this_turn = false - player.rpc("display_message", "Action Points Reset!") + main.request_next_turn() after_action_completed() @@ -55,12 +50,6 @@ func after_action_completed(): if multiplayer.get_unique_id() == player.get_multiplayer_authority(): var main = player.get_tree().get_root().get_node_or_null("Main") if main: - # Add this condition for bots - if not TurnManager.turn_based_mode and (player.action_points <= 0 or player.is_bot): - player.action_points = 20 # For bots in non-turn-based mode, this will keep refreshing - player.has_performed_action = false - player.has_moved_this_turn = false - main.ui_manager.update_button_states() main.ui_manager.update_playerboard_ui() diff --git a/scripts/managers/powerup_manager.gd b/scripts/managers/powerup_manager.gd index cefb1ab..edef68f 100644 --- a/scripts/managers/powerup_manager.gd +++ b/scripts/managers/powerup_manager.gd @@ -47,7 +47,8 @@ func _add_bar(): emit_signal("bar_filled") emit_signal("points_changed", current_points, MAX_POINTS) - player.rpc("display_message", "Power-up bar filled!") + # 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]) # ============================================================================= @@ -74,7 +75,8 @@ func get_bars() -> int: func use_special_effect(): """Consume 1 bar and trigger a random special effect.""" if not can_use_special(): - player.rpc("display_message", "Not enough power-up!") + # Type 3 = WARNING message + player.rpc("display_message", "Not enough power-up!", 3) return false # Consume 1 bar diff --git a/scripts/managers/ui_manager.gd b/scripts/managers/ui_manager.gd index 4591a7d..cdd0427 100644 --- a/scripts/managers/ui_manager.gd +++ b/scripts/managers/ui_manager.gd @@ -97,6 +97,13 @@ func update_playerboard_ui(): if not local_player_character: return + # Center 3x3 slot indices in a 5x5 grid (0-indexed) + # Row 1: 6, 7, 8 + # Row 2: 11, 12, 13 + # Row 3: 16, 17, 18 + var center_slots = [6, 7, 8, 11, 12, 13, 16, 17, 18] + var goals = local_player_character.goals if local_player_character.goals else [] + for i in range(25): var slot = playerboard_ui.get_child(i) @@ -106,13 +113,43 @@ func update_playerboard_ui(): var item = local_player_character.playerboard[i] + # Default texture (empty) slot.texture = item_tex[0] + slot.modulate = Color.WHITE - match item: - 7: slot.texture = item_tex[1] - 8: slot.texture = item_tex[2] - 9: slot.texture = item_tex[3] - 10: slot.texture = item_tex[4] + # Check if this is a center slot that should show a goal + var center_index = center_slots.find(i) + if center_index != -1 and center_index < goals.size(): + var goal_value = goals[center_index] + + # Check if player has collected this tile + var _has_tile = item == goal_value + + if item != -1: + # Player has a tile in this slot - show it at full brightness + match item: + 7: slot.texture = item_tex[1] + 8: slot.texture = item_tex[2] + 9: slot.texture = item_tex[3] + 10: slot.texture = item_tex[4] + slot.modulate = Color.WHITE + else: + # Show goal tile dimmed (not collected yet) + match goal_value: + 7: slot.texture = item_tex[1] + 8: slot.texture = item_tex[2] + 9: slot.texture = item_tex[3] + 10: slot.texture = item_tex[4] + _: slot.texture = item_tex[0] + # Dim uncollected goals with black overlay + slot.modulate = Color(0.3, 0.3, 0.3, 1.0) + else: + # Non-center slot - just show playerboard item normally + match item: + 7: slot.texture = item_tex[1] + 8: slot.texture = item_tex[2] + 9: slot.texture = item_tex[3] + 10: slot.texture = item_tex[4] func update_button_states(): if not local_player_character or local_player_character.is_in_group("Bots"): @@ -184,7 +221,7 @@ func setup_powerup_bar_ui(main_node): segment.add_theme_stylebox_override("panel", style) powerup_segments.append(segment) -func update_powerup_bar(current_points: int, max_points: int): +func update_powerup_bar(current_points: int, _max_points: int): """Update battery segments based on current power-up points.""" var bars_filled = current_points / 4 # 4 points per bar @@ -201,9 +238,30 @@ func update_powerup_bar(current_points: int, max_points: int): segment.add_theme_stylebox_override("panel", style) +var _previous_bars: int = 0 + func _on_powerup_points_changed(current: int, max_points: int): + var new_bars = current / 4 + + # Detect if a new bar was filled + if new_bars > _previous_bars and powerup_bar: + # Pulse effect on newly filled segment + var segment_index = new_bars - 1 + if segment_index >= 0 and segment_index < powerup_segments.size(): + var segment = powerup_segments[segment_index] + _pulse_segment(segment) + + _previous_bars = new_bars update_powerup_bar(current, max_points) +func _pulse_segment(segment: Panel): + """Create a visual pulse effect on a powerup segment.""" + var original_scale = segment.scale + var tween = segment.create_tween() + tween.set_loops(2) + tween.tween_property(segment, "scale", Vector2(1.3, 1.3), 0.1).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT) + tween.tween_property(segment, "scale", original_scale, 0.15).set_trans(Tween.TRANS_SINE) + # ============================================================================= # Leaderboard UI # =============================================================================