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

This commit is contained in:
Eddoursul 2023-12-19 16:58:36 +01:00
parent a72dd6611e
commit ef56d468c5
21 changed files with 302 additions and 94 deletions

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.

Binary file not shown.

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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