Files
Void-Bastion/multiplayer_manager.gd
T

241 lines
8.3 KiB
GDScript

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 local_ready := false
var lobby_players: Array = [null, null]
var reconnect_seconds := 0
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 = ""
local_ready = false
lobby_players = [null, null]
reconnect_seconds = 0
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 = ""
local_ready = false
lobby_players = [null, null]
matchmaking_state = "idle"
state_changed.emit(matchmaking_state)
func toggle_lobby_ready() -> void:
if player_slot < 0 or matchmaking_state not in ["hosting", "matched", "lobby", "reconnected"]:
return
local_ready = not local_ready
var online_manager = get_parent().get_node_or_null("OnlineManager")
if online_manager:
await online_manager.set_lobby_ready(local_ready)
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 = false
matchmaking_state = "matched"
state_changed.emit(matchmaking_state)
func _on_online_snapshot(snapshot: Dictionary) -> void:
latest_snapshot = snapshot
var players = snapshot.get("players", [])
if players is Array and players.size() >= PLAYER_COUNT:
lobby_players = players.duplicate(true)
reconnect_seconds = int(snapshot.get("reconnect_seconds", 0))
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 player_slot >= 0 and player_slot < lobby_players.size() and lobby_players[player_slot] is Dictionary:
local_ready = bool(lobby_players[player_slot].get("ready", false))
if not bool(snapshot.get("started", false)) and player_slot >= 0 and matchmaking_state in ["hosting", "matched", "reconnected"]:
matchmaking_state = "lobby"
state_changed.emit(matchmaking_state)
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