feat: Introduce core game managers and main scene, implementing player UI for board, actions, and power-ups.

This commit is contained in:
Yogi Wiguna
2026-02-05 16:12:34 +08:00
parent fe494b50ef
commit e933773688
6 changed files with 111 additions and 3 deletions
+7
View File
@@ -28,6 +28,7 @@ func _ready():
ui_manager.setup_action_buttons(_set_action_state_callback)
ui_manager.setup_playerboard_ui()
ui_manager.setup_timer_labels(self)
ui_manager.setup_playerboard_label(self) # NEW
ui_manager.setup_leaderboard_ui(self)
ui_manager.setup_powerup_bar_ui(self)
# GlobalMatchTimer is now static in main.tscn - no setup needed
@@ -43,6 +44,11 @@ func _ready():
if multiplayer.is_server():
randomize_game_grid()
_setup_tile_respawn_timer()
func _on_goal_count_updated(peer_id: int, count: int):
# Only update for local player
if peer_id == multiplayer.get_unique_id():
ui_manager.update_goal_count_label(count)
func _setup_tile_respawn_timer():
# Configure respawn rate based on Scarcity Mode from Lobby
@@ -127,6 +133,7 @@ func _init_managers():
# Connect signals for UI updates
goals_cycle_manager.timer_updated.connect(_on_timer_updated)
goals_cycle_manager.score_updated.connect(_on_score_updated)
goals_cycle_manager.goal_count_updated.connect(_on_goal_count_updated) # NEW
goals_cycle_manager.leaderboard_updated.connect(_on_leaderboard_updated)
goals_cycle_manager.global_timer_updated.connect(_on_global_timer_updated)
goals_cycle_manager.match_ended.connect(_on_match_ended)
+13
View File
@@ -961,6 +961,18 @@ grow_horizontal = 2
grow_vertical = 2
texture = ExtResource("9_6gcb6")
[node name="PlayerBoardLabel" type="Label" parent="." unique_id=341385584]
anchors_preset = 4
anchor_top = 0.5
anchor_bottom = 0.5
offset_left = 217.0
offset_top = -20.0
offset_right = 257.0
offset_bottom = 3.0
grow_vertical = 2
theme_override_font_sizes/font_size = 32
text = "X0"
[node name="PowerUpBar" type="PanelContainer" parent="." unique_id=1775378146]
anchors_preset = 4
anchor_top = 0.5
@@ -9415,6 +9427,7 @@ offset_bottom = 24.0
texture = ExtResource("10_my1qp")
[node name="MessageBar" type="PanelContainer" parent="." unique_id=142729129]
visible = false
anchors_preset = 2
anchor_top = 1.0
anchor_bottom = 1.0
+43 -2
View File
@@ -4,8 +4,8 @@ extends Node
# Also handles global match timer that ends the game
const CYCLE_DURATION: float = 30.0
const BASE_SCORE: int = 100
const TIME_BONUS_MULTIPLIER: float = 2.0
const BASE_SCORE: int = 1000
const TIME_BONUS_MULTIPLIER: float = 0.0 # User implied flat 1000, setting bonus to 0 effectively or I can just ignore it in calc.
# Cycle timer state (30-second cycles)
var current_cycle_timer: float = 0.0
@@ -18,6 +18,7 @@ var is_match_active: bool = false
# Score tracking: peer_id -> score
var player_scores: Dictionary = {}
var player_goal_counts: Dictionary = {} # peer_id -> count
# Reference to main scene
var main_scene: Node = null
@@ -26,6 +27,7 @@ signal cycle_started()
signal cycle_ended()
signal timer_updated(time_remaining: float)
signal score_updated(peer_id: int, new_score: int)
signal goal_count_updated(peer_id: int, count: int)
signal leaderboard_updated(sorted_scores: Array)
# Global match signals
@@ -225,11 +227,18 @@ func on_goal_completed(player: Node, time_remaining: float):
player_scores[peer_id] = 0
player_scores[peer_id] += score_earned
# Update goal count
if not player_goal_counts.has(peer_id):
player_goal_counts[peer_id] = 0
player_goal_counts[peer_id] += 1
emit_signal("score_updated", peer_id, player_scores[peer_id])
emit_signal("goal_count_updated", peer_id, player_goal_counts[peer_id])
_update_leaderboard()
# Sync score to all clients
rpc("sync_player_score", peer_id, player_scores[peer_id])
rpc("sync_goal_count", peer_id, player_goal_counts[peer_id])
# Clear playerboard tiles (they convert to powerup bar reward)
player.playerboard.fill(-1)
@@ -251,6 +260,38 @@ func sync_player_score(peer_id: int, total_score: int):
emit_signal("score_updated", peer_id, total_score)
_update_leaderboard()
@rpc("authority", "call_local", "reliable")
func sync_goal_count(peer_id: int, count: int):
player_goal_counts[peer_id] = count
emit_signal("goal_count_updated", peer_id, count)
@rpc("any_peer", "call_local", "reliable")
func request_add_score(amount: int):
"""RPC for clients to request score addition (trusted)."""
if not multiplayer.is_server():
return
var sender_id = multiplayer.get_remote_sender_id()
# If called locally by server, sender_id might be 0 or 1.
if sender_id == 0: sender_id = 1
add_score(sender_id, amount)
func add_score(peer_id: int, amount: int):
"""Add points to a specific player (Server only)."""
if not multiplayer.is_server():
return
if not player_scores.has(peer_id):
player_scores[peer_id] = 0
player_scores[peer_id] += amount
# Sync
rpc("sync_player_score", peer_id, player_scores[peer_id])
print("[GoalsCycle] Added %d points to Player %d. Total: %d" % [amount, peer_id, player_scores[peer_id]])
func _update_leaderboard():
# Sort players by score (descending)
var sorted_scores = []
@@ -142,6 +142,15 @@ func try_push(target_pos: Vector2i, direction: Vector2i) -> bool:
# Consume all available boost to force a full recharge cycle
player.powerup_manager.consume_boost(100.0)
# SCORING: 200 Points for successful attack
if player.is_multiplayer_authority():
var main = player.get_tree().get_root().get_node_or_null("Main")
if main:
var gcm = main.get_node_or_null("GoalsCycleManager")
if gcm:
gcm.rpc("request_add_score", 200)
NotificationManager.send_message(player, "Successful Attack! +200 Pts", NotificationManager.MessageType.GOAL)
# 5. Attack Mode Persistence
# logic moved to consume_boost: checks if <= 0 then disables.
# So we do NOT force disable here.
+14
View File
@@ -294,6 +294,8 @@ func _execute_area_freeze(center_pos: Vector2i = Vector2i.ZERO):
# Initial Check (Instant Feedback)
var all_players = player.get_tree().get_nodes_in_group("Players")
var hit_count = 0
for p in all_players:
# Check distance (Chebyshev distance for square area)
var dx = abs(p.current_position.x - center_pos.x)
@@ -303,6 +305,18 @@ func _execute_area_freeze(center_pos: Vector2i = Vector2i.ZERO):
if dx <= radius and dy <= radius:
p.rpc("apply_slow_effect", FREEZE_SLOW_DURATION)
NotificationManager.send_message(p, "Caught in Freeze Zone!", NotificationManager.MessageType.WARNING)
if p != player: # Don't score for freezing self (unless desired?) - Assuming enemies
hit_count += 1
if hit_count > 0 and player.is_multiplayer_authority():
var points = hit_count * 50
var main = player.get_tree().get_root().get_node_or_null("Main")
if main:
var gcm = main.get_node_or_null("GoalsCycleManager")
if gcm:
gcm.add_score(player.name.to_int(), points)
NotificationManager.send_message(player, "Hit %d Players! +%d Pts" % [hit_count, points], NotificationManager.MessageType.GOAL)
# Visual Feedback (Turn Floor Blue - Item 12 on Layer 0)
if player.is_multiplayer_authority():
+25 -1
View File
@@ -27,6 +27,7 @@ var playerboard_ui
var action_menu_instance
var powerup_inventory_ui
var timer_label: Label
var playerboard_label: Label # Shows (xN) goal completions
var local_player_character
var _previous_playerboard_state: Array = []
@@ -390,9 +391,32 @@ func setup_timer_labels(main_node):
if t_label:
timer_label = t_label # Store reference
t_label.add_theme_color_override("font_color", Color(1.0, 0.85, 0.2))
print("[UIManager] SUCCESS: Found and stored TimerLabel reference.")
else:
print("[UIManager] ERROR: GoalsTimer found but TimerLabel NOT found at node/TimerLabel")
var suffix_label = goals_timer.get_node_or_null("SuffixLabel")
if suffix_label:
suffix_label.add_theme_color_override("font_color", Color(0.7, 0.7, 0.7))
func setup_playerboard_label(main_node):
var lbl = main_node.get_node_or_null("PlayerBoardLabel")
if lbl:
playerboard_label = lbl
playerboard_label.text = "" # Hidden initially
print("[UIManager] Found PlayerBoardLabel")
else:
print("[UIManager] PlayerBoardLabel not found")
func update_goal_count_label(count: int):
if playerboard_label:
if count > 0:
playerboard_label.text = "x%d" % count
# Pop effect
var tween = playerboard_label.create_tween()
playerboard_label.scale = Vector2(1.5, 1.5)
tween.tween_property(playerboard_label, "scale", Vector2(1.0, 1.0), 0.3).set_trans(Tween.TRANS_BOUNCE)
else:
playerboard_label.text = ""
# Method to update leaderboard with all players in match
func initialize_leaderboard_with_players(players: Array):