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