#pragma once #include "CheckInvalidForms.h" #include #include 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::BSScript::Internal::VirtualMachine::GetSingleton()->TypeIsValid("_00E_QuestFunctions")) { RE::DebugMessageBox("OUTDATED MOD: One of your mods modifies the _00E_QuestFunctions script, no longer used in Enderal SE 2.1+"); } CheckWorldspaces(); CheckUnconvertedMap(); CheckSkyrimCells(); CheckEnderalCells(); CheckEnderalContainers(); CheckEnderalNPCs(); CheckEnderalActivators(); CheckEnderalStatics(); } inline bool PapyrusGlobalFunctionExists(const char* scriptName, const char* funcName) { RE::BSTSmartPointer 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, int a_version) : scriptName(a_scriptName), expectedVersion(a_version) { if (PapyrusGlobalFunctionExists(scriptName.c_str(), funcName.c_str())) { const auto vm = RE::BSScript::Internal::VirtualMachine::GetSingleton(); auto callbackPtr = RE::BSTSmartPointer(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::BSTSmartPointer{ new ScriptVersionCallback("_00E_PlayerSetUpScript", 1) }; RE::BSTSmartPointer{ new ScriptVersionCallback("_00E_Phasmalist_NewApparitionAlias", 1) }; RE::BSTSmartPointer{ new ScriptVersionCallback("_00E_Theriantrophist_PlayerAsWerewolf", 1) }; RE::BSTSmartPointer{ new ScriptVersionCallback("_00E_Theriantrophist_AlchemyControl", 1) }; RE::BSTSmartPointer{ new ScriptVersionCallback("_00E_PlayerhousingMaster", 1) }; RE::BSTSmartPointer{ new ScriptVersionCallback("_00E_ContainerController", 1) }; } inline void LoadINI(std::map* 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 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); REL::Relocation func{ REL::RelocationID(51839, 52711) }; 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; } 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(-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(ch); } return sstream.str(); }