1
Fork 0
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

268 lines
8.1 KiB

#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::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<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, 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<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>&) {}
};
RE::BSTSmartPointer<ScriptVersionCallback>{
new ScriptVersionCallback("_00E_PlayerSetUpScript", 1)
};
RE::BSTSmartPointer<ScriptVersionCallback>{
new ScriptVersionCallback("_00E_Phasmalist_NewApparitionAlias", 1)
};
RE::BSTSmartPointer<ScriptVersionCallback>{
new ScriptVersionCallback("_00E_Theriantrophist_PlayerAsWerewolf", 1)
};
RE::BSTSmartPointer<ScriptVersionCallback>{
new ScriptVersionCallback("_00E_Theriantrophist_AlchemyControl", 1)
};
RE::BSTSmartPointer<ScriptVersionCallback>{
new ScriptVersionCallback("_00E_PlayerhousingMaster", 1)
};
RE::BSTSmartPointer<ScriptVersionCallback>{
new ScriptVersionCallback("_00E_ContainerController", 1)
};
}
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);
REL::Relocation<func_t> 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<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();
}