From e8ba3357e8c28f22136babdcd6a6a9e489fc520c Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 4 Dec 2022 14:25:58 -0800 Subject: [PATCH] Add support to reload the config (#868) --- dCommon/dConfig.cpp | 56 ++++++++----------- dCommon/dConfig.h | 24 ++++++-- .../dComponents/ScriptedActivityComponent.cpp | 18 +++++- dGame/dComponents/ScriptedActivityComponent.h | 12 ++++ dGame/dUtilities/SlashCommandHandler.cpp | 15 +++++ dMasterServer/MasterServer.cpp | 29 +++++----- dPhysics/dpGrid.cpp | 2 + dPhysics/dpGrid.h | 10 ++++ dPhysics/dpWorld.cpp | 44 +++++++++++---- dPhysics/dpWorld.h | 4 +- dWorldServer/WorldServer.cpp | 2 - 11 files changed, 150 insertions(+), 66 deletions(-) diff --git a/dCommon/dConfig.cpp b/dCommon/dConfig.cpp index 1bc940e8..f09a44c1 100644 --- a/dCommon/dConfig.cpp +++ b/dCommon/dConfig.cpp @@ -1,61 +1,53 @@ #include "dConfig.h" + #include + #include "BinaryPathFinder.h" +#include "GeneralUtils.h" dConfig::dConfig(const std::string& filepath) { - m_EmptyString = ""; - std::ifstream in(BinaryPathFinder::GetBinaryDir() / filepath); + m_ConfigFilePath = filepath; + LoadConfig(); +} + +void dConfig::LoadConfig() { + std::ifstream in(BinaryPathFinder::GetBinaryDir() / m_ConfigFilePath); if (!in.good()) return; - std::string line; + std::string line{}; while (std::getline(in, line)) { - if (line.length() > 0) { - if (line[0] != '#') ProcessLine(line); - } + if (!line.empty() && line.front() != '#') ProcessLine(line); } std::ifstream sharedConfig(BinaryPathFinder::GetBinaryDir() / "sharedconfig.ini", std::ios::in); if (!sharedConfig.good()) return; + line.clear(); while (std::getline(sharedConfig, line)) { - if (line.length() > 0) { - if (line[0] != '#') ProcessLine(line); - } + if (!line.empty() && line.front() != '#') ProcessLine(line); } } -dConfig::~dConfig(void) { +void dConfig::ReloadConfig() { + this->m_ConfigValues.clear(); + LoadConfig(); } const std::string& dConfig::GetValue(std::string key) { - for (size_t i = 0; i < m_Keys.size(); ++i) { - if (m_Keys[i] == key) return m_Values[i]; - } - - return m_EmptyString; + return this->m_ConfigValues[key]; } void dConfig::ProcessLine(const std::string& line) { - std::stringstream ss(line); - std::string segment; - std::vector seglist; + auto splitLine = GeneralUtils::SplitString(line, '='); - while (std::getline(ss, segment, '=')) { - seglist.push_back(segment); - } - - if (seglist.size() != 2) return; + if (splitLine.size() != 2) return; //Make sure that on Linux, we remove special characters: - if (!seglist[1].empty() && seglist[1][seglist[1].size() - 1] == '\r') - seglist[1].erase(seglist[1].size() - 1); + auto& key = splitLine.at(0); + auto& value = splitLine.at(1); + if (!value.empty() && value.at(value.size() - 1) == '\r') value.erase(value.size() - 1); - for (const auto& key : m_Keys) { - if (seglist[0] == key) { - return; // first loaded key is preferred due to loading shared config secondarily - } - } + if (this->m_ConfigValues.find(key) != this->m_ConfigValues.end()) return; - m_Keys.push_back(seglist[0]); - m_Values.push_back(seglist[1]); + this->m_ConfigValues.insert(std::make_pair(key, value)); } diff --git a/dCommon/dConfig.h b/dCommon/dConfig.h index 7764fa54..a6dd5df7 100644 --- a/dCommon/dConfig.h +++ b/dCommon/dConfig.h @@ -1,20 +1,34 @@ #pragma once #include +#include #include -#include class dConfig { public: dConfig(const std::string& filepath); - ~dConfig(void); + /** + * Gets the specified key from the config. Returns an empty string if the value is not found. + * + * @param key Key to find + * @return The keys value in the config + */ const std::string& GetValue(std::string key); + /** + * Loads the config from a file + */ + void LoadConfig(); + + /** + * Reloads the config file to reset values + */ + void ReloadConfig(); + private: void ProcessLine(const std::string& line); private: - std::vector m_Keys; - std::vector m_Values; - std::string m_EmptyString; + std::map m_ConfigValues; + std::string m_ConfigFilePath; }; diff --git a/dGame/dComponents/ScriptedActivityComponent.cpp b/dGame/dComponents/ScriptedActivityComponent.cpp index 4b5ec41d..f6d50d66 100644 --- a/dGame/dComponents/ScriptedActivityComponent.cpp +++ b/dGame/dComponents/ScriptedActivityComponent.cpp @@ -19,8 +19,9 @@ #include "DestroyableComponent.h" ScriptedActivityComponent::ScriptedActivityComponent(Entity* parent, int activityID) : Component(parent) { + m_ActivityID = activityID; CDActivitiesTable* activitiesTable = CDClientManager::Instance()->GetTable("Activities"); - std::vector activities = activitiesTable->Query([=](CDActivities entry) {return (entry.ActivityID == activityID); }); + std::vector activities = activitiesTable->Query([=](CDActivities entry) {return (entry.ActivityID == m_ActivityID); }); for (CDActivities activity : activities) { m_ActivityInfo = activity; @@ -88,6 +89,21 @@ void ScriptedActivityComponent::Serialize(RakNet::BitStream* outBitStream, bool } } +void ScriptedActivityComponent::ReloadConfig() { + CDActivitiesTable* activitiesTable = CDClientManager::Instance()->GetTable("Activities"); + std::vector activities = activitiesTable->Query([=](CDActivities entry) {return (entry.ActivityID == m_ActivityID); }); + for (auto activity : activities) { + auto mapID = m_ActivityInfo.instanceMapID; + if ((mapID == 1203 || mapID == 1261 || mapID == 1303 || mapID == 1403) && Game::config->GetValue("solo_racing") == "1") { + m_ActivityInfo.minTeamSize = 1; + m_ActivityInfo.minTeams = 1; + } else { + m_ActivityInfo.minTeamSize = activity.minTeamSize; + m_ActivityInfo.minTeams = activity.minTeams; + } + } +} + void ScriptedActivityComponent::HandleMessageBoxResponse(Entity* player, const std::string& id) { if (m_ActivityInfo.ActivityID == 103) { return; diff --git a/dGame/dComponents/ScriptedActivityComponent.h b/dGame/dComponents/ScriptedActivityComponent.h index 66ca799f..8bb17093 100644 --- a/dGame/dComponents/ScriptedActivityComponent.h +++ b/dGame/dComponents/ScriptedActivityComponent.h @@ -276,6 +276,12 @@ public: */ ActivityInstance* GetInstance(const LWOOBJID playerID); + /** + * @brief Reloads the config settings for this component + * + */ + void ReloadConfig(); + /** * Removes all the instances */ @@ -361,6 +367,12 @@ private: * LMIs for team sizes */ std::unordered_map m_ActivityLootMatrices; + + /** + * The activity id + * + */ + int32_t m_ActivityID; }; #endif // SCRIPTEDACTIVITYCOMPONENT_H diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 79e61bae..60862c1a 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -65,6 +65,7 @@ #include "LevelProgressionComponent.h" #include "AssetManager.h" #include "BinaryPathFinder.h" +#include "dConfig.h" void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entity* entity, const SystemAddress& sysAddr) { std::string chatCommand; @@ -1766,6 +1767,20 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit return; } + if (chatCommand == "reloadconfig" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER) { + Game::config->ReloadConfig(); + VanityUtilities::SpawnVanity(); + dpWorld::Instance().Reload(); + auto entities = EntityManager::Instance()->GetEntitiesByComponent(COMPONENT_TYPE_SCRIPTED_ACTIVITY); + for (auto entity : entities) { + auto* scriptedActivityComponent = entity->GetComponent(); + if (!scriptedActivityComponent) continue; + + scriptedActivityComponent->ReloadConfig(); + } + ChatPackets::SendSystemMessage(sysAddr, u"Successfully reloaded config for world!"); + } + if (chatCommand == "rollloot" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_OPERATOR && args.size() >= 3) { uint32_t lootMatrixIndex = 0; uint32_t targetLot = 0; diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 34e2f71a..0a387d8e 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -82,17 +82,15 @@ int main(int argc, char** argv) { Game::logger->Log("MasterServer", "Version: %i.%i", PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR); Game::logger->Log("MasterServer", "Compiled on: %s", __TIMESTAMP__); - //Read our config: - dConfig config("masterconfig.ini"); - Game::config = &config; - Game::logger->SetLogToConsole(bool(std::stoi(config.GetValue("log_to_console")))); - Game::logger->SetLogDebugStatements(config.GetValue("log_debug_statements") == "1"); + Game::config = new dConfig("masterconfig.ini"); + Game::logger->SetLogToConsole(bool(std::stoi(Game::config->GetValue("log_to_console")))); + Game::logger->SetLogDebugStatements(Game::config->GetValue("log_debug_statements") == "1"); //Connect to the MySQL Database - std::string mysql_host = config.GetValue("mysql_host"); - std::string mysql_database = config.GetValue("mysql_database"); - std::string mysql_username = config.GetValue("mysql_username"); - std::string mysql_password = config.GetValue("mysql_password"); + std::string mysql_host = Game::config->GetValue("mysql_host"); + std::string mysql_database = Game::config->GetValue("mysql_database"); + std::string mysql_username = Game::config->GetValue("mysql_username"); + std::string mysql_password = Game::config->GetValue("mysql_password"); try { Database::Connect(mysql_host, mysql_database, mysql_username, mysql_password); @@ -103,7 +101,7 @@ int main(int argc, char** argv) { } try { - std::string clientPathStr = config.GetValue("client_location"); + std::string clientPathStr = Game::config->GetValue("client_location"); if (clientPathStr.empty()) clientPathStr = "./res"; std::filesystem::path clientPath = std::filesystem::path(clientPathStr); if (clientPath.is_relative()) { @@ -223,16 +221,16 @@ int main(int argc, char** argv) { int maxClients = 999; int ourPort = 1000; - if (config.GetValue("max_clients") != "") maxClients = std::stoi(config.GetValue("max_clients")); - if (config.GetValue("port") != "") ourPort = std::stoi(config.GetValue("port")); + if (Game::config->GetValue("max_clients") != "") maxClients = std::stoi(Game::config->GetValue("max_clients")); + if (Game::config->GetValue("port") != "") ourPort = std::stoi(Game::config->GetValue("port")); - Game::server = new dServer(config.GetValue("external_ip"), ourPort, 0, maxClients, true, false, Game::logger, "", 0, ServerType::Master); + Game::server = new dServer(Game::config->GetValue("external_ip"), ourPort, 0, maxClients, true, false, Game::logger, "", 0, ServerType::Master); //Query for the database for a server labeled "master" auto* masterLookupStatement = Database::CreatePreppedStmt("SELECT id FROM `servers` WHERE `name` = 'master'"); auto* result = masterLookupStatement->executeQuery(); - auto master_server_ip = config.GetValue("master_ip"); + auto master_server_ip = Game::config->GetValue("master_ip"); if (master_server_ip == "") { master_server_ip = Game::server->GetIP(); @@ -260,7 +258,7 @@ int main(int argc, char** argv) { Game::im = new InstanceManager(Game::logger, Game::server->GetIP()); //Depending on the config, start up servers: - if (config.GetValue("prestart_servers") != "" && config.GetValue("prestart_servers") == "1") { + if (Game::config->GetValue("prestart_servers") != "" && Game::config->GetValue("prestart_servers") == "1") { StartChatServer(); Game::im->GetInstance(0, false, 0)->SetIsReady(true); @@ -843,6 +841,7 @@ void ShutdownSequence() { int FinalizeShutdown() { //Delete our objects here: Database::Destroy("MasterServer"); + if (Game::config) delete Game::config; if (Game::im) delete Game::im; if (Game::server) delete Game::server; if (Game::logger) delete Game::logger; diff --git a/dPhysics/dpGrid.cpp b/dPhysics/dpGrid.cpp index 6e44ade9..b4fe385e 100644 --- a/dPhysics/dpGrid.cpp +++ b/dPhysics/dpGrid.cpp @@ -6,6 +6,7 @@ dpGrid::dpGrid(int numCells, int cellSize) { NUM_CELLS = numCells; CELL_SIZE = cellSize; + m_DeleteGrid = true; //dumb method but i can't be bothered @@ -23,6 +24,7 @@ dpGrid::dpGrid(int numCells, int cellSize) { } dpGrid::~dpGrid() { + if (!this->m_DeleteGrid) return; for (auto& x : m_Cells) { //x for (auto& y : x) { //y for (auto en : y) { diff --git a/dPhysics/dpGrid.h b/dPhysics/dpGrid.h index a10f165e..229e7449 100644 --- a/dPhysics/dpGrid.h +++ b/dPhysics/dpGrid.h @@ -23,6 +23,15 @@ public: void Update(float deltaTime); + /** + * Sets the delete grid parameter to value. When false, the grid will not clean up memory. + * + * @param value Whether or not to delete entities on deletion of the grid. + */ + void SetDeleteGrid(bool value) { this->m_DeleteGrid = value; }; + + std::vector>> GetCells() { return this->m_Cells; }; + private: void HandleEntity(dpEntity* entity, dpEntity* other); void HandleCell(int x, int z, float deltaTime); @@ -31,4 +40,5 @@ private: //cells on X, cells on Y for that X, then another vector that contains the entities within that cell. std::vector>> m_Cells; std::map m_GargantuanObjects; + bool m_DeleteGrid = true; }; diff --git a/dPhysics/dpWorld.cpp b/dPhysics/dpWorld.cpp index 510da518..70fbfa3a 100644 --- a/dPhysics/dpWorld.cpp +++ b/dPhysics/dpWorld.cpp @@ -9,7 +9,7 @@ #include "dLogger.h" #include "dConfig.h" -void dpWorld::Initialize(unsigned int zoneID) { +void dpWorld::Initialize(unsigned int zoneID, bool generateNewNavMesh) { phys_sp_tilecount = std::atoi(Game::config->GetValue("phys_sp_tilecount").c_str()); phys_sp_tilesize = std::atoi(Game::config->GetValue("phys_sp_tilesize").c_str()); @@ -21,13 +21,37 @@ void dpWorld::Initialize(unsigned int zoneID) { m_Grid = new dpGrid(phys_sp_tilecount, phys_sp_tilesize); } - m_NavMesh = new dNavMesh(zoneID); + if (generateNewNavMesh) m_NavMesh = new dNavMesh(zoneID); Game::logger->Log("dpWorld", "Physics world initialized!"); + m_ZoneID = zoneID; +} + +void dpWorld::Reload() { + if (m_Grid) { + m_Grid->SetDeleteGrid(false); + auto oldGridCells = m_Grid->GetCells(); + delete m_Grid; + m_Grid = nullptr; + + Initialize(m_ZoneID, false); + for (auto column : oldGridCells) { + for (auto row : column) { + for (auto entity : row) { + AddEntity(entity); + } + } + } + Game::logger->Log("dpWorld", "Successfully reloaded physics world!"); + } else { + Game::logger->Log("dpWorld", "No physics world to reload!"); + } } dpWorld::~dpWorld() { if (m_Grid) { + // Triple check this is true + m_Grid->SetDeleteGrid(true); delete m_Grid; m_Grid = nullptr; } @@ -103,14 +127,14 @@ bool dpWorld::ShouldUseSP(unsigned int zoneID) { // Only large maps should be added as tiling likely makes little difference on small maps. switch (zoneID) { - case 1100: // Avant Gardens - case 1200: // Nimbus Station - case 1300: // Gnarled Forest - case 1400: // Forbidden Valley - case 1800: // Crux Prime - case 1900: // Nexus Tower - case 2000: // Ninjago - return true; + case 1100: // Avant Gardens + case 1200: // Nimbus Station + case 1300: // Gnarled Forest + case 1400: // Forbidden Valley + case 1800: // Crux Prime + case 1900: // Nexus Tower + case 2000: // Ninjago + return true; } return false; diff --git a/dPhysics/dpWorld.h b/dPhysics/dpWorld.h index 45e550cb..d48435d0 100644 --- a/dPhysics/dpWorld.h +++ b/dPhysics/dpWorld.h @@ -19,7 +19,8 @@ class dpGrid; class dpWorld : public Singleton { public: - void Initialize(unsigned int zoneID); + void Initialize(unsigned int zoneID, bool generateNewNavMesh = true); + void Reload(); ~dpWorld(); @@ -43,4 +44,5 @@ private: std::vector m_DynamicEntites; dNavMesh* m_NavMesh = nullptr; + uint32_t m_ZoneID = 0; }; diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index bbba1166..b4be7511 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -250,7 +250,6 @@ int main(int argc, char** argv) { //Load our level: if (zoneID != 0) { dpWorld::Instance().Initialize(zoneID); - Game::physicsWorld = &dpWorld::Instance(); //just in case some old code references it dZoneManager::Instance()->Initialize(LWOZONEID(zoneID, instanceID, cloneID)); g_CloneID = cloneID; @@ -1281,7 +1280,6 @@ void WorldShutdownSequence() { void FinalizeShutdown() { //Delete our objects here: - if (Game::physicsWorld) Game::physicsWorld = nullptr; if (Game::zoneManager) delete Game::zoneManager; Game::logger->Log("WorldServer", "Shutdown complete, zone (%i), instance (%i)", Game::server->GetZoneID(), instanceID);