An experimental fix for unresponsive dialogs

This commit is contained in:
Eddoursul 2026-07-30 02:35:00 +02:00
parent 274ed7983f
commit 07787e82ad
2 changed files with 77 additions and 6 deletions

View File

@ -48,6 +48,7 @@ auto EventListener::ProcessEvent(
if (a_event->menuName == RE::DialogueMenu::MENU_NAME) {
// Make sure Tab is not blocked. If it should be, a Papyrus script will block it a few frames later.
DialogueMenuPatch::BlockTab(false);
DialogueMenuPatch::ResetProgressFailsafe();
} else if (a_event->menuName == "CustomMenu") {
auto uiMovie = RE::UI::GetSingleton()->GetMovieView("CustomMenu");
if (uiMovie && std::strstr(uiMovie->GetMovieDef()->GetFileURL(), "_heromenu.swf") != NULL) {
@ -60,6 +61,7 @@ auto EventListener::ProcessEvent(
} else {
if (a_event->menuName == RE::DialogueMenu::MENU_NAME) {
DialogueMenuPatch::BlockTab(false);
DialogueMenuPatch::ResetProgressFailsafe();
}
}

View File

@ -1,5 +1,7 @@
#pragma once
#include <chrono>
inline bool bTabBlocked = false;
class DialogueMenuPatch final : public RE::DialogueMenu
@ -16,15 +18,80 @@ public:
bTabBlocked = a_block;
}
static void ResetProgressFailsafe()
{
progressBlockedSince = {};
}
private:
// dialoguemenu.swf gates every forward interaction - clicking a topic, Activate, and skipping
// a line - on DialogueMenu_mc.bAllowProgress. That flag is cleared after each of them, and is
// set back to true only by a setInterval callback firing ALLOW_PROGRESS_DELAY (750 ms) after
// the engine delivers the NotifyVoiceReady message. If that message never arrives, or the
// interval never fires, the topic list stays on screen but ignores every click and keypress
// for the rest of the session. Tab and Esc keep working, because onCancelPress() ->
// StartHideMenu() is not gated - which is exactly how this reaches us in bug reports:
// "clicking a dialogue option does nothing, but I can still leave with Tab".
static constexpr auto ALLOW_PROGRESS_VAR = "DialogueMenu_mc.bAllowProgress";
static constexpr auto MENU_STATE_VAR = "DialogueMenu_mc.eMenuState";
static constexpr auto TOPIC_LIST_SHOWN = 1; // DialogueMenu.TOPIC_LIST_SHOWN
static constexpr auto PROGRESS_TIMEOUT = std::chrono::milliseconds(2000);
// Re-enables input when the topic list has sat unresponsive for longer than the SWF's own
// debounce can account for.
void UnstickProgress()
{
auto* mtm = RE::MenuTopicManager::GetSingleton();
// currentTopicInfo is only set while the speaker is saying a line. Never touch the
// debounce mid-response, only once the game is idle and waiting for a selection.
if (!uiMovie || !mtm || mtm->currentTopicInfo) {
ResetProgressFailsafe();
return;
}
// Only the topic list waits for input. Greeting, transition and post-click states are
// expected to be unresponsive.
RE::GFxValue menuState;
if (!uiMovie->GetVariable(&menuState, MENU_STATE_VAR) || !menuState.IsNumber() || static_cast<int>(menuState.GetNumber()) != TOPIC_LIST_SHOWN) {
ResetProgressFailsafe();
return;
}
RE::GFxValue allowProgress;
if (!uiMovie->GetVariable(&allowProgress, ALLOW_PROGRESS_VAR) || !allowProgress.IsBool()) {
return;
}
if (allowProgress.GetBool()) {
ResetProgressFailsafe();
return;
}
const auto now = std::chrono::steady_clock::now();
if (progressBlockedSince == std::chrono::steady_clock::time_point{}) {
progressBlockedSince = now;
} else if (now - progressBlockedSince >= PROGRESS_TIMEOUT) {
ResetProgressFailsafe();
allowProgress.SetBoolean(true);
uiMovie->SetVariable(ALLOW_PROGRESS_VAR, allowProgress);
logger::warn("Dialogue menu stopped accepting input, re-enabled bAllowProgress");
}
}
RE::UI_MESSAGE_RESULTS ProcessMessageEx(RE::UIMessage& a_message)
{
// Unblock Tab if Goodbye response has finished playing (End script has already run at this point).
// A failsafe when Goodbye does not close the dialogue menu.
if (bTabBlocked && a_message.type == RE::UI_MESSAGE_TYPE::kUpdate) {
auto* mtm = RE::MenuTopicManager::GetSingleton();
if (mtm && mtm->isSayingGoodbye && !mtm->currentTopicInfo && RE::UI::GetSingleton()->IsMenuOpen(MENU_NAME)) {
BlockTab(false);
if (a_message.type == RE::UI_MESSAGE_TYPE::kUpdate) {
UnstickProgress();
// Unblock Tab if Goodbye response has finished playing (End script has already run at this point).
// A failsafe when Goodbye does not close the dialogue menu.
if (bTabBlocked) {
auto* mtm = RE::MenuTopicManager::GetSingleton();
if (mtm && mtm->isSayingGoodbye && !mtm->currentTopicInfo && RE::UI::GetSingleton()->IsMenuOpen(MENU_NAME)) {
BlockTab(false);
}
}
}
@ -59,6 +126,8 @@ private:
func(a_bool);
}
inline static std::chrono::steady_clock::time_point progressBlockedSince{};
using ProcessMessageFn = decltype(&ProcessMessage);
inline static REL::Relocation<ProcessMessageFn> _ProcessMessageFn;
};