266 lines
8.7 KiB
GDScript
266 lines
8.7 KiB
GDScript
extends Node
|
|
class_name StopNGoManager
|
|
|
|
# StopNGoManager - Handles phase transitions, missions, and movement penalties
|
|
|
|
signal phase_changed(new_phase: String, remaining_time: float)
|
|
signal mission_status_updated(player_id: int, completed: bool)
|
|
signal player_penalized(player_id: int)
|
|
|
|
enum Phase {GO, STOP}
|
|
|
|
const GO_DURATION: float = 12.0
|
|
const STOP_DURATION: float = 6.0
|
|
|
|
var current_phase: Phase = Phase.GO
|
|
var phase_timer: float = GO_DURATION
|
|
var is_active: bool = false
|
|
|
|
var player_missions: Dictionary = {} # player_id -> {tile_id: count, current: count}
|
|
var finish_line_x: int = 21 # Right side of the map for win condition
|
|
|
|
# Tile IDs (Assumed based on analysis)
|
|
const TILE_WALKABLE = 0
|
|
const TILE_SAFE = 2 # Green Safe Zone
|
|
const TILE_OBSTACLE = 4 # Black Obstacle
|
|
|
|
var hud_layer: CanvasLayer
|
|
var phase_label: Label
|
|
var mission_label: Label
|
|
|
|
func _ready():
|
|
set_process(false)
|
|
_setup_hud()
|
|
|
|
func _setup_hud():
|
|
hud_layer = CanvasLayer.new()
|
|
hud_layer.visible = false
|
|
add_child(hud_layer)
|
|
|
|
var vbox = VBoxContainer.new()
|
|
vbox.set_anchors_preset(Control.PRESET_TOP_RIGHT)
|
|
vbox.offset_right = -20
|
|
vbox.offset_top = 100
|
|
hud_layer.add_child(vbox)
|
|
|
|
# Style for HUD
|
|
var style = StyleBoxFlat.new()
|
|
style.bg_color = Color(0, 0, 0, 0.4)
|
|
style.content_margin_left = 10
|
|
style.content_margin_top = 10
|
|
style.content_margin_right = 10
|
|
style.content_margin_bottom = 10
|
|
|
|
var panel = PanelContainer.new()
|
|
panel.add_theme_stylebox_override("panel", style)
|
|
vbox.add_child(panel)
|
|
|
|
var inner_vbox = VBoxContainer.new()
|
|
panel.add_child(inner_vbox)
|
|
|
|
phase_label = Label.new()
|
|
phase_label.text = "PHASE: GO"
|
|
phase_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
|
phase_label.add_theme_font_size_override("font_size", 32)
|
|
phase_label.add_theme_color_override("font_color", Color.GREEN)
|
|
inner_vbox.add_child(phase_label)
|
|
|
|
mission_label = Label.new()
|
|
mission_label.text = "MISSION: Collect 3 Hearts"
|
|
mission_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
|
inner_vbox.add_child(mission_label)
|
|
|
|
func _process(delta):
|
|
if not is_active:
|
|
return
|
|
|
|
if multiplayer.is_server():
|
|
phase_timer -= delta
|
|
if phase_timer <= 0:
|
|
if current_phase == Phase.GO:
|
|
_start_phase(Phase.STOP)
|
|
else:
|
|
_start_phase(Phase.GO)
|
|
|
|
# Update HUD locally
|
|
_update_hud_visuals()
|
|
|
|
func _update_hud_visuals():
|
|
var phase_name = "GO" if current_phase == Phase.GO else "STOP"
|
|
if phase_label:
|
|
phase_label.text = "PHASE: %s (%.0fs)" % [phase_name, max(0, phase_timer)]
|
|
phase_label.add_theme_color_override("font_color", Color.GREEN if current_phase == Phase.GO else Color.RED)
|
|
|
|
var my_id = multiplayer.get_unique_id()
|
|
if mission_label and player_missions.has(my_id):
|
|
var mission = player_missions[my_id]
|
|
mission_label.text = "Tiles: %d / %d" % [mission["current"], mission["required"]]
|
|
if mission["current"] >= mission["required"]:
|
|
mission_label.text = "MISSION COMPLETE! REACH FINISH!"
|
|
mission_label.add_theme_color_override("font_color", Color.GOLD)
|
|
|
|
func activate_client_side():
|
|
is_active = true
|
|
if hud_layer:
|
|
hud_layer.visible = true
|
|
set_process(true)
|
|
|
|
func start_game_mode():
|
|
activate_client_side()
|
|
if multiplayer.is_server():
|
|
_setup_arena()
|
|
_assign_missions()
|
|
_start_phase(Phase.GO)
|
|
|
|
func _start_phase(phase: Phase):
|
|
current_phase = phase
|
|
phase_timer = GO_DURATION if phase == Phase.GO else STOP_DURATION
|
|
|
|
var phase_name = "GO" if phase == Phase.GO else "STOP"
|
|
if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED:
|
|
rpc("sync_phase", phase_name, phase_timer)
|
|
emit_signal("phase_changed", phase_name, phase_timer)
|
|
|
|
@rpc("authority", "call_local", "reliable")
|
|
func sync_phase(phase_name: String, duration: float):
|
|
current_phase = Phase.GO if phase_name == "GO" else Phase.STOP
|
|
phase_timer = duration
|
|
|
|
func _setup_arena():
|
|
var gridmap = get_node("/root/Main/EnhancedGridMap")
|
|
if not gridmap: return
|
|
|
|
print("[StopNGo] Setting up 22x10 Accurate Horizontal Arena...")
|
|
# Set Size for Stop n Go
|
|
gridmap.columns = 22
|
|
gridmap.rows = 10
|
|
|
|
# Clear existing items on all layers
|
|
gridmap.clear()
|
|
|
|
# Create bands based on X (Horizontal Progress)
|
|
for x in range(gridmap.columns):
|
|
var tile_id = TILE_WALKABLE
|
|
# Green Safe Zones at X=7,8 and X=14,15
|
|
if x == 7 or x == 8 or x == 14 or x == 15:
|
|
tile_id = TILE_SAFE
|
|
|
|
for z in range(gridmap.rows):
|
|
gridmap.set_cell_item(Vector3i(x, 0, z), tile_id)
|
|
|
|
# Place Specific Obstacles (Black Bars) to match user images
|
|
# Lane 1 (X=2..6) - Vertical bar at X=4
|
|
for z in range(0, 4):
|
|
gridmap.set_cell_item(Vector3i(4, 0, z), TILE_OBSTACLE)
|
|
for z in range(6, 10):
|
|
gridmap.set_cell_item(Vector3i(4, 0, z), TILE_OBSTACLE)
|
|
|
|
# Lane 2 (X=9..13) - Vertical bar at X=11
|
|
for z in range(3, 7):
|
|
gridmap.set_cell_item(Vector3i(11, 0, z), TILE_OBSTACLE)
|
|
|
|
# Lane 3 (X=16..20) - L-shape at top
|
|
for z in range(0, 3):
|
|
gridmap.set_cell_item(Vector3i(18, 0, z), TILE_OBSTACLE)
|
|
gridmap.set_cell_item(Vector3i(19, 0, 2), TILE_OBSTACLE)
|
|
gridmap.set_cell_item(Vector3i(20, 0, 2), TILE_OBSTACLE)
|
|
|
|
# Another bar at bottom of Lane 3
|
|
for z in range(6, 9):
|
|
gridmap.set_cell_item(Vector3i(18, 0, z), TILE_OBSTACLE)
|
|
|
|
gridmap.update_grid_data()
|
|
gridmap.initialize_astar()
|
|
|
|
# Spawn tiles for mission (Heart = 7)
|
|
if multiplayer.is_server():
|
|
_spawn_mission_tiles()
|
|
|
|
# Sync the WHOLE grid to all clients to ensure size and stripes are correct
|
|
var main = get_node("/root/Main")
|
|
if main and multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED:
|
|
# Gather all floor 0 and floor 1 data
|
|
var floor0_data = gridmap.get_floor_data(0)
|
|
var floor1_data = gridmap.get_floor_data(1)
|
|
main.rpc("sync_full_grid_data_stop_n_go", floor0_data, floor1_data, gridmap.columns, gridmap.rows)
|
|
|
|
func _spawn_mission_tiles():
|
|
var gridmap = get_node("/root/Main/EnhancedGridMap")
|
|
var count = 0
|
|
while count < 40: # Spawn plenty of hearts
|
|
var x = randi() % gridmap.columns
|
|
var z = randi() % gridmap.rows
|
|
# Only spawn in walkable areas, not safe zones or start/finish
|
|
if gridmap.get_cell_item(Vector3i(x, 0, z)) == TILE_WALKABLE:
|
|
if gridmap.get_cell_item(Vector3i(x, 1, z)) == -1:
|
|
gridmap.set_cell_item(Vector3i(x, 1, z), ScarcityModel.TILE_HEART)
|
|
count += 1
|
|
|
|
func _assign_missions():
|
|
# Each player needs to collect 3 specific tiles (e.g. Heart)
|
|
var players = GameStateManager.players
|
|
for p_id in players:
|
|
player_missions[p_id] = {
|
|
"target_tile": ScarcityModel.TILE_HEART,
|
|
"required": 3,
|
|
"current": 0
|
|
}
|
|
if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED:
|
|
rpc("sync_missions", player_missions)
|
|
|
|
@rpc("authority", "call_local", "reliable")
|
|
func sync_missions(missions: Dictionary):
|
|
player_missions = missions
|
|
|
|
func check_movement_violation(player_id: int, from: Vector2i, to: Vector2i) -> bool:
|
|
"""Check if movement is illegal (during STOP phase and not in safe zone)."""
|
|
if current_phase == Phase.STOP:
|
|
var main = get_node("/root/Main")
|
|
var gridmap = main.get_node("EnhancedGridMap") if main else null
|
|
if gridmap:
|
|
var tile_from = gridmap.get_cell_item(Vector3i(from.x, 0, from.y))
|
|
if tile_from != TILE_SAFE:
|
|
_penalize_player(player_id)
|
|
return true
|
|
return false
|
|
|
|
func _penalize_player(player_id: int):
|
|
if not multiplayer.is_server(): return
|
|
|
|
var main = get_node("/root/Main")
|
|
if not main: return
|
|
|
|
var player_node = main.get_node_or_null(str(player_id))
|
|
if player_node:
|
|
if multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED:
|
|
player_node.rpc("on_stop_phase_violation")
|
|
emit_signal("player_penalized", player_id)
|
|
|
|
func update_mission_progress(player_id: int, tile_id: int):
|
|
if not player_missions.has(player_id): return
|
|
|
|
var mission = player_missions[player_id]
|
|
if tile_id == mission["target_tile"]:
|
|
mission["current"] = min(mission["current"] + 1, mission["required"])
|
|
|
|
if mission["current"] >= mission["required"]:
|
|
emit_signal("mission_status_updated", player_id, true)
|
|
|
|
if multiplayer.is_server() and multiplayer.has_multiplayer_peer() and multiplayer.multiplayer_peer.get_connection_status() == MultiplayerPeer.CONNECTION_CONNECTED:
|
|
rpc("sync_mission_progress", player_id, mission["current"])
|
|
|
|
@rpc("any_peer", "call_local", "reliable")
|
|
func sync_mission_progress(player_id: int, current: int):
|
|
if player_missions.has(player_id):
|
|
player_missions[player_id]["current"] = current
|
|
|
|
func check_win_condition(player_id: int, position: Vector2i) -> bool:
|
|
if not player_missions.has(player_id): return false
|
|
|
|
var mission = player_missions[player_id]
|
|
if mission["current"] >= mission["required"]:
|
|
# Win when reaching X >= 21
|
|
if position.x >= finish_line_x:
|
|
return true
|
|
return false
|