fix(lobby): wire OnConfirm to POST /api/game/start, save game_id to PlayerPrefs

- OnConfirm() now calls ApiClient.PostGameStart() with 4-seat config
- On success: saves BattleSeats + GameId + WsUrl to PlayerPrefs,
  loads Battleground scene
- On failure: shows inline error label (red), re-enables Confirm button
- Added StartGameRequest/Response POCOs matching routes/game.py schema
- Added _errorLabel [SerializeField] for user-facing error feedback
This commit is contained in:
KoalaTrapLord committed 2026-03-29 05:22:04 -04:00
1 parent d8238ec11f
commit 2c67222de1
1 file changed
+101 -3
@@ -12,7 +12,10 @@ namespace CommanderAILab.UI
/// <summary>
/// Modal shown at Battleground scene start.
/// Configures 4 seats (Human vs AI, deck choice, AI style).
/// On Confirm writes seat config to PlayerPrefs["BattleSeats"] and starts the game.
/// On Confirm:
/// 1. POSTs seat config to POST /api/game/start
/// 2. Saves game_id + ws_url to PlayerPrefs
/// 3. Loads Battleground scene
/// </summary>
public class LobbySetupModal : MonoBehaviour
{
@@ -23,6 +26,9 @@ namespace CommanderAILab.UI
[SerializeField] private Button confirmButton;
[SerializeField] private Button cancelButton;
[Header("Feedback")]
[SerializeField] private TMP_Text errorLabel; // assign in Inspector; hidden by default
[SerializeField] private ApiClient apiClient;
private List<string> _deckNames = new();
@@ -31,6 +37,8 @@ namespace CommanderAILab.UI
public void Open(string savedSeatsJson)
{
gameObject.SetActive(true);
SetError(null);
confirmButton.onClick.AddListener(OnConfirm);
cancelButton.onClick.AddListener(() => SceneManager.LoadScene("MainMenu"));
@@ -64,19 +72,83 @@ namespace CommanderAILab.UI
}
}
// ── Confirm ─────────────────────────────────────────────
// ── Confirm → POST /api/game/start ──────────────────────
private void OnConfirm()
{
SetError(null);
confirmButton.interactable = false;
// Build local seat list
var configs = new List<SeatConfig>();
foreach (var row in seatRows)
configs.Add(row?.GetConfig() ?? new SeatConfig());
// Save locally so Battleground scene can read seat metadata
PlayerPrefs.SetString("BattleSeats", JsonConvert.SerializeObject(configs));
PlayerPrefs.Save();
gameObject.SetActive(false);
// Build backend request — maps local SeatConfig → StartGameRequest
var apiSeats = new List<ApiSeatConfig>();
for (int i = 0; i < configs.Count; i++)
{
var c = configs[i];
apiSeats.Add(new ApiSeatConfig
{
seat_index = i,
seat_type = c.isHuman ? "Human" : $"AI {c.aiStyle}",
deck_name = c.deckName ?? "",
player_name = c.isHuman ? "Player" : $"AI {i}",
});
}
var requestJson = JsonConvert.SerializeObject(new StartGameRequest { seats = apiSeats });
apiClient.PostGameStart(
requestJson,
onSuccess: responseJson =>
{
try
{
var resp = JsonConvert.DeserializeObject<StartGameResponse>(responseJson);
if (resp == null || string.IsNullOrEmpty(resp.game_id))
{
OnStartError("Server returned an invalid response.");
return;
}
// Persist game_id and ws_url so GameWebSocketClient can connect
PlayerPrefs.SetString("GameId", resp.game_id);
PlayerPrefs.SetString("WsUrl", resp.ws_url ?? $"/ws/game/{resp.game_id}");
PlayerPrefs.Save();
gameObject.SetActive(false);
SceneManager.LoadScene("Battleground");
}
catch (Exception ex)
{
OnStartError($"Parse error: {ex.Message}");
}
},
onError: err => OnStartError($"Failed to start game: {err}"));
}
private void OnStartError(string message)
{
Debug.LogError("[LobbySetupModal] " + message);
SetError(message);
confirmButton.interactable = true;
}
private void SetError(string message)
{
if (errorLabel == null) return;
errorLabel.text = message ?? string.Empty;
errorLabel.gameObject.SetActive(!string.IsNullOrEmpty(message));
}
// ── Models ─────────────────────────────────────────────
/// <summary>Local / PlayerPrefs representation of a seat.</summary>
[Serializable]
public class SeatConfig
{
@@ -85,6 +157,32 @@ namespace CommanderAILab.UI
public string aiStyle; // Aggro / Control / Combo / Political
}
/// <summary>Matches routes/game.py SeatConfig schema.</summary>
[Serializable]
private class ApiSeatConfig
{
public int seat_index;
public string seat_type;
public string deck_name;
public string player_name;
}
/// <summary>Matches routes/game.py StartGameRequest schema.</summary>
[Serializable]
private class StartGameRequest
{
public List<ApiSeatConfig> seats;
}
/// <summary>Matches routes/game.py StartGameResponse schema.</summary>
[Serializable]
private class StartGameResponse
{
public string game_id;
public string status;
public string ws_url;
}
[Serializable] private class DecksWrapper { public List<DeckEntry> decks; }
[Serializable] private class DeckEntry { public string name; }
}