// Assets/Editor/CreateBattlegroundScene.cs // Unity menu: Commander AI Lab ▶ Create Battleground Scene // Builds the entire Battleground scene hierarchy and wires every SerializeField. // Run once on an empty scene, then hit Play. using System.Collections.Generic; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; using TMPro; using CommanderAILab.UI; using CommanderAILab.Services; namespace CommanderAILab.Editor { public static class CreateBattlegroundScene { // ── Phase names in enum order (matches PhaseTrackerWidget.Phase) ────── private static readonly string[] PhaseNames = { "Untap", "Upkeep", "Draw", "Main1", "Begin Combat", "Attackers", "Blockers", "1st Strike", "Damage", "End Combat", "Main2", "End Step", "Cleanup" }; // ── Seat labels ─────────────────────────────────────────────────────── private static readonly string[] SeatNames = { "Bottom", "Right", "Top", "Left" }; // ───────────────────────────────────────────────────────────────────── [MenuItem("Commander AI Lab/Create Battleground Scene")] public static void Build() { // 1. Open a fresh scene if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) return; var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single); // 2. Services (DontDestroyOnLoad singletons — just need to exist in scene) var services = NewGO("Services"); var apiClient = services.AddComponent(); // GameWebSocketClient added below so BattlegroundController can find it var wsClientGO = NewGO("GameWebSocketClient", services.transform); // 3. GameStateManager singleton var gsmGO = NewGO("GameStateManager"); gsmGO.AddComponent(); // 4. EventSystem (required for all UI input) var esGO = NewGO("EventSystem"); esGO.AddComponent(); esGO.AddComponent(); // 5. Main Camera var camGO = NewGO("Main Camera"); var cam = camGO.AddComponent(); cam.clearFlags = CameraClearFlags.SolidColor; cam.backgroundColor = new Color(0.08f, 0.12f, 0.08f); // dark green cam.orthographic = true; cam.orthographicSize = 5f; cam.tag = "MainCamera"; camGO.AddComponent(); // 6. Root Canvas var canvasGO = NewGO("Canvas"); var canvas = canvasGO.AddComponent(); canvas.renderMode = RenderMode.ScreenSpaceOverlay; canvasGO.AddComponent(); canvasGO.AddComponent(); // ── 7. Seat Panels (4 RectTransforms around the board) ──────────── var seatsRoot = NewGO("SeatPanels", canvasGO.transform); var seatPanels = new RectTransform[4]; for (int i = 0; i < 4; i++) { var sp = NewGO($"SeatPanel_{SeatNames[i]}", seatsRoot.transform); var rt = sp.AddComponent(); var img = sp.AddComponent(); img.color = new Color(0f, 0f, 0f, 0.3f); StretchFill(rt); seatPanels[i] = rt; } // ── 8. Center panel ─────────────────────────────────────────────── var centerGO = NewGO("BattlefieldCenter", canvasGO.transform); StretchFill(centerGO.GetComponent() ?? centerGO.AddComponent()); // ── 9. PhaseTrackerWidget ───────────────────────────────────────── var ptwGO = NewGO("PhaseTracker", centerGO.transform); var ptWidget = ptwGO.AddComponent(); SetAnchored(ptwGO, new Vector2(0f, 0f), new Vector2(1f, 0.08f)); // bottom strip // -- Active player + turn number labels var aplGO = MakeTMPText("ActivePlayerLabel", ptwGO.transform, "Player's Turn", 18); var tnlGO = MakeTMPText("TurnNumberLabel", ptwGO.transform, "Turn 1", 16); // -- 13 PhaseStepButtons var stepButtons = new PhaseStepButton[13]; for (int i = 0; i < 13; i++) { var btnGO = NewGO($"PhaseBtn_{PhaseNames[i]}", ptwGO.transform); var img = btnGO.AddComponent(); img.color = new Color(0.2f, 0.2f, 0.2f); var psb = btnGO.AddComponent(); // PhaseStepButton.background + label — set via reflection (private SerializeFields) var labelGO = MakeTMPText("Label", btnGO.transform, PhaseNames[i], 11); SetPrivateField(psb, "background", img); SetPrivateField(psb, "label", labelGO.GetComponent()); stepButtons[i] = psb; } // Wire PhaseTrackerWidget SerializeFields SetPrivateField(ptWidget, "stepButtons", stepButtons); SetPrivateField(ptWidget, "activePlayerLabel", aplGO.GetComponent()); SetPrivateField(ptWidget, "turnNumberLabel", tnlGO.GetComponent()); // ── 10. TurnOrderBar ────────────────────────────────────────────── var tobGO = NewGO("TurnOrderBar", centerGO.transform); var tobBar = tobGO.AddComponent(); SetAnchored(tobGO, new Vector2(0f, 0.92f), new Vector2(1f, 1f)); // top strip // ActiveBorder (sliding highlight) var borderGO = NewGO("ActiveBorder", tobGO.transform); var borderRT = borderGO.AddComponent(); var borderImg = borderGO.AddComponent(); borderImg.color = new Color(1f, 0.85f, 0f, 0.35f); // 4 TurnOrderPills var pills = new TurnOrderPill[4]; for (int i = 0; i < 4; i++) { var pillGO = NewGO($"Pill_{i}", tobGO.transform); var pillImg = pillGO.AddComponent(); pillImg.color = new Color(0.25f, 0.25f, 0.25f); var pill = pillGO.AddComponent(); var nameLabel = MakeTMPText("PlayerName", pillGO.transform, $"Seat {i}", 13); var lifeLabel = MakeTMPText("Life", pillGO.transform, "40", 13); var youBadge = NewGO("YouBadge", pillGO.transform); MakeTMPText("YouText", youBadge.transform, "YOU", 11); youBadge.SetActive(i == 0); SetPrivateField(pill, "playerNameLabel", nameLabel.GetComponent()); SetPrivateField(pill, "lifeLabel", lifeLabel.GetComponent()); SetPrivateField(pill, "pillBackground", pillImg); SetPrivateField(pill, "youBadge", youBadge); pills[i] = pill; } SetPrivateField(tobBar, "pills", pills); SetPrivateField(tobBar, "activeBorder", borderRT); // ── 11. LobbySetupModal ─────────────────────────────────────────── var modalGO = NewGO("LobbySetupModal", canvasGO.transform); var modal = modalGO.AddComponent(); StretchFill(modalGO.GetComponent() ?? modalGO.AddComponent()); // Dark overlay panel var panelGO = NewGO("Panel", modalGO.transform); var panelImg = panelGO.AddComponent(); panelImg.color = new Color(0f, 0f, 0f, 0.85f); SetAnchored(panelGO, new Vector2(0.2f, 0.1f), new Vector2(0.8f, 0.9f)); MakeTMPText("Title", panelGO.transform, "Configure Seats", 24); // 4 seat rows var seatRows = new LobbyRowUI[4]; for (int i = 0; i < 4; i++) { var rowGO = NewGO($"SeatRow_{i}", panelGO.transform); rowGO.AddComponent(); var row = rowGO.AddComponent(); var seatLbl = MakeTMPText("SeatLabel", rowGO.transform, $"Seat {i}", 14); var togGO = MakeToggle("HumanToggle", rowGO.transform); var deckDD = MakeTMPDropdown("DeckDropdown", rowGO.transform); var styleDD = MakeTMPDropdown("AiStyleDropdown", rowGO.transform); SetPrivateField(row, "seatLabel", seatLbl.GetComponent()); SetPrivateField(row, "humanToggle", togGO.GetComponent()); SetPrivateField(row, "deckDropdown", deckDD.GetComponent()); SetPrivateField(row, "aiStyleDropdown", styleDD.GetComponent()); seatRows[i] = row; } // Error label (starts inactive) var errGO = MakeTMPText("ErrorLabel", panelGO.transform, "", 13); errGO.GetComponent().color = Color.red; errGO.SetActive(false); // Confirm + Cancel buttons var confirmBtn = MakeButton("ConfirmButton", panelGO.transform, "Confirm"); var cancelBtn = MakeButton("CancelButton", panelGO.transform, "Cancel"); // Wire LobbySetupModal SerializeFields SetPrivateField(modal, "seatRows", seatRows); SetPrivateField(modal, "confirmButton", confirmBtn.GetComponent