using AcDream.App.Rendering; using AcDream.App.UI; using AcDream.App.UI.Layout; using AcDream.Core.Combat; using AcDream.Core.Items; using AcDream.Runtime.Gameplay; using AcDream.UI.Abstractions.Panels.Settings; using DatReaderWriter; using AcDream.Content; namespace AcDream.App.Studio; /// /// Populates a loaded panel with sample data by calling the production /// controller Bind methods against . /// /// /// The studio is intentionally thin — there is no live game session, no /// server connection, and no network. FixtureProvider bridges that gap by /// feeding static fixtures (vitals percentages, a fake inventory, empty /// shortcut lists) so the bound widgets show plausible state instead of /// empty zeroes. /// /// /// /// IconIds approach: raw-resolve stub — resolve the base iconId /// via and return the GL handle /// directly. This is intentionally simpler than GameWindow's full /// (5-layer composite). The raw icon is enough /// to confirm the grid cells draw something in the studio; the full /// compositor is the live-game concern, not the layout preview concern. /// /// public static class FixtureProvider { /// /// Populate with sample data appropriate for /// . Calls the production controller Bind /// methods so the panel's widgets drive off the same code path as the /// full game. /// /// The LayoutDesc dat id used to import the layout. /// The imported layout whose widgets to populate. /// The live render stack (for /// and ). /// A already seeded by /// . /// The live DAT collection used to resolve per-list empty-slot sprites /// (same lookup GameWindow.OnLoad performs for the production binding). public static IRetainedPanelController? Populate( uint layoutId, ImportedLayout layout, RenderStack stack, ClientObjectTable objects, IDatReaderWriter dats) { switch (layoutId) { case 0x2100006Cu: // vitals VitalsController.Bind(layout, healthPct: () => SampleData.HealthPct, staminaPct: () => SampleData.StaminaPct, manaPct: () => SampleData.ManaPct, healthText: () => "80/100", staminaText: () => "60/100", manaText: () => "90/100"); return null; case RadarController.LayoutId: // gmRadarUI return RadarController.Bind( layout, () => new UiRadarSnapshot( PlayerHeadingDegrees: 32f, Blips: [ new UiRadarBlip(0x50000001u, "Portal to Holtburg", 82f, 43f, RadarColor(AcDream.Core.Ui.RadarBlipColors.Portal), AcDream.Core.Ui.RadarBlipShape.Plus), new UiRadarBlip(0x50000002u, "Drudge Prowler", 43f, 76f, RadarColor(AcDream.Core.Ui.RadarBlipColors.Creature), AcDream.Core.Ui.RadarBlipShape.Plus), new UiRadarBlip(0x50000003u, "Town Crier", 68f, 87f, RadarColor(AcDream.Core.Ui.RadarBlipColors.NPC), AcDream.Core.Ui.RadarBlipShape.Box), new UiRadarBlip(0x50000004u, "Selected PK", 39f, 39f, RadarColor(AcDream.Core.Ui.RadarBlipColors.PlayerKiller), AcDream.Core.Ui.RadarBlipShape.X, Selected: true), ], CoordinatesText: "42.1N,33.3E"), datFont: stack.VitalsDatFont); case CombatUiController.LayoutId: { var combat = new CombatState(); var attacks = new RuntimeCombatAttackState( combat, canStartAttack: () => true, sendAttack: (_, _) => true, autoRepeatAttack: () => false); GameplaySettings gameplay = GameplaySettings.Default; CombatUiController? controller = CombatUiController.Bind( layout, combat, attacks, () => gameplay, value => gameplay = value, new CombatUiLabels( "Speed", "Power", "Repeat Attacks", "Auto Target", "Keep in View", "High", "Medium", "Low"), visible => layout.Root.Visible = visible); combat.SetCombatMode(CombatMode.Melee); return controller; } case 0x21000016u: // toolbar return ToolbarController.Bind( layout, objects, shortcuts: new ShortcutStore(), iconIds: MakeIconIds(stack), useItem: _ => { }, combatState: null, regularDigits: null, ghostedDigits: null, emptyDigits: null, sendAddShortcut: null, sendRemoveShortcut: null); case 0x21000023u: // inventory + paperdoll { // Resolve the per-list empty-slot art from the dat cell template, matching the // exact lookup GameWindow.OnLoad performs (UIElement_ItemList::InternalCreateItem // 0x004e3570 → attr 0x1000000e → catalog 0x21000037 → ItemSlot_Empty). uint contentsEmpty = ItemListCellTemplate.ResolveEmptySprite(dats, 0x21000021u, 0x100001C6u); uint sideBagEmpty = ItemListCellTemplate.ResolveEmptySprite(dats, 0x21000022u, 0x100001CAu); uint mainPackEmpty = ItemListCellTemplate.ResolveEmptySprite(dats, 0x21000022u, 0x100001C9u); var paperdollEmpty = PaperdollSlotBackgrounds.ResolveEmptySprites(dats); var iconIds = MakeIconIds(stack); var selection = new AcDream.Core.Selection.SelectionState(); var itemInteraction = new ItemInteractionController( objects, new AcDream.Runtime.Gameplay.RuntimeInteractionTransactionState( new InventoryTransactionState(objects)), new AcDream.Runtime.Gameplay.InteractionState(), () => SampleData.PlayerGuid, sendUse: null, sendUseWithTarget: null, sendWield: null, sendDrop: null); InventoryController inventory = InventoryController.Bind( layout, objects, playerGuid: () => SampleData.PlayerGuid, iconIds: iconIds, strength: () => 100, selection: selection, datFont: stack.VitalsDatFont, contentsEmptySprite: contentsEmpty, sideBagEmptySprite: sideBagEmpty, mainPackEmptySprite: mainPackEmpty); // Bind the paperdoll equip slots with their authored per-location UIItem prototypes. PaperdollController paperdoll = PaperdollController.Bind( layout, objects, playerGuid: () => SampleData.PlayerGuid, iconIds: iconIds, selection: selection, itemInteraction: itemInteraction, emptySlotSprite: contentsEmpty, datFont: stack.VitalsDatFont, emptySlotSprites: paperdollEmpty, ownsItemInteraction: true); return new RetainedPanelControllerGroup(inventory, paperdoll); } case 0x2100002Eu: // gmStatManagementUI — Attributes/Skills/Titles window (LayoutDesc 0x2100002E) // Bind the REAL importer-mounted header + list elements (name/heritage/PK/level/ // total-XP/XP-meter + the 9-row attribute list + footer State-A). NOT the text-report // sub-panel (that is gmCharacterInfoUI 0x2100001A → CharacterController). // LargeDatFont (0x40000001, MaxCharHeight=18) is used for the attribute row text; // fallback to VitalsDatFont (0x40000000, 16px) if unavailable. CharacterStatController.Bind( layout, data: SampleData.SampleCharacter, datFont: stack.VitalsDatFont, rowDatFont: stack.LargeDatFont ?? stack.VitalsDatFont, spriteResolve: stack.ResolveChrome); return null; default: // Unknown layout — no-op; the panel renders structurally. return null; } } // ── Helpers ───────────────────────────────────────────────────────────── /// /// Build the iconIds delegate for toolbar / inventory controllers. /// /// /// Raw-resolve stub: resolve the base (arg 2) /// via and return its GL handle. /// The remaining args (type, underlayId, overlayId, effects) are ignored /// for the studio — a single-layer icon is sufficient for layout preview. /// /// /// This is what the task spec calls "v1 raw-resolve stub". /// private static Func MakeIconIds(RenderStack stack) => (_, iconId, _, _, _) => { if (iconId == 0u) return 0u; var (handle, _, _) = stack.ResolveChrome(iconId); return handle; }; private static System.Numerics.Vector4 RadarColor(AcDream.Core.Ui.RadarBlipColors.Rgba color) => new(color.Red, color.Green, color.Blue, color.Alpha); }