feat: add Quest Journal Overhaul compatibility#32
Conversation
There was a problem hiding this comment.
Pull request overview
Adds Quest Journal Overhaul (QJO) compatibility for HoldFast’s “open Journal on specific tab” long-press actions by switching the Journal page via Scaleform after the menu is shown, bypassing QJO’s sJournalTabIdx reset.
Changes:
- Track a pending target Journal tab (
_pendingTab) to apply onMenuOpenCloseEvent(opening=true). - Invoke Scaleform
SwitchPageToFront(tabIndex, animate=false)on the live JournaluiMoviewhen the Journal opens. - Clear pending state on restore/close paths to avoid stale tab switches.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/InputHandler.h | Adds _pendingTab state and declares InvokeScaleformTab for Scaleform-based tab switching. |
| src/InputHandler.cpp | Implements the opening-event Scaleform invocation and wires pending-tab lifecycle into Journal open/restore logic. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
379e289 to
e03bde9
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/InputHandler.cpp:330
OpenJournalOnTabsets_pendingTabbefore checkingsJournalTabIdx, but then clears_pendingTaband returns when the relocation is unavailable. That means the Scaleform path won’t run either, so the comment about working withoutsJournalTabIdxdoesn’t match behavior. Either keep_pendingTaband add a safe mechanism to clear it if the Journal never opens (separate from_tabRestorePending), or update the comment/logic so the intended fallback is accurate.
// Set pendingTab before the sJournalTabIdx guard so the Scaleform path (QJO compat)
// works even if the sJournalTabIdx relocation is unavailable.
_pendingTab = tab;
if (!sJournalTabIdx.get()) {
logger::warn("{} long press: sJournalTabIdx unavailable — Journal will open on default tab", buttonName);
_pendingTab.reset(); // Dispatch may succeed but opening=true may never fire; avoid stale fire.
return;
e03bde9 to
5b3ffed
Compare
5b3ffed to
d1ca967
Compare
d1ca967 to
4c3b836
Compare
4c3b836 to
5b25ce8
Compare
5b25ce8 to
7e7a086
Compare
7e7a086 to
d03d018
Compare
…redundant GetMenu lookup
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
src/InputHandler.cpp:475
current.GetNumber()is cast directly tostd::uint32_t. If the Scaleform variable ever becomes NaN/Inf or otherwise out of integer range, this conversion is undefined behavior. Validate the double (finite + expected bounds) before casting to an integer.
const bool got = journal->uiMovie->GetVariable(¤t, "_root.QuestJournalFader.Menu_mc.iCurrentTab");
if (!got || current.GetType() != RE::GFxValue::ValueType::kNumber) {
logger::warn("QJO tab restore: could not read iCurrentTab");
return;
}
const auto currentIdx = static_cast<std::uint32_t>(current.GetNumber());
if (currentIdx == tabIdx) {
return;
…ading iCurrentTab
Summary
Adds compatibility with Quest Journal Overhaul.
The problem: QJO hooks
JournalMenu::ProcessMessageand resets the tab index to System on everykShow, so the Journal always opens on the System tab regardless of HoldFast's configured action — and regardless of which tab the player was last on.The fix has two parts:
1. Long-press tab dispatch (
_pendingTabpath)When HoldFast triggers a Journal long-press,
MenuOpenCloseEvent(opening=true)calls into the live Scaleform movie directly, after QJO's hook has already run:RestoreSavedSettings(tabIdx, false)— sets the active tab, updates the tab bar highlight, and populates page dataQJO_EndPage()— closes the Journal and opens QuestMenu (QJO's native Quests navigation path); falls back to vanillaSwitchPageToFrontwithout QJO2. General tab restore (
_lastKnownTabpath)To also fix QJO's forced System override on normal (non-HoldFast) Journal opens, HoldFast tracks the player's last active tab:
SnapshotJournalTab— called on every input tick while the Journal is open (duringGameIsPaused); readsiCurrentTabfrom the live SWF and caches it as_lastKnownTabInvokeRestoreTabIfNeeded— called onopening=truewhen no_pendingTabis set; if QJO has reset the tab away from_lastKnownTab, callsRestoreSavedSettingsto restore itQJO detection
DetectQJOIfNeededlazily probes forQJO_EndPagein the Quests page SWF and caches the result in_qjoInstalled. Both restore paths are skipped entirely if QJO is not installed, leaving vanilla behaviour unchanged.Backwards compatibility
Without QJO installed,
_qjoInstalledcachesfalseon the first Journal open and all QJO paths are bypassed — behaviour is identical to pre-patch.