r5sdk/r5dev/pluginsystem/modsystem.cpp
Kawe Mazidjatari 398c0f1ddc ConVar flags tools refactor
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.
2023-07-03 13:48:13 +02:00

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();