From d8945e9067fe56175259f943fa432d57c4da471c Mon Sep 17 00:00:00 2001
From: Aaron Kimbrell <aronwk.aaron@gmail.com>
Date: Fri, 2 Dec 2022 04:46:54 -0700
Subject: [PATCH 1/5] Add migration to make play_key_id nullable (#857)

since there is an option not to use play_keys
---
 migrations/dlu/7_make_play_key_id_nullable.sql | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 migrations/dlu/7_make_play_key_id_nullable.sql

diff --git a/migrations/dlu/7_make_play_key_id_nullable.sql b/migrations/dlu/7_make_play_key_id_nullable.sql
new file mode 100644
index 00000000..7491874f
--- /dev/null
+++ b/migrations/dlu/7_make_play_key_id_nullable.sql
@@ -0,0 +1 @@
+ALTER TABLE account MODIFY play_key_id INT DEFAULT 0;

From e1af528d9b7fa849d99264dcae2971bac305419a Mon Sep 17 00:00:00 2001
From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>
Date: Fri, 2 Dec 2022 03:47:27 -0800
Subject: [PATCH 2/5] Add SlashCommand for spawngroup (#858)

---
 dGame/dUtilities/SlashCommandHandler.cpp | 51 ++++++++++++++++++++++++
 1 file changed, 51 insertions(+)

diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp
index 2f4d9211..79e61bae 100644
--- a/dGame/dUtilities/SlashCommandHandler.cpp
+++ b/dGame/dUtilities/SlashCommandHandler.cpp
@@ -1264,6 +1264,57 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
 		EntityManager::Instance()->ConstructEntity(newEntity);
 	}
 
+	if (chatCommand == "spawngroup" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER && args.size() >= 3) {
+		auto controllablePhysicsComponent = entity->GetComponent<ControllablePhysicsComponent>();
+		if (!controllablePhysicsComponent) return;
+
+		LOT lot{};
+		uint32_t numberToSpawn{};
+		float radiusToSpawnWithin{};
+
+		if (!GeneralUtils::TryParse(args[0], lot)) {
+			ChatPackets::SendSystemMessage(sysAddr, u"Invalid lot.");
+			return;
+		}
+
+		if (!GeneralUtils::TryParse(args[1], numberToSpawn) && numberToSpawn > 0) {
+			ChatPackets::SendSystemMessage(sysAddr, u"Invalid number of enemies to spawn.");
+			return;
+		}
+
+		// Must spawn within a radius of at least 0.0f
+		if (!GeneralUtils::TryParse(args[2], radiusToSpawnWithin) && radiusToSpawnWithin < 0.0f) {
+			ChatPackets::SendSystemMessage(sysAddr, u"Invalid radius to spawn within.");
+			return;
+		}
+
+		EntityInfo info;
+		info.lot = lot;
+		info.spawner = nullptr;
+		info.spawnerID = entity->GetObjectID();
+		info.spawnerNodeID = 0;
+
+		auto playerPosition = controllablePhysicsComponent->GetPosition();
+		while (numberToSpawn > 0) {
+			auto randomAngle = GeneralUtils::GenerateRandomNumber<float>(0.0f, 2 * PI);
+			auto randomRadius = GeneralUtils::GenerateRandomNumber<float>(0.0f, radiusToSpawnWithin);
+
+			// Set the position to the generated random position plus the player position.  This will
+			// spawn the entity in a circle around the player.  As you get further from the player, the angle chosen will get less accurate.
+			info.pos = playerPosition + NiPoint3(cos(randomAngle) * randomRadius, 0.0f, sin(randomAngle) * randomRadius);
+			info.rot = NiQuaternion();
+
+			auto newEntity = EntityManager::Instance()->CreateEntity(info);
+			if (newEntity == nullptr) {
+				ChatPackets::SendSystemMessage(sysAddr, u"Failed to spawn entity.");
+				return;
+			}
+
+			EntityManager::Instance()->ConstructEntity(newEntity);
+			numberToSpawn--;
+		}
+	}
+
 	if ((chatCommand == "giveuscore") && args.size() == 1 && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER) {
 		int32_t uscore;
 

From ab5adea24c7b603b58b2c57d39fc53a17dde9113 Mon Sep 17 00:00:00 2001
From: Wincent Holm <wincent.holm@gmail.com>
Date: Sat, 3 Dec 2022 13:17:13 +0100
Subject: [PATCH 3/5] Move CDServer migration history table (#867)

---
 dDatabase/MigrationRunner.cpp | 37 +++++++++++++++++++++++++++++------
 1 file changed, 31 insertions(+), 6 deletions(-)

diff --git a/dDatabase/MigrationRunner.cpp b/dDatabase/MigrationRunner.cpp
index fe0f933a..31fb9148 100644
--- a/dDatabase/MigrationRunner.cpp
+++ b/dDatabase/MigrationRunner.cpp
@@ -94,6 +94,10 @@ void MigrationRunner::RunMigrations() {
 }
 
 void MigrationRunner::RunSQLiteMigrations() {
+	auto cdstmt = CDClientDatabase::CreatePreppedStmt("CREATE TABLE IF NOT EXISTS migration_history (name TEXT NOT NULL, date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP);");
+	cdstmt.execQuery().finalize();
+	cdstmt.finalize();
+
 	auto* stmt = Database::CreatePreppedStmt("CREATE TABLE IF NOT EXISTS migration_history (name TEXT NOT NULL, date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP());");
 	stmt->execute();
 	delete stmt;
@@ -103,13 +107,31 @@ void MigrationRunner::RunSQLiteMigrations() {
 
 		if (migration.data.empty()) continue;
 
+		// Check if there is an entry in the migration history table on the cdclient database.
+		cdstmt = CDClientDatabase::CreatePreppedStmt("SELECT name FROM migration_history WHERE name = ?;");
+		cdstmt.bind((int32_t) 1, migration.name.c_str());
+		auto cdres = cdstmt.execQuery();
+		bool doExit = !cdres.eof();
+		cdres.finalize();
+		cdstmt.finalize();
+
+		if (doExit) continue;
+
+		// Check first if there is entry in the migration history table on the main database.
 		stmt = Database::CreatePreppedStmt("SELECT name FROM migration_history WHERE name = ?;");
 		stmt->setString(1, migration.name.c_str());
 		auto* res = stmt->executeQuery();
-		bool doExit = res->next();
+		doExit = res->next();
 		delete res;
 		delete stmt;
-		if (doExit) continue;
+		if (doExit) {
+			// Insert into cdclient database if there is an entry in the main database but not the cdclient database.
+			cdstmt = CDClientDatabase::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);");
+			cdstmt.bind((int32_t) 1, migration.name.c_str());
+			cdstmt.execQuery().finalize();
+			cdstmt.finalize();
+			continue;
+		}
 
 		// Doing these 1 migration at a time since one takes a long time and some may think it is crashing.
 		// This will at the least guarentee that the full migration needs to be run in order to be counted as "migrated".
@@ -122,10 +144,13 @@ void MigrationRunner::RunSQLiteMigrations() {
 				Game::logger->Log("MigrationRunner", "Encountered error running DML command: (%i) : %s", e.errorCode(), e.errorMessage());
 			}
 		}
-		stmt = Database::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);");
-		stmt->setString(1, migration.name);
-		stmt->execute();
-		delete stmt;
+
+		// Insert into cdclient database.
+		cdstmt = CDClientDatabase::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);");
+		cdstmt.bind((int32_t) 1, migration.name.c_str());
+		cdstmt.execQuery().finalize();
+		cdstmt.finalize();
 	}
+
 	Game::logger->Log("MigrationRunner", "CDServer database is up to date.");
 }

From de3e53de6cbcbf599d2c6f9fedbd14883ee6b0a1 Mon Sep 17 00:00:00 2001
From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>
Date: Sun, 4 Dec 2022 14:25:25 -0800
Subject: [PATCH 4/5] Fix Model Vault (#870)

Allow pets, rockets and racecars to be stored in vault
---
 dGame/dComponents/InventoryComponent.cpp |  6 ++++--
 dGame/dGameMessages/GameMessages.cpp     | 14 +++++++-------
 2 files changed, 11 insertions(+), 9 deletions(-)

diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp
index acf1ae6a..8215664c 100644
--- a/dGame/dComponents/InventoryComponent.cpp
+++ b/dGame/dComponents/InventoryComponent.cpp
@@ -330,7 +330,9 @@ void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType in
 
 	const auto lot = item->GetLot();
 
-	if (item->GetConfig().empty() && !item->GetBound() || (item->GetBound() && item->GetInfo().isBOP)) {
+	const auto subkey = item->GetSubKey();
+
+	if (subkey == LWOOBJID_EMPTY && item->GetConfig().empty() && (!item->GetBound() || (item->GetBound() && item->GetInfo().isBOP))) {
 		auto left = std::min<uint32_t>(count, origin->GetLotCount(lot));
 
 		while (left > 0) {
@@ -361,7 +363,7 @@ void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType in
 
 		const auto delta = std::min<uint32_t>(item->GetCount(), count);
 
-		AddItem(lot, delta, eLootSourceType::LOOT_SOURCE_NONE, inventory, config, LWOOBJID_EMPTY, showFlyingLot, isModMoveAndEquip, LWOOBJID_EMPTY, origin->GetType(), 0, item->GetBound(), preferredSlot);
+		AddItem(lot, delta, eLootSourceType::LOOT_SOURCE_NONE, inventory, config, LWOOBJID_EMPTY, showFlyingLot, isModMoveAndEquip, subkey, origin->GetType(), 0, item->GetBound(), preferredSlot);
 
 		item->SetCount(item->GetCount() - delta, false, false);
 	}
diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp
index 6639197c..fcc25fdc 100644
--- a/dGame/dGameMessages/GameMessages.cpp
+++ b/dGame/dGameMessages/GameMessages.cpp
@@ -4462,13 +4462,13 @@ void GameMessages::SendAddBuff(LWOOBJID& objectID, const LWOOBJID& casterID, uin
 // NT
 
 void GameMessages::HandleRequestMoveItemBetweenInventoryTypes(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
-	bool bAllowPartial;
+	bool bAllowPartial{};
 	int32_t destSlot = -1;
 	int32_t iStackCount = 1;
 	eInventoryType invTypeDst = ITEMS;
 	eInventoryType invTypeSrc = ITEMS;
 	LWOOBJID itemID = LWOOBJID_EMPTY;
-	bool showFlyingLoot;
+	bool showFlyingLoot{};
 	LWOOBJID subkey = LWOOBJID_EMPTY;
 	LOT itemLOT = 0;
 
@@ -4492,12 +4492,12 @@ void GameMessages::HandleRequestMoveItemBetweenInventoryTypes(RakNet::BitStream*
 		if (itemID != LWOOBJID_EMPTY) {
 			auto* item = inventoryComponent->FindItemById(itemID);
 
-			if (item == nullptr) {
-				return;
-			}
+			if (!item) return;
 
-			if (inventoryComponent->IsPet(item->GetSubKey()) || !item->GetConfig().empty()) {
-				return;
+			// Despawn the pet if we are moving that pet to the vault.
+			auto* petComponent = PetComponent::GetActivePet(entity->GetObjectID());
+			if (petComponent && petComponent->GetDatabaseId() == item->GetSubKey()) {
+				inventoryComponent->DespawnPet();
 			}
 
 			inventoryComponent->MoveItemToInventory(item, invTypeDst, iStackCount, showFlyingLoot, false, false, destSlot);

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 5/5] 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 <sstream>
+
 #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<std::string> 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 <fstream>
+#include <map>
 #include <string>
-#include <vector>
 
 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<std::string> m_Keys;
-	std::vector<std::string> m_Values;
-	std::string m_EmptyString;
+	std::map<std::string, std::string> 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<CDActivitiesTable>("Activities");
-	std::vector<CDActivities> activities = activitiesTable->Query([=](CDActivities entry) {return (entry.ActivityID == activityID); });
+	std::vector<CDActivities> 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<CDActivitiesTable>("Activities");
+	std::vector<CDActivities> 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<uint32_t, uint32_t> 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<ScriptedActivityComponent>();
+			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<std::vector<std::forward_list<dpEntity*>>> 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<std::vector<std::forward_list<dpEntity*>>> m_Cells;
 	std::map<LWOOBJID, dpEntity*> 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<dpWorld> {
 public:
-	void Initialize(unsigned int zoneID);
+	void Initialize(unsigned int zoneID, bool generateNewNavMesh = true);
+	void Reload();
 
 	~dpWorld();
 
@@ -43,4 +44,5 @@ private:
 	std::vector<dpEntity*> 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);