merge: integrate main (D.2b paperdoll/inventory UI line) into the physics/collision branch
Brings the 134 D.2b UI commits onto the physics/collision development line so main can fast-forward. Today's #149 (BSP-less static collision) + #150 (open doors fully passable) + the full collision/streaming/dense-town-FPS arc meet the paperdoll/inventory work. # Conflicts: # docs/ISSUES.md # docs/architecture/retail-divergence-register.md
This commit is contained in:
commit
01594b4cfd
95 changed files with 17201 additions and 167 deletions
|
|
@ -663,6 +663,10 @@ public sealed class GameWindow : IDisposable
|
|||
private AcDream.App.UI.Layout.ToolbarController? _toolbarController;
|
||||
// Phase D.5.3a — selected-object strip controller (name, overlay state, health meter).
|
||||
private AcDream.App.UI.Layout.SelectedObjectController? _selectedObjectController;
|
||||
// Phase D.2b-B — inventory controller (backpack grid + pack-selector + burden meter).
|
||||
private AcDream.App.UI.Layout.InventoryController? _inventoryController;
|
||||
// Phase D.2b Sub-phase C — paperdoll equip-slot controller (wield drag handler).
|
||||
private AcDream.App.UI.Layout.PaperdollController? _paperdollController;
|
||||
// Phase D.2b Task 9 — plugin UI registrations buffered before OnLoad; drained in OnLoad.
|
||||
private readonly AcDream.App.Plugins.BufferedUiRegistry? _uiRegistry;
|
||||
// Phase I.2: ImGui debug panel ViewModel. Lives for as long as
|
||||
|
|
@ -2059,7 +2063,9 @@ public sealed class GameWindow : IDisposable
|
|||
combatState: Combat,
|
||||
peaceDigits: toolbarPeaceDigits,
|
||||
warDigits: toolbarWarDigits,
|
||||
emptyDigits: toolbarEmptyDigits);
|
||||
emptyDigits: toolbarEmptyDigits,
|
||||
sendAddShortcut: (i, g) => _liveSession?.SendAddShortcut(i, g),
|
||||
sendRemoveShortcut: i => _liveSession?.SendRemoveShortcut(i));
|
||||
|
||||
// Phase D.5.3a — selected-object strip (name, overlay state, health meter).
|
||||
// Analogue of retail gmToolbarUI::HandleSelectionChanged
|
||||
|
|
@ -2083,7 +2089,7 @@ public sealed class GameWindow : IDisposable
|
|||
// thickness on every side, giving an outer window of 310×132.
|
||||
const int toolbarBorder = AcDream.App.UI.RetailChromeSprites.Border;
|
||||
float toolbarContentW = 300f, toolbarContentH = toolbarRoot.Height;
|
||||
var toolbarFrame = new AcDream.App.UI.UiNineSlicePanel(ResolveChrome)
|
||||
var toolbarFrame = new AcDream.App.UI.UiCollapsibleFrame(ResolveChrome)
|
||||
{
|
||||
Left = 10, Top = 300,
|
||||
Width = toolbarContentW + 2 * toolbarBorder,
|
||||
|
|
@ -2096,14 +2102,59 @@ public sealed class GameWindow : IDisposable
|
|||
toolbarRoot.Top = toolbarBorder;
|
||||
toolbarRoot.Width = toolbarContentW;
|
||||
toolbarRoot.Height = toolbarContentH;
|
||||
// Anchor content to all four edges so it reflows if the frame is resized.
|
||||
// Anchor content to Left|Top|Right only — drop Bottom so the dat content
|
||||
// keeps its full height when the frame collapses; row 2 hides via Visible,
|
||||
// not reflow. (Width is fixed so the horizontal anchors are inert but harmless.)
|
||||
toolbarRoot.Anchors = AcDream.App.UI.AnchorEdges.Left | AcDream.App.UI.AnchorEdges.Top
|
||||
| AcDream.App.UI.AnchorEdges.Right | AcDream.App.UI.AnchorEdges.Bottom;
|
||||
// The frame is the draggable window; the content itself is not.
|
||||
toolbarRoot.ClickThrough = false;
|
||||
| AcDream.App.UI.AnchorEdges.Right;
|
||||
// The frame is the draggable window; the content itself is not. ClickThrough so the
|
||||
// content panel never CLAIMS a hit — its behavioral children (slots, indicators) are
|
||||
// hit children-first, and its empty areas fall through to the frame (move). Critically,
|
||||
// when collapsed the row-2 band is hidden, so below the collapsed frame the content has
|
||||
// no visible/hit children and ClickThrough lets clicks fall through (no phantom
|
||||
// window-drag from where row 2 used to be). Same pattern as the chat content panel.
|
||||
toolbarRoot.ClickThrough = true;
|
||||
toolbarRoot.Draggable = false;
|
||||
toolbarRoot.Resizable = false;
|
||||
toolbarFrame.AddChild(toolbarRoot);
|
||||
|
||||
// Collapse-to-one-row: the frame's bottom edge snaps between a one-row (row 2 hidden)
|
||||
// and two-row height. CollapsedHeight is computed from the layout (just above row 2),
|
||||
// so there's no magic constant. Bottom-edge only; default expanded.
|
||||
// The full row-2 band (all at content-y 90, toolbar dump 0x21000016): a 6px left
|
||||
// edge-piece (0x100006B6) + the 9 slots (0x100006B7..BF) + an 8px right edge-piece
|
||||
// (0x100006C0). Hide ALL 11 when collapsed — hiding only the 9 slots leaves the two
|
||||
// edge-pieces drawing as black pillars below the bar.
|
||||
uint[] row2Ids =
|
||||
{
|
||||
0x100006B6u,
|
||||
0x100006B7u, 0x100006B8u, 0x100006B9u, 0x100006BAu, 0x100006BBu,
|
||||
0x100006BCu, 0x100006BDu, 0x100006BEu, 0x100006BFu,
|
||||
0x100006C0u,
|
||||
};
|
||||
var toolbarRow2 = new System.Collections.Generic.List<AcDream.App.UI.UiElement>();
|
||||
float minRow2Top = float.MaxValue;
|
||||
foreach (var id in row2Ids)
|
||||
if (toolbarLayout.FindElement(id) is { } e2)
|
||||
{
|
||||
toolbarRow2.Add(e2);
|
||||
if (e2.Top < minRow2Top) minRow2Top = e2.Top;
|
||||
}
|
||||
if (toolbarRow2.Count > 0)
|
||||
{
|
||||
float expandedH = toolbarContentH + 2 * toolbarBorder; // today's full height
|
||||
float collapsedH = minRow2Top + 2 * toolbarBorder; // just above row 2
|
||||
toolbarFrame.CollapsedHeight = collapsedH;
|
||||
toolbarFrame.ExpandedHeight = expandedH;
|
||||
toolbarFrame.SecondRow = toolbarRow2;
|
||||
toolbarFrame.Resizable = true;
|
||||
toolbarFrame.ResizableEdges = AcDream.App.UI.ResizeEdges.Bottom; // bottom edge only
|
||||
toolbarFrame.MinHeight = collapsedH;
|
||||
toolbarFrame.MaxHeight = expandedH;
|
||||
// Height stays expandedH (already set at construction) = default expanded.
|
||||
Console.WriteLine($"[D.2b] toolbar collapse: collapsed={collapsedH:0} expanded={expandedH:0} (row2Top={minRow2Top:0}).");
|
||||
}
|
||||
|
||||
_uiHost.Root.AddChild(toolbarFrame);
|
||||
|
||||
Console.WriteLine("[D.5.1] retail toolbar window from LayoutDesc importer (0x21000016).");
|
||||
|
|
@ -2131,6 +2182,125 @@ public sealed class GameWindow : IDisposable
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase D.2b-B — the real inventory window from LayoutDesc 0x21000023 (gmInventoryUI),
|
||||
// via the LayoutImporter. The sub-window mount pulls in the nested paperdoll/backpack/
|
||||
// 3D-items panels (Type-0 leaves inheriting BaseLayoutId 0x21000024/22/21). Starts
|
||||
// HIDDEN; F12 toggles it via the window manager. Position is the dat's own (X=500,Y=138).
|
||||
AcDream.App.UI.Layout.ImportedLayout? invLayout;
|
||||
lock (_datLock)
|
||||
invLayout = AcDream.App.UI.Layout.LayoutImporter.Import(
|
||||
_dats!, 0x21000023u, ResolveChrome, vitalsDatFont);
|
||||
if (invLayout is not null)
|
||||
{
|
||||
var inventoryRoot = invLayout.Root;
|
||||
// Wrap the dat content in the universal 8-piece beveled window chrome — the
|
||||
// SAME UiNineSlicePanel the vitals/chat/toolbar windows use. The gmInventoryUI
|
||||
// LayoutDesc (0x21000023) is 300×362; the frame adds one border thickness on
|
||||
// every side. The frame is the draggable window; the dat content sits inside it.
|
||||
const int invBorder = AcDream.App.UI.RetailChromeSprites.Border;
|
||||
float invContentW = inventoryRoot.Width, invContentH = inventoryRoot.Height;
|
||||
var inventoryFrame = new AcDream.App.UI.UiNineSlicePanel(ResolveChrome)
|
||||
{
|
||||
Left = inventoryRoot.Left, Top = inventoryRoot.Top, // keep the dat window position
|
||||
Width = invContentW + 2 * invBorder,
|
||||
Height = invContentH + 2 * invBorder,
|
||||
Opacity = 1.0f,
|
||||
Visible = false, // F12 toggles it
|
||||
Anchors = AcDream.App.UI.AnchorEdges.None, // user-positioned
|
||||
Draggable = true,
|
||||
};
|
||||
// Content offset by the border so it sits inside the chrome.
|
||||
inventoryRoot.Left = invBorder; inventoryRoot.Top = invBorder;
|
||||
inventoryRoot.Width = invContentW; inventoryRoot.Height = invContentH;
|
||||
// Content fills the frame vertically (Left|Top|Bottom = fixed width + height tracks
|
||||
// the frame) so a vertical resize grows the inner content.
|
||||
inventoryRoot.Anchors = AcDream.App.UI.AnchorEdges.Left | AcDream.App.UI.AnchorEdges.Top
|
||||
| AcDream.App.UI.AnchorEdges.Bottom;
|
||||
inventoryRoot.Draggable = false;
|
||||
inventoryFrame.AddChild(inventoryRoot);
|
||||
|
||||
// Vertical-only resize: drag the BOTTOM edge to expand and see more of the pack.
|
||||
// ResizeX=false + ResizableEdges=Bottom blocks horizontal resize (user request).
|
||||
// MinHeight = the dat default (no shrink below it); MaxHeight = toward full screen.
|
||||
inventoryFrame.Resizable = true;
|
||||
inventoryFrame.ResizeX = false;
|
||||
inventoryFrame.ResizableEdges = AcDream.App.UI.ResizeEdges.Bottom;
|
||||
inventoryFrame.MinHeight = invContentH + 2 * invBorder;
|
||||
inventoryFrame.MaxHeight = 560f;
|
||||
|
||||
// Stretch the contents region with the window: the gm3DItemsUI sub-window + its grid
|
||||
// + scrollbar + the full-window backdrop grow vertically (Left|Top|Bottom); the
|
||||
// paperdoll + side-bag column stay pinned at the top (Left|Top). The grid's ViewHeight
|
||||
// grows → more rows + the scrollbar thumb ratio (view/content) reflects it.
|
||||
void StretchV(uint id)
|
||||
{
|
||||
if (invLayout!.FindElement(id) is { } el)
|
||||
el.Anchors = AcDream.App.UI.AnchorEdges.Left | AcDream.App.UI.AnchorEdges.Top
|
||||
| AcDream.App.UI.AnchorEdges.Bottom;
|
||||
}
|
||||
void PinTopLeft(uint id)
|
||||
{
|
||||
if (invLayout!.FindElement(id) is { } el)
|
||||
el.Anchors = AcDream.App.UI.AnchorEdges.Left | AcDream.App.UI.AnchorEdges.Top;
|
||||
}
|
||||
StretchV(0x100001D0u); // full-window backdrop
|
||||
StretchV(0x100001CFu); // gm3DItemsUI sub-window (contents-grid container)
|
||||
StretchV(0x100001C6u); // contents grid (UiItemList)
|
||||
StretchV(0x100001C7u); // contents scrollbar
|
||||
PinTopLeft(0x100001CDu); // paperdoll (fixed at top)
|
||||
PinTopLeft(0x100001CEu); // side-bag column (fixed at top)
|
||||
|
||||
_uiHost.Root.AddChild(inventoryFrame);
|
||||
_uiHost.RegisterWindow(AcDream.App.UI.WindowNames.Inventory, inventoryFrame);
|
||||
|
||||
// Phase D.2b-B — populate the inventory from ClientObjectTable: the
|
||||
// "Contents of Backpack" grid + the pack-selector strip + the burden meter +
|
||||
// captions. Analogue of gmInventoryUI/gmBackpackUI/gm3DItemsUI::PostInit.
|
||||
|
||||
// Resolve each inventory list's empty-slot art from the dat cell template
|
||||
// (retail UIElement_ItemList::InternalCreateItem 0x004e3570: attr 0x1000000e ->
|
||||
// catalog 0x21000037 prototype's ItemSlot_Empty). The contents grid lives in
|
||||
// gm3DItemsUI (0x21000021); the side-bag + main-pack in gmBackpackUI (0x21000022).
|
||||
uint contentsEmpty, sideBagEmpty, mainPackEmpty;
|
||||
lock (_datLock)
|
||||
{
|
||||
contentsEmpty = AcDream.App.UI.Layout.ItemListCellTemplate.ResolveEmptySprite(_dats!, 0x21000021u, 0x100001C6u);
|
||||
sideBagEmpty = AcDream.App.UI.Layout.ItemListCellTemplate.ResolveEmptySprite(_dats!, 0x21000022u, 0x100001CAu);
|
||||
mainPackEmpty = AcDream.App.UI.Layout.ItemListCellTemplate.ResolveEmptySprite(_dats!, 0x21000022u, 0x100001C9u);
|
||||
}
|
||||
Console.WriteLine($"[D.2b empty-slot] contents=0x{contentsEmpty:X8} sideBag=0x{sideBagEmpty:X8} mainPack=0x{mainPackEmpty:X8}");
|
||||
|
||||
_inventoryController = AcDream.App.UI.Layout.InventoryController.Bind(
|
||||
invLayout, Objects,
|
||||
playerGuid: () => _playerServerGuid,
|
||||
iconIds: (type, icon, under, over, effects) => iconComposer.GetIcon(type, icon, under, over, effects),
|
||||
strength: () => LocalPlayer.GetAttribute(
|
||||
AcDream.Core.Player.LocalPlayerState.AttributeKind.Strength) is { } sa
|
||||
? (int?)sa.Current : null,
|
||||
datFont: vitalsDatFont,
|
||||
contentsEmptySprite: contentsEmpty,
|
||||
sideBagEmptySprite: sideBagEmpty,
|
||||
mainPackEmptySprite: mainPackEmpty,
|
||||
sendUse: g => _liveSession?.SendUse(g),
|
||||
sendNoLongerViewing: g => _liveSession?.SendNoLongerViewingContents(g),
|
||||
sendPutItemInContainer: (item, container, placement) =>
|
||||
_liveSession?.SendPutItemInContainer(item, container, placement));
|
||||
|
||||
// Slice 1: bind the paperdoll equip slots (same imported subtree) — show equipped gear +
|
||||
// wield-on-drop. Unwield is handled by InventoryController (drag an equipped item to the grid).
|
||||
_paperdollController = AcDream.App.UI.Layout.PaperdollController.Bind(
|
||||
invLayout, Objects,
|
||||
playerGuid: () => _playerServerGuid,
|
||||
iconIds: (type, icon, under, over, effects) => iconComposer.GetIcon(type, icon, under, over, effects),
|
||||
sendWield: (item, mask) => _liveSession?.SendGetAndWieldItem(item, mask),
|
||||
// Empty equip slots show a visible frame (same square as the inventory grid) so every
|
||||
// slot position is seen + usable; the live 3D character (the doll) is Slice 2.
|
||||
emptySlotSprite: contentsEmpty);
|
||||
|
||||
Console.WriteLine("[D.2b-B] retail inventory window from LayoutDesc importer (0x21000023).");
|
||||
}
|
||||
else Console.WriteLine("[D.2b-B] inventory: LayoutDesc 0x21000023 not found.");
|
||||
}
|
||||
|
||||
// Phase N.4+N.5 — WB rendering pipeline foundation. The modern path is
|
||||
|
|
@ -2433,7 +2603,7 @@ public sealed class GameWindow : IDisposable
|
|||
// D.5.4: ingest CreateObject into the object table (upsert) and wire Delete +
|
||||
// UiEffects live update. Wire BEFORE EntitySpawned += OnLiveEntitySpawned so
|
||||
// the table is populated before the render handler runs.
|
||||
AcDream.Core.Net.ObjectTableWiring.Wire(session, Objects);
|
||||
AcDream.Core.Net.ObjectTableWiring.Wire(session, Objects, () => _playerServerGuid);
|
||||
_liveSession.EntitySpawned += OnLiveEntitySpawned;
|
||||
_liveSession.EntityDeleted += OnLiveEntityDeleted;
|
||||
_liveSession.MotionUpdated += OnLiveMotionUpdated;
|
||||
|
|
@ -2531,7 +2701,8 @@ public sealed class GameWindow : IDisposable
|
|||
Console.WriteLine($"player: applied server skills run={_lastSeenRunSkill} jump={_lastSeenJumpSkill}");
|
||||
}
|
||||
},
|
||||
onShortcuts: list => Shortcuts = list);
|
||||
onShortcuts: list => Shortcuts = list,
|
||||
playerGuid: () => _playerServerGuid);
|
||||
|
||||
// Phase I.7: subscribe to CombatState events and emit
|
||||
// retail-faithful "You hit X for Y damage" chat lines into
|
||||
|
|
@ -11709,6 +11880,13 @@ public sealed class GameWindow : IDisposable
|
|||
|
||||
switch (action)
|
||||
{
|
||||
case AcDream.UI.Abstractions.Input.InputAction.ToggleInventoryPanel:
|
||||
// Retail F12 (rebindable). Gated upstream by WantsKeyboard, so it
|
||||
// does not fire while the chat input holds focus. Null _uiHost =
|
||||
// retail UI off (ACDREAM_RETAIL_UI unset) → no-op.
|
||||
_uiHost?.ToggleWindow(AcDream.App.UI.WindowNames.Inventory);
|
||||
break;
|
||||
|
||||
case AcDream.UI.Abstractions.Input.InputAction.AcdreamToggleDebugPanel:
|
||||
foreach (var panel in EnumerateDebugPanel())
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue