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:
1 parent
d8238ec11f
commit
2c67222de1
1 file changed
+101
-3
@@ -12,7 +12,10 @@ namespace CommanderAILab.UI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Modal shown at Battleground scene start.
|
/// Modal shown at Battleground scene start.
|
||||||
/// Configures 4 seats (Human vs AI, deck choice, AI style).
|
/// 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>
|
/// </summary>
|
||||||
public class LobbySetupModal : MonoBehaviour
|
public class LobbySetupModal : MonoBehaviour
|
||||||
{
|
{
|
||||||
@@ -23,6 +26,9 @@ namespace CommanderAILab.UI
|
|||||||
[SerializeField] private Button confirmButton;
|
[SerializeField] private Button confirmButton;
|
||||||
[SerializeField] private Button cancelButton;
|
[SerializeField] private Button cancelButton;
|
||||||
|
|
||||||
|
[Header("Feedback")]
|
||||||
|
[SerializeField] private TMP_Text errorLabel; // assign in Inspector; hidden by default
|
||||||
|
|
||||||
[SerializeField] private ApiClient apiClient;
|
[SerializeField] private ApiClient apiClient;
|
||||||
|
|
||||||
private List<string> _deckNames = new();
|
private List<string> _deckNames = new();
|
||||||
@@ -31,6 +37,8 @@ namespace CommanderAILab.UI
|
|||||||
public void Open(string savedSeatsJson)
|
public void Open(string savedSeatsJson)
|
||||||
{
|
{
|
||||||
gameObject.SetActive(true);
|
gameObject.SetActive(true);
|
||||||
|
SetError(null);
|
||||||
|
|
||||||
confirmButton.onClick.AddListener(OnConfirm);
|
confirmButton.onClick.AddListener(OnConfirm);
|
||||||
cancelButton.onClick.AddListener(() => SceneManager.LoadScene("MainMenu"));
|
cancelButton.onClick.AddListener(() => SceneManager.LoadScene("MainMenu"));
|
||||||
|
|
||||||
@@ -64,19 +72,83 @@ namespace CommanderAILab.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Confirm ─────────────────────────────────────────────
|
// ── Confirm → POST /api/game/start ──────────────────────
|
||||||
private void OnConfirm()
|
private void OnConfirm()
|
||||||
{
|
{
|
||||||
|
SetError(null);
|
||||||
|
confirmButton.interactable = false;
|
||||||
|
|
||||||
|
// Build local seat list
|
||||||
var configs = new List<SeatConfig>();
|
var configs = new List<SeatConfig>();
|
||||||
foreach (var row in seatRows)
|
foreach (var row in seatRows)
|
||||||
configs.Add(row?.GetConfig() ?? new SeatConfig());
|
configs.Add(row?.GetConfig() ?? new SeatConfig());
|
||||||
|
|
||||||
|
// Save locally so Battleground scene can read seat metadata
|
||||||
PlayerPrefs.SetString("BattleSeats", JsonConvert.SerializeObject(configs));
|
PlayerPrefs.SetString("BattleSeats", JsonConvert.SerializeObject(configs));
|
||||||
PlayerPrefs.Save();
|
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 ─────────────────────────────────────────────
|
// ── Models ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// <summary>Local / PlayerPrefs representation of a seat.</summary>
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class SeatConfig
|
public class SeatConfig
|
||||||
{
|
{
|
||||||
@@ -85,6 +157,32 @@ namespace CommanderAILab.UI
|
|||||||
public string aiStyle; // Aggro / Control / Combo / Political
|
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 DecksWrapper { public List<DeckEntry> decks; }
|
||||||
[Serializable] private class DeckEntry { public string name; }
|
[Serializable] private class DeckEntry { public string name; }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user