feat: Introduce core game managers and main scene, implementing player UI for board, actions, and power-ups.
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user