mirror of
https://github.com/Mauler125/r5sdk.git
synced 2025-02-09 19:15:03 +01:00
Previously, we had 3 containers mapping ConVar flags for utilities, and none of them contained all flags. This refactor moved everything into a single container class where user can get strings by flags, or the other way around. The new class contains every flag. This also means that every flag can be used to list convars by flags using the command 'convar_findByFlags'. The 'ConVar_ParseFlagString' now also supports every flag. Code has been tested and confirmed to work as designed.
224 lines
6.7 KiB
C++
224 lines
6.7 KiB
C++
//=============================================================================//
|
|
//
|
|
// Purpose: Manage loading mods
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
//=============================================================================//
|
|
|
|
#include "core/stdafx.h"
|
|
#include "modsystem.h"
|
|
#include "localize/localize.h"
|
|
#include "tier1/cvar.h"
|
|
#include "vpc/rson.h"
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: initialize the mod system
|
|
// Input :
|
|
//-----------------------------------------------------------------------------
|
|
void CModSystem::Init()
|
|
{
|
|
LoadModStatusList();
|
|
|
|
CreateDirectories("platform\\mods");
|
|
|
|
for (auto& it : fs::directory_iterator("platform\\mods"))
|
|
{
|
|
if (!fs::is_directory(it))
|
|
continue;
|
|
|
|
fs::path basePath = it.path();
|
|
DevMsg(eDLL_T::ENGINE, "Found mod at '%s'.\n", basePath.string().c_str());
|
|
fs::path settingsPath = basePath / "mod.vdf";
|
|
|
|
if (fs::exists(settingsPath))
|
|
{
|
|
CModSystem::ModInstance_t modInst = CModSystem::ModInstance_t(basePath);
|
|
if (modInst.m_iState != eModState::UNLOADED)
|
|
m_vModList.push_back(modInst);
|
|
}
|
|
}
|
|
|
|
WriteModStatusList();
|
|
}
|
|
|
|
void CModSystem::LoadModStatusList()
|
|
{
|
|
if (FileSystem()->FileExists("platform/mods.vdf"))
|
|
{
|
|
KeyValues* pModList = FileSystem()->LoadKeyValues(IFileSystem::TYPE_COMMON, "platform/mods.vdf", "GAME");
|
|
|
|
for (KeyValues* pSubKey = pModList->GetFirstSubKey(); pSubKey != nullptr; pSubKey = pSubKey->GetNextKey())
|
|
{
|
|
size_t idHash = std::hash<std::string>{}(std::string(pSubKey->GetName()));
|
|
m_vEnabledList.emplace(idHash, pSubKey->GetBool());
|
|
}
|
|
}
|
|
}
|
|
|
|
void CModSystem::WriteModStatusList()
|
|
{
|
|
KeyValues kv = KeyValues("ModList");
|
|
KeyValues* pModListKV = kv.FindKey("ModList", true);
|
|
|
|
for (auto& it : m_vModList)
|
|
{
|
|
bool enabled = false;
|
|
if (it.m_iState == eModState::ENABLED)
|
|
enabled = true;
|
|
|
|
pModListKV->SetBool(it.m_szModID.c_str(), enabled);
|
|
}
|
|
|
|
CUtlBuffer uBuf = CUtlBuffer(int64_t(0), 0, CUtlBuffer::TEXT_BUFFER);
|
|
|
|
kv.RecursiveSaveToFile(uBuf, 0);
|
|
|
|
FileSystem()->WriteFile("platform/mods.vdf", NULL, uBuf); // NULL instead of "GAME" because otherwise for some reason the file clears itself when the process exits
|
|
}
|
|
|
|
|
|
CModSystem::ModInstance_t::ModInstance_t(const fs::path& basePath) : m_szName(std::string()), m_szModID(std::string()), m_BasePath(basePath), m_szDescription(std::string()), m_szVersion(std::string()), m_SettingsKV(nullptr), m_iState(eModState::LOADING)
|
|
{
|
|
std::string settingsPath = (m_BasePath / "mod.vdf").string();
|
|
|
|
KeyValues* pSettingsKV = FileSystem()->LoadKeyValues(IFileSystem::TYPE_COMMON, settingsPath.c_str(), "GAME");
|
|
m_SettingsKV = pSettingsKV;
|
|
|
|
if (!m_SettingsKV)
|
|
{
|
|
SetState(eModState::UNLOADED);
|
|
Error(eDLL_T::ENGINE, NO_ERROR, "Failed to parse mod.vdf for mod at path '%s'\n", m_BasePath.string().c_str());
|
|
return;
|
|
}
|
|
|
|
/////////////////////////////
|
|
// "name" "An R5Reloaded Mod"
|
|
// [rexx]: could be optional and have id as fallback
|
|
KeyValues* pName = pSettingsKV->FindKey("name");
|
|
|
|
if (!pName)
|
|
{
|
|
SetState(eModState::UNLOADED);
|
|
Error(eDLL_T::ENGINE, NO_ERROR, "Mod settings file '%s' was missing required 'name' field. Skipping mod...\n", settingsPath.c_str());
|
|
return;
|
|
}
|
|
|
|
m_szName = pName->GetString();
|
|
|
|
/////////////////////////////
|
|
// "version" "1.0.0"
|
|
KeyValues* pVersion = pSettingsKV->FindKey("version");
|
|
|
|
if (!pVersion)
|
|
{
|
|
SetState(eModState::UNLOADED);
|
|
Error(eDLL_T::ENGINE, NO_ERROR, "Mod settings file '%s' was missing required 'version' field. Skipping mod...\n", settingsPath.c_str());
|
|
return;
|
|
}
|
|
|
|
m_szVersion = pVersion->GetString();
|
|
|
|
/////////////////////////////
|
|
// "id" "r5reloaded.TestMod"
|
|
KeyValues* pId = pSettingsKV->FindKey("id");
|
|
|
|
if (!pId)
|
|
{
|
|
SetState(eModState::UNLOADED);
|
|
Error(eDLL_T::ENGINE, NO_ERROR, "Mod settings file '%s' was missing required 'id' field. Skipping mod...\n", settingsPath.c_str());
|
|
return;
|
|
}
|
|
|
|
m_szModID = pId->GetString();
|
|
|
|
/////////////////////////////
|
|
// optional mod description field
|
|
m_szDescription = pSettingsKV->GetString("description");
|
|
|
|
size_t idHash = std::hash<std::string>{}(m_szModID);
|
|
|
|
auto& enabledList = g_pModSystem->GetEnabledList();
|
|
if (enabledList.count(idHash) == 0)
|
|
{
|
|
DevMsg(eDLL_T::ENGINE, "Mod does not exist in 'mods.vdf'. Enabling...\n");
|
|
SetState(eModState::ENABLED);
|
|
}
|
|
else
|
|
{
|
|
bool bEnable = enabledList[idHash];
|
|
SetState(bEnable ? eModState::ENABLED : eModState::DISABLED);
|
|
|
|
DevMsg(eDLL_T::ENGINE, "Mod exists in 'mods.vdf' and is %s.\n", bEnable ? "enabled" : "disabled");
|
|
}
|
|
|
|
if (m_iState != eModState::ENABLED)
|
|
return;
|
|
|
|
// parse any additional info from mod.vdf
|
|
|
|
// add mod folder to search paths so files can be easily loaded from here
|
|
// [rexx]: maybe this isn't ideal as the only way of finding the mod's files, as there may be name clashes in files where the engine
|
|
// won't really care about the input file name. it may be better to, where possible, request files by file path relative to root (i.e. including platform/mods/{mod}/)
|
|
FileSystem()->AddSearchPath(m_BasePath.string().c_str(), "GAME", SearchPathAdd_t::PATH_ADD_TO_TAIL);
|
|
|
|
KeyValues* pLocalizationFiles = pSettingsKV->FindKey("LocalizationFiles");
|
|
|
|
if (pLocalizationFiles)
|
|
{
|
|
for (KeyValues* pSubKey = pLocalizationFiles->GetFirstSubKey(); pSubKey != nullptr; pSubKey = pSubKey->GetNextKey())
|
|
{
|
|
this->m_vszLocalizationFiles.push_back(pSubKey->GetName());
|
|
}
|
|
}
|
|
|
|
KeyValues* pConVars = pSettingsKV->FindKey("ConVars");
|
|
|
|
if (pConVars)
|
|
{
|
|
for (KeyValues* pSubKey = pConVars->GetFirstSubKey(); pSubKey != nullptr; pSubKey = pSubKey->GetNextKey())
|
|
{
|
|
const char* pszName = pSubKey->GetName();
|
|
const char* pszHelpString = pSubKey->GetString("helpString");
|
|
const char* pszFlagsString = pSubKey->GetString("flags", "NONE");
|
|
|
|
KeyValues* pValues = pSubKey->FindKey("Values");
|
|
|
|
const char* pszDefaultValue = "0";
|
|
bool bMin = false;
|
|
bool bMax = false;
|
|
float fMin = 0.f;
|
|
float fMax = 0.f;
|
|
|
|
if (pValues)
|
|
{
|
|
pszDefaultValue = pValues->GetString("default", "0");
|
|
|
|
// minimum cvar value
|
|
if (pValues->FindKey("min"))
|
|
{
|
|
bMin = true; // has min value
|
|
fMin = pValues->GetFloat("min", 0.f);
|
|
}
|
|
|
|
// maximum cvar value
|
|
if (pValues->FindKey("max"))
|
|
{
|
|
bMax = true; // has max value
|
|
fMax = pValues->GetFloat("max", 1.f);
|
|
}
|
|
}
|
|
|
|
int flags = FCVAR_NONE;
|
|
if (ConVar_ParseFlagString(pszFlagsString, flags, pszName))
|
|
ConVar::StaticCreate(pszName, pszDefaultValue, flags, pszHelpString, bMin, fMin, bMax, fMax, nullptr, nullptr);
|
|
}
|
|
}
|
|
|
|
std::string scriptsRsonPath = (m_BasePath / "scripts/vscripts/scripts.rson").string();
|
|
|
|
if (FileSystem()->FileExists(scriptsRsonPath.c_str(), "GAME"))
|
|
m_bHasScriptCompileList = true;
|
|
};
|
|
|
|
CModSystem* g_pModSystem = new CModSystem(); |