4
Fork 0

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

remove-levelsystem
Eddoursul 4 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/EventListener.cpp
src/Papyrus.cpp
src/Patches/TweenMenuPatch.cpp
src/Patches/HeroMenuPatch.cpp
${CMAKE_CURRENT_BINARY_DIR}/version.rc)

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

@ -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")) {

@ -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);
}
}

@ -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
#include "HeroMenuPatch.h"
class TweenMenuPatch final : public RE::TweenMenu
{
public:
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;
}
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<decltype(&Accept)> _AcceptFn;
inline static REL::Relocation<decltype(OpenStats)> _OpenStats;
};

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

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

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

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

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

Loading…
Cancel
Save