Carry signed index, object id, and raw spell word losslessly through PlayerDescription, session storage, drag mutation, and AddShortcut wire serialization while keeping gmToolbarUI object-only. Retire AP-103 and record the live ACE relog persistence gate. Co-Authored-By: Codex <codex@openai.com>
189 lines
9 KiB
C#
189 lines
9 KiB
C#
using AcDream.App.Rendering;
|
|
using AcDream.App.UI;
|
|
using AcDream.App.UI.Layout;
|
|
using AcDream.Core.Items;
|
|
using DatReaderWriter;
|
|
|
|
namespace AcDream.App.Studio;
|
|
|
|
/// <summary>
|
|
/// Populates a loaded panel with sample data by calling the production
|
|
/// controller Bind methods against <see cref="SampleData"/>.
|
|
///
|
|
/// <para>
|
|
/// 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.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// <b>IconIds approach:</b> raw-resolve stub — resolve the base <c>iconId</c>
|
|
/// via <see cref="RenderStack.ResolveChrome"/> and return the GL handle
|
|
/// directly. This is intentionally simpler than GameWindow's full
|
|
/// <see cref="IconComposer"/> (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.
|
|
/// </para>
|
|
/// </summary>
|
|
public static class FixtureProvider
|
|
{
|
|
/// <summary>
|
|
/// Populate <paramref name="layout"/> with sample data appropriate for
|
|
/// <paramref name="layoutId"/>. Calls the production controller Bind
|
|
/// methods so the panel's widgets drive off the same code path as the
|
|
/// full game.
|
|
/// </summary>
|
|
/// <param name="layoutId">The LayoutDesc dat id used to import the layout.</param>
|
|
/// <param name="layout">The imported layout whose widgets to populate.</param>
|
|
/// <param name="stack">The live render stack (for <see cref="RenderStack.ResolveChrome"/>
|
|
/// and <see cref="RenderStack.VitalsDatFont"/>).</param>
|
|
/// <param name="objects">A <see cref="ClientObjectTable"/> already seeded by
|
|
/// <see cref="SampleData.BuildObjectTable"/>.</param>
|
|
/// <param name="dats">The live DAT collection used to resolve per-list empty-slot sprites
|
|
/// (same lookup GameWindow.OnLoad performs for the production binding).</param>
|
|
public static void Populate(
|
|
uint layoutId,
|
|
ImportedLayout layout,
|
|
RenderStack stack,
|
|
ClientObjectTable objects,
|
|
DatCollection 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");
|
|
break;
|
|
|
|
case RadarController.LayoutId: // gmRadarUI
|
|
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);
|
|
break;
|
|
|
|
case 0x21000016u: // toolbar
|
|
ToolbarController.Bind(
|
|
layout,
|
|
objects,
|
|
shortcuts: () => System.Array.Empty<ShortcutEntry>(),
|
|
iconIds: MakeIconIds(stack),
|
|
useItem: _ => { },
|
|
combatState: null,
|
|
peaceDigits: null,
|
|
warDigits: null,
|
|
emptyDigits: null,
|
|
sendAddShortcut: null,
|
|
sendRemoveShortcut: null);
|
|
break;
|
|
|
|
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 iconIds = MakeIconIds(stack);
|
|
var selection = new AcDream.Core.Selection.SelectionState();
|
|
|
|
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 (same imported subtree as the inventory).
|
|
// Mirrors GameWindow:2257-2265: PaperdollController.Bind with contentsEmpty as
|
|
// emptySlotSprite (each slot shows the same square-frame placeholder as the grid).
|
|
PaperdollController.Bind(
|
|
layout,
|
|
objects,
|
|
playerGuid: () => SampleData.PlayerGuid,
|
|
iconIds: iconIds,
|
|
selection: selection,
|
|
sendWield: null, // no live session in the studio
|
|
emptySlotSprite: contentsEmpty,
|
|
datFont: stack.VitalsDatFont);
|
|
break;
|
|
}
|
|
|
|
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);
|
|
break;
|
|
|
|
default:
|
|
// Unknown layout — no-op; the panel renders structurally.
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ── Helpers ─────────────────────────────────────────────────────────────
|
|
|
|
/// <summary>
|
|
/// Build the <c>iconIds</c> delegate for toolbar / inventory controllers.
|
|
///
|
|
/// <para>
|
|
/// Raw-resolve stub: resolve the base <paramref name="iconId"/> (arg 2)
|
|
/// via <see cref="RenderStack.ResolveChrome"/> 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.
|
|
/// </para>
|
|
///
|
|
/// <para>This is what the task spec calls "v1 raw-resolve stub".</para>
|
|
/// </summary>
|
|
private static Func<ItemType, uint, uint, uint, uint, uint> 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);
|
|
|
|
}
|