feat(journal): QJ3/QJ4/QJ5 — both remaining tabs are live
The Journal panel now has all three tabs working: contracts from the server, and a per-character notebook with its searchable index. Two ported details that a reimplementation would get wrong in a way nobody notices until they lose work: Every navigation button commits the current page FIRST. Retail's ListenToElementMessage @0x004968D0 calls SaveThisPage on the way out of all five of them, which is why paging away never eats what you just typed. And the file is written when the notes page is HIDDEN, not only at exit — a crash then costs at most the page in front of you. The search is CASE-SENSITIVE across label, title and notes: retail compares with wcsstr and lowercases neither side. Making it insensitive would be friendlier and would be a divergence, so it is ported as-is with a test naming the reason. The double-click window is a full SECOND (m_LastClickTime + 1.0, @0x00493158) rather than the 500 ms the item-interaction path uses, and firing it clears the tracker so a third click does not re-open. Two unlabelled buttons on the notes page turned out to be prev/next: retail switches on (idElement - 0x10000565), which names them without a caption. The running-timer readout is authored at the same x as the three day/hour/minute boxes, so the strip is one or the other — that overlap is the data form of ShowEditableTimer versus ShowRunningTimer, not a layout bug. DeltaTimeToString moved out of the contract code into AcDream.Core.Ui. It is ClientUISystem's, not gmContractsUI's — the journal timer and the contract repeat countdown both call it, and it only lived under Quests because that was its first caller. A bridge class to reach it across features would have been the wrong answer to the same observation. The journal file lives in the client's data directory rather than beside the executable, for the same reason the chat log does. Register QJ-1. Campaign QJ slices 3, 4 and 5 of 5 — code-complete, connected gate owed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
536d17456d
commit
c5cc8ae5fc
15 changed files with 1380 additions and 76 deletions
|
|
@ -317,7 +317,17 @@ public sealed record MapHouseRuntimeBindings(
|
|||
/// </summary>
|
||||
public sealed record QuestRuntimeBindings(
|
||||
AcDream.Runtime.Gameplay.IRuntimeContractView Contracts,
|
||||
Func<AcDream.Core.Quests.ContractCatalog> Catalog);
|
||||
Func<AcDream.Core.Quests.ContractCatalog> Catalog,
|
||||
// Campaign QJ (2026-08-21): the notebook the other two tabs share. The
|
||||
// command owner is passed alongside its own read view because the journal
|
||||
// is WRITTEN by the panel — unlike contracts, which are server state.
|
||||
AcDream.Runtime.Gameplay.IRuntimeJournalView Journal,
|
||||
AcDream.Runtime.Gameplay.RuntimeJournalState JournalCommands,
|
||||
Func<uint> PlayerCell,
|
||||
/// <summary>Where the per-character journal file lives.</summary>
|
||||
string JournalDirectory,
|
||||
/// <summary>How a load or save failure reaches the player.</summary>
|
||||
Action<string> Report);
|
||||
|
||||
public sealed record InventoryRuntimeBindings(
|
||||
ClientObjectTable Objects,
|
||||
|
|
@ -705,6 +715,29 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
|
||||
/// <summary>Campaign QT slice QT5 — the three-tab Journal panel.</summary>
|
||||
public Layout.JournalPanelController? JournalPanelController { get; private set; }
|
||||
|
||||
private JournalPersistence? _journalFile;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign QJ slice QJ5. One owner, here rather than in the session
|
||||
/// factory, because the panel that writes the journal lives here — two
|
||||
/// instances would each track their own file path and overwrite each
|
||||
/// other.
|
||||
/// </summary>
|
||||
private JournalPersistence JournalFile =>
|
||||
_journalFile ??= new JournalPersistence(
|
||||
_bindings.Quests.JournalCommands,
|
||||
_bindings.Quests.JournalDirectory,
|
||||
_bindings.Quests.Report);
|
||||
|
||||
/// <summary>Loads a character's journal — called on entering the world.</summary>
|
||||
public void LoadJournal(string characterName) => JournalFile.Load(characterName);
|
||||
|
||||
/// <summary>
|
||||
/// Writes the journal if anything changed. Retail's own moment is the notes
|
||||
/// page being hidden, not only session exit.
|
||||
/// </summary>
|
||||
public void SaveJournal() => JournalFile.Save(DateTime.UtcNow);
|
||||
public MapHousePanelController? MapHousePanelController { get; private set; }
|
||||
internal CharacterManagementUiController? CharacterManagementController =>
|
||||
_characterManagementMount?.Controller;
|
||||
|
|
@ -3520,7 +3553,23 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
{
|
||||
lock (_bindings.Assets.DatLock)
|
||||
return rowTemplates.Resolve(templateLayoutId, templateElementId);
|
||||
}));
|
||||
}),
|
||||
Notes: new Layout.JournalNotesPageController.Bindings(
|
||||
Journal: _bindings.Quests.Journal,
|
||||
Commands: _bindings.Quests.JournalCommands,
|
||||
PlayerCell: _bindings.Quests.PlayerCell,
|
||||
Now: () => DateTime.UtcNow),
|
||||
SaveJournal: SaveJournal,
|
||||
PageList: openPage => new Layout.JournalPageListController.Bindings(
|
||||
Journal: _bindings.Quests.Journal,
|
||||
Commands: _bindings.Quests.JournalCommands,
|
||||
OpenPage: openPage,
|
||||
TemplateResolver: (templateLayoutId, templateElementId) =>
|
||||
{
|
||||
lock (_bindings.Assets.DatLock)
|
||||
return rowTemplates.Resolve(templateLayoutId, templateElementId);
|
||||
},
|
||||
Now: () => DateTime.UtcNow));
|
||||
|
||||
Layout.JournalPanelController? controller;
|
||||
lock (_bindings.Assets.DatLock)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue