1
Fork 0

Hero menu: moved handling to DLL, opens on Quick Stats hotkey, added widescreen support

development
Eddoursul 5 months ago
parent a72dd6611e
commit ef56d468c5
  1. BIN
      Enderal - Forgotten Stories.esm
  2. BIN
      SKSE/Plugins/EnderalSE.dll
  3. BIN
      scripts/_00E_AffinityControl.pex
  4. BIN
      scripts/_00E_Func_ComputeNeededExp.pex
  5. BIN
      scripts/_00e_epupdatefunctions.pex
  6. BIN
      scripts/_00e_heromenualias.pex
  7. BIN
      scripts/enderalfunctions.pex
  8. 2
      source/Enderal DLL/CMakeLists.txt
  9. 14
      source/Enderal DLL/src/EventListener.cpp
  10. 2
      source/Enderal DLL/src/Main.cpp
  11. 9
      source/Enderal DLL/src/PapyrusFunctions.h
  12. 153
      source/Enderal DLL/src/Patches/HeroMenuPatch.cpp
  13. 22
      source/Enderal DLL/src/Patches/HeroMenuPatch.h
  14. 8
      source/Enderal DLL/src/Patches/TweenMenuPatch.cpp
  15. 31
      source/Enderal DLL/src/Patches/TweenMenuPatch.h
  16. 26
      source/Enderal DLL/src/Util.h
  17. 6
      source/scripts/_00E_AffinityControl.psc
  18. 54
      source/scripts/_00E_Func_ComputeNeededExp.psc
  19. 58
      source/scripts/_00e_epupdatefunctions.psc
  20. 5
      source/scripts/_00e_heromenualias.psc
  21. 2
      source/scripts/enderalfunctions.psc

Binary file not shown.

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -26,6 +26,8 @@ set(sources
src/Main.cpp src/Main.cpp
src/EventListener.cpp src/EventListener.cpp
src/Papyrus.cpp src/Papyrus.cpp
src/Patches/TweenMenuPatch.cpp
src/Patches/HeroMenuPatch.cpp
${CMAKE_CURRENT_BINARY_DIR}/version.rc) ${CMAKE_CURRENT_BINARY_DIR}/version.rc)

@ -1,4 +1,5 @@
#include "EventListener.h" #include "EventListener.h"
#include "Patches/HeroMenuPatch.h"
auto EventListener::GetSingleton() -> EventListener* auto EventListener::GetSingleton() -> EventListener*
{ {
@ -22,9 +23,6 @@ auto EventListener::ProcessEvent(
if (a_event->eventName == "Enderal_OpenHelpURL") { if (a_event->eventName == "Enderal_OpenHelpURL") {
ShowWindow(GetForegroundWindow(), SW_MINIMIZE); ShowWindow(GetForegroundWindow(), SW_MINIMIZE);
ShellExecuteA(NULL, "open", "https://go.eddoursul.win/enderal-se-support", NULL, NULL, SW_SHOWNORMAL); 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; return RE::BSEventNotifyControl::kContinue;
@ -49,8 +47,14 @@ auto EventListener::ProcessEvent(
if (a_event->menuName == RE::DialogueMenu::MENU_NAME) { 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. // Make sure Tab is not blocked. If it should be, a Papyrus script will block it a few frames later.
DialogueMenuPatch::BlockTab(false); DialogueMenuPatch::BlockTab(false);
} else if (a_event->menuName == "CustomMenu" && RE::UI::GetSingleton()->IsMenuOpen(RE::TweenMenu::MENU_NAME)) { } else if (a_event->menuName == "CustomMenu") {
CloseTweenMenu(); 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 { } else {
if (a_event->menuName == RE::DialogueMenu::MENU_NAME) { if (a_event->menuName == RE::DialogueMenu::MENU_NAME) {

@ -8,6 +8,7 @@
#include "BinkInterruptPatch.h" #include "BinkInterruptPatch.h"
#include "DialogueMenuPatch.h" #include "DialogueMenuPatch.h"
#include "Patches/TweenMenuPatch.h" #include "Patches/TweenMenuPatch.h"
#include "Patches/HeroMenuPatch.h"
using namespace SKSE; using namespace SKSE;
@ -170,6 +171,7 @@ SKSEPluginLoad(const LoadInterface* skse) {
GetPapyrusInterface()->Register(Papyrus::Bind); GetPapyrusInterface()->Register(Papyrus::Bind);
DialogueMenuPatch::Install(); DialogueMenuPatch::Install();
HeroMenuPatch::Install();
TweenMenuPatch::Install(); TweenMenuPatch::Install();
if (g_settings.at("VideoInterruptPatch")) { if (g_settings.at("VideoInterruptPatch")) {

@ -98,6 +98,11 @@ namespace Papyrus::PapyrusFunctions
DialogueMenuPatch::BlockTab(false); 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) inline void Bind(VM& a_vm)
{ {
BIND(CreatePotion); BIND(CreatePotion);
@ -114,7 +119,7 @@ namespace Papyrus::PapyrusFunctions
logger::info("{}", "Registered StringToHex"sv); logger::info("{}", "Registered StringToHex"sv);
BIND(DisableDialogueQuitting); BIND(DisableDialogueQuitting);
logger::info("{}", "Registered DisableDialogueQuitting"sv); logger::info("{}", "Registered DisableDialogueQuitting"sv);
BIND(EnableDialogueQuitting); BIND(ComputeNeededExp);
logger::info("{}", "Registered EnableDialogueQuitting"sv); logger::info("{}", "Registered ComputeNeededExp"sv);
} }
} }

@ -0,0 +1,153 @@
#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")) {
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<std::uintptr_t> vTable(RE::VTABLE_MenuControls[0]);
_ProcessEvent = vTable.write_vfunc(0x1, &HeroMenuPatch::ProcessEvent_Hook);
// Patch the Quick Stats hotkey
REL::Relocation<std::uintptr_t> 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<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)
{
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<RE::BSScript::Object>&) {}
};
auto uiMovie = RE::UI::GetSingleton()->GetMovieView("CustomMenu");
uiMovie->SetViewScaleMode(RE::BSScaleformManager::ScaleModeType::kShowAll);
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("");
uiMovie->Invoke("_root.heromenu_mc.SetStringValues", nullptr, args, 2);
RE::BSTSmartPointer<RE::BSScript::IStackCallbackFunctor> 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);
}

@ -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<decltype(OpenStats)> _OpenStats;
RE::BSEventNotifyControl ProcessEvent_Hook(RE::InputEvent** a_event, RE::BSTEventSource<RE::InputEvent*>* a_source);
using ProcessEvent_t = decltype(static_cast<RE::BSEventNotifyControl (RE::MenuControls::*)(RE::InputEvent* const*, RE::BSTEventSource<RE::InputEvent*>*)>(&RE::MenuControls::ProcessEvent));
static inline REL::Relocation<ProcessEvent_t> _ProcessEvent;
};

@ -0,0 +1,8 @@
#include "TweenMenuPatch.h"
void TweenMenuPatch::Install()
{
// Patch the Tween Menu
REL::Relocation<uintptr_t> vtbl(RE::VTABLE_TweenMenu[0]);
_AcceptFn = vtbl.write_vfunc(0x1, &AcceptEx);
}

@ -1,34 +1,13 @@
#pragma once #pragma once
#include "HeroMenuPatch.h"
class TweenMenuPatch final : public RE::TweenMenu class TweenMenuPatch final : public RE::TweenMenu
{ {
public: public:
static void Install() static void Install();
{
REL::Relocation<uintptr_t> vtbl(RE::VTABLE_TweenMenu[0]);
_AcceptFn = vtbl.write_vfunc(0x1, &AcceptEx);
REL::Relocation<std::uintptr_t> 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<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));
}
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;
}
private: private:
static void OpenMenu(RE::IMenu* tweenMenu, std::int32_t index) static void OpenMenu(RE::IMenu* tweenMenu, std::int32_t index)
{ {
using func_t = decltype(&OpenMenu); using func_t = decltype(&OpenMenu);
@ -44,8 +23,7 @@ private:
a_cbReg->Process("OpenHighlightedMenu", [](const RE::FxDelegateArgs& args) { a_cbReg->Process("OpenHighlightedMenu", [](const RE::FxDelegateArgs& args) {
auto index = args[0].GetSInt(); auto index = args[0].GetSInt();
if (index <= 1) { if (index <= 1) {
SKSE::ModCallbackEvent modEvent{ "Enderal_OpenHeroMenu", "", 0.0f, nullptr }; HeroMenuPatch::OpenHeroMenu();
SKSE::GetModCallbackEventSource()->SendEvent(&modEvent);
} else if (index <= 4) { } else if (index <= 4) {
OpenMenu(RE::UI::GetSingleton()->GetMenu(MENU_NAME).get(), index); OpenMenu(RE::UI::GetSingleton()->GetMenu(MENU_NAME).get(), index);
} }
@ -53,5 +31,4 @@ private:
} }
inline static REL::Relocation<decltype(&Accept)> _AcceptFn; inline static REL::Relocation<decltype(&Accept)> _AcceptFn;
inline static REL::Relocation<decltype(OpenStats)> _OpenStats;
}; };

@ -178,3 +178,29 @@ inline void CloseTweenMenu()
return func(); 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;
}

@ -466,6 +466,12 @@ String Function GetPlayerClassName()
EndIf EndIf
EndFunction 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 ; PROPERTIES

@ -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

@ -284,7 +284,7 @@ bool function receiveEP(int amount)
iLevelUpsNeeded = iLevelUpCount iLevelUpsNeeded = iLevelUpCount
_UnlockLevelUpSystem() _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()) int iNeededExpNextLevel = (PlayerNeededExp.GetValueInt()-PlayerExp.GetValueInt())
_00E_sEPNeeded.Show(amount, iNeededExpNextLevel) _00E_sEPNeeded.Show(amount, iNeededExpNextLevel)
@ -303,7 +303,7 @@ int function getLevelUpCount()
While LevelCheckRequired While LevelCheckRequired
LevelCheckRequired = false LevelCheckRequired = false
NeededExp = ComputeNeededExp(iCurrentLevel, EXPMultSlope.GetValue(), EXPMult.GetValue()) NeededExp = _00E_Func_ComputeNeededExp.Run(iCurrentLevel, EXPMultSlope.GetValue(), EXPMult.GetValue())
if iCurrentExp >= NeededExp if iCurrentExp >= NeededExp
LevelCheckRequired = true LevelCheckRequired = true
@ -317,60 +317,6 @@ int function getLevelUpCount()
EndFunction 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 ; PROPERTIES

@ -1,4 +1,5 @@
Scriptname _00E_HeroMenuAlias extends ReferenceAlias Hidden 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 int function _GetScriptVersion() Global
return 1 return 1
@ -99,8 +100,8 @@ Float[] Function GetFloats()
int iPlayerLevel = PlayerLevel.GetValueInt() int iPlayerLevel = PlayerLevel.GetValueInt()
float fEXPMultSlope = EXPMultSlope.GetValue() float fEXPMultSlope = EXPMultSlope.GetValue()
float fEXPMult = EXPMult.GetValue() float fEXPMult = EXPMult.GetValue()
float fEXPNeededForCurrentLevel = (PlayerREF as _00E_EPUpdateFunctions).ComputeNeededExp(iPlayerLevel - 1, fEXPMultSlope, fEXPMult) float fEXPNeededForCurrentLevel = _00E_Func_ComputeNeededExp.Run(iPlayerLevel - 1, fEXPMultSlope, fEXPMult)
float fEXPNeededForNextLevel = (PlayerREF as _00E_EPUpdateFunctions).ComputeNeededExp(iPlayerLevel, fEXPMultSlope, fEXPMult) float fEXPNeededForNextLevel = _00E_Func_ComputeNeededExp.Run(iPlayerLevel, fEXPMultSlope, fEXPMult)
int iPlayerExp = PlayerExp.GetValueInt() int iPlayerExp = PlayerExp.GetValueInt()
SkillmenuFloats[0] = (AiHealth.GetBaseValue(PlayerREF)) as Int SkillmenuFloats[0] = (AiHealth.GetBaseValue(PlayerREF)) as Int

@ -19,6 +19,8 @@ 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
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. ; Disables the TAB Key during dialogue. Resets automatically upon dialogue exit via Goodbye.
Function DisableDialogueQuitting() Global Native Function DisableDialogueQuitting() Global Native
Function EnableDialogueQuitting() Global Native Function EnableDialogueQuitting() Global Native

Loading…
Cancel
Save