* chore: add battleground UI task tracker (2026-03-28) * feat(#142): add [NonSerialized] battleground runtime state to CardModel * feat(#143): add LoadArtCrop and LoadCardRaw (RawImage) to ImageCache * feat(#148): add GameStateManager singleton and GameState/SeatState POCOs * feat(#138): add BattlegroundController scene orchestrator * feat(#139): add LifeTrackerWidget, CommanderDamageRow, PoisonCounter * feat(#140): add PhaseTrackerWidget and PhaseStepButton * feat(#141): add TurnOrderBar and TurnOrderPill * feat(#142): add CardZone and CardView battlefield zone components * feat(#143): add CommanderZoneWidget with art crop, cast counter, and tax display * feat(#144): add TokenModel POCO, TokenPanelController, and TokenRowView * chore: mark issues 1-8 as complete in task tracker * feat(#146): add StackZoneController, StackItemView, and ManaPoolDisplay * feat(#147 #150 #151): add LobbySetupModal, ZoneViewerModal, EliminationHandler; add Play Live button to SimulationController * feat(#151): add Play Live button to SimulationController * feat(#149): add GameWebSocketClient with reconnect, ping, and main-thread dispatch * fix: add PostGameAction to ApiClient + update TODO.md all issues complete - ApiClient.cs: add PostGameAction() wrapper → POST /api/game/action (fixes compile error in HumanActionBar.cs) - TODO.md: mark issues #145, #146, #147, #149, #150, #151 as ✅ all 14 battleground issues now committed * feat(game): add routes/game.py + wire lab_api + fix GameWebSocketClient game_id URL - routes/game.py: POST /api/game/start, POST /api/game/action (new) - lab_api.py: import + register game_router - GameWebSocketClient.cs: read game_id from PlayerPrefs, build ws://{host}/ws/game/{game_id} — fixes URL mismatch with ws_game.py * 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
118 lines
4.7 KiB
C#
118 lines
4.7 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using TMPro;
|
||
using CommanderAILab.Models;
|
||
|
||
namespace CommanderAILab.UI
|
||
{
|
||
/// <summary>
|
||
/// A single token row in TokenPanelController's list.
|
||
/// Supports qty +/-, tap all, counter editing, and removal.
|
||
/// </summary>
|
||
public class TokenRowView : MonoBehaviour
|
||
{
|
||
[Header("Labels")]
|
||
[SerializeField] private TMP_Text nameLabel;
|
||
[SerializeField] private TMP_Text ptLabel;
|
||
[SerializeField] private TMP_Text qtyLabel;
|
||
[SerializeField] private TMP_Text tappedLabel;
|
||
|
||
[Header("Buttons")]
|
||
[SerializeField] private Button qtyPlusBtn;
|
||
[SerializeField] private Button qtyMinusBtn;
|
||
[SerializeField] private Button tapAllBtn;
|
||
[SerializeField] private Button deleteBtn;
|
||
|
||
[Header("Counter Editor")]
|
||
[SerializeField] private GameObject counterEditorPanel;
|
||
[SerializeField] private TMP_Dropdown counterTypeDropdown;
|
||
[SerializeField] private Button counterAddBtn;
|
||
[SerializeField] private Button counterRemoveBtn;
|
||
[SerializeField] private TMP_Text counterSummaryLabel;
|
||
[SerializeField] private Button toggleCounterEditorBtn;
|
||
|
||
private static readonly List<string> CounterTypes =
|
||
new() { "+1/+1", "-1/-1", "loyalty", "charge", "quest", "time" };
|
||
|
||
private TokenModel _token;
|
||
private Action<TokenModel> _onDelete;
|
||
|
||
// ── Binding ───────────────────────────────────────────
|
||
public void Bind(TokenModel token, Action<TokenModel> onDelete)
|
||
{
|
||
_token = token;
|
||
_onDelete = onDelete;
|
||
|
||
// Wire buttons
|
||
qtyPlusBtn ?.onClick.AddListener(() => AdjustQty(+1));
|
||
qtyMinusBtn?.onClick.AddListener(() => AdjustQty(-1));
|
||
tapAllBtn ?.onClick.AddListener(TapAll);
|
||
deleteBtn ?.onClick.AddListener(() => _onDelete?.Invoke(_token));
|
||
toggleCounterEditorBtn?.onClick.AddListener(ToggleCounterEditor);
|
||
counterAddBtn ?.onClick.AddListener(() => AdjustCounter(+1));
|
||
counterRemoveBtn?.onClick.AddListener(() => AdjustCounter(-1));
|
||
|
||
// Populate counter type dropdown
|
||
if (counterTypeDropdown != null)
|
||
{
|
||
counterTypeDropdown.ClearOptions();
|
||
counterTypeDropdown.AddOptions(CounterTypes);
|
||
}
|
||
|
||
if (counterEditorPanel) counterEditorPanel.SetActive(false);
|
||
RefreshDisplay();
|
||
}
|
||
|
||
// ── Actions ────────────────────────────────────────────
|
||
private void AdjustQty(int delta)
|
||
{
|
||
_token.qty = Mathf.Max(0, _token.qty + delta);
|
||
if (_token.qty == 0) { _onDelete?.Invoke(_token); return; }
|
||
RefreshDisplay();
|
||
}
|
||
|
||
private void TapAll()
|
||
{
|
||
_token.isTapped = !_token.isTapped;
|
||
RefreshDisplay();
|
||
}
|
||
|
||
private void ToggleCounterEditor()
|
||
{
|
||
if (counterEditorPanel)
|
||
counterEditorPanel.SetActive(!counterEditorPanel.activeSelf);
|
||
}
|
||
|
||
private void AdjustCounter(int delta)
|
||
{
|
||
if (counterTypeDropdown == null) return;
|
||
string counterType = CounterTypes[Mathf.Clamp(counterTypeDropdown.value, 0, CounterTypes.Count - 1)];
|
||
_token.counters.TryGetValue(counterType, out int current);
|
||
int next = Mathf.Max(0, current + delta);
|
||
if (next == 0) _token.counters.Remove(counterType);
|
||
else _token.counters[counterType] = next;
|
||
RefreshDisplay();
|
||
}
|
||
|
||
// ── Display ────────────────────────────────────────────
|
||
private void RefreshDisplay()
|
||
{
|
||
if (nameLabel) nameLabel.text = _token.name;
|
||
if (ptLabel) ptLabel.text = _token.PTString;
|
||
if (qtyLabel) qtyLabel.text = $"×{_token.qty}";
|
||
if (tappedLabel) tappedLabel.text = _token.isTapped ? "[Tapped]" : "";
|
||
|
||
// Counter summary
|
||
if (counterSummaryLabel)
|
||
{
|
||
var parts = new System.Text.StringBuilder();
|
||
foreach (var kv in _token.counters)
|
||
parts.Append($"{kv.Key}:{kv.Value} ");
|
||
counterSummaryLabel.text = parts.Length > 0 ? parts.ToString().Trim() : "No counters";
|
||
}
|
||
}
|
||
}
|
||
}
|