extends Node signal state_changed(state: String) signal online_game_ready(player_slot: int) signal online_snapshot_updated(snapshot: Dictionary) signal online_event_received(event: Dictionary) const PLAYER_COUNT := 2 const COOP_START_CREDITS := 450 const COOP_REACTOR_LIVES := 30 var matchmaking_state := "idle" var matchmaking_mode := "" var matchmaking_difficulty := 1 var private_room_code := "" var online_match = null var online_mode := false var player_slot := -1 var latest_snapshot: Dictionary = {} var player_credits := [COOP_START_CREDITS, COOP_START_CREDITS] var shared_reactor_lives := COOP_REACTOR_LIVES func reset_coop_session(start_credits := COOP_START_CREDITS, reactor_lives := COOP_REACTOR_LIVES) -> void: player_credits = [start_credits, start_credits] shared_reactor_lives = reactor_lives online_mode = false player_slot = -1 latest_snapshot = {} private_room_code = "" func spend_player_credits(player_index: int, amount: int) -> bool: if player_index < 0 or player_index >= PLAYER_COUNT or amount < 0: return false if player_credits[player_index] < amount: return false player_credits[player_index] -= amount return true func add_player_credits(player_index: int, amount: int) -> void: if player_index >= 0 and player_index < PLAYER_COUNT: player_credits[player_index] += maxi(0, amount) func damage_shared_reactor(amount: int) -> void: shared_reactor_lives = maxi(0, shared_reactor_lives - maxi(0, amount)) func _ready() -> void: call_deferred("_bind_online_manager") func _bind_online_manager() -> void: var online_manager = get_parent().get_node_or_null("OnlineManager") if not online_manager: return if not online_manager.matchmaking_state_changed.is_connected(_on_online_matchmaking_state_changed): online_manager.matchmaking_state_changed.connect(_on_online_matchmaking_state_changed) if not online_manager.match_found.is_connected(_on_online_match_found): online_manager.match_found.connect(_on_online_match_found) if not online_manager.match_snapshot_received.is_connected(_on_online_snapshot): online_manager.match_snapshot_received.connect(_on_online_snapshot) if not online_manager.match_event_received.is_connected(_on_online_event): online_manager.match_event_received.connect(_on_online_event) func begin_search(mode := "quick", difficulty := 1) -> void: matchmaking_mode = mode matchmaking_difficulty = clampi(difficulty, 0, 2) online_match = null online_mode = false player_slot = -1 latest_snapshot = {} matchmaking_state = "waiting_for_service" state_changed.emit(matchmaking_state) call_deferred("_begin_online_search", mode) func create_private_room(difficulty := 1) -> void: matchmaking_mode = "private_host" matchmaking_difficulty = clampi(difficulty, 0, 2) private_room_code = "" online_match = null online_mode = false player_slot = -1 latest_snapshot = {} matchmaking_state = "waiting_for_service" state_changed.emit(matchmaking_state) call_deferred("_create_private_room_online") func join_private_room(room_code: String) -> void: matchmaking_mode = "private_join" private_room_code = room_code.to_upper().strip_edges() online_match = null online_mode = false player_slot = -1 latest_snapshot = {} matchmaking_state = "waiting_for_service" state_changed.emit(matchmaking_state) call_deferred("_join_private_room_online") func cancel_search() -> void: var online_manager = get_parent().get_node_or_null("OnlineManager") if online_manager: online_manager.cancel_matchmaking() matchmaking_mode = "" private_room_code = "" matchmaking_state = "idle" state_changed.emit(matchmaking_state) func _begin_online_search(mode: String) -> void: if matchmaking_state == "idle" or matchmaking_mode != mode: return var online_manager = get_parent().get_node_or_null("OnlineManager") if not online_manager: matchmaking_state = "offline" state_changed.emit(matchmaking_state) return if not online_manager.is_authenticated(): await online_manager.connect_and_authenticate() if matchmaking_state == "idle" or matchmaking_mode != mode: return var started: bool = await online_manager.begin_matchmaking(mode, matchmaking_difficulty) matchmaking_state = "searching" if started else "offline" state_changed.emit(matchmaking_state) func _ensure_online_manager(): var online_manager = get_parent().get_node_or_null("OnlineManager") if not online_manager: matchmaking_state = "offline" state_changed.emit(matchmaking_state) return null if not online_manager.is_authenticated(): await online_manager.connect_and_authenticate() return online_manager func _create_private_room_online() -> void: var online_manager = await _ensure_online_manager() if not online_manager or matchmaking_mode != "private_host": return var created: bool = await online_manager.create_private_room(matchmaking_difficulty) private_room_code = online_manager.private_room_code if created else "" matchmaking_state = "hosting" if created else online_manager.connection_state state_changed.emit(matchmaking_state) func _join_private_room_online() -> void: var online_manager = await _ensure_online_manager() if not online_manager or matchmaking_mode != "private_join": return var joined: bool = await online_manager.join_private_room(private_room_code) matchmaking_state = "matched" if joined else "room_not_found" state_changed.emit(matchmaking_state) func _on_online_matchmaking_state_changed(value: String) -> void: if matchmaking_state == "idle": return matchmaking_state = value var online_manager = get_parent().get_node_or_null("OnlineManager") if online_manager and not online_manager.private_room_code.is_empty(): private_room_code = online_manager.private_room_code state_changed.emit(matchmaking_state) func _on_online_match_found(match_data) -> void: online_match = match_data online_mode = true matchmaking_state = "matched" state_changed.emit(matchmaking_state) func _on_online_snapshot(snapshot: Dictionary) -> void: latest_snapshot = snapshot var credits = snapshot.get("credits", []) if credits is Array and credits.size() >= PLAYER_COUNT: player_credits = [int(credits[0]), int(credits[1])] shared_reactor_lives = int(snapshot.get("reactor_lives", shared_reactor_lives)) var online_manager = get_parent().get_node_or_null("OnlineManager") if online_manager and online_manager.session: var slots: Dictionary = snapshot.get("slots", {}) var resolved_slot := int(slots.get(online_manager.session.user_id, -1)) if resolved_slot >= 0 and resolved_slot != player_slot: player_slot = resolved_slot if bool(snapshot.get("started", false)) and player_slot >= 0 and not online_mode: online_mode = true online_game_ready.emit(player_slot) elif bool(snapshot.get("started", false)) and player_slot >= 0 and matchmaking_state == "matched": matchmaking_state = "playing" state_changed.emit(matchmaking_state) online_game_ready.emit(player_slot) online_snapshot_updated.emit(snapshot) func _on_online_event(event: Dictionary) -> void: online_event_received.emit(event) func mirrored_point(point: Vector2, arena_width := 1600.0) -> Vector2: return Vector2(arena_width - point.x, point.y) func mirrored_path(points: PackedVector2Array, arena_width := 1600.0) -> PackedVector2Array: var result := PackedVector2Array() for point in points: result.append(mirrored_point(point, arena_width)) return result