feat(quest): QT5/QT6 — the Journal panel, and the button that was already there
The quest log is on screen. Rows come from the live tracker joined to the authored catalog, the Status column runs QT4's port of FillProgressString, and the detail pane shows contact, locations, description and the other timer. Two things measured rather than assumed, each now pinned by an installed-DAT test rather than left to the commit message: The tab pairing is read from the authored 0x2E table, not inferred from x-order — the FA campaign had to correct exactly that mistake, and Contracts turns out to be the authored DEFAULT tab (0x32 = True), so opening on the wrong one would have looked like an empty panel. The open path needed no keybind at all. Toolbar button 0x1000055A authors 0x10000029 = 0x19 and has been sitting in ToolbarController.PanelButtonIds since the toolbar was ported — it just had no panel behind it, so clicking it did nothing. Registering slot 25 finished a wiring that was already three-quarters present. The list rebuild is revision-gated while the repeat countdown is not: nothing on the wire changes as a cooldown runs down, so a rebuild-gated timer would freeze on screen, and a per-frame rebuild would reset the player's scroll under them. Both directions have a test. Deliberately inert: the Abandon button (retail's abandon path is a contract-registry command this campaign did not port — authored and visible, but wiring a no-op handler would look responsive and lie), and the Journal notes and Page List tabs, which are their own feature. Campaign QT slices 5 and 6 of 6 — code-complete, connected gate owed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
fac2dc7248
commit
ec6eeb120d
12 changed files with 1109 additions and 5 deletions
|
|
@ -311,6 +311,14 @@ public sealed record MapHouseRuntimeBindings(
|
|||
Func<IReadOnlyList<string>>? HouseLines = null,
|
||||
Action? HouseShown = null);
|
||||
|
||||
/// <summary>
|
||||
/// Campaign QT slice QT5: what the Journal panel's Contracts page reads —
|
||||
/// the canonical tracker view plus the authored contract text.
|
||||
/// </summary>
|
||||
public sealed record QuestRuntimeBindings(
|
||||
AcDream.Runtime.Gameplay.IRuntimeContractView Contracts,
|
||||
Func<AcDream.Core.Quests.ContractCatalog> Catalog);
|
||||
|
||||
public sealed record InventoryRuntimeBindings(
|
||||
ClientObjectTable Objects,
|
||||
Func<uint> PlayerGuid,
|
||||
|
|
@ -474,6 +482,7 @@ public sealed record RetailUiRuntimeBindings(
|
|||
OptionsRuntimeBindings Options,
|
||||
SocialRuntimeBindings Social,
|
||||
MapHouseRuntimeBindings MapHouse,
|
||||
QuestRuntimeBindings Quests,
|
||||
StackSplitQuantityState StackSplitQuantity,
|
||||
BufferedUiRegistry? Plugins,
|
||||
RetailUiPersistenceBindings? Persistence,
|
||||
|
|
@ -569,6 +578,7 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
MountTooltipPresenter();
|
||||
MountSocialPanel();
|
||||
MountMapHousePanel();
|
||||
MountJournalPanel();
|
||||
MountCharacter();
|
||||
MountPlugins();
|
||||
MountInventory();
|
||||
|
|
@ -692,6 +702,9 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
public VendorUiController? VendorController { get; private set; }
|
||||
public OptionsPanelController? OptionsPanelController { get; private set; }
|
||||
public SocialPanelController? SocialPanelController { get; private set; }
|
||||
|
||||
/// <summary>Campaign QT slice QT5 — the three-tab Journal panel.</summary>
|
||||
public Layout.JournalPanelController? JournalPanelController { get; private set; }
|
||||
public MapHousePanelController? MapHousePanelController { get; private set; }
|
||||
internal CharacterManagementUiController? CharacterManagementController =>
|
||||
_characterManagementMount?.Controller;
|
||||
|
|
@ -873,6 +886,7 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
SelectedObjectController?.Tick(deltaSeconds);
|
||||
ExternalContainerController?.Tick();
|
||||
SocialPanelController?.Tick();
|
||||
JournalPanelController?.Tick();
|
||||
MapHousePanelController?.Tick(deltaSeconds);
|
||||
_itemCooldownController?.Tick();
|
||||
_characterManagementMount?.Tick();
|
||||
|
|
@ -3445,6 +3459,115 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
Console.WriteLine("[UI] retail social panel from LayoutDesc importer (0x2100006E slot 0x1000018F).");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign QT slice QT5: retail's three-tab Journal panel — host
|
||||
/// <c>0x2100006E</c> slot <c>0x10000559</c>,
|
||||
/// <see cref="RetailPanelCatalog.Journal"/> id 25. Same import/Build/Bind
|
||||
/// recipe as <see cref="MountSocialPanel"/>. The Contracts page's list has
|
||||
/// ONE authored row template resolved out of a DIFFERENT layout
|
||||
/// (<c>0x21000069</c>) than the panel itself, which is why it needs the
|
||||
/// caching <see cref="Layout.RowTemplateResolver"/> rather than
|
||||
/// <see cref="UiTemplateListBox.AddItemFromTemplateList"/>'s own path.
|
||||
/// </summary>
|
||||
private void MountJournalPanel()
|
||||
{
|
||||
ElementInfo? rootInfo;
|
||||
ImportedLayout? layout;
|
||||
lock (_bindings.Assets.DatLock)
|
||||
{
|
||||
rootInfo = LayoutImporter.ImportInfos(
|
||||
_bindings.Assets.Dats,
|
||||
Layout.JournalPanelController.HostLayoutId,
|
||||
Layout.JournalPanelController.SlotElementId);
|
||||
var resolver = new DatStringResolver(_bindings.Assets.Dats);
|
||||
layout = rootInfo is null
|
||||
? null
|
||||
: LayoutImporter.Build(
|
||||
rootInfo,
|
||||
_bindings.Assets.ResolveSprite,
|
||||
_bindings.Assets.DefaultFont,
|
||||
_bindings.Assets.ResolveFont,
|
||||
resolver.Resolve);
|
||||
}
|
||||
if (rootInfo is null || layout is null)
|
||||
{
|
||||
Console.WriteLine(
|
||||
"[UI] journal panel: LayoutDesc 0x2100006E slot 0x10000559 not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
var rowTemplates = new Layout.RowTemplateResolver(
|
||||
(layoutId, elementId) => LayoutImporter.ImportInfos(
|
||||
_bindings.Assets.Dats, layoutId, elementId),
|
||||
info =>
|
||||
{
|
||||
var strings = new DatStringResolver(_bindings.Assets.Dats);
|
||||
return LayoutImporter.Build(
|
||||
info,
|
||||
_bindings.Assets.ResolveSprite,
|
||||
_bindings.Assets.DefaultFont,
|
||||
_bindings.Assets.ResolveFont,
|
||||
strings.Resolve).Root;
|
||||
});
|
||||
|
||||
var callbacks = new Layout.JournalPanelController.Callbacks(
|
||||
Toggle: () => ToggleWindow(WindowNames.Journal),
|
||||
Contracts: new Layout.JournalContractsPageController.Bindings(
|
||||
Contracts: _bindings.Quests.Contracts,
|
||||
Catalog: _bindings.Quests.Catalog,
|
||||
Now: () => DateTime.UtcNow,
|
||||
TemplateResolver: (templateLayoutId, templateElementId) =>
|
||||
{
|
||||
lock (_bindings.Assets.DatLock)
|
||||
return rowTemplates.Resolve(templateLayoutId, templateElementId);
|
||||
}));
|
||||
|
||||
Layout.JournalPanelController? controller;
|
||||
lock (_bindings.Assets.DatLock)
|
||||
controller = Layout.JournalPanelController.Bind(layout, callbacks);
|
||||
if (controller is null)
|
||||
{
|
||||
Console.WriteLine("[UI] journal panel: required root did not build as UiTabPanel.");
|
||||
return;
|
||||
}
|
||||
|
||||
controller.ActivateTabs();
|
||||
JournalPanelController = controller;
|
||||
|
||||
RetailWindowHandle handle = RetailWindowFrame.Mount(
|
||||
Host.Root,
|
||||
controller.Root,
|
||||
_bindings.Assets.ResolveSprite,
|
||||
new RetailWindowFrame.Options
|
||||
{
|
||||
WindowName = WindowNames.Journal,
|
||||
Chrome = RetailWindowChrome.NineSlice,
|
||||
Left = 230f,
|
||||
Top = 160f,
|
||||
Visible = false,
|
||||
ResizeX = false,
|
||||
ResizeY = true,
|
||||
ResizableEdges = ResizeEdges.Bottom,
|
||||
ConstrainDragToParent = true,
|
||||
ConstrainResizeToParent = true,
|
||||
ContentAnchors = AnchorEdges.Left | AnchorEdges.Top
|
||||
| AnchorEdges.Right | AnchorEdges.Bottom,
|
||||
ContentClickThrough = false,
|
||||
DrawChromeCenter = !AuthorsFullPanelCenter(rootInfo),
|
||||
Controller = controller,
|
||||
});
|
||||
_panelUi.RegisterMainPanel(
|
||||
RetailPanelCatalog.Journal,
|
||||
WindowNames.Journal,
|
||||
handle,
|
||||
rootInfo.TryGetEffectiveBool(
|
||||
RetailPanelUiController.RestorePreviousPropertyId,
|
||||
out bool restorePrevious)
|
||||
&& restorePrevious);
|
||||
Console.WriteLine(
|
||||
"[UI] retail journal panel from LayoutDesc importer (0x2100006E slot 0x10000559).");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Batch C (overnight hover/UI round, 2026-08-17): the two-tab Map/House
|
||||
/// panel — host <c>0x2100006E</c> slot <c>0x1000018C</c>,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue