2023-12-19 15:58:36 +00:00
|
|
|
#include "HeroMenuPatch.h"
|
|
|
|
|
|
|
|
RE::BSEventNotifyControl HeroMenuPatch::ProcessEvent_Hook(RE::InputEvent** a_event, RE::BSTEventSource<RE::InputEvent*>* a_source)
|
|
|
|
{
|
|
|
|
if (RE::UI::GetSingleton()->GameIsPaused() && a_event && *a_event) {
|
|
|
|
auto buttonEvent = (*a_event)->AsButtonEvent();
|
|
|
|
if (buttonEvent && buttonEvent->IsDown() && RE::UI::GetSingleton()->IsMenuOpen("CustomMenu")) {
|
2023-12-21 11:41:08 +00:00
|
|
|
|
2023-12-19 15:58:36 +00:00
|
|
|
auto& eventName = (*a_event)->QUserEvent();
|
|
|
|
auto userEvents = RE::UserEvents::GetSingleton();
|
2023-12-21 11:41:08 +00:00
|
|
|
|
2023-12-19 15:58:36 +00:00
|
|
|
if (eventName == userEvents->cancel || eventName == userEvents->quickStats) {
|
|
|
|
auto uiMovie = RE::UI::GetSingleton()->GetMovieView("CustomMenu");
|
2024-01-31 00:57:20 +00:00
|
|
|
if (uiMovie && std::strstr(uiMovie->GetMovieDef()->GetFileURL(), "_heromenu.swf") != NULL) {
|
2023-12-19 15:58:36 +00:00
|
|
|
RE::UIMessageQueue::GetSingleton()->AddMessage("CustomMenu", RE::UI_MESSAGE_TYPE::kHide, nullptr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return _ProcessEvent(this, a_event, a_source);
|
|
|
|
}
|
|
|
|
|
|
|
|
void HeroMenuPatch::Install()
|
|
|
|
{
|
2023-12-21 11:41:08 +00:00
|
|
|
// Patch MenuControls to close the menu
|
2023-12-19 15:58:36 +00:00
|
|
|
REL::Relocation<std::uintptr_t> vTable(RE::VTABLE_MenuControls[0]);
|
|
|
|
_ProcessEvent = vTable.write_vfunc(0x1, &HeroMenuPatch::ProcessEvent_Hook);
|
|
|
|
|
2024-02-15 20:38:08 +00:00
|
|
|
if (REL::Module::IsVR()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-12-21 11:41:08 +00:00
|
|
|
// Patch the Quick Stats hotkey to open the menu
|
|
|
|
// Checked: 1.5.97, 1.6.640, 1.6.659, 1.6.1130
|
2023-12-19 15:58:36 +00:00
|
|
|
REL::Relocation<std::uintptr_t> target{ REL::RelocationID(51400, 52249), REL::Relocate(0x41E, 0x421) };
|
|
|
|
REL::safe_fill(target.address(), REL::NOP, 45);
|
|
|
|
|
|
|
|
SKSE::AllocTrampoline(14);
|
2023-12-21 11:41:08 +00:00
|
|
|
_OpenStats = SKSE::GetTrampoline().write_call<5>(target.address(), OpenStats);
|
2023-12-19 15:58:36 +00:00
|
|
|
|
|
|
|
REL::Relocation<std::uintptr_t> target2{ REL::RelocationID(51400, 52249), REL::Relocate(0x436, 0x439) };
|
|
|
|
std::uint8_t code[] = { 0x40, 0xB5, 0x01 };
|
|
|
|
REL::safe_write(target2.address(), code, sizeof(code));
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t HeroMenuPatch::OpenStats(uint32_t arg_1, uint32_t arg_2, uint32_t arg_3, uint32_t arg_4, uint32_t arg_5)
|
|
|
|
{
|
|
|
|
OpenHeroMenu();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void HeroMenuPatch::OpenHeroMenu()
|
|
|
|
{
|
|
|
|
RE::BSTSmartPointer<RE::BSScript::IStackCallbackFunctor> stackCallback;
|
|
|
|
RE::BSScript::Internal::VirtualMachine::GetSingleton()->DispatchStaticCall("UI", "OpenCustomMenu", RE::MakeFunctionArguments<RE::BSString, std::uint32_t>("00e_heromenu", 0), stackCallback);
|
|
|
|
}
|
|
|
|
|
|
|
|
void HeroMenuPatch::FillMenuValues()
|
|
|
|
{
|
|
|
|
class ScriptClassNameCallback : public RE::BSScript::IStackCallbackFunctor
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
void operator()(RE::BSScript::Variable a_result)
|
|
|
|
{
|
2023-12-20 15:27:06 +00:00
|
|
|
if (a_result.IsString()) {
|
|
|
|
auto uiMovie = RE::UI::GetSingleton()->GetMovieView("CustomMenu");
|
|
|
|
if (uiMovie) {
|
2023-12-21 11:41:08 +00:00
|
|
|
uiMovie->SetVariable("heromenu_mc.stats.playerclass.stat_value.text", a_result.GetString(), RE::GFxMovie::SetVarType::kNormal);
|
2023-12-20 15:27:06 +00:00
|
|
|
}
|
|
|
|
}
|
2023-12-19 15:58:36 +00:00
|
|
|
}
|
|
|
|
bool CanSave() { return false; }
|
|
|
|
void SetObject(const RE::BSTSmartPointer<RE::BSScript::Object>&) {}
|
|
|
|
};
|
|
|
|
|
|
|
|
auto uiMovie = RE::UI::GetSingleton()->GetMovieView("CustomMenu");
|
2023-12-21 11:41:08 +00:00
|
|
|
|
|
|
|
if (!uiMovie) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-02-15 20:38:08 +00:00
|
|
|
if (!REL::Module::IsVR()) {
|
|
|
|
// Fit the movie into the screen (widescreen support)
|
|
|
|
uiMovie->SetViewScaleMode(RE::BSScaleformManager::ScaleModeType::kShowAll);
|
|
|
|
}
|
2023-12-19 15:58:36 +00:00
|
|
|
|
|
|
|
const auto dataHandler = RE::TESDataHandler::GetSingleton();
|
|
|
|
RE::TESGlobal* playerLevel = dataHandler->LookupForm<RE::TESGlobal>(0x12595, "Skyrim.esm");
|
|
|
|
RE::TESGlobal* playerExp = dataHandler->LookupForm<RE::TESGlobal>(0x12596, "Skyrim.esm");
|
|
|
|
RE::TESGlobal* EXPMultSlope = dataHandler->LookupForm<RE::TESGlobal>(0xD0EDB, "Skyrim.esm");
|
|
|
|
RE::TESGlobal* EXPMult = dataHandler->LookupForm<RE::TESGlobal>(0x8D2B, "Skyrim.esm");
|
|
|
|
RE::TESGlobal* Lernpunkte = dataHandler->LookupForm<RE::TESGlobal>(0x31ACB, "Skyrim.esm");
|
|
|
|
RE::TESGlobal* Handwerkspunkte = dataHandler->LookupForm<RE::TESGlobal>(0x85A79, "Skyrim.esm");
|
|
|
|
RE::TESGlobal* TalentPoints = dataHandler->LookupForm<RE::TESGlobal>(0x5BCFA, "Skyrim.esm");
|
|
|
|
|
|
|
|
auto player = RE::PlayerCharacter::GetSingleton();
|
|
|
|
auto playerAV = player->AsActorValueOwner();
|
|
|
|
|
|
|
|
float fEXPNeededForCurrentLevel = ComputeNeededExpPoints(playerLevel->value - 1, EXPMultSlope->value, EXPMult->value);
|
|
|
|
float fEXPNeededForNextLevel = ComputeNeededExpPoints(playerLevel->value, EXPMultSlope->value, EXPMult->value);
|
|
|
|
|
|
|
|
RE::GFxValue args[2];
|
|
|
|
args[0].SetString(player->GetDisplayFullName());
|
|
|
|
args[1].SetString("");
|
2023-12-21 11:41:08 +00:00
|
|
|
uiMovie->Invoke("heromenu_mc.SetStringValues", nullptr, args, 2);
|
2023-12-19 15:58:36 +00:00
|
|
|
|
|
|
|
RE::BSTSmartPointer<RE::BSScript::IStackCallbackFunctor> stackCallback{ new ScriptClassNameCallback };
|
2024-01-11 11:33:30 +00:00
|
|
|
RE::BSScript::Internal::VirtualMachine::GetSingleton()->DispatchStaticCall("EnderalFunctions", "GetPlayerClassNameGlobal", RE::MakeFunctionArguments(), stackCallback);
|
2023-12-19 15:58:36 +00:00
|
|
|
|
|
|
|
RE::GFxValue args2[33];
|
|
|
|
args2[0].SetNumber(playerAV->GetBaseActorValue(RE::ActorValue::kHealth));
|
|
|
|
args2[1].SetNumber(playerAV->GetActorValue(RE::ActorValue::kHealth));
|
|
|
|
args2[2].SetNumber(playerAV->GetBaseActorValue(RE::ActorValue::kMagicka));
|
|
|
|
args2[3].SetNumber(playerAV->GetActorValue(RE::ActorValue::kMagicka));
|
|
|
|
args2[4].SetNumber(playerAV->GetBaseActorValue(RE::ActorValue::kStamina));
|
|
|
|
args2[5].SetNumber(playerAV->GetActorValue(RE::ActorValue::kStamina));
|
|
|
|
args2[6].SetNumber(-playerAV->GetActorValue(RE::ActorValue::kLastFlattered));
|
|
|
|
args2[7].SetNumber(fEXPNeededForNextLevel);
|
|
|
|
args2[8].SetNumber(playerExp->value);
|
|
|
|
args2[9].SetNumber(playerLevel->value);
|
|
|
|
args2[10].SetNumber(Lernpunkte->value);
|
|
|
|
args2[11].SetNumber(Handwerkspunkte->value);
|
|
|
|
args2[12].SetNumber(TalentPoints->value);
|
2024-02-19 09:20:33 +00:00
|
|
|
args2[13].SetNumber(playerAV->GetPermanentActorValue(RE::ActorValue::kIllusion));
|
|
|
|
args2[14].SetNumber(playerAV->GetPermanentActorValue(RE::ActorValue::kDestruction));
|
|
|
|
args2[15].SetNumber(playerAV->GetPermanentActorValue(RE::ActorValue::kAlteration));
|
|
|
|
args2[16].SetNumber(playerAV->GetPermanentActorValue(RE::ActorValue::kOneHanded));
|
|
|
|
args2[17].SetNumber(playerAV->GetPermanentActorValue(RE::ActorValue::kBlock));
|
|
|
|
args2[18].SetNumber(playerAV->GetPermanentActorValue(RE::ActorValue::kArchery));
|
|
|
|
args2[19].SetNumber(playerAV->GetPermanentActorValue(RE::ActorValue::kConjuration));
|
|
|
|
args2[20].SetNumber(playerAV->GetPermanentActorValue(RE::ActorValue::kRestoration));
|
|
|
|
args2[21].SetNumber(playerAV->GetPermanentActorValue(RE::ActorValue::kTwoHanded));
|
|
|
|
args2[22].SetNumber(playerAV->GetPermanentActorValue(RE::ActorValue::kLightArmor));
|
|
|
|
args2[23].SetNumber(playerAV->GetPermanentActorValue(RE::ActorValue::kHeavyArmor));
|
|
|
|
args2[24].SetNumber(playerAV->GetPermanentActorValue(RE::ActorValue::kSneak));
|
|
|
|
args2[25].SetNumber(playerAV->GetPermanentActorValue(RE::ActorValue::kAlchemy));
|
|
|
|
args2[26].SetNumber(playerAV->GetPermanentActorValue(RE::ActorValue::kPickpocket));
|
|
|
|
args2[27].SetNumber(playerAV->GetPermanentActorValue(RE::ActorValue::kLockpicking));
|
|
|
|
args2[28].SetNumber(playerAV->GetPermanentActorValue(RE::ActorValue::kEnchanting));
|
|
|
|
args2[29].SetNumber(playerAV->GetPermanentActorValue(RE::ActorValue::kSmithing));
|
|
|
|
args2[30].SetNumber(playerAV->GetPermanentActorValue(RE::ActorValue::kSpeech));
|
2023-12-19 15:58:36 +00:00
|
|
|
|
|
|
|
args2[31].SetNumber(playerExp->value - fEXPNeededForCurrentLevel);
|
|
|
|
args2[32].SetNumber(fEXPNeededForNextLevel - fEXPNeededForCurrentLevel);
|
2023-12-21 11:41:08 +00:00
|
|
|
uiMovie->Invoke("heromenu_mc.SetIntValues", nullptr, args2, 33);
|
2023-12-19 15:58:36 +00:00
|
|
|
|
|
|
|
RE::GFxValue args3[21];
|
|
|
|
args3[0].SetNumber(0);
|
|
|
|
args3[1].SetNumber(0);
|
|
|
|
args3[2].SetNumber(0);
|
2024-02-19 09:20:33 +00:00
|
|
|
args3[3].SetNumber(playerAV->GetActorValue(RE::ActorValue::kIllusion) - playerAV->GetPermanentActorValue(RE::ActorValue::kIllusion));
|
|
|
|
args3[4].SetNumber(playerAV->GetActorValue(RE::ActorValue::kDestruction) - playerAV->GetPermanentActorValue(RE::ActorValue::kDestruction));
|
|
|
|
args3[5].SetNumber(playerAV->GetActorValue(RE::ActorValue::kAlteration) - playerAV->GetPermanentActorValue(RE::ActorValue::kAlteration));
|
|
|
|
args3[6].SetNumber(playerAV->GetActorValue(RE::ActorValue::kOneHanded) - playerAV->GetPermanentActorValue(RE::ActorValue::kOneHanded));
|
|
|
|
args3[7].SetNumber(playerAV->GetActorValue(RE::ActorValue::kBlock) - playerAV->GetPermanentActorValue(RE::ActorValue::kBlock));
|
|
|
|
args3[8].SetNumber(playerAV->GetActorValue(RE::ActorValue::kArchery) - playerAV->GetPermanentActorValue(RE::ActorValue::kArchery));
|
|
|
|
args3[9].SetNumber(playerAV->GetActorValue(RE::ActorValue::kConjuration) - playerAV->GetPermanentActorValue(RE::ActorValue::kConjuration));
|
|
|
|
args3[10].SetNumber(playerAV->GetActorValue(RE::ActorValue::kRestoration) - playerAV->GetPermanentActorValue(RE::ActorValue::kRestoration));
|
|
|
|
args3[11].SetNumber(playerAV->GetActorValue(RE::ActorValue::kTwoHanded) - playerAV->GetPermanentActorValue(RE::ActorValue::kTwoHanded));
|
|
|
|
args3[12].SetNumber(playerAV->GetActorValue(RE::ActorValue::kLightArmor) - playerAV->GetPermanentActorValue(RE::ActorValue::kLightArmor));
|
|
|
|
args3[13].SetNumber(playerAV->GetActorValue(RE::ActorValue::kHeavyArmor) - playerAV->GetPermanentActorValue(RE::ActorValue::kHeavyArmor));
|
|
|
|
args3[14].SetNumber(playerAV->GetActorValue(RE::ActorValue::kSneak) - playerAV->GetPermanentActorValue(RE::ActorValue::kSneak));
|
|
|
|
args3[15].SetNumber(playerAV->GetActorValue(RE::ActorValue::kAlchemy) - playerAV->GetPermanentActorValue(RE::ActorValue::kAlchemy));
|
|
|
|
args3[16].SetNumber(playerAV->GetActorValue(RE::ActorValue::kPickpocket) - playerAV->GetPermanentActorValue(RE::ActorValue::kPickpocket));
|
|
|
|
args3[17].SetNumber(playerAV->GetActorValue(RE::ActorValue::kLockpicking) - playerAV->GetPermanentActorValue(RE::ActorValue::kLockpicking));
|
|
|
|
args3[18].SetNumber(playerAV->GetActorValue(RE::ActorValue::kEnchanting) - playerAV->GetPermanentActorValue(RE::ActorValue::kEnchanting));
|
|
|
|
args3[19].SetNumber(playerAV->GetActorValue(RE::ActorValue::kSmithing) - playerAV->GetPermanentActorValue(RE::ActorValue::kSmithing));
|
|
|
|
args3[20].SetNumber(playerAV->GetActorValue(RE::ActorValue::kSpeech) - playerAV->GetPermanentActorValue(RE::ActorValue::kSpeech));
|
2023-12-21 11:41:08 +00:00
|
|
|
uiMovie->Invoke("heromenu_mc.SetModifier", nullptr, args3, 21);
|
2023-12-19 15:58:36 +00:00
|
|
|
}
|