fix(journal): the location readout is a FIELD, refresh at the click, and Abandon works

Three fixes from the first connected round.

The location readout is authored EDITABLE (0x16), so it builds as a UiField —
not the UiText its "00.0S, 00.0W" placeholder suggests. The controller resolved
it as text, got null, and threw every write away in silence: Record reached the
model and reached the FILE, and never reached the screen. That is exactly what
was reported, and it is a whole class of bug, so the sweep that found it is now
a test over every element all three controllers bind.

The handlers mutated the model and left redrawing to the next frame's Tick.
Retail's ListenToElementMessage @0x004968D0 ends every one of them in Update()
instead — at the moment of the click. The deferred version happened to work in
the client and made the behaviour untestable and a frame late; the notes-page
tests I had not written until now fail against it.

Abandon is wired. "Retail's abandon path is a contract-registry command we have
not ported" was wrong — it is game action 0x0316 with a single contract id, and
ACE replies with the 0x0315 delete QT3 already handles. Nothing is removed
locally, so a refusal leaves the quest visibly intact rather than vanishing it
optimistically and having it reappear on the next full table.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-21 16:19:18 +02:00
parent 0b27c5d0fe
commit e35d9386e4
12 changed files with 556 additions and 16 deletions

View file

@ -69,7 +69,15 @@ public sealed class JournalNotesPageController
private readonly UiField? _timerHours;
private readonly UiField? _timerMinutes;
private readonly UiText? _pageNumber;
private readonly UiText? _location;
/// <summary>
/// The location readout is authored EDITABLE (<c>0x16</c>), so it builds as
/// a <see cref="UiField"/> — not the <see cref="UiText"/> its placeholder
/// text suggests. Resolving it as text yielded null and threw every write
/// away silently: Record reached the model and the file, and never the
/// screen.
/// </summary>
private readonly UiField? _location;
private readonly UiText? _runningTimer;
private readonly UiButton? _start;
@ -87,7 +95,7 @@ public sealed class JournalNotesPageController
_timerHours = UiElement.FindDescendant(page, TimerHoursFieldId) as UiField;
_timerMinutes = UiElement.FindDescendant(page, TimerMinutesFieldId) as UiField;
_pageNumber = UiElement.FindDescendant(page, PageNumberId) as UiText;
_location = UiElement.FindDescendant(page, LocationTextId) as UiText;
_location = UiElement.FindDescendant(page, LocationTextId) as UiField;
_runningTimer = UiElement.FindDescendant(page, RunningTimerTextId) as UiText;
_start = UiElement.FindDescendant(page, StartButtonId) as UiButton;
@ -108,7 +116,16 @@ public sealed class JournalNotesPageController
field.OnFocusLost = _ => CommitText();
}
Bind(page, NewButtonId, () => { CommitText(); _bindings.Commands.NewPage(); });
// Retail's ListenToElementMessage @0x004968D0 ends every one of these
// in Update() — the panel redraws at the moment of the click, not on
// the next frame. Deferring to Tick would work in the client and makes
// the behaviour untestable and a frame late.
Bind(page, NewButtonId, () =>
{
CommitText();
_bindings.Commands.NewPage();
Refresh();
});
Bind(page, FirstButtonId, () => Navigate(1));
Bind(page, LastButtonId, () => Navigate(_bindings.Journal.Snapshot.PageCount));
Bind(page, PreviousButtonId,
@ -172,7 +189,7 @@ public sealed class JournalNotesPageController
? string.Empty
: $"~ {snapshot.CurrentPage.ToString(CultureInfo.InvariantCulture)} ~");
SetText(_location, page.HasLocation
_location?.SetText(page.HasLocation
? FormatLocation(page.LocationX, page.LocationY)
: string.Empty);
@ -210,6 +227,7 @@ public sealed class JournalNotesPageController
{
CommitText();
_bindings.Commands.GotoPage(pageNumber);
Refresh();
}
private void RecordLocation()
@ -222,6 +240,7 @@ public sealed class JournalNotesPageController
return; // indoors: retail's own gid_to_lcoord failure
_bindings.Commands.RecordLocation((float)coordinates.X, (float)coordinates.Y);
Refresh();
}
private void ToggleTimer()
@ -229,11 +248,13 @@ public sealed class JournalNotesPageController
if (_bindings.Journal.RemainingTimerSeconds(_bindings.Now()) > 0d)
{
_bindings.Commands.ResetTimer();
Refresh();
return;
}
CommitText(); // the fields the countdown reads
_bindings.Commands.StartTimer(_bindings.Now());
Refresh();
}
/// <summary>