329 lines
11 KiB
C++
329 lines
11 KiB
C++
#pragma once
|
|
|
|
#include "CheckInvalidForms.h"
|
|
#include <SimpleIni.h>
|
|
#include <regex>
|
|
|
|
inline const SKSE::LoadInterface* GetLoadInterface(const SKSE::LoadInterface* loadInterface = nullptr)
|
|
{
|
|
const static SKSE::LoadInterface* singleton;
|
|
if (loadInterface) {
|
|
singleton = loadInterface;
|
|
}
|
|
return singleton;
|
|
}
|
|
|
|
inline uint8_t NewGameCount(bool increment = false)
|
|
{
|
|
static uint8_t g_NewGameStarted = 0;
|
|
|
|
if (increment) {
|
|
g_NewGameStarted++;
|
|
}
|
|
|
|
return g_NewGameStarted;
|
|
}
|
|
|
|
inline void CheckIncompatibleMods()
|
|
{
|
|
bool bPrinted = false;
|
|
if (std::filesystem::exists("Data\\SKSE\\Plugins\\EnderalVersion.ini")) {
|
|
CSimpleIniA ini;
|
|
ini.SetUnicode(false);
|
|
ini.SetMultiKey(false);
|
|
ini.LoadFile("Data/SKSE/Plugins/EnderalVersion.ini");
|
|
const char* version = ini.GetValue("", "version", "2.0.x");
|
|
std::regex version_expr("^[\\d\\.]+$");
|
|
if (std::regex_match(version, version_expr)) {
|
|
RE::ConsoleLog::GetSingleton()->Print(std::format("Loaded SureAI's Enderal: Forgotten Stories | Special Edition v{} by Eddoursul and contributors", version).c_str());
|
|
bPrinted = true;
|
|
}
|
|
}
|
|
|
|
if (!bPrinted) {
|
|
RE::ConsoleLog::GetSingleton()->Print("Loaded SureAI's Enderal: Forgotten Stories | Special Edition v2.0.x by Eddoursul and contributors");
|
|
}
|
|
|
|
if (RE::BSResourceNiBinaryStream("scripts/_00e_questfunctions.pex").good() || RE::BSResourceNiBinaryStream("scripts/_00e_game_skillmenusc.pex").good() || RE::TESDataHandler::GetSingleton()->LookupForm<RE::TESQuest>(0x10AA2, "Skyrim.esm"))
|
|
{
|
|
RE::DebugMessageBox("OUTDATED MOD: One of your mods modifies the Levelsystem quest or its scripts (_00E_QuestFunctions or _00E_Game_SkillmenuSC), no longer used in Enderal SE 2.1+");
|
|
}
|
|
|
|
CheckWorldspaces();
|
|
CheckUnconvertedMap();
|
|
CheckSkyrimCells();
|
|
}
|
|
|
|
inline bool PapyrusGlobalFunctionExists(const char* scriptName, const char* funcName)
|
|
{
|
|
RE::BSTSmartPointer<RE::BSScript::ObjectTypeInfo> typeInfoPtr;
|
|
RE::BSScript::Internal::VirtualMachine::GetSingleton()->GetScriptObjectType(RE::BSFixedString(scriptName), typeInfoPtr);
|
|
auto functionCount = typeInfoPtr->GetNumGlobalFuncs();
|
|
auto functions = typeInfoPtr->GetGlobalFuncIter();
|
|
for (uint32_t i = 0; i < functionCount; i++) {
|
|
if (functions[i].func->GetName().c_str() == funcName) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline void CheckScriptVersions()
|
|
{
|
|
class ScriptVersionCallback : public RE::BSScript::IStackCallbackFunctor
|
|
{
|
|
private:
|
|
std::uint32_t expectedVersion;
|
|
const RE::BSFixedString scriptName;
|
|
const RE::BSFixedString funcName = "_GetScriptVersion";
|
|
public:
|
|
ScriptVersionCallback(const RE::BSFixedString a_scriptName, short a_version) :
|
|
scriptName(a_scriptName),
|
|
expectedVersion(a_version)
|
|
{
|
|
const auto vm = RE::BSScript::Internal::VirtualMachine::GetSingleton();
|
|
|
|
if (vm->TypeIsValid(scriptName) && PapyrusGlobalFunctionExists(scriptName.c_str(), funcName.c_str())) {
|
|
auto callbackPtr = RE::BSTSmartPointer<RE::BSScript::IStackCallbackFunctor>(this);
|
|
vm->DispatchStaticCall(scriptName, funcName, RE::MakeFunctionArguments(), callbackPtr);
|
|
} else {
|
|
auto a_script = scriptName;
|
|
auto a_version = expectedVersion;
|
|
SKSE::GetTaskInterface()->AddTask([a_script, a_version]() {
|
|
RE::DebugMessageBox(std::format("Script {} is overwritten by a mod, incompatible with current version of Enderal. Expected version: {}, installed version: N/A.", a_script.c_str(), a_version).c_str());
|
|
});
|
|
}
|
|
}
|
|
|
|
void operator()(RE::BSScript::Variable a_result)
|
|
{
|
|
if (a_result.GetUInt() != expectedVersion) {
|
|
RE::DebugMessageBox(std::format("Script {} is overwritten by a mod, incompatible with current version of Enderal. Expected version: {}, installed version: {}.", scriptName.c_str(), expectedVersion, a_result.GetUInt()).c_str());
|
|
}
|
|
}
|
|
bool CanSave() { return false; }
|
|
void SetObject(const RE::BSTSmartPointer<RE::BSScript::Object>&) {}
|
|
};
|
|
|
|
std::map<std::string, short> scripts;
|
|
scripts["EnderalFunctions"] = 1;
|
|
scripts["_00E_PlayerFunctions"] = 2;
|
|
scripts["_00E_PlayerSetUpScript"] = 1;
|
|
scripts["_00E_EngineBugfixAlias"] = 1;
|
|
scripts["_00E_Phasmalist_NewApparitionAlias"] = 1;
|
|
scripts["_FS_Phasmalist_ControlQuest"] = 1;
|
|
scripts["_00E_Phasmalist_Workbench"] = 1;
|
|
scripts["_00E_Theriantrophist_PlayerAsWerewolf"] = 2;
|
|
scripts["_00E_Theriantrophist_AlchemyControl"] = 1;
|
|
scripts["_00E_PlayerhousingMaster"] = 1;
|
|
scripts["_00E_ContainerBonusControl"] = 2;
|
|
scripts["_00E_AffinityControl"] = 1;
|
|
scripts["_00E_SympathyControl"] = 1;
|
|
scripts["_00E_EnderalMCM"] = 1;
|
|
scripts["_00E_SkillControl"] = 1;
|
|
scripts["_00E_VisionControl"] = 1;
|
|
scripts["_00E_EnderalControls"] = 1;
|
|
scripts["_00E_PeaceweedPlayerAliasScript"] = 1;
|
|
scripts["_00E_NQ_Bounty01_BoardScript"] = 1;
|
|
scripts["_00E_EPUpdateFunctions"] = 1;
|
|
scripts["_00E_GUI_ActorHealthBar"] = 1;
|
|
scripts["MineOreEnderal"] = 1;
|
|
scripts["_00e_fs_nqr05_functions"] = 1;
|
|
scripts["_00e_mq11a_functions"] = 1;
|
|
scripts["_00e_nq_g_07_functions"] = 1;
|
|
scripts["_00e_fs_nq02_functions"] = 1;
|
|
scripts["_00e_mq17_functions"] = 1;
|
|
scripts["_00e_mq16_functions"] = 1;
|
|
scripts["_00e_mq12b_functions"] = 1;
|
|
scripts["_00e_mq13c_functions"] = 1;
|
|
scripts["_00e_mq07a_functions"] = 1;
|
|
scripts["_00e_mq11c_functions"] = 1;
|
|
scripts["_00e_fs_nq01_functions"] = 1;
|
|
scripts["_00e_nqr01_functions"] = 1;
|
|
scripts["_00e_mq14_functions"] = 1;
|
|
scripts["_00e_mq06_functions"] = 1;
|
|
scripts["_00e_mq13a_functions"] = 1;
|
|
scripts["_00e_mq13b_functions"] = 1;
|
|
scripts["_00e_fs_nq07_functions"] = 1;
|
|
scripts["_00e_fs_nq02_functions2"] = 1;
|
|
scripts["_00e_fs_nq03_functions"] = 1;
|
|
scripts["_00e_mqp02_functions"] = 1;
|
|
scripts["_00e_mq12c_functions"] = 1;
|
|
scripts["_00e_mq02_functions"] = 1;
|
|
scripts["mq03_functions"] = 1;
|
|
scripts["_00e_mq05_functions"] = 1;
|
|
scripts["_00e_fs_nqr04_functions"] = 1;
|
|
scripts["_00e_cqc05_functions"] = 1;
|
|
scripts["_00e_fs_nqr03_functions"] = 1;
|
|
scripts["_00e_mq15_functions"] = 1;
|
|
scripts["_00e_fs_mq18c_functions"] = 1;
|
|
scripts["_00e_mqp01_functions"] = 1;
|
|
scripts["_00e_nq_g_06_functions"] = 1;
|
|
scripts["_00e_mq01_functions"] = 1;
|
|
scripts["_00e_fs_nqr02_functions"] = 1;
|
|
scripts["_00e_fs_nq05_functions"] = 1;
|
|
scripts["_00e_nq_g_05_functions"] = 1;
|
|
scripts["_00e_nq_g_03_functions"] = 1;
|
|
scripts["_00e_mqp03_functions"] = 1;
|
|
scripts["_00e_mq10a_functions"] = 1;
|
|
scripts["_00e_mq12a_functions"] = 1;
|
|
scripts["_00e_mq03_functions"] = 1;
|
|
scripts["_00e_nq_g_02_functions"] = 1;
|
|
scripts["_00e_cqj02_functions"] = 1;
|
|
scripts["_00e_cqc06_functions"] = 1;
|
|
scripts["_00e_fs_nq06_functions"] = 1;
|
|
scripts["_00e_cqj05_functions"] = 1;
|
|
scripts["_00e_mq18b_functions"] = 1;
|
|
scripts["_00e_nq06_functions"] = 1;
|
|
scripts["_00e_mq18a_functions"] = 1;
|
|
scripts["_00e_mq05prologue_functions"] = 1;
|
|
scripts["_00E_A2_EldritchBloodSC"] = 1;
|
|
scripts["_00E_A2_EldritchBloodPlayerSC"] = 1;
|
|
scripts["_00E_A2_EldritchBloodPlayerMarkSC"] = 1;
|
|
|
|
for (const auto& entry : scripts) {
|
|
RE::BSTSmartPointer<ScriptVersionCallback>{
|
|
new ScriptVersionCallback(entry.first, entry.second)
|
|
};
|
|
}
|
|
}
|
|
|
|
inline void LoadINI(std::map<std::string, bool>* settings, const char* iniPath)
|
|
{
|
|
for (auto it = settings->begin(); it != settings->end(); it++) {
|
|
logger::info("[DEFAULT] {} = {}", it->first, it->second);
|
|
}
|
|
|
|
if (!std::filesystem::exists(iniPath)) {
|
|
logger::warn("{} does not exist, using default values.", iniPath);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
CSimpleIniA ini;
|
|
ini.SetUnicode(false);
|
|
ini.SetMultiKey(false);
|
|
ini.LoadFile(iniPath);
|
|
|
|
std::list<CSimpleIniA::Entry> keysList;
|
|
ini.GetAllKeys("", keysList);
|
|
|
|
bool bUpdateINI = false;
|
|
for (auto it = settings->begin(); it != settings->end(); it++) {
|
|
bool bExists = false;
|
|
for (const auto& k : keysList) {
|
|
if (it->first == k.pItem) {
|
|
settings->insert_or_assign(k.pItem, ini.GetBoolValue("", k.pItem, settings->at(k.pItem)));
|
|
logger::info("[INI] {} = {}", k.pItem, settings->at(k.pItem));
|
|
bExists = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!bExists) {
|
|
ini.SetBoolValue("", it->first.c_str(), it->second);
|
|
bUpdateINI = true;
|
|
}
|
|
}
|
|
|
|
if (bUpdateINI) {
|
|
logger::info("New settings detected, adding to EnderalSE.ini");
|
|
ini.SaveFile(iniPath);
|
|
}
|
|
|
|
} catch (const std::exception& e) {
|
|
logger::error("{}", e.what());
|
|
}
|
|
}
|
|
|
|
inline void CloseTweenMenu()
|
|
{
|
|
if (RE::UI::GetSingleton()->IsMenuOpen(RE::TweenMenu::MENU_NAME)) {
|
|
using func_t = decltype(&CloseTweenMenu);
|
|
// Checked: 1.5.97, 1.6.640, 1.6.659, 1.6.1130, 1.6.1170, 1.6.1179
|
|
REL::Relocation<func_t> func{ REL::VariantID(51839, 52711, 0x8FE180) };
|
|
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;
|
|
}
|
|
|
|
inline std::uint32_t GetItemCount(RE::TESObjectREFR* a_container, RE::TESForm* a_form)
|
|
{
|
|
std::int32_t iResult = 0;
|
|
|
|
auto invChanges = a_container->GetInventoryChanges();
|
|
if (invChanges && invChanges->entryList) {
|
|
for (auto& entry : *invChanges->entryList) {
|
|
if (entry && entry->object == a_form) {
|
|
if (entry->IsLeveled()) {
|
|
return entry->countDelta > 0 ? entry->countDelta : 0;
|
|
} else {
|
|
iResult = entry->countDelta;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
auto container = a_container->GetContainer();
|
|
if (container) {
|
|
container->ForEachContainerObject([&](RE::ContainerObject& a_entry) {
|
|
if (a_entry.obj == a_form) {
|
|
iResult += a_entry.count;
|
|
return RE::BSContainer::ForEachResult::kStop;
|
|
}
|
|
return RE::BSContainer::ForEachResult::kContinue;
|
|
});
|
|
}
|
|
|
|
return iResult > 0 ? iResult : 0;
|
|
}
|
|
|
|
// Copied from Named Quicksaves by Ryan McKenzie (MIT)
|
|
inline RE::BSFixedString GetPlayerHash()
|
|
{
|
|
char buf[] = "DEADBEEF";
|
|
auto saveData = RE::BSWin32SaveDataSystemUtility::GetSingleton();
|
|
if (saveData->profileHash == static_cast<std::uint32_t>(-1)) {
|
|
std::snprintf(buf, sizeof(buf), "%08o", 0);
|
|
} else {
|
|
std::snprintf(buf, sizeof(buf), "%08X", saveData->profileHash);
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
inline RE::BSFixedString StringToHex(RE::BSFixedString a_string)
|
|
{
|
|
std::string_view str(a_string);
|
|
|
|
std::stringstream sstream;
|
|
for (auto ch : str) {
|
|
sstream << std::uppercase << std::hex << static_cast<int>(ch);
|
|
}
|
|
|
|
return sstream.str();
|
|
} |