Handle blocking dialogs with DLL, replaced dialoguemenu.swf with unchanged Better Dialogue Controls

This commit is contained in:
Eddoursul 2023-12-15 07:26:39 +01:00
parent aab2a8c217
commit 13635df9f8
12 changed files with 108 additions and 62 deletions

BIN
SKSE/Plugins/EnderalSE.dll (Stored with Git LFS)

Binary file not shown.

BIN
interface/dialoguemenu.swf (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,47 @@
#pragma once
inline bool bTabBlocked = false;
class DialogueMenuPatch final : public RE::DialogueMenu
{
public:
static void Install()
{
REL::Relocation<std::uintptr_t> OpenJournalMenuFunc{ 0x0092412C };
REL::Relocation<uintptr_t> vtbl(RE::VTABLE_DialogueMenu[0]);
_ProcessMessageFn = vtbl.write_vfunc(0x4, &ProcessMessageEx);
}
static void BlockTab(bool a_block = true)
{
bTabBlocked = a_block;
}
private:
RE::UI_MESSAGE_RESULTS ProcessMessageEx(RE::UIMessage& a_message)
{
if (bTabBlocked && a_message.type == RE::UI_MESSAGE_TYPE::kScaleformEvent) {
RE::BSUIScaleformData* data = static_cast<RE::BSUIScaleformData*>(a_message.data);
if (data && data->scaleformEvent->type == RE::GFxEvent::EventType::kKeyDown) {
RE::GFxKeyEvent* key = (RE::GFxKeyEvent*)data->scaleformEvent;
// Escape triggers a kTab event, added kEscape just in case
if (key && (key->keyCode == RE::GFxKey::kTab || key->keyCode == RE::GFxKey::kEscape) && !RE::UI::GetSingleton()->IsMenuOpen(RE::ContainerMenu::MENU_NAME) && !RE::UI::GetSingleton()->IsMenuOpen(RE::BarterMenu::MENU_NAME)) {
// The best solution is to show the quest journal, like in LE, but showing a journal seems to mess with framerate of the UI, see dialogue menu after showing journal
//RE::UIMessageQueue::GetSingleton()->AddMessage(RE::InterfaceStrings::GetSingleton()->journalMenu, RE::UI_MESSAGE_TYPE::kShow, 0i64);
RE::BSTSmartPointer<RE::BSScript::IStackCallbackFunctor> stackCallback;
RE::BSScript::Internal::VirtualMachine::GetSingleton()->DispatchStaticCall("_00E_ConfirmDialogQuit", "Show", RE::MakeFunctionArguments(), stackCallback);
return RE::UI_MESSAGE_RESULTS::kIgnore;
}
}
}
return _ProcessMessageFn(this, a_message);
}
using ProcessMessageFn = decltype(&RE::DialogueMenu::ProcessMessage);
inline static REL::Relocation<ProcessMessageFn> _ProcessMessageFn;
};

View File

@ -1,5 +1,6 @@
#include "EventListener.h" #include "EventListener.h"
#include "Util.h" #include "Util.h"
#include "DialogueMenuPatch.h"
#include <shellapi.h> #include <shellapi.h>
auto EventListener::GetSingleton() -> EventListener* auto EventListener::GetSingleton() -> EventListener*
@ -11,8 +12,9 @@ auto EventListener::GetSingleton() -> EventListener*
// Unused // Unused
void EventListener::Install() void EventListener::Install()
{ {
RE::ScriptEventSourceHolder::GetSingleton()->AddEventSink<RE::TESContainerChangedEvent>(EventListener::GetSingleton()); //RE::ScriptEventSourceHolder::GetSingleton()->AddEventSink<RE::TESContainerChangedEvent>(EventListener::GetSingleton());
RE::UI::GetSingleton()->AddEventSink<RE::MenuOpenCloseEvent>(EventListener::GetSingleton()); RE::UI::GetSingleton()->AddEventSink<RE::MenuOpenCloseEvent>(EventListener::GetSingleton());
SKSE::GetModCallbackEventSource()->AddEventSink(EventListener::GetSingleton());
} }
auto EventListener::ProcessEvent( auto EventListener::ProcessEvent(
@ -46,21 +48,29 @@ auto EventListener::ProcessEvent(
RE::BSTEventSource<RE::MenuOpenCloseEvent>* a_eventSource) RE::BSTEventSource<RE::MenuOpenCloseEvent>* a_eventSource)
-> RE::BSEventNotifyControl -> RE::BSEventNotifyControl
{ {
if (a_event->opening && a_event->menuName == RE::JournalMenu::MENU_NAME) { if (a_event->opening) {
if (RE::UI::GetSingleton()->IsMenuOpen(RE::DialogueMenu::MENU_NAME)) { if (a_event->menuName == RE::JournalMenu::MENU_NAME) {
const auto movie = RE::UI::GetSingleton()->GetMovieView(RE::JournalMenu::MENU_NAME); if (RE::UI::GetSingleton()->IsMenuOpen(RE::DialogueMenu::MENU_NAME)) {
if (movie) { const auto movie = RE::UI::GetSingleton()->GetMovieView(RE::JournalMenu::MENU_NAME);
// skse.OpenMenu("Journal Menu") opens quest journal uninitialized, without any tab active and hotkeys bound if (movie) {
// We have to activate a tab for it to work properly // UIMessage opens quest journal uninitialized, without any tab active and hotkeys bound
std::array<RE::GFxValue, 2> args; // We have to activate a tab for it to work properly
args[0].SetNumber(2); std::array<RE::GFxValue, 2> args;
args[1].SetBoolean(true); args[0].SetNumber(2);
movie->Invoke("_root.QuestJournalFader.Menu_mc.RestoreSavedSettings", nullptr, args.data(), static_cast<std::uint32_t>(args.size())); args[1].SetBoolean(false);
movie->Invoke("_root.QuestJournalFader.Menu_mc.RestoreSavedSettings", nullptr, args.data(), static_cast<std::uint32_t>(args.size()));
}
} }
} else 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);
}
} else {
if (a_event->menuName == RE::DialogueMenu::MENU_NAME) {
DialogueMenuPatch::BlockTab(false);
} else if (a_event->menuName == RE::MainMenu::MENU_NAME) {
CheckScriptVersions();
} }
} else if (!a_event->opening && a_event->menuName == RE::MainMenu::MENU_NAME) {
RE::UI::GetSingleton()->RemoveEventSink<RE::MenuOpenCloseEvent>(EventListener::GetSingleton());
CheckScriptVersions();
} }
return RE::BSEventNotifyControl::kContinue; return RE::BSEventNotifyControl::kContinue;

View File

@ -6,6 +6,7 @@
#include "MapMarkerPlacement.h" #include "MapMarkerPlacement.h"
#include "AchievementFix.h" #include "AchievementFix.h"
#include "BinkInterruptPatch.h" #include "BinkInterruptPatch.h"
#include "DialogueMenuPatch.h"
using namespace SKSE; using namespace SKSE;
@ -158,11 +159,12 @@ SKSEPluginLoad(const LoadInterface* skse) {
setting->data.s = nullptr; setting->data.s = nullptr;
RE::INISettingCollection::GetSingleton()->WriteSetting(setting); RE::INISettingCollection::GetSingleton()->WriteSetting(setting);
SKSE::GetModCallbackEventSource()->AddEventSink(EventListener::GetSingleton()); EventListener::Install();
RE::UI::GetSingleton()->AddEventSink<RE::MenuOpenCloseEvent>(EventListener::GetSingleton());
GetPapyrusInterface()->Register(Papyrus::Bind); GetPapyrusInterface()->Register(Papyrus::Bind);
DialogueMenuPatch::Install();
if (g_settings.at("VideoInterruptPatch")) { if (g_settings.at("VideoInterruptPatch")) {
logger::info("Making videos interruptible..."); logger::info("Making videos interruptible...");
BinkInterruptPatch::Install(); BinkInterruptPatch::Install();

View File

@ -2,6 +2,7 @@
#include "Util.h" #include "Util.h"
#include "PersistentFormManager.h" #include "PersistentFormManager.h"
#include "DialogueMenuPatch.h"
namespace Papyrus::PapyrusFunctions namespace Papyrus::PapyrusFunctions
{ {
@ -87,6 +88,16 @@ namespace Papyrus::PapyrusFunctions
return sstream.str(); return sstream.str();
} }
void DisableDialogueQuitting(RE::StaticFunctionTag*)
{
DialogueMenuPatch::BlockTab();
}
void EnableDialogueQuitting(RE::StaticFunctionTag*)
{
DialogueMenuPatch::BlockTab(false);
}
inline void Bind(VM& a_vm) inline void Bind(VM& a_vm)
{ {
BIND(CreatePotion); BIND(CreatePotion);
@ -101,5 +112,9 @@ namespace Papyrus::PapyrusFunctions
logger::info("{}", "Registered GetPlayerHash"sv); logger::info("{}", "Registered GetPlayerHash"sv);
BIND(StringToHex); BIND(StringToHex);
logger::info("{}", "Registered StringToHex"sv); logger::info("{}", "Registered StringToHex"sv);
BIND(DisableDialogueQuitting);
logger::info("{}", "Registered DisableDialogueQuitting"sv);
BIND(EnableDialogueQuitting);
logger::info("{}", "Registered EnableDialogueQuitting"sv);
} }
} }

View File

@ -0,0 +1,7 @@
Scriptname _00E_ConfirmDialogQuit Hidden
function Show() global
if (Game.GetFormFromFile(0x163D, "Update.esm") as Message).Show() == 1
Game.QuitToMainMenu()
endif
endfunction

View File

@ -139,58 +139,21 @@ Function DisableDialogueQuitting() Global
{Disables the TAB Key during dialogue. Resets automatically upon dialogue exit via Goodbye.} {Disables the TAB Key during dialogue. Resets automatically upon dialogue exit via Goodbye.}
if ! UI.IsMenuOpen("Dialogue Menu") if ! UI.IsMenuOpen("Dialogue Menu")
UnregisterDialogQuitKey() EnableDialogueQuitting()
return return
endif endif
if UI.GetBool("Dialogue Menu", "_root.DialogueMenu_mc.bEnableTab") != true EnderalFunctions.DisableDialogueQuitting()
; Suspected non-Enderal dialoguemenu.swf replacer, rechecking value in order to be sure.
UI.InvokeBool("Dialogue Menu", "_root.DialogueMenu_mc.SetVariable", True)
if UI.GetBool("Dialogue Menu", "_root.DialogueMenu_mc.bEnableTab") != true
Debug.Notification("Detected incompatible dialoguemenu.swf!")
endif
endif
UI.InvokeBool("Dialogue Menu", "_root.DialogueMenu_mc.SetVariable", False)
Quest Levelsystem = Game.GetForm(0x10AA2) as Quest
Levelsystem.RegisterForKey(1) ; Escape
Levelsystem.RegisterForKey(15) ; Tab
int iControllerKey = Input.GetMappedKey("Tween Menu", 0x02)
if iControllerKey > -1
Levelsystem.RegisterForKey(iControllerKey) ; Controller
endif
Levelsystem.RegisterForMenu("Dialogue Menu")
EndFunction EndFunction
Function EnableDialogueQuitting() Global Function EnableDialogueQuitting() Global
{Disables the TAB Key during dialogue. Resets automatically upon dialogue exit via Goodbye.} {Disables the TAB Key during dialogue. Resets automatically upon dialogue exit via Goodbye.}
UI.InvokeBool("Dialogue Menu", "_root.DialogueMenu_mc.SetVariable", True) EnderalFunctions.EnableDialogueQuitting()
UnregisterDialogQuitKey()
EndFunction EndFunction
function UnregisterDialogQuitKey() Global
Quest Levelsystem = Game.GetForm(0x10AA2) as Quest
Levelsystem.UnregisterForAllKeys()
Levelsystem.UnregisterForMenu("Dialogue Menu")
endfunction
Event OnKeyDown(Int KeyCode)
if UI.IsMenuOpen("Dialogue Menu")
if ! UI.IsMenuOpen("MessageBoxMenu") && ! UI.GetBool("Dialogue Menu", "_root.DialogueMenu_mc.bEnableTab")
if (Game.GetFormFromFile(0x163D, "Update.esm") as Message).Show() == 1
Game.QuitToMainMenu()
endif
endif
else
UnregisterDialogQuitKey()
endif
EndEvent
Function RefreshFace() Global Function RefreshFace() Global
String facegen = "bUseFaceGenPreprocessedHeads:General" String facegen = "bUseFaceGenPreprocessedHeads:General"
@ -934,8 +897,6 @@ Event OnMenuClose(String MenuName)
If MenuName == "Book Menu" If MenuName == "Book Menu"
MTToRemove.Remove() MTToRemove.Remove()
UnregisterForMenu("Book Menu") UnregisterForMenu("Book Menu")
elseif MenuName == "Dialogue Menu"
UnregisterDialogQuitKey()
EndIf EndIf
EndEvent EndEvent

View File

@ -19,6 +19,10 @@ String Function GetPlayerHash() Global Native
; RETURN - Returns the hexadecimal equivalent of the passed string. ; RETURN - Returns the hexadecimal equivalent of the passed string.
String Function StringToHex(String a_string) Global Native String Function StringToHex(String a_string) Global Native
; Disables the TAB Key during dialogue. Resets automatically upon dialogue exit via Goodbye.
Function DisableDialogueQuitting() Global Native
Function EnableDialogueQuitting() Global Native
bool function IsDLLLoaded() global bool function IsDLLLoaded() global
int iVer = SKSE.GetPluginVersion("EnderalSE") int iVer = SKSE.GetPluginVersion("EnderalSE")
return iVer > 0 return iVer > 0