diff --git a/Enderal - Forgotten Stories.esm b/Enderal - Forgotten Stories.esm index 5fd023b5..69fa031e 100644 Binary files a/Enderal - Forgotten Stories.esm and b/Enderal - Forgotten Stories.esm differ diff --git a/SKSE/Plugins/EnderalSE.dll b/SKSE/Plugins/EnderalSE.dll index db60213e..f3275206 100644 --- a/SKSE/Plugins/EnderalSE.dll +++ b/SKSE/Plugins/EnderalSE.dll @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2672a34820e8f3cac71ba28296fbafb5a92142f00e847e398e4319627cc81a8b -size 737280 +oid sha256:c7bf01547071c10496b6bf64ed446f7e8ab6572b62b10aae26b34d93292b7417 +size 750080 diff --git a/scripts/_00E_AffinityControl.pex b/scripts/_00E_AffinityControl.pex index 0b662a03..24b984e1 100644 Binary files a/scripts/_00E_AffinityControl.pex and b/scripts/_00E_AffinityControl.pex differ diff --git a/scripts/_00E_Func_ComputeNeededExp.pex b/scripts/_00E_Func_ComputeNeededExp.pex new file mode 100644 index 00000000..5cc1c366 Binary files /dev/null and b/scripts/_00E_Func_ComputeNeededExp.pex differ diff --git a/scripts/_00e_epupdatefunctions.pex b/scripts/_00e_epupdatefunctions.pex index c4a17cb9..1fd2b68b 100644 Binary files a/scripts/_00e_epupdatefunctions.pex and b/scripts/_00e_epupdatefunctions.pex differ diff --git a/scripts/_00e_heromenualias.pex b/scripts/_00e_heromenualias.pex index 55570de1..0ec18363 100644 Binary files a/scripts/_00e_heromenualias.pex and b/scripts/_00e_heromenualias.pex differ diff --git a/scripts/enderalfunctions.pex b/scripts/enderalfunctions.pex index 9cedf241..105b5ca8 100644 Binary files a/scripts/enderalfunctions.pex and b/scripts/enderalfunctions.pex differ diff --git a/source/Enderal DLL/CMakeLists.txt b/source/Enderal DLL/CMakeLists.txt index 493c5a47..d5ae852c 100644 --- a/source/Enderal DLL/CMakeLists.txt +++ b/source/Enderal DLL/CMakeLists.txt @@ -26,6 +26,8 @@ set(sources src/Main.cpp src/EventListener.cpp src/Papyrus.cpp + src/Patches/TweenMenuPatch.cpp + src/Patches/HeroMenuPatch.cpp ${CMAKE_CURRENT_BINARY_DIR}/version.rc) diff --git a/source/Enderal DLL/src/EventListener.cpp b/source/Enderal DLL/src/EventListener.cpp index 45df00b6..bc93217c 100644 --- a/source/Enderal DLL/src/EventListener.cpp +++ b/source/Enderal DLL/src/EventListener.cpp @@ -1,4 +1,5 @@ #include "EventListener.h" +#include "Patches/HeroMenuPatch.h" auto EventListener::GetSingleton() -> EventListener* { @@ -22,9 +23,6 @@ auto EventListener::ProcessEvent( if (a_event->eventName == "Enderal_OpenHelpURL") { ShowWindow(GetForegroundWindow(), SW_MINIMIZE); ShellExecuteA(NULL, "open", "https://go.eddoursul.win/enderal-se-support", NULL, NULL, SW_SHOWNORMAL); - - //} else if (a_event->eventName == "Enderal_OpenQuestJournal") { - // RE::UIMessageQueue::GetSingleton()->AddMessage(RE::InterfaceStrings::GetSingleton()->journalMenu, RE::UI_MESSAGE_TYPE::kShow, 0i64); } return RE::BSEventNotifyControl::kContinue; @@ -49,8 +47,14 @@ 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); - } else if (a_event->menuName == "CustomMenu" && RE::UI::GetSingleton()->IsMenuOpen(RE::TweenMenu::MENU_NAME)) { - CloseTweenMenu(); + } else if (a_event->menuName == "CustomMenu") { + auto uiMovie = RE::UI::GetSingleton()->GetMovieView("CustomMenu"); + if (uiMovie && std::strstr(uiMovie->GetMovieDef()->GetFileURL(), "00e_heromenu.swf") != NULL) { + if (RE::UI::GetSingleton()->IsMenuOpen(RE::TweenMenu::MENU_NAME)) { + CloseTweenMenu(); + } + HeroMenuPatch::FillMenuValues(); + } } } else { if (a_event->menuName == RE::DialogueMenu::MENU_NAME) { diff --git a/source/Enderal DLL/src/Main.cpp b/source/Enderal DLL/src/Main.cpp index 3d22c370..cce768c4 100644 --- a/source/Enderal DLL/src/Main.cpp +++ b/source/Enderal DLL/src/Main.cpp @@ -8,6 +8,7 @@ #include "BinkInterruptPatch.h" #include "DialogueMenuPatch.h" #include "Patches/TweenMenuPatch.h" +#include "Patches/HeroMenuPatch.h" using namespace SKSE; @@ -170,6 +171,7 @@ SKSEPluginLoad(const LoadInterface* skse) { GetPapyrusInterface()->Register(Papyrus::Bind); DialogueMenuPatch::Install(); + HeroMenuPatch::Install(); TweenMenuPatch::Install(); if (g_settings.at("VideoInterruptPatch")) { diff --git a/source/Enderal DLL/src/PapyrusFunctions.h b/source/Enderal DLL/src/PapyrusFunctions.h index 18720627..f36360d5 100644 --- a/source/Enderal DLL/src/PapyrusFunctions.h +++ b/source/Enderal DLL/src/PapyrusFunctions.h @@ -98,6 +98,11 @@ namespace Papyrus::PapyrusFunctions DialogueMenuPatch::BlockTab(false); } + float ComputeNeededExp(RE::StaticFunctionTag*, std::uint32_t CurrentLevel, float Slope, float Mult, float fExpAcc = 1.0f, float fExpAcc_Level20 = 1.2f, float fExpAcc_Level30 = 1.5f, float fExpAcc_Level40 = 2.0f) + { + return ComputeNeededExpPoints(CurrentLevel, Slope, Mult, fExpAcc, fExpAcc_Level20, fExpAcc_Level30, fExpAcc_Level40); + } + inline void Bind(VM& a_vm) { BIND(CreatePotion); @@ -114,7 +119,7 @@ namespace Papyrus::PapyrusFunctions logger::info("{}", "Registered StringToHex"sv); BIND(DisableDialogueQuitting); logger::info("{}", "Registered DisableDialogueQuitting"sv); - BIND(EnableDialogueQuitting); - logger::info("{}", "Registered EnableDialogueQuitting"sv); + BIND(ComputeNeededExp); + logger::info("{}", "Registered ComputeNeededExp"sv); } } diff --git a/source/Enderal DLL/src/Patches/HeroMenuPatch.cpp b/source/Enderal DLL/src/Patches/HeroMenuPatch.cpp new file mode 100644 index 00000000..2120a9fa --- /dev/null +++ b/source/Enderal DLL/src/Patches/HeroMenuPatch.cpp @@ -0,0 +1,153 @@ +#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(), "00e_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 + REL::Relocation vTable(RE::VTABLE_MenuControls[0]); + _ProcessEvent = vTable.write_vfunc(0x1, &HeroMenuPatch::ProcessEvent_Hook); + + // Patch the Quick Stats hotkey + REL::Relocation target{ REL::RelocationID(51400, 52249), REL::Relocate(0x41E, 0x421) }; + REL::safe_fill(target.address(), REL::NOP, 45); + + auto& trampoline = SKSE::GetTrampoline(); + SKSE::AllocTrampoline(14); + _OpenStats = trampoline.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) + { + auto uiMovie = RE::UI::GetSingleton()->GetMovieView("CustomMenu"); + uiMovie->SetVariable("_root.heromenu_mc.stats.playerclass.stat_value.text", a_result.GetString()); + } + bool CanSave() { return false; } + void SetObject(const RE::BSTSmartPointer&) {} + }; + + auto uiMovie = RE::UI::GetSingleton()->GetMovieView("CustomMenu"); + 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("_root.heromenu_mc.SetStringValues", nullptr, args, 2); + + RE::BSTSmartPointer stackCallback{ new ScriptClassNameCallback }; + RE::BSScript::Internal::VirtualMachine::GetSingleton()->DispatchStaticCall("_00E_AffinityControl", "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("_root.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("_root.heromenu_mc.SetModifier", nullptr, args3, 21); +} diff --git a/source/Enderal DLL/src/Patches/HeroMenuPatch.h b/source/Enderal DLL/src/Patches/HeroMenuPatch.h new file mode 100644 index 00000000..28a20779 --- /dev/null +++ b/source/Enderal DLL/src/Patches/HeroMenuPatch.h @@ -0,0 +1,22 @@ +#pragma once + +#include "Util.h" + +class HeroMenuPatch : public RE::MenuControls +{ +public: + static void Install(); + + static uint64_t OpenStats(uint32_t arg_1, uint32_t arg_2, uint32_t arg_3, uint32_t arg_4, uint32_t arg_5); + + static void FillMenuValues(); + + static void OpenHeroMenu(); + + inline static REL::Relocation _OpenStats; + + RE::BSEventNotifyControl ProcessEvent_Hook(RE::InputEvent** a_event, RE::BSTEventSource* a_source); + + using ProcessEvent_t = decltype(static_cast*)>(&RE::MenuControls::ProcessEvent)); + static inline REL::Relocation _ProcessEvent; +}; diff --git a/source/Enderal DLL/src/Patches/TweenMenuPatch.cpp b/source/Enderal DLL/src/Patches/TweenMenuPatch.cpp new file mode 100644 index 00000000..6a8cd67b --- /dev/null +++ b/source/Enderal DLL/src/Patches/TweenMenuPatch.cpp @@ -0,0 +1,8 @@ +#include "TweenMenuPatch.h" + +void TweenMenuPatch::Install() +{ + // Patch the Tween Menu + REL::Relocation vtbl(RE::VTABLE_TweenMenu[0]); + _AcceptFn = vtbl.write_vfunc(0x1, &AcceptEx); +} diff --git a/source/Enderal DLL/src/Patches/TweenMenuPatch.h b/source/Enderal DLL/src/Patches/TweenMenuPatch.h index 6277a1e6..347a34c2 100644 --- a/source/Enderal DLL/src/Patches/TweenMenuPatch.h +++ b/source/Enderal DLL/src/Patches/TweenMenuPatch.h @@ -1,34 +1,13 @@ #pragma once +#include "HeroMenuPatch.h" + class TweenMenuPatch final : public RE::TweenMenu { public: - static void Install() - { - REL::Relocation vtbl(RE::VTABLE_TweenMenu[0]); - _AcceptFn = vtbl.write_vfunc(0x1, &AcceptEx); - - REL::Relocation target{ REL::RelocationID(51400, 52249), REL::Relocate(0x41E, 0x421) }; - REL::safe_fill(target.address(), REL::NOP, 45); - - auto& trampoline = SKSE::GetTrampoline(); - SKSE::AllocTrampoline(14); - _OpenStats = trampoline.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)); - } - - static uint64_t OpenStats(uint32_t arg_1, uint32_t arg_2, uint32_t arg_3, uint32_t arg_4, uint32_t arg_5) - { - SKSE::ModCallbackEvent modEvent{ "Enderal_OpenHeroMenu", "", 0.0f, nullptr }; - SKSE::GetModCallbackEventSource()->SendEvent(&modEvent); - return 0; - } + static void Install(); private: - static void OpenMenu(RE::IMenu* tweenMenu, std::int32_t index) { using func_t = decltype(&OpenMenu); @@ -44,8 +23,7 @@ private: a_cbReg->Process("OpenHighlightedMenu", [](const RE::FxDelegateArgs& args) { auto index = args[0].GetSInt(); if (index <= 1) { - SKSE::ModCallbackEvent modEvent{ "Enderal_OpenHeroMenu", "", 0.0f, nullptr }; - SKSE::GetModCallbackEventSource()->SendEvent(&modEvent); + HeroMenuPatch::OpenHeroMenu(); } else if (index <= 4) { OpenMenu(RE::UI::GetSingleton()->GetMenu(MENU_NAME).get(), index); } @@ -53,5 +31,4 @@ private: } inline static REL::Relocation _AcceptFn; - inline static REL::Relocation _OpenStats; }; diff --git a/source/Enderal DLL/src/Util.h b/source/Enderal DLL/src/Util.h index e54a0452..83c3e9c1 100644 --- a/source/Enderal DLL/src/Util.h +++ b/source/Enderal DLL/src/Util.h @@ -178,3 +178,29 @@ inline void CloseTweenMenu() return func(); } } + +inline float ComputeNeededExpPoints(std::uint32_t CurrentLevel, float Slope, float Mult, float fExpAcc = 1.0f, float fExpAcc_Level20 = 1.2f, float fExpAcc_Level30 = 1.5f, float fExpAcc_Level40 = 2.0f) +{ + Mult *= fExpAcc; + + if (CurrentLevel <= 20) { + return pow(CurrentLevel, Slope) * Mult; + } + + float result = pow(20, Slope) * Mult; + + if (CurrentLevel <= 30) { + return result + (pow(CurrentLevel, Slope) - pow(20, Slope)) * Mult * fExpAcc_Level20; + } + + if (CurrentLevel <= 40) { + result += (pow(30, Slope) - pow(20, Slope)) * Mult * fExpAcc_Level20; + result += (pow(CurrentLevel, Slope) - pow(30, Slope)) * Mult * fExpAcc_Level30; + return result; + } + + result += (pow(30, Slope) - pow(20, Slope)) * Mult * fExpAcc_Level20; + result += (pow(40, Slope) - pow(30, Slope)) * Mult * fExpAcc_Level30; + result += (pow(CurrentLevel, Slope) - pow(40, Slope)) * Mult * fExpAcc_Level40; + return result; +} \ No newline at end of file diff --git a/source/scripts/_00E_AffinityControl.psc b/source/scripts/_00E_AffinityControl.psc index 78f208f0..01e87099 100644 --- a/source/scripts/_00E_AffinityControl.psc +++ b/source/scripts/_00E_AffinityControl.psc @@ -466,6 +466,12 @@ String Function GetPlayerClassName() EndIf EndFunction +String Function GetPlayerClassNameGlobal() Global +{Called by EnderalSE.dll} + Quest AffinityQuest = Game.GetFormFromFile(0x1597B, "Enderal - Forgotten Stories.esm") as Quest; + return (AffinityQuest.GetAlias(0) as _00E_AffinityControl).GetPlayerClassName() +endfunction + ;===================================================================================== ; PROPERTIES diff --git a/source/scripts/_00E_Func_ComputeNeededExp.psc b/source/scripts/_00E_Func_ComputeNeededExp.psc new file mode 100644 index 00000000..21e4bcf6 --- /dev/null +++ b/source/scripts/_00E_Func_ComputeNeededExp.psc @@ -0,0 +1,54 @@ +Scriptname _00E_Func_ComputeNeededExp Hidden + +;import Math + +float Function Run(int CurrentLevel, float Slope, float Mult, float fExpAcc = 1.0, float fExpAcc_Level20 = 1.2, float fExpAcc_Level30 = 1.5, float fExpAcc_Level40 = 2.0) Global +{Computes the total Experience (EP) needed by the player to reach the level CurrentLevel + 1. This includes the experience needed for the previous level.} + + return EnderalFunctions.ComputeNeededExp(CurrentLevel, Slope, Mult, fExpAcc, fExpAcc_Level20, fExpAcc_Level30, fExpAcc_Level40); + + ; This can be used as a quick way to scale the leveling process + ; for all levels: + ;/ + float fExpAcc = 1.0 + float fExpAcc_Level20 = 1.2 + float fExpAcc_Level30 = 1.5 + float fExpAcc_Level40 = 2.0 + + Mult *= fExpAcc + + If CurrentLevel <= 20 + ; no changes to the old formula until level 20. + return pow(CurrentLevel, Slope) * Mult + Else + ; no changes to the old formula for the first 20 levels. + float result = pow(20, Slope) * Mult + ; Progressive taxation: + if CurrentLevel <= 30 + result += (pow(CurrentLevel, Slope) - pow(20, Slope)) * Mult * fExpAcc_Level20 + return result + elseif CurrentLevel <= 40 + result += (pow(30, Slope) - pow(20, Slope)) * Mult * fExpAcc_Level20 + result += (pow(CurrentLevel, Slope) - pow(30, Slope)) * Mult * fExpAcc_Level30 + return result + else + result += (pow(30, Slope) - pow(20, Slope)) * Mult * fExpAcc_Level20 + result += (pow(40, Slope) - pow(30, Slope)) * Mult * fExpAcc_Level30 + result += (pow(CurrentLevel, Slope) - pow(40, Slope)) * Mult * fExpAcc_Level40 + return result + endif + EndIf + /; +EndFunction + +; A debugging function: +;function DumpLevelCurve() +;{Dump a table of required EP for each level from 0 to 100 to the Papyrus log} +; string aua = "" +; int iIndex = 0 +; while iIndex < 100 +; aua += "To get to level " + (iIndex + 1) + ", you need " + ComputeNeededExp(iIndex, EXPMultSlope.GetValue(), EXPMult.GetValue()) + "EP. \n " +; iIndex += 1 +; endwhile +; Debug.Trace(aua) +;Endfunction diff --git a/source/scripts/_00e_epupdatefunctions.psc b/source/scripts/_00e_epupdatefunctions.psc index f6530ad4..2358b6e9 100644 --- a/source/scripts/_00e_epupdatefunctions.psc +++ b/source/scripts/_00e_epupdatefunctions.psc @@ -284,7 +284,7 @@ bool function receiveEP(int amount) iLevelUpsNeeded = iLevelUpCount _UnlockLevelUpSystem() - PlayerNeededExp.SetValue(ComputeNeededExp((PlayerLevel.GetValueInt()+iLevelUpCount), EXPMultSlope.GetValue(), EXPMult.GetValue())) + PlayerNeededExp.SetValue(_00E_Func_ComputeNeededExp.Run((PlayerLevel.GetValueInt()+iLevelUpCount), EXPMultSlope.GetValue(), EXPMult.GetValue())) int iNeededExpNextLevel = (PlayerNeededExp.GetValueInt()-PlayerExp.GetValueInt()) _00E_sEPNeeded.Show(amount, iNeededExpNextLevel) @@ -303,7 +303,7 @@ int function getLevelUpCount() While LevelCheckRequired LevelCheckRequired = false - NeededExp = ComputeNeededExp(iCurrentLevel, EXPMultSlope.GetValue(), EXPMult.GetValue()) + NeededExp = _00E_Func_ComputeNeededExp.Run(iCurrentLevel, EXPMultSlope.GetValue(), EXPMult.GetValue()) if iCurrentExp >= NeededExp LevelCheckRequired = true @@ -317,60 +317,6 @@ int function getLevelUpCount() EndFunction -float Function ComputeNeededExp(int CurrentLevel, float Slope, float Mult) -{Computes the total Experience (EP) needed by the player to reach the level CurrentLevel + 1. This includes the experience needed for the previous level.} - - ; commented out as it is useless upon the Steam releases - ; GlobalVariable _00E_FS_IsForgottenStoriesActivated = Game.GetForm(0x0004320E) as GlobalVariable - ; If _00E_FS_IsForgottenStoriesActivated.GetValueInt() == 0 - ; Pre-DLC formula: - ; return pow(CurrentLevel, Slope) * Mult - ; EndIf - - ; This can be used as a quick way to scale the leveling process - ; for all levels: - float fExpAcc = 1.0 - float fExpAcc_Level20 = 1.2 - float fExpAcc_Level30 = 1.5 - float fExpAcc_Level40 = 2.0 - - Mult *= fExpAcc - - If CurrentLevel <= 20 - ; no changes to the old formula until level 20. - return pow(CurrentLevel, Slope) * Mult - Else - ; no changes to the old formula for the first 20 levels. - float result = pow(20, Slope) * Mult - ; Progressive taxation: - if CurrentLevel <= 30 - result += (pow(CurrentLevel, Slope) - pow(20, Slope)) * Mult * fExpAcc_Level20 - return result - elseif CurrentLevel <= 40 - result += (pow(30, Slope) - pow(20, Slope)) * Mult * fExpAcc_Level20 - result += (pow(CurrentLevel, Slope) - pow(30, Slope)) * Mult * fExpAcc_Level30 - return result - else - result += (pow(30, Slope) - pow(20, Slope)) * Mult * fExpAcc_Level20 - result += (pow(40, Slope) - pow(30, Slope)) * Mult * fExpAcc_Level30 - result += (pow(CurrentLevel, Slope) - pow(40, Slope)) * Mult * fExpAcc_Level40 - return result - endif - EndIf - -EndFunction - -; A debugging function: -;function DumpLevelCurve() -;{Dump a table of required EP for each level from 0 to 100 to the Papyrus log} -; string aua = "" -; int iIndex = 0 -; while iIndex < 100 -; aua += "To get to level " + (iIndex + 1) + ", you need " + ComputeNeededExp(iIndex, EXPMultSlope.GetValue(), EXPMult.GetValue()) + "EP. \n " -; iIndex += 1 -; endwhile -; Debug.Trace(aua) -;Endfunction ;===================================================================================== ; PROPERTIES diff --git a/source/scripts/_00e_heromenualias.psc b/source/scripts/_00e_heromenualias.psc index 0c5dc1a9..0380a334 100644 --- a/source/scripts/_00e_heromenualias.psc +++ b/source/scripts/_00e_heromenualias.psc @@ -1,4 +1,5 @@ Scriptname _00E_HeroMenuAlias extends ReferenceAlias Hidden +{This script is unused, hero menu handling has been moved to DLL in 2.1+.} int function _GetScriptVersion() Global return 1 @@ -99,8 +100,8 @@ Float[] Function GetFloats() int iPlayerLevel = PlayerLevel.GetValueInt() float fEXPMultSlope = EXPMultSlope.GetValue() float fEXPMult = EXPMult.GetValue() - float fEXPNeededForCurrentLevel = (PlayerREF as _00E_EPUpdateFunctions).ComputeNeededExp(iPlayerLevel - 1, fEXPMultSlope, fEXPMult) - float fEXPNeededForNextLevel = (PlayerREF as _00E_EPUpdateFunctions).ComputeNeededExp(iPlayerLevel, fEXPMultSlope, fEXPMult) + float fEXPNeededForCurrentLevel = _00E_Func_ComputeNeededExp.Run(iPlayerLevel - 1, fEXPMultSlope, fEXPMult) + float fEXPNeededForNextLevel = _00E_Func_ComputeNeededExp.Run(iPlayerLevel, fEXPMultSlope, fEXPMult) int iPlayerExp = PlayerExp.GetValueInt() SkillmenuFloats[0] = (AiHealth.GetBaseValue(PlayerREF)) as Int diff --git a/source/scripts/enderalfunctions.psc b/source/scripts/enderalfunctions.psc index 80d94a0f..541fb41d 100644 --- a/source/scripts/enderalfunctions.psc +++ b/source/scripts/enderalfunctions.psc @@ -19,6 +19,8 @@ String Function GetPlayerHash() Global Native ; RETURN - Returns the hexadecimal equivalent of the passed string. String Function StringToHex(String a_string) Global Native +float function ComputeNeededExp(int CurrentLevel, float Slope, float Mult, float fExpAcc = 1.0, float fExpAcc_Level20 = 1.2, float fExpAcc_Level30 = 1.5, float fExpAcc_Level40 = 2.0) global native + ; Disables the TAB Key during dialogue. Resets automatically upon dialogue exit via Goodbye. Function DisableDialogueQuitting() Global Native Function EnableDialogueQuitting() Global Native