feat: add Stop N Go game mode manager with phase transitions, HUD, traffic light visuals, and dynamic arena generation.
This commit is contained in:
@@ -1065,6 +1065,32 @@ theme_override_fonts/font = ExtResource("13_j8jky")
|
|||||||
theme_override_font_sizes/font_size = 32
|
theme_override_font_sizes/font_size = 32
|
||||||
text = "X0"
|
text = "X0"
|
||||||
|
|
||||||
|
[node name="StopTimer" type="PanelContainer" parent="." unique_id=991540081]
|
||||||
|
anchors_preset = 5
|
||||||
|
anchor_left = 0.5
|
||||||
|
anchor_right = 0.5
|
||||||
|
offset_left = -58.0
|
||||||
|
offset_top = 80.0
|
||||||
|
offset_right = 58.0
|
||||||
|
offset_bottom = 113.00012
|
||||||
|
grow_horizontal = 2
|
||||||
|
|
||||||
|
[node name="HBox" type="HBoxContainer" parent="StopTimer" unique_id=1541315617]
|
||||||
|
layout_mode = 2
|
||||||
|
theme_override_constants/separation = 4
|
||||||
|
|
||||||
|
[node name="Segment0" type="Panel" parent="StopTimer/HBox" unique_id=307582302]
|
||||||
|
custom_minimum_size = Vector2(36, 20)
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Segment1" type="Panel" parent="StopTimer/HBox" unique_id=615805583]
|
||||||
|
custom_minimum_size = Vector2(36, 20)
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Segment2" type="Panel" parent="StopTimer/HBox" unique_id=1041800993]
|
||||||
|
custom_minimum_size = Vector2(36, 20)
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
[node name="PowerUpBar" type="PanelContainer" parent="." unique_id=1775378146]
|
[node name="PowerUpBar" type="PanelContainer" parent="." unique_id=1775378146]
|
||||||
anchors_preset = 4
|
anchors_preset = 4
|
||||||
anchor_top = 0.5
|
anchor_top = 0.5
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ enum Phase {GO, STOP}
|
|||||||
|
|
||||||
const GO_DURATION: float = 12.0
|
const GO_DURATION: float = 12.0
|
||||||
const STOP_DURATION: float = 6.0
|
const STOP_DURATION: float = 6.0
|
||||||
|
const REQUIRED_GOALS: int = 2
|
||||||
|
|
||||||
var current_phase: Phase = Phase.GO
|
var current_phase: Phase = Phase.GO
|
||||||
var phase_timer: float = GO_DURATION
|
var phase_timer: float = GO_DURATION
|
||||||
@@ -30,6 +31,13 @@ var phase_label: Label
|
|||||||
var mission_label: Label
|
var mission_label: Label
|
||||||
var red_tint_overlay: ColorRect
|
var red_tint_overlay: ColorRect
|
||||||
|
|
||||||
|
# Traffic Light / StopTimer Visuals
|
||||||
|
var stop_timer_node: PanelContainer
|
||||||
|
var stop_segments: Array[Panel] = []
|
||||||
|
var lit_style: StyleBoxFlat
|
||||||
|
var dim_style: StyleBoxFlat
|
||||||
|
var red_style: StyleBoxFlat
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
set_process(false)
|
set_process(false)
|
||||||
_setup_hud()
|
_setup_hud()
|
||||||
@@ -83,8 +91,10 @@ func _process(delta):
|
|||||||
if not is_active:
|
if not is_active:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Decrement timer locally for all peers (smoother HUD than waiting for RPC)
|
||||||
|
phase_timer -= delta
|
||||||
|
|
||||||
if multiplayer.is_server():
|
if multiplayer.is_server():
|
||||||
phase_timer -= delta
|
|
||||||
if phase_timer <= 0:
|
if phase_timer <= 0:
|
||||||
if current_phase == Phase.GO:
|
if current_phase == Phase.GO:
|
||||||
_start_phase(Phase.STOP)
|
_start_phase(Phase.STOP)
|
||||||
@@ -112,14 +122,69 @@ func _update_hud_visuals():
|
|||||||
# Get count from GoalsCycleManager (Source of truth for PlayerBoardLabel)
|
# Get count from GoalsCycleManager (Source of truth for PlayerBoardLabel)
|
||||||
var completed_count = goals_cycle_manager.player_goal_counts.get(my_id, 0) if goals_cycle_manager else 0
|
var completed_count = goals_cycle_manager.player_goal_counts.get(my_id, 0) if goals_cycle_manager else 0
|
||||||
|
|
||||||
mission_label.text = "GOALS (%d/3)" % completed_count
|
mission_label.text = "GOALS (%d/%d)" % [completed_count, REQUIRED_GOALS]
|
||||||
|
|
||||||
if completed_count >= 3:
|
if completed_count >= REQUIRED_GOALS:
|
||||||
mission_label.text = "ALL GOALS COMPLETE!\nREACH THE FINISH!"
|
mission_label.text = "ALL GOALS COMPLETE!\nREACH THE FINISH!"
|
||||||
mission_label.add_theme_color_override("font_color", Color.GOLD)
|
mission_label.add_theme_color_override("font_color", Color.GOLD)
|
||||||
else:
|
else:
|
||||||
mission_label.add_theme_color_override("font_color", Color.WHITE)
|
mission_label.add_theme_color_override("font_color", Color.WHITE)
|
||||||
|
|
||||||
|
# Update StopTimer (Traffic Light)
|
||||||
|
_update_stop_timer_visuals()
|
||||||
|
|
||||||
|
func _update_stop_timer_visuals():
|
||||||
|
if not stop_timer_node:
|
||||||
|
# Try to find it once
|
||||||
|
var main = get_node_or_null("/root/Main")
|
||||||
|
if main:
|
||||||
|
stop_timer_node = main.get_node_or_null("StopTimer")
|
||||||
|
if stop_timer_node:
|
||||||
|
var hbox = stop_timer_node.get_node_or_null("HBox")
|
||||||
|
if hbox:
|
||||||
|
stop_segments.clear()
|
||||||
|
for i in range(4):
|
||||||
|
var seg = hbox.get_node_or_null("Segment%d" % i)
|
||||||
|
if seg: stop_segments.append(seg)
|
||||||
|
|
||||||
|
# Prepare styles
|
||||||
|
lit_style = StyleBoxFlat.new()
|
||||||
|
lit_style.bg_color = Color.YELLOW
|
||||||
|
lit_style.border_width_left = 2
|
||||||
|
lit_style.border_width_top = 2
|
||||||
|
lit_style.border_width_right = 2
|
||||||
|
lit_style.border_width_bottom = 2
|
||||||
|
lit_style.border_color = Color(1.0, 1.0, 1.0, 0.5)
|
||||||
|
|
||||||
|
dim_style = StyleBoxFlat.new()
|
||||||
|
dim_style.bg_color = Color(0.1, 0.1, 0.1, 0.8) # Dark dim
|
||||||
|
|
||||||
|
red_style = StyleBoxFlat.new()
|
||||||
|
red_style.bg_color = Color.RED
|
||||||
|
red_style.border_width_left = 2
|
||||||
|
red_style.border_width_top = 2
|
||||||
|
red_style.border_width_right = 2
|
||||||
|
red_style.border_width_bottom = 2
|
||||||
|
red_style.border_color = Color(1.0, 0.5, 0.5, 0.5)
|
||||||
|
|
||||||
|
if not stop_timer_node: return
|
||||||
|
|
||||||
|
# ALWAYS VISIBLE in Stop n Go mode
|
||||||
|
stop_timer_node.visible = true
|
||||||
|
|
||||||
|
if current_phase == Phase.GO:
|
||||||
|
# GO Phase: All dim unless in last 4 seconds
|
||||||
|
for i in range(stop_segments.size()):
|
||||||
|
var threshold = 4.0 - i
|
||||||
|
if phase_timer <= threshold:
|
||||||
|
stop_segments[i].add_theme_stylebox_override("panel", lit_style)
|
||||||
|
else:
|
||||||
|
stop_segments[i].add_theme_stylebox_override("panel", dim_style)
|
||||||
|
else:
|
||||||
|
# STOP Phase: All Red
|
||||||
|
for seg in stop_segments:
|
||||||
|
seg.add_theme_stylebox_override("panel", red_style)
|
||||||
|
|
||||||
func activate_client_side():
|
func activate_client_side():
|
||||||
is_active = true
|
is_active = true
|
||||||
if hud_layer:
|
if hud_layer:
|
||||||
@@ -355,7 +420,7 @@ func check_win_condition(player_id: int, position: Vector2i) -> bool:
|
|||||||
if position.x < finish_line_x:
|
if position.x < finish_line_x:
|
||||||
return false
|
return false
|
||||||
|
|
||||||
# 2. Must have 3 Goal Completions (tracked by GoalsCycleManager)
|
# 2. Must have enough Goal Completions (tracked by GoalsCycleManager)
|
||||||
var main = get_node_or_null("/root/Main")
|
var main = get_node_or_null("/root/Main")
|
||||||
if not main: return false
|
if not main: return false
|
||||||
|
|
||||||
@@ -364,14 +429,14 @@ func check_win_condition(player_id: int, position: Vector2i) -> bool:
|
|||||||
|
|
||||||
var completed_count = goals_cycle_manager.player_goal_counts.get(player_id, 0)
|
var completed_count = goals_cycle_manager.player_goal_counts.get(player_id, 0)
|
||||||
|
|
||||||
if completed_count >= 3:
|
if completed_count >= REQUIRED_GOALS:
|
||||||
print("[StopNGo] Player %d REACHED FINISH with %d goals complete!" % [player_id, completed_count])
|
print("[StopNGo] Player %d REACHED FINISH with %d goals complete!" % [player_id, completed_count])
|
||||||
return true
|
return true
|
||||||
else:
|
else:
|
||||||
# Inform the player locally if they reach the end without goals
|
# Inform the player locally if they reach the end without goals
|
||||||
var player_node = main.get_node_or_null(str(player_id))
|
var player_node = main.get_node_or_null(str(player_id))
|
||||||
if player_node:
|
if player_node:
|
||||||
NotificationManager.send_message(player_node, "Incomplete! Achieve 3 goals (x3) to win!", NotificationManager.MessageType.WARNING)
|
NotificationManager.send_message(player_node, "Incomplete! Achieve %d goals (x%d) to win!" % [REQUIRED_GOALS, REQUIRED_GOALS], NotificationManager.MessageType.WARNING)
|
||||||
|
|
||||||
print("[StopNGo] Player %d reached finish but goal count too low: %d/3" % [player_id, completed_count])
|
print("[StopNGo] Player %d reached finish but goal count too low: %d/%d" % [player_id, completed_count, REQUIRED_GOALS])
|
||||||
return false
|
return false
|
||||||
|
|||||||
Reference in New Issue
Block a user