From 24de0e5fdb8ddd634a61de8de166de174db49734 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Wed, 3 Apr 2024 17:06:29 -0700 Subject: [PATCH 01/78] Update GeneralUtils.cpp (#1528) same check as the header --- dCommon/GeneralUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dCommon/GeneralUtils.cpp b/dCommon/GeneralUtils.cpp index 27ebfb2c..159cc127 100644 --- a/dCommon/GeneralUtils.cpp +++ b/dCommon/GeneralUtils.cpp @@ -320,7 +320,7 @@ std::vector GeneralUtils::GetSqlFileNamesFromFolder(const std::stri return sortedFiles; } -#ifdef DARKFLAME_PLATFORM_MACOS +#if !(__GNUC__ >= 11 || _MSC_VER >= 1924) // MacOS floating-point parse function specializations namespace GeneralUtils::details { From b340d7c8f9b7bf0f43913b5b766db97968ca6227 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 4 Apr 2024 22:51:40 -0700 Subject: [PATCH 02/78] replace white and blacklist (#1530) --- CMakeLists.txt | 2 +- dChatFilter/dChatFilter.cpp | 30 +++++------ dChatFilter/dChatFilter.h | 8 +-- dCommon/dEnums/eWorldMessageType.h | 4 +- dGame/UserManager.cpp | 2 +- .../PropertyManagementComponent.cpp | 47 ++++++------------ dGame/dGameMessages/GameMessages.cpp | 16 ++---- resources/{blacklist.dcf => blocklist.dcf} | Bin 8 files changed, 43 insertions(+), 66 deletions(-) rename resources/{blacklist.dcf => blocklist.dcf} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index ae6455b0..00b9383f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,7 +103,7 @@ make_directory(${CMAKE_BINARY_DIR}/resServer) make_directory(${CMAKE_BINARY_DIR}/logs) # Copy resource files on first build -set(RESOURCE_FILES "sharedconfig.ini" "authconfig.ini" "chatconfig.ini" "worldconfig.ini" "masterconfig.ini" "blacklist.dcf") +set(RESOURCE_FILES "sharedconfig.ini" "authconfig.ini" "chatconfig.ini" "worldconfig.ini" "masterconfig.ini" "blocklist.dcf") message(STATUS "Checking resource file integrity") include(Utils) diff --git a/dChatFilter/dChatFilter.cpp b/dChatFilter/dChatFilter.cpp index 6e81db3b..844e3411 100644 --- a/dChatFilter/dChatFilter.cpp +++ b/dChatFilter/dChatFilter.cpp @@ -27,8 +27,8 @@ dChatFilter::dChatFilter(const std::string& filepath, bool dontGenerateDCF) { ExportWordlistToDCF(filepath + ".dcf", true); } - if (BinaryIO::DoesFileExist("blacklist.dcf")) { - ReadWordlistDCF("blacklist.dcf", false); + if (BinaryIO::DoesFileExist("blocklist.dcf")) { + ReadWordlistDCF("blocklist.dcf", false); } //Read player names that are ok as well: @@ -44,20 +44,20 @@ dChatFilter::~dChatFilter() { m_DeniedWords.clear(); } -void dChatFilter::ReadWordlistPlaintext(const std::string& filepath, bool whiteList) { +void dChatFilter::ReadWordlistPlaintext(const std::string& filepath, bool allowList) { std::ifstream file(filepath); if (file) { std::string line; while (std::getline(file, line)) { line.erase(std::remove(line.begin(), line.end(), '\r'), line.end()); std::transform(line.begin(), line.end(), line.begin(), ::tolower); //Transform to lowercase - if (whiteList) m_ApprovedWords.push_back(CalculateHash(line)); + if (allowList) m_ApprovedWords.push_back(CalculateHash(line)); else m_DeniedWords.push_back(CalculateHash(line)); } } } -bool dChatFilter::ReadWordlistDCF(const std::string& filepath, bool whiteList) { +bool dChatFilter::ReadWordlistDCF(const std::string& filepath, bool allowList) { std::ifstream file(filepath, std::ios::binary); if (file) { fileHeader hdr; @@ -70,13 +70,13 @@ bool dChatFilter::ReadWordlistDCF(const std::string& filepath, bool whiteList) { if (hdr.formatVersion == formatVersion) { size_t wordsToRead = 0; BinaryIO::BinaryRead(file, wordsToRead); - if (whiteList) m_ApprovedWords.reserve(wordsToRead); + if (allowList) m_ApprovedWords.reserve(wordsToRead); else m_DeniedWords.reserve(wordsToRead); size_t word = 0; for (size_t i = 0; i < wordsToRead; ++i) { BinaryIO::BinaryRead(file, word); - if (whiteList) m_ApprovedWords.push_back(word); + if (allowList) m_ApprovedWords.push_back(word); else m_DeniedWords.push_back(word); } @@ -90,14 +90,14 @@ bool dChatFilter::ReadWordlistDCF(const std::string& filepath, bool whiteList) { return false; } -void dChatFilter::ExportWordlistToDCF(const std::string& filepath, bool whiteList) { +void dChatFilter::ExportWordlistToDCF(const std::string& filepath, bool allowList) { std::ofstream file(filepath, std::ios::binary | std::ios_base::out); if (file) { BinaryIO::BinaryWrite(file, uint32_t(dChatFilterDCF::header)); BinaryIO::BinaryWrite(file, uint32_t(dChatFilterDCF::formatVersion)); - BinaryIO::BinaryWrite(file, size_t(whiteList ? m_ApprovedWords.size() : m_DeniedWords.size())); + BinaryIO::BinaryWrite(file, size_t(allowList ? m_ApprovedWords.size() : m_DeniedWords.size())); - for (size_t word : whiteList ? m_ApprovedWords : m_DeniedWords) { + for (size_t word : allowList ? m_ApprovedWords : m_DeniedWords) { BinaryIO::BinaryWrite(file, word); } @@ -105,10 +105,10 @@ void dChatFilter::ExportWordlistToDCF(const std::string& filepath, bool whiteLis } } -std::vector> dChatFilter::IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool whiteList) { +std::vector> dChatFilter::IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool allowList) { if (gmLevel > eGameMasterLevel::FORUM_MODERATOR) return { }; //If anything but a forum mod, return true. if (message.empty()) return { }; - if (!whiteList && m_DeniedWords.empty()) return { { 0, message.length() } }; + if (!allowList && m_DeniedWords.empty()) return { { 0, message.length() } }; std::stringstream sMessage(message); std::string segment; @@ -126,16 +126,16 @@ std::vector> dChatFilter::IsSentenceOkay(const std:: size_t hash = CalculateHash(segment); - if (std::find(m_UserUnapprovedWordCache.begin(), m_UserUnapprovedWordCache.end(), hash) != m_UserUnapprovedWordCache.end() && whiteList) { + if (std::find(m_UserUnapprovedWordCache.begin(), m_UserUnapprovedWordCache.end(), hash) != m_UserUnapprovedWordCache.end() && allowList) { listOfBadSegments.emplace_back(position, originalSegment.length()); } - if (std::find(m_ApprovedWords.begin(), m_ApprovedWords.end(), hash) == m_ApprovedWords.end() && whiteList) { + if (std::find(m_ApprovedWords.begin(), m_ApprovedWords.end(), hash) == m_ApprovedWords.end() && allowList) { m_UserUnapprovedWordCache.push_back(hash); listOfBadSegments.emplace_back(position, originalSegment.length()); } - if (std::find(m_DeniedWords.begin(), m_DeniedWords.end(), hash) != m_DeniedWords.end() && !whiteList) { + if (std::find(m_DeniedWords.begin(), m_DeniedWords.end(), hash) != m_DeniedWords.end() && !allowList) { m_UserUnapprovedWordCache.push_back(hash); listOfBadSegments.emplace_back(position, originalSegment.length()); } diff --git a/dChatFilter/dChatFilter.h b/dChatFilter/dChatFilter.h index d00525ce..0f1e49ba 100644 --- a/dChatFilter/dChatFilter.h +++ b/dChatFilter/dChatFilter.h @@ -21,10 +21,10 @@ public: dChatFilter(const std::string& filepath, bool dontGenerateDCF); ~dChatFilter(); - void ReadWordlistPlaintext(const std::string& filepath, bool whiteList); - bool ReadWordlistDCF(const std::string& filepath, bool whiteList); - void ExportWordlistToDCF(const std::string& filepath, bool whiteList); - std::vector> IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool whiteList = true); + void ReadWordlistPlaintext(const std::string& filepath, bool allowList); + bool ReadWordlistDCF(const std::string& filepath, bool allowList); + void ExportWordlistToDCF(const std::string& filepath, bool allowList); + std::vector> IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool allowList = true); private: bool m_DontGenerateDCF; diff --git a/dCommon/dEnums/eWorldMessageType.h b/dCommon/dEnums/eWorldMessageType.h index bfaa110b..92081055 100644 --- a/dCommon/dEnums/eWorldMessageType.h +++ b/dCommon/dEnums/eWorldMessageType.h @@ -29,8 +29,8 @@ enum class eWorldMessageType : uint32_t { ROUTE_PACKET, // Social? POSITION_UPDATE, MAIL, - WORD_CHECK, // Whitelist word check - STRING_CHECK, // Whitelist string check + WORD_CHECK, // AllowList word check + STRING_CHECK, // AllowList string check GET_PLAYERS_IN_ZONE, REQUEST_UGC_MANIFEST_INFO, BLUEPRINT_GET_ALL_DATA_REQUEST, diff --git a/dGame/UserManager.cpp b/dGame/UserManager.cpp index b94e9de5..da7e9e23 100644 --- a/dGame/UserManager.cpp +++ b/dGame/UserManager.cpp @@ -83,7 +83,7 @@ void UserManager::Initialize() { auto chatListStream = Game::assetManager->GetFile("chatplus_en_us.txt"); if (!chatListStream) { LOG("Failed to load %s", (Game::assetManager->GetResPath() / "chatplus_en_us.txt").string().c_str()); - throw std::runtime_error("Aborting initialization due to missing chat whitelist file."); + throw std::runtime_error("Aborting initialization due to missing chat allowlist file."); } while (std::getline(chatListStream, line, '\n')) { diff --git a/dGame/dComponents/PropertyManagementComponent.cpp b/dGame/dComponents/PropertyManagementComponent.cpp index 2acc6a5d..95a6f3e0 100644 --- a/dGame/dComponents/PropertyManagementComponent.cpp +++ b/dGame/dComponents/PropertyManagementComponent.cpp @@ -352,16 +352,11 @@ void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const N auto* spawner = Game::zoneManager->GetSpawner(spawnerId); - auto ldfModelBehavior = new LDFData(u"modelBehaviors", 0); - auto userModelID = new LDFData(u"userModelID", info.spawnerID); - auto modelType = new LDFData(u"modelType", 2); - auto propertyObjectID = new LDFData(u"propertyObjectID", true); - auto componentWhitelist = new LDFData(u"componentWhitelist", 1); - info.nodes[0]->config.push_back(componentWhitelist); - info.nodes[0]->config.push_back(ldfModelBehavior); - info.nodes[0]->config.push_back(modelType); - info.nodes[0]->config.push_back(propertyObjectID); - info.nodes[0]->config.push_back(userModelID); + info.nodes[0]->config.push_back(new LDFData(u"modelBehaviors", 0)); + info.nodes[0]->config.push_back(new LDFData(u"userModelID", info.spawnerID)); + info.nodes[0]->config.push_back(new LDFData(u"modelType", 2)); + info.nodes[0]->config.push_back(new LDFData(u"propertyObjectID", true)); + info.nodes[0]->config.push_back(new LDFData(u"componentWhitelist", 1)); auto* model = spawner->Spawn(); @@ -585,29 +580,17 @@ void PropertyManagementComponent::Load() { GeneralUtils::SetBit(blueprintID, eObjectBits::CHARACTER); GeneralUtils::SetBit(blueprintID, eObjectBits::PERSISTENT); - LDFBaseData* ldfBlueprintID = new LDFData(u"blueprintid", blueprintID); - LDFBaseData* componentWhitelist = new LDFData(u"componentWhitelist", 1); - LDFBaseData* modelType = new LDFData(u"modelType", 2); - LDFBaseData* propertyObjectID = new LDFData(u"propertyObjectID", true); - LDFBaseData* userModelID = new LDFData(u"userModelID", databaseModel.id); - - settings.push_back(ldfBlueprintID); - settings.push_back(componentWhitelist); - settings.push_back(modelType); - settings.push_back(propertyObjectID); - settings.push_back(userModelID); + settings.push_back(new LDFData(u"blueprintid", blueprintID)); + settings.push_back(new LDFData(u"componentWhitelist", 1)); + settings.push_back(new LDFData(u"modelType", 2)); + settings.push_back(new LDFData(u"propertyObjectID", true)); + settings.push_back(new LDFData(u"userModelID", databaseModel.id)); } else { - auto modelType = new LDFData(u"modelType", 2); - auto userModelID = new LDFData(u"userModelID", databaseModel.id); - auto ldfModelBehavior = new LDFData(u"modelBehaviors", 0); - auto propertyObjectID = new LDFData(u"propertyObjectID", true); - auto componentWhitelist = new LDFData(u"componentWhitelist", 1); - - settings.push_back(componentWhitelist); - settings.push_back(ldfModelBehavior); - settings.push_back(modelType); - settings.push_back(propertyObjectID); - settings.push_back(userModelID); + settings.push_back(new LDFData(u"modelType", 2)); + settings.push_back(new LDFData(u"userModelID", databaseModel.id)); + settings.push_back(new LDFData(u"modelBehaviors", 0)); + settings.push_back(new LDFData(u"propertyObjectID", true)); + settings.push_back(new LDFData(u"componentWhitelist", 1)); } node->config = settings; diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index c144675b..291dc151 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -2663,17 +2663,11 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent info.spawnerID = entity->GetObjectID(); info.spawnerNodeID = 0; - LDFBaseData* ldfBlueprintID = new LDFData(u"blueprintid", blueprintID); - LDFBaseData* componentWhitelist = new LDFData(u"componentWhitelist", 1); - LDFBaseData* modelType = new LDFData(u"modelType", 2); - LDFBaseData* propertyObjectID = new LDFData(u"propertyObjectID", true); - LDFBaseData* userModelID = new LDFData(u"userModelID", newIDL); - - info.settings.push_back(ldfBlueprintID); - info.settings.push_back(componentWhitelist); - info.settings.push_back(modelType); - info.settings.push_back(propertyObjectID); - info.settings.push_back(userModelID); + info.settings.push_back(new LDFData(u"blueprintid", blueprintID)); + info.settings.push_back(new LDFData(u"componentWhitelist", 1)); + info.settings.push_back(new LDFData(u"modelType", 2)); + info.settings.push_back(new LDFData(u"propertyObjectID", true)); + info.settings.push_back(new LDFData(u"userModelID", newIDL)); Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr); if (newEntity) { diff --git a/resources/blacklist.dcf b/resources/blocklist.dcf similarity index 100% rename from resources/blacklist.dcf rename to resources/blocklist.dcf From 06e7d57e0d9c9929175956a03c2b16b12162e873 Mon Sep 17 00:00:00 2001 From: jadebenn Date: Fri, 5 Apr 2024 00:52:26 -0500 Subject: [PATCH 03/78] chore: Remove dpEntity pointers from collision checking (#1529) * chore: Remove dpEntity pointers from collision checking * Update fn documentation in ProximityMonitorComponent.h * use more idiomatic method to calculate vector index * feedback * missed a ranges::find replacement * adjust for feedback. last changes tonight. * okay, also remove unneeded include. then sleep. * for real tho * update to use unordered_set instead of set --- dGame/dComponents/BaseCombatAIComponent.cpp | 8 +++--- dGame/dComponents/PhantomPhysicsComponent.cpp | 18 ++++++------- .../dComponents/ProximityMonitorComponent.cpp | 26 +++++++++---------- dGame/dComponents/ProximityMonitorComponent.h | 8 +++--- dPhysics/dpEntity.cpp | 17 ++++++------ dPhysics/dpEntity.h | 15 ++++++----- dScripts/02_server/Map/AM/WanderingVendor.cpp | 2 +- dScripts/ai/AG/AgBusDoor.cpp | 8 +++--- 8 files changed, 51 insertions(+), 51 deletions(-) diff --git a/dGame/dComponents/BaseCombatAIComponent.cpp b/dGame/dComponents/BaseCombatAIComponent.cpp index 2bd2b6f1..0c2a796c 100644 --- a/dGame/dComponents/BaseCombatAIComponent.cpp +++ b/dGame/dComponents/BaseCombatAIComponent.cpp @@ -150,13 +150,13 @@ void BaseCombatAIComponent::Update(const float deltaTime) { m_dpEntityEnemy->SetPosition(m_Parent->GetPosition()); //Process enter events - for (auto en : m_dpEntity->GetNewObjects()) { - m_Parent->OnCollisionPhantom(en->GetObjectID()); + for (const auto id : m_dpEntity->GetNewObjects()) { + m_Parent->OnCollisionPhantom(id); } //Process exit events - for (auto en : m_dpEntity->GetRemovedObjects()) { - m_Parent->OnCollisionLeavePhantom(en->GetObjectID()); + for (const auto id : m_dpEntity->GetRemovedObjects()) { + m_Parent->OnCollisionLeavePhantom(id); } // Check if we should stop the tether effect diff --git a/dGame/dComponents/PhantomPhysicsComponent.cpp b/dGame/dComponents/PhantomPhysicsComponent.cpp index 276184b1..ba0c2495 100644 --- a/dGame/dComponents/PhantomPhysicsComponent.cpp +++ b/dGame/dComponents/PhantomPhysicsComponent.cpp @@ -187,7 +187,7 @@ PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent) : PhysicsCompon //add fallback cube: m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 2.0f, 2.0f, 2.0f); } - + m_dpEntity->SetScale(m_Scale); m_dpEntity->SetRotation(m_Rotation); m_dpEntity->SetPosition(m_Position); @@ -323,14 +323,13 @@ void PhantomPhysicsComponent::Update(float deltaTime) { if (!m_dpEntity) return; //Process enter events - for (auto en : m_dpEntity->GetNewObjects()) { - if (!en) continue; - ApplyCollisionEffect(en->GetObjectID(), m_EffectType, m_DirectionalMultiplier); - m_Parent->OnCollisionPhantom(en->GetObjectID()); + for (const auto id : m_dpEntity->GetNewObjects()) { + ApplyCollisionEffect(id, m_EffectType, m_DirectionalMultiplier); + m_Parent->OnCollisionPhantom(id); //If we are a respawn volume, inform the client: if (m_IsRespawnVolume) { - auto entity = Game::entityManager->GetEntity(en->GetObjectID()); + auto* const entity = Game::entityManager->GetEntity(id); if (entity) { GameMessages::SendPlayerReachedRespawnCheckpoint(entity, m_RespawnPos, m_RespawnRot); @@ -341,10 +340,9 @@ void PhantomPhysicsComponent::Update(float deltaTime) { } //Process exit events - for (auto en : m_dpEntity->GetRemovedObjects()) { - if (!en) continue; - ApplyCollisionEffect(en->GetObjectID(), m_EffectType, 1.0f); - m_Parent->OnCollisionLeavePhantom(en->GetObjectID()); + for (const auto id : m_dpEntity->GetRemovedObjects()) { + ApplyCollisionEffect(id, m_EffectType, 1.0f); + m_Parent->OnCollisionLeavePhantom(id); } } diff --git a/dGame/dComponents/ProximityMonitorComponent.cpp b/dGame/dComponents/ProximityMonitorComponent.cpp index 9ab3f1db..3338dd43 100644 --- a/dGame/dComponents/ProximityMonitorComponent.cpp +++ b/dGame/dComponents/ProximityMonitorComponent.cpp @@ -5,7 +5,7 @@ #include "EntityManager.h" #include "SimplePhysicsComponent.h" -const std::map ProximityMonitorComponent::m_EmptyObjectMap = {}; +const std::unordered_set ProximityMonitorComponent::m_EmptyObjectSet = {}; ProximityMonitorComponent::ProximityMonitorComponent(Entity* parent, int radiusSmall, int radiusLarge) : Component(parent) { if (radiusSmall != -1 && radiusLarge != -1) { @@ -38,26 +38,26 @@ void ProximityMonitorComponent::SetProximityRadius(dpEntity* entity, const std:: m_ProximitiesData.insert(std::make_pair(name, entity)); } -const std::map& ProximityMonitorComponent::GetProximityObjects(const std::string& name) { - const auto& iter = m_ProximitiesData.find(name); +const std::unordered_set& ProximityMonitorComponent::GetProximityObjects(const std::string& name) { + const auto iter = m_ProximitiesData.find(name); - if (iter == m_ProximitiesData.end()) { - return m_EmptyObjectMap; + if (iter == m_ProximitiesData.cend()) { + return m_EmptyObjectSet; } return iter->second->GetCurrentlyCollidingObjects(); } bool ProximityMonitorComponent::IsInProximity(const std::string& name, LWOOBJID objectID) { - const auto& iter = m_ProximitiesData.find(name); + const auto iter = m_ProximitiesData.find(name); - if (iter == m_ProximitiesData.end()) { + if (iter == m_ProximitiesData.cend()) { return false; } - const auto& collitions = iter->second->GetCurrentlyCollidingObjects(); + const auto& collisions = iter->second->GetCurrentlyCollidingObjects(); - return collitions.find(objectID) != collitions.end(); + return collisions.contains(objectID); } void ProximityMonitorComponent::Update(float deltaTime) { @@ -66,13 +66,13 @@ void ProximityMonitorComponent::Update(float deltaTime) { prox.second->SetPosition(m_Parent->GetPosition()); //Process enter events - for (auto* en : prox.second->GetNewObjects()) { - m_Parent->OnCollisionProximity(en->GetObjectID(), prox.first, "ENTER"); + for (const auto id : prox.second->GetNewObjects()) { + m_Parent->OnCollisionProximity(id, prox.first, "ENTER"); } //Process exit events - for (auto* en : prox.second->GetRemovedObjects()) { - m_Parent->OnCollisionProximity(en->GetObjectID(), prox.first, "LEAVE"); + for (const auto id : prox.second->GetRemovedObjects()) { + m_Parent->OnCollisionProximity(id, prox.first, "LEAVE"); } } } diff --git a/dGame/dComponents/ProximityMonitorComponent.h b/dGame/dComponents/ProximityMonitorComponent.h index 512b2848..e80f1b5b 100644 --- a/dGame/dComponents/ProximityMonitorComponent.h +++ b/dGame/dComponents/ProximityMonitorComponent.h @@ -6,6 +6,8 @@ #ifndef PROXIMITYMONITORCOMPONENT_H #define PROXIMITYMONITORCOMPONENT_H +#include + #include "BitStream.h" #include "Entity.h" #include "dpWorld.h" @@ -42,9 +44,9 @@ public: /** * Returns the last of entities that are used to check proximity, given a name * @param name the proximity name to retrieve physics objects for - * @return a map of physics entities for this name, indexed by object ID + * @return a set of physics entity object IDs for this name */ - const std::map& GetProximityObjects(const std::string& name); + const std::unordered_set& GetProximityObjects(const std::string& name); /** * Checks if the passed object is in proximity of the named proximity sensor @@ -70,7 +72,7 @@ private: /** * Default value for the proximity data */ - static const std::map m_EmptyObjectMap; + static const std::unordered_set m_EmptyObjectSet; }; #endif // PROXIMITYMONITORCOMPONENT_H diff --git a/dPhysics/dpEntity.cpp b/dPhysics/dpEntity.cpp index cbe3f5e8..6fc40452 100644 --- a/dPhysics/dpEntity.cpp +++ b/dPhysics/dpEntity.cpp @@ -3,8 +3,6 @@ #include "dpShapeBox.h" #include "dpGrid.h" -#include - dpEntity::dpEntity(const LWOOBJID& objectID, dpShapeType shapeType, bool isStatic) { m_ObjectID = objectID; m_IsStatic = isStatic; @@ -76,16 +74,17 @@ void dpEntity::CheckCollision(dpEntity* other) { return; } - bool wasFound = m_CurrentlyCollidingObjects.contains(other->GetObjectID()); - - bool isColliding = m_CollisionShape->IsColliding(other->GetShape()); + const auto objId = other->GetObjectID(); + const auto objItr = m_CurrentlyCollidingObjects.find(objId); + const bool wasFound = objItr != m_CurrentlyCollidingObjects.cend(); + const bool isColliding = m_CollisionShape->IsColliding(other->GetShape()); if (isColliding && !wasFound) { - m_CurrentlyCollidingObjects.emplace(other->GetObjectID(), other); - m_NewObjects.push_back(other); + m_CurrentlyCollidingObjects.emplace(objId); + m_NewObjects.push_back(objId); } else if (!isColliding && wasFound) { - m_CurrentlyCollidingObjects.erase(other->GetObjectID()); - m_RemovedObjects.push_back(other); + m_CurrentlyCollidingObjects.erase(objItr); + m_RemovedObjects.push_back(objId); } } diff --git a/dPhysics/dpEntity.h b/dPhysics/dpEntity.h index ea7a49b2..3dea1b61 100644 --- a/dPhysics/dpEntity.h +++ b/dPhysics/dpEntity.h @@ -2,7 +2,8 @@ #include "NiPoint3.h" #include "NiQuaternion.h" #include -#include +#include +#include #include "dCommonVars.h" #include "dpCommon.h" @@ -49,9 +50,9 @@ public: bool GetSleeping() const { return m_Sleeping; } void SetSleeping(bool value) { m_Sleeping = value; } - const std::vector& GetNewObjects() const { return m_NewObjects; } - const std::vector& GetRemovedObjects() const { return m_RemovedObjects; } - const std::map& GetCurrentlyCollidingObjects() const { return m_CurrentlyCollidingObjects; } + const std::span GetNewObjects() const { return m_NewObjects; } + const std::span GetRemovedObjects() const { return m_RemovedObjects; } + const std::unordered_set& GetCurrentlyCollidingObjects() const { return m_CurrentlyCollidingObjects; } void PreUpdate() { m_NewObjects.clear(); m_RemovedObjects.clear(); } @@ -80,7 +81,7 @@ private: bool m_IsGargantuan = false; - std::vector m_NewObjects; - std::vector m_RemovedObjects; - std::map m_CurrentlyCollidingObjects; + std::vector m_NewObjects; + std::vector m_RemovedObjects; + std::unordered_set m_CurrentlyCollidingObjects; }; diff --git a/dScripts/02_server/Map/AM/WanderingVendor.cpp b/dScripts/02_server/Map/AM/WanderingVendor.cpp index 742741d3..d6bb3247 100644 --- a/dScripts/02_server/Map/AM/WanderingVendor.cpp +++ b/dScripts/02_server/Map/AM/WanderingVendor.cpp @@ -21,7 +21,7 @@ void WanderingVendor::OnProximityUpdate(Entity* self, Entity* entering, std::str const auto proxObjs = proximityMonitorComponent->GetProximityObjects("playermonitor"); bool foundPlayer = false; - for (const auto id : proxObjs | std::views::keys) { + for (const auto id : proxObjs) { auto* entity = Game::entityManager->GetEntity(id); if (entity && entity->IsPlayer()) { foundPlayer = true; diff --git a/dScripts/ai/AG/AgBusDoor.cpp b/dScripts/ai/AG/AgBusDoor.cpp index fd6c272e..a4106aaf 100644 --- a/dScripts/ai/AG/AgBusDoor.cpp +++ b/dScripts/ai/AG/AgBusDoor.cpp @@ -23,13 +23,13 @@ void AgBusDoor::OnProximityUpdate(Entity* self, Entity* entering, std::string na m_Counter = 0; m_OuterCounter = 0; - for (const auto& pair : proximityMonitorComponent->GetProximityObjects("busDoor")) { - auto* entity = Game::entityManager->GetEntity(pair.first); + for (const auto id : proximityMonitorComponent->GetProximityObjects("busDoor")) { + const auto* const entity = Game::entityManager->GetEntity(id); if (entity != nullptr && entity->IsPlayer()) m_Counter++; } - for (const auto& pair : proximityMonitorComponent->GetProximityObjects("busDoorOuter")) { - auto* entity = Game::entityManager->GetEntity(pair.first); + for (const auto id : proximityMonitorComponent->GetProximityObjects("busDoorOuter")) { + const auto* const entity = Game::entityManager->GetEntity(id); if (entity != nullptr && entity->IsPlayer()) m_OuterCounter++; } From bcfaa6c7fed659597afda8ab52dc0c8d058fc2a6 Mon Sep 17 00:00:00 2001 From: jadebenn Date: Fri, 5 Apr 2024 03:14:52 -0500 Subject: [PATCH 04/78] const return oversight (#1532) --- dPhysics/dpEntity.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dPhysics/dpEntity.h b/dPhysics/dpEntity.h index 3dea1b61..cc47d718 100644 --- a/dPhysics/dpEntity.h +++ b/dPhysics/dpEntity.h @@ -50,8 +50,8 @@ public: bool GetSleeping() const { return m_Sleeping; } void SetSleeping(bool value) { m_Sleeping = value; } - const std::span GetNewObjects() const { return m_NewObjects; } - const std::span GetRemovedObjects() const { return m_RemovedObjects; } + std::span GetNewObjects() const { return m_NewObjects; } + std::span GetRemovedObjects() const { return m_RemovedObjects; } const std::unordered_set& GetCurrentlyCollidingObjects() const { return m_CurrentlyCollidingObjects; } void PreUpdate() { m_NewObjects.clear(); m_RemovedObjects.clear(); } From 18c27b14c879a1ae6366c37b069c0cdfe5abe94b Mon Sep 17 00:00:00 2001 From: jadebenn Date: Fri, 5 Apr 2024 12:56:23 -0500 Subject: [PATCH 05/78] disable non conforming volatile behavior on MSVC (#1534) --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 00b9383f..aa517182 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,7 +73,8 @@ if(UNIX) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -O2 -fPIC") elseif(MSVC) # Skip warning for invalid conversion from size_t to uint32_t for all targets below for now - add_compile_options("/wd4267" "/utf-8") + # Also disable non-portable MSVC volatile behavior + add_compile_options("/wd4267" "/utf-8" "/volatile:iso") elseif(WIN32) add_compile_definitions(_CRT_SECURE_NO_WARNINGS) endif() From feeac2e0413a9ae03c675330375f2c381724ed1e Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Mon, 8 Apr 2024 15:11:59 -0500 Subject: [PATCH 06/78] feat: refactor slash commands system into more scalable system (#1510) * WIP, but working * Scaffolding * testing and making it compile again * move all commands to functions * renaming to compile * fix failing tests idk how these werent failing before. Seems to have been magic. * move commandss into their namespace make help command useful fix mac error TODO: remove the multiple not founds/ rework the structure to split into help and handling * Just need to fill out the fields, but it's all there templated * Add all aliases, register missing commands * All help text * remove test logs * improvements pass through added code for optimizations and cleanup as well as reduce the amount of scoping for readability and maintainability * Update SlashCommandHandler.cpp * only save command if it is a GM command * simplify if checks * remove broken delimiter * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp --------- Co-authored-by: David Markowitz --- dCommon/dEnums/eGameMessageType.h | 5 +- dGame/dGameMessages/GameMessages.cpp | 12 + dGame/dGameMessages/GameMessages.h | 2 + dGame/dUtilities/SlashCommandHandler.cpp | 2321 ++++++++++++----- dGame/dUtilities/SlashCommandHandler.h | 121 +- dWorldServer/WorldServer.cpp | 4 + .../dEnumsTests/MagicEnumTests.cpp | 2 +- 7 files changed, 1793 insertions(+), 674 deletions(-) diff --git a/dCommon/dEnums/eGameMessageType.h b/dCommon/dEnums/eGameMessageType.h index e3fc22b6..8e6980d6 100644 --- a/dCommon/dEnums/eGameMessageType.h +++ b/dCommon/dEnums/eGameMessageType.h @@ -790,9 +790,10 @@ enum class eGameMessageType : uint16_t { GET_MISSION_TYPE_STATES = 853, GET_TIME_PLAYED = 854, SET_MISSION_VIEWED = 855, - SLASH_COMMAND_TEXT_FEEDBACK = 856, - HANDLE_SLASH_COMMAND_KORE_DEBUGGER = 857, + HKX_VEHICLE_LOADED = 856, + SLASH_COMMAND_TEXT_FEEDBACK = 857, BROADCAST_TEXT_TO_CHATBOX = 858, + HANDLE_SLASH_COMMAND_KORE_DEBUGGER = 859, OPEN_PROPERTY_MANAGEMENT = 860, OPEN_PROPERTY_VENDOR = 861, VOTE_ON_PROPERTY = 862, diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 291dc151..3ef9f3b5 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -6197,3 +6197,15 @@ void GameMessages::HandleCancelDonationOnPlayer(RakNet::BitStream& inStream, Ent if (!characterComponent) return; characterComponent->SetCurrentInteracting(LWOOBJID_EMPTY); } + +void GameMessages::SendSlashCommandFeedbackText(Entity* entity, std::u16string text) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(entity->GetObjectID()); + bitStream.Write(eGameMessageType::SLASH_COMMAND_TEXT_FEEDBACK); + bitStream.Write(text.size()); + bitStream.Write(text); + auto sysAddr = entity->GetSystemAddress(); + SEND_PACKET; +} diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 96bbf7c3..b842710e 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -664,6 +664,8 @@ namespace GameMessages { void HandleRemoveDonationItem(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); void HandleConfirmDonationOnPlayer(RakNet::BitStream& inStream, Entity* entity); void HandleCancelDonationOnPlayer(RakNet::BitStream& inStream, Entity* entity); + + void SendSlashCommandFeedbackText(Entity* entity, std::u16string text); }; #endif // GAMEMESSAGES_H diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 0b26e85a..34ae013c 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -1,6 +1,6 @@ /* * Darkflame Universe - * Copyright 2018 + * Copyright 2024 */ #include "SlashCommandHandler.h" @@ -11,15 +11,6 @@ #include #include "dZoneManager.h" -#include /* defines FILENAME_MAX */ -#ifdef _WIN32 -#include -#define GetCurrentDir _getcwd -#else -#include -#define GetCurrentDir getcwd -#endif - #include "Metrics.hpp" #include "User.h" @@ -88,112 +79,967 @@ #include "CDZoneTableTable.h" #include "ePlayerFlag.h" #include "dNavMesh.h" +#include -void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entity* entity, const SystemAddress& sysAddr) { - auto commandCopy = command; - // Sanity check that a command was given - if (command.empty() || command.front() != u'/') return; - commandCopy.erase(commandCopy.begin()); +namespace { + std::vector CommandInfos; + std::map RegisteredCommands; +} - // Split the command by spaces - std::string chatCommand; - std::vector args; - auto wideCommand = GeneralUtils::SplitString(commandCopy, u' '); - if (wideCommand.empty()) return; +void SlashCommandHandler::RegisterCommand(Command command) { + if (command.aliases.empty()) { + LOG("Command %s has no aliases! Skipping!", command.help.c_str()); + return; + } - // Convert the command to lowercase - chatCommand = GeneralUtils::UTF16ToWTF8(wideCommand.front()); - std::transform(chatCommand.begin(), chatCommand.end(), chatCommand.begin(), ::tolower); - wideCommand.erase(wideCommand.begin()); - - // Convert the arguements to not u16strings - for (auto wideArg : wideCommand) args.push_back(GeneralUtils::UTF16ToWTF8(wideArg)); - - User* user = UserManager::Instance()->GetUser(sysAddr); - if ((chatCommand == "setgmlevel" || chatCommand == "makegm" || chatCommand == "gmlevel") && user->GetMaxGMLevel() > eGameMasterLevel::CIVILIAN) { - if (args.size() != 1) return; - - const auto level_intermed = GeneralUtils::TryParse(args[0]); - - if (!level_intermed) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid gm level."); - return; + for (const auto& alias : command.aliases) { + LOG_DEBUG("Registering command %s", alias.c_str()); + auto [_, success] = RegisteredCommands.try_emplace(alias, command); + // Don't allow duplicate commands + if (!success) { + LOG_DEBUG("Command alias %s is already registered! Skipping!", alias.c_str()); + continue; } - eGameMasterLevel level = static_cast(level_intermed.value()); + } -#ifndef DEVELOPER_SERVER - if (user->GetMaxGMLevel() == eGameMasterLevel::JUNIOR_DEVELOPER) { - level = eGameMasterLevel::CIVILIAN; + CommandInfos.push_back(command); +}; + +void SlashCommandHandler::HandleChatCommand(const std::u16string& chat, Entity* entity, const SystemAddress& sysAddr) { + auto input = GeneralUtils::UTF16ToWTF8(chat); + if (input.empty() || input.front() != '/') return; + const auto pos = input.find(' '); + std::string command = input.substr(1, pos - 1); + + std::string args; + // make sure the space exists and isn't the last character + if (pos != std::string::npos && pos != input.size()) args = input.substr(input.find(' ') + 1); + LOG_DEBUG("Handling command \"%s\" with args \"%s\"", command.c_str(), args.c_str()); + + const auto commandItr = RegisteredCommands.find(command); + std::string error; + if (commandItr != RegisteredCommands.end()) { + auto& [alias, commandHandle] = *commandItr; + if (entity->GetGMLevel() >= commandHandle.requiredLevel) { + if (commandHandle.requiredLevel > eGameMasterLevel::CIVILIAN) Database::Get()->InsertSlashCommandUsage(entity->GetObjectID(), input); + commandHandle.handle(entity, sysAddr, args); + } else if (entity->GetGMLevel() != eGameMasterLevel::CIVILIAN) { + // We don't need to tell civilians they aren't high enough level + error = "You are not high enough GM level to use \"" + command + "\""; } -#endif + } else if (entity->GetGMLevel() == eGameMasterLevel::CIVILIAN) { + // We don't need to tell civilians commands don't exist + error = "Command " + command + " does not exist!"; + } - if (level > user->GetMaxGMLevel()) { - level = user->GetMaxGMLevel(); - } + if (!error.empty()) { + GameMessages::SendSlashCommandFeedbackText(entity, GeneralUtils::ASCIIToUTF16(error)); + } +} - if (level == entity->GetGMLevel()) return; - bool success = user->GetMaxGMLevel() >= level; +void SlashCommandHandler::Startup() { - if (success) { + // Register Dev Commands + Command SetGMLevelCommand{ + .help = "Change the GM level of your character", + .info = "Within the authorized range of levels for the current account, changes the character's game master level to the specified value. This is required to use certain commands", + .aliases = { "setgmlevel", "makegm", "gmlevel" }, + .handle = DEVGMCommands::SetGMLevel, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(SetGMLevelCommand); - if (entity->GetGMLevel() > eGameMasterLevel::CIVILIAN && level == eGameMasterLevel::CIVILIAN) { - GameMessages::SendToggleGMInvis(entity->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS); - } else if (entity->GetGMLevel() == eGameMasterLevel::CIVILIAN && level > eGameMasterLevel::CIVILIAN) { - GameMessages::SendToggleGMInvis(entity->GetObjectID(), true, UNASSIGNED_SYSTEM_ADDRESS); + Command ToggleNameplateCommand{ + .help = "Toggle the visibility of your nameplate. This must be enabled by a server admin to be used.", + .info = "Turns the nameplate above your head that is visible to other players off and on. This must be enabled by a server admin to be used.", + .aliases = { "togglenameplate", "tnp" }, + .handle = DEVGMCommands::ToggleNameplate, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(ToggleNameplateCommand); + + Command ToggleSkipCinematicsCommand{ + .help = "Toggle Skipping Cinematics", + .info = "Skips mission and world load related cinematics", + .aliases = { "toggleskipcinematics", "tsc" }, + .handle = DEVGMCommands::ToggleSkipCinematics, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(ToggleSkipCinematicsCommand); + + Command KillCommand{ + .help = "Smash a user", + .info = "Smashes the character whom the given user is playing", + .aliases = { "kill" }, + .handle = DEVGMCommands::Kill, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(KillCommand); + + Command MetricsCommand{ + .help = "Display server metrics", + .info = "Prints some information about the server's performance", + .aliases = { "metrics" }, + .handle = DEVGMCommands::Metrics, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(MetricsCommand); + + Command AnnounceCommand{ + .help = " Send and announcement", + .info = "Sends an announcement. `/setanntitle` and `/setannmsg` must be called first to configure the announcement.", + .aliases = { "announce" }, + .handle = DEVGMCommands::Announce, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(AnnounceCommand); + + Command SetAnnTitleCommand{ + .help = "Sets the title of an announcement", + .info = "Sets the title of an announcement. Use with `/setannmsg` and `/announce`", + .aliases = { "setanntitle" }, + .handle = DEVGMCommands::SetAnnTitle, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetAnnTitleCommand); + + Command SetAnnMsgCommand{ + .help = "Sets the message of an announcement", + .info = "Sets the message of an announcement. Use with `/setannmtitle` and `/announce`", + .aliases = { "setannmsg" }, + .handle = DEVGMCommands::SetAnnMsg, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetAnnMsgCommand); + + Command ShutdownUniverseCommand{ + .help = "Sends a shutdown message to the master server", + .info = "Sends a shutdown message to the master server. This will send an announcement to all players that the universe will shut down in 10 minutes.", + .aliases = { "shutdownuniverse" }, + .handle = DEVGMCommands::ShutdownUniverse + }; + RegisterCommand(ShutdownUniverseCommand); + + Command SetMinifigCommand{ + .help = "Alters your player's minifig", + .info = "Alters your player's minifig. Body part can be one of \"Eyebrows\", \"Eyes\", \"HairColor\", \"HairStyle\", \"Pants\", \"LeftHand\", \"Mouth\", \"RightHand\", \"Shirt\", or \"Hands\". Changing minifig parts could break the character so this command is limited to GMs.", + .aliases = { "setminifig" }, + .handle = DEVGMCommands::SetMinifig, + .requiredLevel = eGameMasterLevel::FORUM_MODERATOR + }; + RegisterCommand(SetMinifigCommand); + + Command TestMapCommand{ + .help = "Transfers you to the given zone", + .info = "Transfers you to the given zone by id and clone id. Add \"force\" to skip checking if the zone is accessible (this can softlock your character, though, if you e.g. try to teleport to Frostburgh).", + .aliases = { "testmap", "tm" }, + .handle = DEVGMCommands::TestMap, + .requiredLevel = eGameMasterLevel::FORUM_MODERATOR + }; + RegisterCommand(TestMapCommand); + + Command ReportProxPhysCommand{ + .help = "Display proximity sensor info", + .info = "Prints to console the position and radius of proximity sensors.", + .aliases = { "reportproxphys" }, + .handle = DEVGMCommands::ReportProxPhys, + .requiredLevel = eGameMasterLevel::OPERATOR + }; + RegisterCommand(ReportProxPhysCommand); + + Command SpawnPhysicsVertsCommand{ + .help = "Spawns a 1x1 brick at all vertices of phantom physics objects", + .info = "Spawns a 1x1 brick at all vertices of phantom physics objects", + .aliases = { "spawnphysicsverts" }, + .handle = DEVGMCommands::SpawnPhysicsVerts, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SpawnPhysicsVertsCommand); + + Command TeleportCommand{ + .help = "Teleports you", + .info = "Teleports you. If no Y is given, you are teleported to the height of the terrain or physics object at (x, z)", + .aliases = { "teleport", "tele", "tp" }, + .handle = DEVGMCommands::Teleport, + .requiredLevel = eGameMasterLevel::JUNIOR_DEVELOPER + }; + RegisterCommand(TeleportCommand); + + Command ActivateSpawnerCommand{ + .help = "Activates spawner by name", + .info = "Activates spawner by name", + .aliases = { "activatespawner" }, + .handle = DEVGMCommands::ActivateSpawner, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ActivateSpawnerCommand); + + Command AddMissionCommand{ + .help = "Accepts the mission, adding it to your journal.", + .info = "Accepts the mission, adding it to your journal.", + .aliases = { "addmission" }, + .handle = DEVGMCommands::AddMission, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(AddMissionCommand); + + Command BoostCommand{ + .help = "Adds boost to a vehicle", + .info = "Adds a passive boost action if you are in a vehicle. If time is given it will end after that amount of time", + .aliases = { "boost" }, + .handle = DEVGMCommands::Boost, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(BoostCommand); + + Command UnboostCommand{ + .help = "Removes a passive vehicle boost", + .info = "Removes a passive vehicle boost", + .aliases = { "unboost" }, + .handle = DEVGMCommands::Unboost, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(UnboostCommand); + + Command BuffCommand{ + .help = "Applies a buff", + .info = "Applies a buff with the given id for the given number of seconds", + .aliases = { "buff" }, + .handle = DEVGMCommands::Buff, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(BuffCommand); + + Command BuffMeCommand{ + .help = "Sets health, armor, and imagination to 999", + .info = "Sets health, armor, and imagination to 999", + .aliases = { "buffme" }, + .handle = DEVGMCommands::BuffMe, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(BuffMeCommand); + + Command BuffMedCommand{ + .help = "Sets health, armor, and imagination to 9", + .info = "Sets health, armor, and imagination to 9", + .aliases = { "buffmed" }, + .handle = DEVGMCommands::BuffMed, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(BuffMedCommand); + + Command ClearFlagCommand{ + .help = "Clear a player flag", + .info = "Removes the given health or inventory flag from your player. Equivalent of calling `/setflag off `", + .aliases = { "clearflag" }, + .handle = DEVGMCommands::ClearFlag, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ClearFlagCommand); + + Command CompleteMissionCommand{ + .help = "Completes the mission", + .info = "Completes the mission, removing it from your journal", + .aliases = { "completemission" }, + .handle = DEVGMCommands::CompleteMission, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(CompleteMissionCommand); + + Command CreatePrivateCommand{ + .help = "Creates a private zone with password", + .info = "Creates a private zone with password", + .aliases = { "createprivate" }, + .handle = DEVGMCommands::CreatePrivate, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(CreatePrivateCommand); + + Command DebugUiCommand{ + .help = "Toggle Debug UI", + .info = "Toggle Debug UI", + .aliases = { "debugui" }, + .handle = DEVGMCommands::DebugUi, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(DebugUiCommand); + + Command DismountCommand{ + .help = "Dismounts you from the vehicle or mount", + .info = "Dismounts you from the vehicle or mount", + .aliases = { "dismount" }, + .handle = DEVGMCommands::Dismount, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(DismountCommand); + + Command ReloadConfigCommand{ + .help = "Reload Server configs", + .info = "Reloads the server with the new config values.", + .aliases = { "reloadconfig", "reload-config" }, + .handle = DEVGMCommands::ReloadConfig, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ReloadConfigCommand); + + Command ForceSaveCommand{ + .help = "Force save your player", + .info = "While saving to database usually happens on regular intervals and when you disconnect from the server, this command saves your player's data to the database", + .aliases = { "forcesave", "force-save" }, + .handle = DEVGMCommands::ForceSave, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ForceSaveCommand); + + Command FreecamCommand{ + .help = "Toggles freecam mode", + .info = "Toggles freecam mode", + .aliases = { "freecam" }, + .handle = DEVGMCommands::Freecam, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(FreecamCommand); + + Command FreeMoneyCommand{ + .help = "Give yourself coins", + .info = "Give yourself coins", + .aliases = { "freemoney", "givemoney", "money", "givecoins", "coins"}, + .handle = DEVGMCommands::FreeMoney, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(FreeMoneyCommand); + + Command GetNavmeshHeightCommand{ + .help = "Display the navmesh height", + .info = "Display the navmesh height at your current position", + .aliases = { "getnavmeshheight" }, + .handle = DEVGMCommands::GetNavmeshHeight, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(GetNavmeshHeightCommand); + + Command GiveUScoreCommand{ + .help = "Gives uscore", + .info = "Gives uscore", + .aliases = { "giveuscore" }, + .handle = DEVGMCommands::GiveUScore, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(GiveUScoreCommand); + + Command GmAddItemCommand{ + .help = "Give yourseld an item", + .info = "Adds the given item to your inventory by id", + .aliases = { "gmadditem", "give" }, + .handle = DEVGMCommands::GmAddItem, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(GmAddItemCommand); + + Command InspectCommand{ + .help = "Inspect an object", + .info = "Finds the closest entity with the given component or LNV variable (ignoring players and racing cars), printing its ID, distance from the player, and whether it is sleeping, as well as the the IDs of all components the entity has. See detailed usage in the DLU docs", + .aliases = { "inspect" }, + .handle = DEVGMCommands::Inspect, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(InspectCommand); + + Command ListSpawnsCommand{ + .help = "List spawn points for players", + .info = "Lists all the character spawn points in the zone. Additionally, this command will display the current scene that plays when the character lands in the next zone, if there is one.", + .aliases = { "list-spawns", "listspawns" }, + .handle = DEVGMCommands::ListSpawns, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ListSpawnsCommand); + + Command LocRowCommand{ + .help = "Prints the your current position and rotation information to the console", + .info = "Prints the your current position and rotation information to the console", + .aliases = { "locrow" }, + .handle = DEVGMCommands::LocRow, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(LocRowCommand); + + Command LookupCommand{ + .help = "Lookup an object", + .info = "Searches through the Objects table in the client SQLite database for items whose display name, name, or description contains the query. Query can be multiple words delimited by spaces.", + .aliases = { "lookup" }, + .handle = DEVGMCommands::Lookup, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(LookupCommand); + + Command PlayAnimationCommand{ + .help = "Play an animation with given ID", + .info = "Play an animation with given ID", + .aliases = { "playanimation", "playanim" }, + .handle = DEVGMCommands::PlayAnimation, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(PlayAnimationCommand); + + Command PlayEffectCommand{ + .help = "Plays an effect", + .info = "Plays an effect", + .aliases = { "playeffect" }, + .handle = DEVGMCommands::PlayEffect, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(PlayEffectCommand); + + Command PlayLvlFxCommand{ + .help = "Plays the level up animation on your character", + .info = "Plays the level up animation on your character", + .aliases = { "playlvlfx" }, + .handle = DEVGMCommands::PlayLvlFx, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(PlayLvlFxCommand); + + Command PlayRebuildFxCommand{ + .help = "Plays the quickbuild animation on your character", + .info = "Plays the quickbuild animation on your character", + .aliases = { "playrebuildfx" }, + .handle = DEVGMCommands::PlayRebuildFx, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(PlayRebuildFxCommand); + + Command PosCommand{ + .help = "Displays your current position in chat and in the console", + .info = "Displays your current position in chat and in the console", + .aliases = { "pos" }, + .handle = DEVGMCommands::Pos, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(PosCommand); + + Command RefillStatsCommand{ + .help = "Refills health, armor, and imagination to their maximum level", + .info = "Refills health, armor, and imagination to their maximum level", + .aliases = { "refillstats" }, + .handle = DEVGMCommands::RefillStats, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(RefillStatsCommand); + + Command ReforgeCommand{ + .help = "Reforges an item", + .info = "Reforges an item", + .aliases = { "reforge" }, + .handle = DEVGMCommands::Reforge, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ReforgeCommand); + + Command ResetMissionCommand{ + .help = "Sets the state of the mission to accepted but not yet started", + .info = "Sets the state of the mission to accepted but not yet started", + .aliases = { "resetmission" }, + .handle = DEVGMCommands::ResetMission, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ResetMissionCommand); + + Command RotCommand{ + .help = "Displays your current rotation in chat and in the console", + .info = "Displays your current rotation in chat and in the console", + .aliases = { "rot" }, + .handle = DEVGMCommands::Rot, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(RotCommand); + + Command RunMacroCommand{ + .help = "Run a macro", + .info = "Runs any command macro found in `./res/macros/`", + .aliases = { "runmacro" }, + .handle = DEVGMCommands::RunMacro, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(RunMacroCommand); + + Command SetControlSchemeCommand{ + .help = "Sets the character control scheme to the specified number", + .info = "Sets the character control scheme to the specified number", + .aliases = { "setcontrolscheme" }, + .handle = DEVGMCommands::SetControlScheme, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetControlSchemeCommand); + + Command SetCurrencyCommand{ + .help = "Sets your coins", + .info = "Sets your coins", + .aliases = { "setcurrency", "setcoins" }, + .handle = DEVGMCommands::SetCurrency, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetCurrencyCommand); + + Command SetFlagCommand{ + .help = "Set a player flag", + .info = "Sets the given inventory or health flag to the given value, where value can be one of \"on\" or \"off\". If no value is given, by default this adds the flag to your character (equivalent of calling `/setflag on `)", + .aliases = { "setflag" }, + .handle = DEVGMCommands::SetFlag, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetFlagCommand); + + Command SetInventorySizeCommand{ + .help = "Set your inventory size", + .info = "Sets your inventory size to the given size. If `inventory` is provided, the number or string will be used to set that inventory to the requested size", + .aliases = { "setinventorysize", "setinvsize", "setinvensize" }, + .handle = DEVGMCommands::SetInventorySize, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetInventorySizeCommand); + + Command SetUiStateCommand{ + .help = "Changes UI state", + .info = "Changes UI state", + .aliases = { "setuistate" }, + .handle = DEVGMCommands::SetUiState, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetUiStateCommand); + + Command SpawnCommand{ + .help = "Spawns an object at your location by id", + .info = "Spawns an object at your location by id", + .aliases = { "spawn" }, + .handle = DEVGMCommands::Spawn, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SpawnCommand); + + Command SpawnGroupCommand{ + .help = "", + .info = "", + .aliases = { "spawngroup" }, + .handle = DEVGMCommands::SpawnGroup, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SpawnGroupCommand); + + Command SpeedBoostCommand{ + .help = "Set the players speed multiplier", + .info = "Sets the speed multiplier to the given amount. `/speedboost 1.5` will set the speed multiplier to 1.5x the normal speed", + .aliases = { "speedboost" }, + .handle = DEVGMCommands::SpeedBoost, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SpeedBoostCommand); + + Command StartCelebrationCommand{ + .help = "Starts a celebration effect on your character", + .info = "Starts a celebration effect on your character", + .aliases = { "startcelebration" }, + .handle = DEVGMCommands::StartCelebration, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(StartCelebrationCommand); + + Command StopEffectCommand{ + .help = "Stops the given effect", + .info = "Stops the given effect", + .aliases = { "stopeffect" }, + .handle = DEVGMCommands::StopEffect, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(StopEffectCommand); + + Command ToggleCommand{ + .help = "Toggles UI state", + .info = "Toggles UI state", + .aliases = { "toggle" }, + .handle = DEVGMCommands::Toggle, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ToggleCommand); + + Command TpAllCommand{ + .help = "Teleports all characters to your current position", + .info = "Teleports all characters to your current position", + .aliases = { "tpall" }, + .handle = DEVGMCommands::TpAll, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(TpAllCommand); + + Command TriggerSpawnerCommand{ + .help = "Triggers spawner by name", + .info = "Triggers spawner by name", + .aliases = { "triggerspawner" }, + .handle = DEVGMCommands::TriggerSpawner, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(TriggerSpawnerCommand); + + Command UnlockEmoteCommand{ + .help = "Unlocks for your character the emote of the given id", + .info = "Unlocks for your character the emote of the given id", + .aliases = { "unlock-emote", "unlockemote" }, + .handle = DEVGMCommands::UnlockEmote, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(UnlockEmoteCommand); + + Command SetLevelCommand{ + .help = "Set player level", + .info = "Sets the using entities level to the requested level. Takes an optional parameter of an in-game players username to set the level of", + .aliases = { "setlevel" }, + .handle = DEVGMCommands::SetLevel, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetLevelCommand); + + Command SetSkillSlotCommand{ + .help = "Set an action slot to a specific skill", + .info = "Set an action slot to a specific skill", + .aliases = { "setskillslot" }, + .handle = DEVGMCommands::SetSkillSlot, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetSkillSlotCommand); + + Command SetFactionCommand{ + .help = "Set the players faction", + .info = "Clears the users current factions and sets it", + .aliases = { "setfaction" }, + .handle = DEVGMCommands::SetFaction, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetFactionCommand); + + Command AddFactionCommand{ + .help = "Add the faction to the users list of factions", + .info = "Add the faction to the users list of factions", + .aliases = { "addfaction" }, + .handle = DEVGMCommands::AddFaction, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(AddFactionCommand); + + Command GetFactionsCommand{ + .help = "Shows the player's factions", + .info = "Shows the player's factions", + .aliases = { "getfactions" }, + .handle = DEVGMCommands::GetFactions, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(GetFactionsCommand); + + Command SetRewardCodeCommand{ + .help = "Set a reward code for your account", + .info = "Sets the rewardcode for the account you are logged into if it's a valid rewardcode, See cdclient table `RewardCodes`", + .aliases = { "setrewardcode" }, + .handle = DEVGMCommands::SetRewardCode, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetRewardCodeCommand); + + Command CrashCommand{ + .help = "Crash the server", + .info = "Crashes the server", + .aliases = { "crash", "pumpkin" }, + .handle = DEVGMCommands::Crash, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(CrashCommand); + + Command RollLootCommand{ + .help = "Simulate loot rolls", + .info = "Given a `loot matrix index`, look for `item id` in that matrix `amount` times and print to the chat box statistics of rolling that loot matrix.", + .aliases = { "rollloot", "roll-loot" }, + .handle = DEVGMCommands::RollLoot, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(RollLootCommand); + + Command CastSkillCommand{ + .help = "Casts the skill as the player", + .info = "Casts the skill as the player", + .aliases = { "castskill" }, + .handle = DEVGMCommands::CastSkill, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(CastSkillCommand); + + Command DeleteInvenCommand{ + .help = "Delete all items from a specified inventory", + .info = "Delete all items from a specified inventory", + .aliases = { "deleteinven" }, + .handle = DEVGMCommands::DeleteInven, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(DeleteInvenCommand); + + // Register Greater Than Zero Commands + + Command KickCommand{ + .help = "Kicks the player off the server", + .info = "Kicks the player off the server", + .aliases = { "kick" }, + .handle = GMGreaterThanZeroCommands::Kick, + .requiredLevel = eGameMasterLevel::JUNIOR_MODERATOR + }; + RegisterCommand(KickCommand); + + Command MailItemCommand{ + .help = "Mails an item to the given player", + .info = "Mails an item to the given player. The mailed item has predetermined content. The sender name is set to \"Darkflame Universe\". The title of the message is \"Lost item\". The body of the message is \"This is a replacement item for one you lost\".", + .aliases = { "mailitem" }, + .handle = GMGreaterThanZeroCommands::MailItem, + .requiredLevel = eGameMasterLevel::MODERATOR + }; + RegisterCommand(MailItemCommand); + + Command BanCommand{ + .help = "Bans a user from the server", + .info = "Bans a user from the server", + .aliases = { "ban" }, + .handle = GMGreaterThanZeroCommands::Ban, + .requiredLevel = eGameMasterLevel::SENIOR_MODERATOR + }; + RegisterCommand(BanCommand); + + Command ApprovePropertyCommand{ + .help = "Approves a property", + .info = "Approves the property the player is currently visiting", + .aliases = { "approveproperty" }, + .handle = GMGreaterThanZeroCommands::ApproveProperty, + .requiredLevel = eGameMasterLevel::LEAD_MODERATOR + }; + RegisterCommand(ApprovePropertyCommand); + + Command MuteCommand{ + .help = "Mute a player", + .info = "Mute player for the given amount of time. If no time is given, the mute is indefinite.", + .aliases = { "mute" }, + .handle = GMGreaterThanZeroCommands::Mute, + .requiredLevel = eGameMasterLevel::JUNIOR_DEVELOPER + }; + RegisterCommand(MuteCommand); + + Command FlyCommand{ + .help = "Toggle flying", + .info = "Toggles your flying state with an optional parameter for the speed scale.", + .aliases = { "fly" }, + .handle = GMGreaterThanZeroCommands::Fly, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(FlyCommand); + + Command AttackImmuneCommand{ + .help = "Make yourself immune to attacks", + .info = "Sets the character's immunity to basic attacks state, where value can be one of \"1\", to make yourself immune to basic attack damage, or \"0\" to undo", + .aliases = { "attackimmune" }, + .handle = GMGreaterThanZeroCommands::AttackImmune, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(AttackImmuneCommand); + + Command GmImmuneCommand{ + .help = "Sets the character's GMImmune state", + .info = "Sets the character's GMImmune state, where value can be one of \"1\", to make yourself immune to damage, or \"0\" to undo", + .aliases = { "gmimmune" }, + .handle = GMGreaterThanZeroCommands::GmImmune, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(GmImmuneCommand); + + Command GmInvisCommand{ + .help = "Toggles invisibility for the character", + .info = "Toggles invisibility for the character, though it's currently a bit buggy. Requires nonzero GM Level for the character, but the account must have a GM level of 8", + .aliases = { "gminvis" }, + .handle = GMGreaterThanZeroCommands::GmInvis, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(GmInvisCommand); + + Command SetNameCommand{ + + .help = "Sets a temporary name for your player", + .info = "Sets a temporary name for your player. The name resets when you log out", + .aliases = { "setname" }, + .handle = GMGreaterThanZeroCommands::SetName, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SetNameCommand); + + Command TitleCommand{ + .help = "Give your character a title", + .info = "Temporarily appends your player's name with \" - <title>\". This resets when you log out", + .aliases = { "title" }, + .handle = GMGreaterThanZeroCommands::Title, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(TitleCommand); + + + // Register GM Zero Commands + Command HelpCommand{ + .help = "Display command info", + .info = "If a command is given, display detailed info on that command. Otherwise display a list of commands with short desctiptions.", + .aliases = { "help", "h"}, + .handle = GMZeroCommands::Help, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(HelpCommand); + + Command CreditsCommand{ + .help = "Displays DLU Credits", + .info = "Displays the names of the people behind Darkflame Universe.", + .aliases = { "credits" }, + .handle = GMZeroCommands::Credits, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(CreditsCommand); + + Command InfoCommand{ + .help = "Displays server info", + .info = "Displays server info to the user, including where to find the server's source code", + .aliases = { "info" }, + .handle = GMZeroCommands::Info, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(InfoCommand); + + Command DieCommand{ + .help = "Smashes the player", + .info = "Smashes the player as if they were killed by something", + .aliases = { "die" }, + .handle = GMZeroCommands::Die, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(DieCommand); + + Command PingCommand{ + .help = "Displays your average ping.", + .info = "Displays your average ping. If the `-l` flag is used, the latest ping is displayed.", + .aliases = { "ping" }, + .handle = GMZeroCommands::Ping, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(PingCommand); + + Command PvpCommand{ + .help = "Toggle your PVP flag", + .info = "Toggle your PVP flag", + .aliases = { "pvp" }, + .handle = GMZeroCommands::Pvp, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(PvpCommand); + + Command RequestMailCountCommand{ + .help = "Gets the players mail count", + .info = "Sends notification with number of unread messages in the player's mailbox", + .aliases = { "requestmailcount" }, + .handle = GMZeroCommands::RequestMailCount, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(RequestMailCountCommand); + + Command WhoCommand{ + .help = "Displays all players on the instance", + .info = "Displays all players on the instance", + .aliases = { "who" }, + .handle = GMZeroCommands::Who, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(WhoCommand); + + Command FixStatsCommand{ + .help = "Resets skills, buffs, and destroyables", + .info = "Resets skills, buffs, and destroyables", + .aliases = { "fix-stats" }, + .handle = GMZeroCommands::FixStats, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(FixStatsCommand); + + Command JoinCommand{ + .help = "Join a private zone", + .info = "Join a private zone with given password", + .aliases = { "join" }, + .handle = GMZeroCommands::Join, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(JoinCommand); + + Command LeaveZoneCommand{ + .help = "Leave an instanced zone", + .info = "If you are in an instanced zone, transfers you to the closest main world. For example, if you are in an instance of Avant Gardens Survival or the Spider Queen Battle, you are sent to Avant Gardens. If you are in the Battle of Nimbus Station, you are sent to Nimbus Station.", + .aliases = { "leave-zone", "leavezone" }, + .handle = GMZeroCommands::LeaveZone, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(LeaveZoneCommand); + + Command ResurrectCommand{ + .help = "Resurrects the player", + .info = "Resurrects the player", + .aliases = { "resurrect" }, + .handle = GMZeroCommands::Resurrect, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(ResurrectCommand); + + Command InstanceInfoCommand{ + .help = "Display LWOZoneID info for the current zone", + .info = "Display LWOZoneID info for the current zone", + .aliases = { "instanceinfo" }, + .handle = GMZeroCommands::InstanceInfo, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(InstanceInfoCommand); + +} + +namespace GMZeroCommands { + // The star delimiter is to be used for marking the start and end of a localized string. + void Help(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + std::ostringstream feedback; + if (args.empty()) { + feedback << "----- Commands -----"; + for (const auto& command : CommandInfos) { + // TODO: Limit displaying commands based on GM level they require + if (command.requiredLevel > entity->GetGMLevel()) continue; + LOG("Help command: %s", command.aliases[0].c_str()); + feedback << "\n/" << command.aliases[0] << ": " << command.help; + } + } else { + bool foundCommand = false; + for (const auto& command : CommandInfos) { + if (std::ranges::find(command.aliases, args) == command.aliases.end()) continue; + + if (entity->GetGMLevel() < command.requiredLevel) break; + foundCommand = true; + feedback << "----- " << command.aliases.at(0) << " -----\n"; + // info can be a localizable string + feedback << command.info; + if (command.aliases.size() == 1) break; + + feedback << "\nAliases: "; + for (size_t i = 0; i < command.aliases.size(); i++) { + if (i > 0) feedback << ", "; + feedback << command.aliases[i]; + } } - WorldPackets::SendGMLevelChange(sysAddr, success, user->GetMaxGMLevel(), entity->GetGMLevel(), level); - GameMessages::SendChatModeUpdate(entity->GetObjectID(), level); - entity->SetGMLevel(level); - LOG("User %s (%i) has changed their GM level to %i for charID %llu", user->GetUsername().c_str(), user->GetAccountID(), level, entity->GetObjectID()); + // Let GameMasters know if the command doesn't exist + if (!foundCommand && entity->GetGMLevel() > eGameMasterLevel::CIVILIAN) feedback << "Command " << std::quoted(args) << " does not exist!"; } + const auto feedbackStr = feedback.str(); + if (!feedbackStr.empty()) GameMessages::SendSlashCommandFeedbackText(entity, GeneralUtils::ASCIIToUTF16(feedbackStr)); } -#ifndef DEVELOPER_SERVER - if ((entity->GetGMLevel() > user->GetMaxGMLevel()) || (entity->GetGMLevel() > eGameMasterLevel::CIVILIAN && user->GetMaxGMLevel() == eGameMasterLevel::JUNIOR_DEVELOPER)) { - WorldPackets::SendGMLevelChange(sysAddr, true, user->GetMaxGMLevel(), entity->GetGMLevel(), eGameMasterLevel::CIVILIAN); - GameMessages::SendChatModeUpdate(entity->GetObjectID(), eGameMasterLevel::CIVILIAN); - entity->SetGMLevel(eGameMasterLevel::CIVILIAN); - - GameMessages::SendToggleGMInvis(entity->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS); - - ChatPackets::SendSystemMessage(sysAddr, u"Your game master level has been changed, you may not be able to use all commands."); - } -#endif - - if (chatCommand == "togglenameplate" && (Game::config->GetValue("allow_nameplate_off") == "1" || entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER)) { - auto* character = entity->GetCharacter(); - - if (character && character->GetBillboardVisible()) { - character->SetBillboardVisible(false); - ChatPackets::SendSystemMessage(sysAddr, u"Your nameplate has been turned off and is not visible to players currently in this zone."); - } else { - character->SetBillboardVisible(true); - ChatPackets::SendSystemMessage(sysAddr, u"Your nameplate is now on and visible to all players."); - } - return; - } - - if (chatCommand == "toggleskipcinematics" && (Game::config->GetValue("allow_players_to_skip_cinematics") == "1" || entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER)) { - auto* character = entity->GetCharacter(); - if (!character) return; - bool current = character->GetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS); - character->SetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS, !current); - if (!current) { - ChatPackets::SendSystemMessage(sysAddr, u"You have elected to skip cinematics. Note that not all cinematics can be skipped, but most will be skipped now."); - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Cinematics will no longer be skipped."); - } - - return; - } - - - //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - //HANDLE ALL NON GM SLASH COMMANDS RIGHT HERE! - //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - if (chatCommand == "pvp") { + void Pvp(Entity* entity, const SystemAddress& sysAddr, const std::string args) { auto* character = entity->GetComponent(); if (character == nullptr) { @@ -208,11 +1054,9 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit message << character->GetName() << " changed their PVP flag to " << std::to_string(character->GetPvpEnabled()) << "!"; ChatPackets::SendSystemMessage(UNASSIGNED_SYSTEM_ADDRESS, GeneralUtils::UTF8ToUTF16(message.str()), true); - - return; } - if (chatCommand == "who") { + void Who(Entity* entity, const SystemAddress& sysAddr, const std::string args) { ChatPackets::SendSystemMessage( sysAddr, u"Players in this instance: (" + GeneralUtils::to_u16string(PlayerManager::GetAllPlayers().size()) + u")" @@ -228,8 +1072,8 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "ping") { - if (!args.empty() && args[0] == "-l") { + void Ping(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (!args.empty() && args.starts_with("-l")) { std::stringstream message; message << "Your latest ping: " << std::to_string(Game::server->GetLatestPing(sysAddr)) << "ms"; @@ -240,10 +1084,9 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(message.str())); } - return; } - if (chatCommand == "fix-stats") { + void FixStats(Entity* entity, const SystemAddress& sysAddr, const std::string args) { // Reset skill component and buff component auto* skillComponent = entity->GetComponent(); auto* buffComponent = entity->GetComponent(); @@ -264,8 +1107,8 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit destroyableComponent->FixStats(); } - if (chatCommand == "credits" || chatCommand == "info") { - const auto& customText = chatCommand == "credits" ? VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/CREDITS.md").string()) : VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/INFO.md").string()); + void Credits(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto& customText = VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/CREDITS.md").string()); { AMFArrayValue args; @@ -285,11 +1128,32 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit GameMessages::SendUIMessageServerToSingleClient(entity, entity->GetSystemAddress(), "ToggleStoryBox", args); }); - - return; } - if (chatCommand == "leave-zone" || chatCommand == "leavezone") { + void Info(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto& customText = VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/INFO.md").string()); + + { + AMFArrayValue args; + + args.Insert("state", "Story"); + + GameMessages::SendUIMessageServerToSingleClient(entity, entity->GetSystemAddress(), "pushGameState", args); + } + + entity->AddCallbackTimer(0.5f, [customText, entity]() { + AMFArrayValue args; + + args.Insert("visible", true); + args.Insert("text", customText); + + LOG("Sending %s", customText.c_str()); + + GameMessages::SendUIMessageServerToSingleClient(entity, entity->GetSystemAddress(), "ToggleStoryBox", args); + }); + } + + void LeaveZone(Entity* entity, const SystemAddress& sysAddr, const std::string args) { const auto currentZone = Game::zoneManager->GetZone()->GetZoneID().GetMapID(); LWOMAPID newZone = 0; @@ -329,9 +1193,12 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit }); } - if (chatCommand == "join" && !args.empty()) { + void Join(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + ChatPackets::SendSystemMessage(sysAddr, u"Requesting private map..."); - const auto& password = args[0]; + const auto& password = splitArgs[0]; ZoneInstanceManager::Instance()->RequestPrivateZone(Game::server, false, password, [=](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) { LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", sysAddr.ToString(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort); @@ -348,52 +1215,121 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit }); } - if (user->GetMaxGMLevel() == eGameMasterLevel::CIVILIAN || entity->GetGMLevel() >= eGameMasterLevel::CIVILIAN) { - if (chatCommand == "die") { - entity->Smash(entity->GetObjectID()); - } - - if (chatCommand == "resurrect") { - ScriptedActivityComponent* scriptedActivityComponent = Game::zoneManager->GetZoneControlObject()->GetComponent(); - - if (scriptedActivityComponent) { // check if user is in activity world and if so, they can't resurrect - ChatPackets::SendSystemMessage(sysAddr, u"You cannot resurrect in an activity world."); - return; - } - - GameMessages::SendResurrect(entity); - } - - if (chatCommand == "requestmailcount") { - Mail::HandleNotificationRequest(entity->GetSystemAddress(), entity->GetObjectID()); - } - - if (chatCommand == "instanceinfo") { - const auto zoneId = Game::zoneManager->GetZone()->GetZoneID(); - - ChatPackets::SendSystemMessage(sysAddr, u"Map: " + (GeneralUtils::to_u16string(zoneId.GetMapID())) + u"\nClone: " + (GeneralUtils::to_u16string(zoneId.GetCloneID())) + u"\nInstance: " + (GeneralUtils::to_u16string(zoneId.GetInstanceID()))); - } - - if (entity->GetGMLevel() == eGameMasterLevel::CIVILIAN) return; + void Die(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + entity->Smash(entity->GetObjectID()); } - if (chatCommand == "resetmission" && args.size() >= 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - const auto missionId = GeneralUtils::TryParse(args[0]); + void Resurrect(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + ScriptedActivityComponent* scriptedActivityComponent = Game::zoneManager->GetZoneControlObject()->GetComponent(); + + if (scriptedActivityComponent) { // check if user is in activity world and if so, they can't resurrect + ChatPackets::SendSystemMessage(sysAddr, u"You cannot resurrect in an activity world."); + return; + } + + GameMessages::SendResurrect(entity); + } + + void RequestMailCount(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + Mail::HandleNotificationRequest(entity->GetSystemAddress(), entity->GetObjectID()); + } + + void InstanceInfo(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto zoneId = Game::zoneManager->GetZone()->GetZoneID(); + + ChatPackets::SendSystemMessage(sysAddr, u"Map: " + (GeneralUtils::to_u16string(zoneId.GetMapID())) + u"\nClone: " + (GeneralUtils::to_u16string(zoneId.GetCloneID())) + u"\nInstance: " + (GeneralUtils::to_u16string(zoneId.GetInstanceID()))); + } +}; + +namespace DEVGMCommands { + void SetGMLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + User* user = UserManager::Instance()->GetUser(entity->GetSystemAddress()); + + const auto level_intermed = GeneralUtils::TryParse(args); + if (!level_intermed) { + GameMessages::SendSlashCommandFeedbackText(entity, u"Invalid GM level."); + return; + } + eGameMasterLevel level = static_cast(level_intermed.value()); + +#ifndef DEVELOPER_SERVER + if (user->GetMaxGMLevel() == eGameMasterLevel::JUNIOR_DEVELOPER) { + level = eGameMasterLevel::CIVILIAN; + } +#endif + + if (level > user->GetMaxGMLevel()) level = user->GetMaxGMLevel(); + + if (level == entity->GetGMLevel()) return; + bool success = user->GetMaxGMLevel() >= level; + + if (success) { + WorldPackets::SendGMLevelChange(entity->GetSystemAddress(), success, user->GetMaxGMLevel(), entity->GetGMLevel(), level); + GameMessages::SendChatModeUpdate(entity->GetObjectID(), level); + entity->SetGMLevel(level); + LOG("User %s (%i) has changed their GM level to %i for charID %llu", user->GetUsername().c_str(), user->GetAccountID(), level, entity->GetObjectID()); + } + +#ifndef DEVELOPER_SERVER + if ((entity->GetGMLevel() > user->GetMaxGMLevel()) || (entity->GetGMLevel() > eGameMasterLevel::CIVILIAN && user->GetMaxGMLevel() == eGameMasterLevel::JUNIOR_DEVELOPER)) { + WorldPackets::SendGMLevelChange(entity->GetSystemAddress(), true, user->GetMaxGMLevel(), entity->GetGMLevel(), eGameMasterLevel::CIVILIAN); + GameMessages::SendChatModeUpdate(entity->GetObjectID(), eGameMasterLevel::CIVILIAN); + entity->SetGMLevel(eGameMasterLevel::CIVILIAN); + + GameMessages::SendToggleGMInvis(entity->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS); + + GameMessages::SendSlashCommandFeedbackText(entity, u"Your game master level has been changed, you may not be able to use all commands."); + } +#endif + } + + + void ToggleNameplate(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if ((Game::config->GetValue("allow_nameplate_off") != "1" && entity->GetGMLevel() < eGameMasterLevel::DEVELOPER)) return; + + auto* character = entity->GetCharacter(); + if (character && character->GetBillboardVisible()) { + character->SetBillboardVisible(false); + GameMessages::SendSlashCommandFeedbackText(entity, u"Your nameplate has been turned off and is not visible to players currently in this zone."); + } else { + character->SetBillboardVisible(true); + GameMessages::SendSlashCommandFeedbackText(entity, u"Your nameplate is now on and visible to all players."); + } + } + + void ToggleSkipCinematics(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (Game::config->GetValue("allow_players_to_skip_cinematics") != "1" && entity->GetGMLevel() < eGameMasterLevel::DEVELOPER) return; + auto* character = entity->GetCharacter(); + if (!character) return; + bool current = character->GetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS); + character->SetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS, !current); + if (!current) { + GameMessages::SendSlashCommandFeedbackText(entity, u"You have elected to skip cinematics. Note that not all cinematics can be skipped, but most will be skipped now."); + } else { + GameMessages::SendSlashCommandFeedbackText(entity, u"Cinematics will no longer be skipped."); + } + } + + void ResetMission(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto missionId = GeneralUtils::TryParse(splitArgs[0]); if (!missionId) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid mission ID."); return; } - + auto* missionComponent = entity->GetComponent(); if (!missionComponent) return; missionComponent->ResetMission(missionId.value()); } - // Log command to database - Database::Get()->InsertSlashCommandUsage(entity->GetObjectID(), chatCommand); + void SetMinifig(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 2) return; - if (chatCommand == "setminifig" && args.size() == 2 && entity->GetGMLevel() >= eGameMasterLevel::FORUM_MODERATOR) { // could break characters so only allow if GM > 0 - const auto minifigItemIdExists = GeneralUtils::TryParse(args[1]); + const auto minifigItemIdExists = GeneralUtils::TryParse(splitArgs[1]); if (!minifigItemIdExists) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid Minifig Item Id ID."); return; @@ -401,7 +1337,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit const int32_t minifigItemId = minifigItemIdExists.value(); Game::entityManager->DestructEntity(entity, sysAddr); auto* charComp = entity->GetComponent(); - std::string lowerName = args[0]; + std::string lowerName = splitArgs[0]; if (lowerName.empty()) return; std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower); if (lowerName == "eyebrows") { @@ -437,8 +1373,11 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit GameMessages::SendToggleGMInvis(entity->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS); // need to retoggle because it gets reenabled on creation of new character } - if ((chatCommand == "playanimation" || chatCommand == "playanim") && args.size() == 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - std::u16string anim = GeneralUtils::ASCIIToUTF16(args[0], args[0].size()); + void PlayAnimation(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + std::u16string anim = GeneralUtils::ASCIIToUTF16(splitArgs[0], splitArgs[0].size()); RenderComponent::PlayAnimation(entity, anim); auto* possessorComponent = entity->GetComponent(); if (possessorComponent) { @@ -447,18 +1386,19 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "list-spawns" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void ListSpawns(Entity* entity, const SystemAddress& sysAddr, const std::string args) { for (const auto& pair : Game::entityManager->GetSpawnPointEntities()) { ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(pair.first)); } ChatPackets::SendSystemMessage(sysAddr, u"Current: " + GeneralUtils::ASCIIToUTF16(entity->GetCharacter()->GetTargetScene())); - - return; } - if (chatCommand == "unlock-emote" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - const auto emoteID = GeneralUtils::TryParse(args.at(0)); + void UnlockEmote(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto emoteID = GeneralUtils::TryParse(splitArgs[0]); if (!emoteID) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid emote ID."); @@ -468,14 +1408,17 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit entity->GetCharacter()->UnlockEmote(emoteID.value()); } - if (chatCommand == "force-save" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void ForceSave(Entity* entity, const SystemAddress& sysAddr, const std::string args) { entity->GetCharacter()->SaveXMLToDatabase(); } - if (chatCommand == "kill" && args.size() == 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void Kill(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + ChatPackets::SendSystemMessage(sysAddr, u"Brutally murdering that player, if online on this server."); - auto* player = PlayerManager::GetPlayer(args[0]); + auto* player = PlayerManager::GetPlayer(splitArgs[0]); if (player) { player->Smash(entity->GetObjectID()); ChatPackets::SendSystemMessage(sysAddr, u"It has been done, do you feel good about yourself now?"); @@ -486,8 +1429,11 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit return; } - if (chatCommand == "speedboost" && args.size() == 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - const auto boostOptional = GeneralUtils::TryParse(args[0]); + void SpeedBoost(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto boostOptional = GeneralUtils::TryParse(splitArgs[0]); if (!boostOptional) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid boost."); return; @@ -517,18 +1463,20 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit Game::entityManager->SerializeEntity(entity); } - if (chatCommand == "freecam" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void Freecam(Entity* entity, const SystemAddress& sysAddr, const std::string args) { const auto state = !entity->GetVar(u"freecam"); entity->SetVar(u"freecam", state); GameMessages::SendSetPlayerControlScheme(entity, static_cast(state ? 9 : 1)); ChatPackets::SendSystemMessage(sysAddr, u"Toggled freecam."); - return; } - if (chatCommand == "setcontrolscheme" && args.size() == 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - const auto scheme = GeneralUtils::TryParse(args[0]); + void SetControlScheme(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto scheme = GeneralUtils::TryParse(splitArgs[0]); if (!scheme) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid control scheme."); @@ -538,44 +1486,39 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit GameMessages::SendSetPlayerControlScheme(entity, static_cast(scheme.value())); ChatPackets::SendSystemMessage(sysAddr, u"Switched control scheme."); - return; } - if (chatCommand == "approveproperty" && entity->GetGMLevel() >= eGameMasterLevel::LEAD_MODERATOR) { + void SetUiState(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; - if (PropertyManagementComponent::Instance() != nullptr) { - PropertyManagementComponent::Instance()->UpdateApprovedStatus(true); - } - - return; - } - - if (chatCommand == "setuistate" && args.size() == 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { AMFArrayValue uiState; - uiState.Insert("state", args.at(0)); + uiState.Insert("state", splitArgs[0]); GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, "pushGameState", uiState); ChatPackets::SendSystemMessage(sysAddr, u"Switched UI state."); - - return; } - if (chatCommand == "toggle" && args.size() == 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void Toggle(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + AMFArrayValue amfArgs; amfArgs.Insert("visible", true); - GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, args[0], amfArgs); + GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, splitArgs[0], amfArgs); ChatPackets::SendSystemMessage(sysAddr, u"Toggled UI state."); - - return; } - if ((chatCommand == "setinventorysize" || chatCommand == "setinvsize") && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { - const auto sizeOptional = GeneralUtils::TryParse(args[0]); + void SetInventorySize(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto sizeOptional = GeneralUtils::TryParse(splitArgs[0]); if (!sizeOptional) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid size."); return; @@ -585,21 +1528,21 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit eInventoryType selectedInventory = eInventoryType::ITEMS; // a possible inventory was provided if we got more than 1 argument - if (args.size() >= 2) { - selectedInventory = GeneralUtils::TryParse(args.at(1)).value_or(eInventoryType::INVALID); + if (splitArgs.size() >= 2) { + selectedInventory = GeneralUtils::TryParse(splitArgs.at(1)).value_or(eInventoryType::INVALID); if (selectedInventory == eInventoryType::INVALID) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid inventory."); return; } else { // In this case, we treat the input as a string and try to find it in the reflection list - std::transform(args.at(1).begin(), args.at(1).end(), args.at(1).begin(), ::toupper); + std::transform(splitArgs.at(1).begin(), splitArgs.at(1).end(), splitArgs.at(1).begin(), ::toupper); for (uint32_t index = 0; index < NUMBER_OF_INVENTORIES; index++) { - if (std::string_view(args.at(1)) == std::string_view(InventoryType::InventoryTypeToString(static_cast(index)))) selectedInventory = static_cast(index); + if (std::string_view(splitArgs.at(1)) == std::string_view(InventoryType::InventoryTypeToString(static_cast(index)))) selectedInventory = static_cast(index); } } ChatPackets::SendSystemMessage(sysAddr, u"Setting inventory " + - GeneralUtils::ASCIIToUTF16(args.at(1)) + + GeneralUtils::ASCIIToUTF16(splitArgs.at(1)) + u" to size " + GeneralUtils::to_u16string(size)); } else ChatPackets::SendSystemMessage(sysAddr, u"Setting inventory ITEMS to size " + GeneralUtils::to_u16string(size)); @@ -610,18 +1553,17 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit inventory->SetSize(size); } - - return; } - if (chatCommand == "runmacro" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (args.size() != 1) return; + void RunMacro(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; // Only process if input does not contain separator charaters - if (args[0].find("/") != std::string::npos) return; - if (args[0].find("\\") != std::string::npos) return; + if (splitArgs[0].find("/") != std::string::npos) return; + if (splitArgs[0].find("\\") != std::string::npos) return; - auto infile = Game::assetManager->GetFile(("macros/" + args[0] + ".scm").c_str()); + auto infile = Game::assetManager->GetFile(("macros/" + splitArgs[0] + ".scm").c_str()); if (!infile) { ChatPackets::SendSystemMessage(sysAddr, u"Unknown macro! Is the filename right?"); @@ -639,14 +1581,13 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } else { ChatPackets::SendSystemMessage(sysAddr, u"Unknown macro! Is the filename right?"); } - - return; } - if (chatCommand == "addmission" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (args.size() == 0) return; + void AddMission(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; - const auto missionID = GeneralUtils::TryParse(args.at(0)); + const auto missionID = GeneralUtils::TryParse(splitArgs.at(0)); if (!missionID) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid mission id."); @@ -655,13 +1596,13 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit auto comp = static_cast(entity->GetComponent(eReplicaComponentType::MISSION)); if (comp) comp->AcceptMission(missionID.value(), true); - return; } - if (chatCommand == "completemission" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (args.size() == 0) return; + void CompleteMission(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; - const auto missionID = GeneralUtils::TryParse(args.at(0)); + const auto missionID = GeneralUtils::TryParse(splitArgs.at(0)); if (!missionID) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid mission id."); @@ -670,35 +1611,41 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit auto comp = static_cast(entity->GetComponent(eReplicaComponentType::MISSION)); if (comp) comp->CompleteMission(missionID.value(), true); - return; } - if (chatCommand == "setflag" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() == 1) { - const auto flagId = GeneralUtils::TryParse(args.at(0)); + void SetFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() == 1) { + const auto flagId = GeneralUtils::TryParse(splitArgs.at(0)); - if (!flagId) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id."); - return; + if (!flagId) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id."); + return; + } + + entity->GetCharacter()->SetPlayerFlag(flagId.value(), true); + } else if (splitArgs.size() >= 2) { + const auto flagId = GeneralUtils::TryParse(splitArgs.at(1)); + std::string onOffFlag = splitArgs.at(0); + if (!flagId) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id."); + return; + } + + if (onOffFlag != "off" && onOffFlag != "on") { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag type."); + return; + } + + entity->GetCharacter()->SetPlayerFlag(flagId.value(), onOffFlag == "on"); } - - entity->GetCharacter()->SetPlayerFlag(flagId.value(), true); } - if (chatCommand == "setflag" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() == 2) { - const auto flagId = GeneralUtils::TryParse(args.at(1)); - std::string onOffFlag = args.at(0); - if (!flagId) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id."); - return; - } - if (onOffFlag != "off" && onOffFlag != "on") { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag type."); - return; - } - entity->GetCharacter()->SetPlayerFlag(flagId.value(), onOffFlag == "on"); - } - if (chatCommand == "clearflag" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() == 1) { - const auto flagId = GeneralUtils::TryParse(args.at(0)); + void ClearFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto flagId = GeneralUtils::TryParse(splitArgs.at(0)); if (!flagId) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id."); @@ -708,52 +1655,43 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit entity->GetCharacter()->SetPlayerFlag(flagId.value(), false); } - if (chatCommand == "playeffect" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 3) { - const auto effectID = GeneralUtils::TryParse(args.at(0)); + void PlayEffect(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 3) return; + + const auto effectID = GeneralUtils::TryParse(splitArgs.at(0)); if (!effectID) return; // FIXME: use fallible ASCIIToUTF16 conversion, because non-ascii isn't valid anyway - GameMessages::SendPlayFXEffect(entity->GetObjectID(), effectID.value(), GeneralUtils::ASCIIToUTF16(args.at(1)), args.at(2)); + GameMessages::SendPlayFXEffect(entity->GetObjectID(), effectID.value(), GeneralUtils::ASCIIToUTF16(splitArgs.at(1)), splitArgs.at(2)); } - if (chatCommand == "stopeffect" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { - GameMessages::SendStopFXEffect(entity, true, args[0]); + void StopEffect(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + GameMessages::SendStopFXEffect(entity, true, splitArgs[0]); } - if (chatCommand == "setanntitle" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (args.size() < 0) return; - - std::stringstream ss; - for (auto string : args) - ss << string << " "; - - entity->GetCharacter()->SetAnnouncementTitle(ss.str()); - return; + void SetAnnTitle(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + entity->GetCharacter()->SetAnnouncementTitle(args); } - if (chatCommand == "setannmsg" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (args.size() < 0) return; - - std::stringstream ss; - for (auto string : args) - ss << string << " "; - - entity->GetCharacter()->SetAnnouncementMessage(ss.str()); - return; + void SetAnnMsg(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + entity->GetCharacter()->SetAnnouncementMessage(args); } - if (chatCommand == "announce" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (entity->GetCharacter()->GetAnnouncementTitle().size() == 0 || entity->GetCharacter()->GetAnnouncementMessage().size() == 0) { + void Announce(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (entity->GetCharacter()->GetAnnouncementTitle().empty() || entity->GetCharacter()->GetAnnouncementMessage().empty()) { ChatPackets::SendSystemMessage(sysAddr, u"Use /setanntitle & /setannmsg <msg> first!"); return; } - SendAnnouncement(entity->GetCharacter()->GetAnnouncementTitle(), entity->GetCharacter()->GetAnnouncementMessage()); - return; + SlashCommandHandler::SendAnnouncement(entity->GetCharacter()->GetAnnouncementTitle(), entity->GetCharacter()->GetAnnouncementMessage()); } - if (chatCommand == "shutdownuniverse" && entity->GetGMLevel() == eGameMasterLevel::OPERATOR) { + void ShutdownUniverse(Entity* entity, const SystemAddress& sysAddr, const std::string args) { //Tell the master server that we're going to be shutting down whole "universe": CBITSTREAM; BitStreamUtils::WriteHeader(bitStream, eConnectionType::MASTER, eMasterMessageType::SHUTDOWN_UNIVERSE); @@ -761,11 +1699,10 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ChatPackets::SendSystemMessage(sysAddr, u"Sent universe shutdown notification to master."); //Tell chat to send an announcement to all servers - SendAnnouncement("Servers Closing Soon!", "DLU servers will close for maintenance in 10 minutes from now."); - return; + SlashCommandHandler::SendAnnouncement("Servers Closing Soon!", "DLU servers will close for maintenance in 10 minutes from now."); } - if (chatCommand == "getnavmeshheight" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void GetNavmeshHeight(Entity* entity, const SystemAddress& sysAddr, const std::string args) { auto control = static_cast<ControllablePhysicsComponent*>(entity->GetComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS)); if (!control) return; @@ -774,9 +1711,11 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ChatPackets::SendSystemMessage(sysAddr, msg); } - if (chatCommand == "gmadditem" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - if (args.size() == 1) { - const auto itemLOT = GeneralUtils::TryParse<uint32_t>(args.at(0)); + void GmAddItem(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + + if (splitArgs.size() == 1) { + const auto itemLOT = GeneralUtils::TryParse<uint32_t>(splitArgs.at(0)); if (!itemLOT) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid item LOT."); @@ -786,14 +1725,14 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit InventoryComponent* inventory = static_cast<InventoryComponent*>(entity->GetComponent(eReplicaComponentType::INVENTORY)); inventory->AddItem(itemLOT.value(), 1, eLootSourceType::MODERATION); - } else if (args.size() == 2) { - const auto itemLOT = GeneralUtils::TryParse<uint32_t>(args.at(0)); + } else if (splitArgs.size() == 2) { + const auto itemLOT = GeneralUtils::TryParse<uint32_t>(splitArgs.at(0)); if (!itemLOT) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid item LOT."); return; } - const auto count = GeneralUtils::TryParse<uint32_t>(args.at(1)); + const auto count = GeneralUtils::TryParse<uint32_t>(splitArgs.at(1)); if (!count) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid item count."); return; @@ -807,82 +1746,25 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "mailitem" && entity->GetGMLevel() >= eGameMasterLevel::MODERATOR && args.size() >= 2) { - const auto& playerName = args[0]; + void Teleport(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); - auto playerInfo = Database::Get()->GetCharacterInfo(playerName); - - uint32_t receiverID = 0; - if (!playerInfo) { - ChatPackets::SendSystemMessage(sysAddr, u"Failed to find that player"); - - return; - } - - receiverID = playerInfo->id; - - const auto lot = GeneralUtils::TryParse<LOT>(args.at(1)); - - if (!lot) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid item lot."); - return; - } - - IMail::MailInfo mailInsert; - mailInsert.senderId = entity->GetObjectID(); - mailInsert.senderUsername = "Darkflame Universe"; - mailInsert.receiverId = receiverID; - mailInsert.recipient = playerName; - mailInsert.subject = "Lost item"; - mailInsert.body = "This is a replacement item for one you lost."; - mailInsert.itemID = LWOOBJID_EMPTY; - mailInsert.itemLOT = lot.value(); - mailInsert.itemSubkey = LWOOBJID_EMPTY; - mailInsert.itemCount = 1; - Database::Get()->InsertNewMail(mailInsert); - - ChatPackets::SendSystemMessage(sysAddr, u"Mail sent"); - - return; - } - - if (chatCommand == "setname" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - std::string name = ""; - - for (const auto& arg : args) { - name += arg + " "; - } - - GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(name), UNASSIGNED_SYSTEM_ADDRESS); - } - - if (chatCommand == "title" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - std::string name = entity->GetCharacter()->GetName() + " - "; - - for (const auto& arg : args) { - name += arg + " "; - } - - GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(name), UNASSIGNED_SYSTEM_ADDRESS); - } - - if ((chatCommand == "teleport" || chatCommand == "tele") && entity->GetGMLevel() >= eGameMasterLevel::JUNIOR_MODERATOR) { NiPoint3 pos{}; - if (args.size() == 3) { + if (splitArgs.size() == 3) { - const auto x = GeneralUtils::TryParse<float>(args.at(0)); + const auto x = GeneralUtils::TryParse<float>(splitArgs.at(0)); if (!x) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid x."); return; } - const auto y = GeneralUtils::TryParse<float>(args.at(1)); + const auto y = GeneralUtils::TryParse<float>(splitArgs.at(1)); if (!y) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid y."); return; } - const auto z = GeneralUtils::TryParse<float>(args.at(2)); + const auto z = GeneralUtils::TryParse<float>(splitArgs.at(2)); if (!z) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid z."); return; @@ -894,15 +1776,15 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit LOG("Teleporting objectID: %llu to %f, %f, %f", entity->GetObjectID(), pos.x, pos.y, pos.z); GameMessages::SendTeleport(entity->GetObjectID(), pos, NiQuaternion(), sysAddr); - } else if (args.size() == 2) { + } else if (splitArgs.size() == 2) { - const auto x = GeneralUtils::TryParse<float>(args.at(0)); + const auto x = GeneralUtils::TryParse<float>(splitArgs.at(0)); if (!x) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid x."); return; } - const auto z = GeneralUtils::TryParse<float>(args.at(1)); + const auto z = GeneralUtils::TryParse<float>(splitArgs.at(1)); if (!z) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid z."); return; @@ -918,7 +1800,6 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /teleport <x> (<y>) <z> - if no Y given, will teleport to the height of the terrain (or any physics object)."); } - auto* possessorComponent = entity->GetComponent<PossessorComponent>(); if (possessorComponent) { auto* possassableEntity = Game::entityManager->GetEntity(possessorComponent->GetPossessable()); @@ -933,7 +1814,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "tpall" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void TpAll(Entity* entity, const SystemAddress& sysAddr, const std::string args) { const auto pos = entity->GetPosition(); const auto characters = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::CHARACTER); @@ -941,11 +1822,9 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit for (auto* character : characters) { GameMessages::SendTeleport(character->GetObjectID(), pos, NiQuaternion(), character->GetSystemAddress()); } - - return; } - if (chatCommand == "dismount" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void Dismount(Entity* entity, const SystemAddress& sysAddr, const std::string args) { auto* possessorComponent = entity->GetComponent<PossessorComponent>(); if (possessorComponent) { auto possessableId = possessorComponent->GetPossessable(); @@ -956,181 +1835,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "fly" && entity->GetGMLevel() >= eGameMasterLevel::JUNIOR_DEVELOPER) { - auto* character = entity->GetCharacter(); - - if (character) { - bool isFlying = character->GetIsFlying(); - - if (isFlying) { - GameMessages::SendSetJetPackMode(entity, false); - - character->SetIsFlying(false); - } else { - float speedScale = 1.0f; - - if (args.size() >= 1) { - const auto tempScaleStore = GeneralUtils::TryParse<float>(args.at(0)); - - if (tempScaleStore) { - speedScale = tempScaleStore.value(); - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Failed to parse speed scale argument."); - } - } - - float airSpeed = 20 * speedScale; - float maxAirSpeed = 30 * speedScale; - float verticalVelocity = 1.5 * speedScale; - - GameMessages::SendSetJetPackMode(entity, true, true, false, 167, airSpeed, maxAirSpeed, verticalVelocity); - - character->SetIsFlying(true); - } - } - } - - //------- GM COMMANDS TO ACTUALLY MODERATE -------- - - if (chatCommand == "mute" && entity->GetGMLevel() >= eGameMasterLevel::JUNIOR_DEVELOPER) { - if (args.size() >= 1) { - auto* player = PlayerManager::GetPlayer(args[0]); - - uint32_t accountId = 0; - LWOOBJID characterId = 0; - - if (player == nullptr) { - auto characterInfo = Database::Get()->GetCharacterInfo(args[0]); - - if (characterInfo) { - accountId = characterInfo->accountId; - characterId = characterInfo->id; - - GeneralUtils::SetBit(characterId, eObjectBits::CHARACTER); - GeneralUtils::SetBit(characterId, eObjectBits::PERSISTENT); - } - - if (accountId == 0) { - ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + GeneralUtils::UTF8ToUTF16(args[0])); - - return; - } - } else { - auto* character = player->GetCharacter(); - auto* user = character != nullptr ? character->GetParentUser() : nullptr; - if (user) accountId = user->GetAccountID(); - characterId = player->GetObjectID(); - } - - time_t expire = 1; // Default to indefinate mute - - if (args.size() >= 2) { - const auto days = GeneralUtils::TryParse<uint32_t>(args[1]); - if (!days) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid days."); - - return; - } - - std::optional<uint32_t> hours; - if (args.size() >= 3) { - hours = GeneralUtils::TryParse<uint32_t>(args[2]); - if (!hours) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid hours."); - - return; - } - } - - expire = time(NULL); - expire += 24 * 60 * 60 * days.value(); - expire += 60 * 60 * hours.value_or(0); - } - - if (accountId != 0) Database::Get()->UpdateAccountUnmuteTime(accountId, expire); - - char buffer[32] = "brought up for review.\0"; - - if (expire != 1) { - std::tm* ptm = std::localtime(&expire); - // Format: Mo, 15.06.2009 20:20:00 - std::strftime(buffer, 32, "%a, %d.%m.%Y %H:%M:%S", ptm); - } - - const auto timeStr = GeneralUtils::ASCIIToUTF16(std::string(buffer)); - - ChatPackets::SendSystemMessage(sysAddr, u"Muted: " + GeneralUtils::UTF8ToUTF16(args[0]) + u" until " + timeStr); - - //Notify chat about it - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::GM_MUTE); - - bitStream.Write(characterId); - bitStream.Write(expire); - - Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /mute <username> <days (optional)> <hours (optional)>"); - } - } - - if (chatCommand == "kick" && entity->GetGMLevel() >= eGameMasterLevel::JUNIOR_MODERATOR) { - if (args.size() == 1) { - auto* player = PlayerManager::GetPlayer(args[0]); - - std::u16string username = GeneralUtils::UTF8ToUTF16(args[0]); - if (player == nullptr) { - ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + username); - return; - } - - Game::server->Disconnect(player->GetSystemAddress(), eServerDisconnectIdentifiers::KICK); - - ChatPackets::SendSystemMessage(sysAddr, u"Kicked: " + username); - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /kick <username>"); - } - } - - if (chatCommand == "ban" && entity->GetGMLevel() >= eGameMasterLevel::SENIOR_MODERATOR) { - if (args.size() == 1) { - auto* player = PlayerManager::GetPlayer(args[0]); - - uint32_t accountId = 0; - - if (player == nullptr) { - auto characterInfo = Database::Get()->GetCharacterInfo(args[0]); - - if (characterInfo) { - accountId = characterInfo->accountId; - } - - if (accountId == 0) { - ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + GeneralUtils::UTF8ToUTF16(args[0])); - - return; - } - } else { - auto* character = player->GetCharacter(); - auto* user = character != nullptr ? character->GetParentUser() : nullptr; - if (user) accountId = user->GetAccountID(); - } - - if (accountId != 0) Database::Get()->UpdateAccountBan(accountId, true); - - if (player != nullptr) { - Game::server->Disconnect(player->GetSystemAddress(), eServerDisconnectIdentifiers::FREE_TRIAL_EXPIRED); - } - - ChatPackets::SendSystemMessage(sysAddr, u"Banned: " + GeneralUtils::ASCIIToUTF16(args[0])); - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /ban <username>"); - } - } - - //------------------------------------------------- - - if (chatCommand == "buffme" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void BuffMe(Entity* entity, const SystemAddress& sysAddr, const std::string args) { auto dest = static_cast<DestroyableComponent*>(entity->GetComponent(eReplicaComponentType::DESTROYABLE)); if (dest) { dest->SetHealth(999); @@ -1143,8 +1848,11 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit Game::entityManager->SerializeEntity(entity); } - if (chatCommand == "startcelebration" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() == 1) { - const auto celebration = GeneralUtils::TryParse<int32_t>(args.at(0)); + void StartCelebration(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto celebration = GeneralUtils::TryParse<int32_t>(splitArgs.at(0)); if (!celebration) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid celebration."); @@ -1154,7 +1862,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit GameMessages::SendStartCelebrationEffect(entity, entity->GetSystemAddress(), celebration.value()); } - if (chatCommand == "buffmed" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void BuffMed(Entity* entity, const SystemAddress& sysAddr, const std::string args) { auto dest = static_cast<DestroyableComponent*>(entity->GetComponent(eReplicaComponentType::DESTROYABLE)); if (dest) { dest->SetHealth(9); @@ -1167,8 +1875,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit Game::entityManager->SerializeEntity(entity); } - if (chatCommand == "refillstats" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - + void RefillStats(Entity* entity, const SystemAddress& sysAddr, const std::string args) { auto dest = static_cast<DestroyableComponent*>(entity->GetComponent(eReplicaComponentType::DESTROYABLE)); if (dest) { dest->SetHealth(static_cast<int32_t>(dest->GetMaxHealth())); @@ -1179,17 +1886,11 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit Game::entityManager->SerializeEntity(entity); } - if (chatCommand == "lookup" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { + void Lookup(Entity* entity, const SystemAddress& sysAddr, const std::string args) { auto query = CDClientDatabase::CreatePreppedStmt( "SELECT `id`, `name` FROM `Objects` WHERE `displayName` LIKE ?1 OR `name` LIKE ?1 OR `description` LIKE ?1 LIMIT 50"); - // Concatenate all of the arguments into a single query so a multi word query can be used properly. - std::string conditional = args[0]; - args.erase(args.begin()); - for (auto& argument : args) { - conditional += ' ' + argument; - } - const std::string query_text = "%" + conditional + "%"; + const std::string query_text = "%" + args + "%"; query.bind(1, query_text.c_str()); auto tables = query.execQuery(); @@ -1201,11 +1902,14 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "spawn" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { + void Spawn(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + ControllablePhysicsComponent* comp = static_cast<ControllablePhysicsComponent*>(entity->GetComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS)); if (!comp) return; - const auto lot = GeneralUtils::TryParse<uint32_t>(args[0]); + const auto lot = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); if (!lot) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid lot."); @@ -1230,17 +1934,17 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit Game::entityManager->ConstructEntity(newEntity); } - if (chatCommand == "spawngroup" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 3) { - auto controllablePhysicsComponent = entity->GetComponent<ControllablePhysicsComponent>(); - if (!controllablePhysicsComponent) return; + void SpawnGroup(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 3) return; - const auto lot = GeneralUtils::TryParse<LOT>(args[0]); + const auto lot = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); if (!lot) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid lot."); return; } - const auto numberToSpawnOptional = GeneralUtils::TryParse<uint32_t>(args[1]); + const auto numberToSpawnOptional = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); if (!numberToSpawnOptional && numberToSpawnOptional.value() > 0) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid number of enemies to spawn."); return; @@ -1248,7 +1952,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit uint32_t numberToSpawn = numberToSpawnOptional.value(); // Must spawn within a radius of at least 0.0f - const auto radiusToSpawnWithinOptional = GeneralUtils::TryParse<float>(args[2]); + const auto radiusToSpawnWithinOptional = GeneralUtils::TryParse<float>(splitArgs[2]); if (!radiusToSpawnWithinOptional && radiusToSpawnWithinOptional.value() < 0.0f) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid radius to spawn within."); return; @@ -1261,7 +1965,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit info.spawnerID = entity->GetObjectID(); info.spawnerNodeID = 0; - auto playerPosition = controllablePhysicsComponent->GetPosition(); + auto playerPosition = entity->GetPosition(); while (numberToSpawn > 0) { auto randomAngle = GeneralUtils::GenerateRandomNumber<float>(0.0f, 2 * PI); auto randomRadius = GeneralUtils::GenerateRandomNumber<float>(0.0f, radiusToSpawnWithin); @@ -1282,8 +1986,11 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if ((chatCommand == "giveuscore") && args.size() >= 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - const auto uscoreOptional = GeneralUtils::TryParse<int32_t>(args[0]); + void GiveUScore(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto uscoreOptional = GeneralUtils::TryParse<int32_t>(splitArgs[0]); if (!uscoreOptional) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid uscore."); return; @@ -1296,19 +2003,22 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit eLootSourceType lootType = eLootSourceType::MODERATION; - if (args.size() >= 2) { - const auto type = GeneralUtils::TryParse<eLootSourceType>(args[1]); + if (splitArgs.size() >= 2) { + const auto type = GeneralUtils::TryParse<eLootSourceType>(splitArgs[1]); lootType = type.value_or(lootType); } GameMessages::SendModifyLEGOScore(entity, entity->GetSystemAddress(), uscore, lootType); } - if ((chatCommand == "setlevel") && args.size() >= 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void SetLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + // We may be trying to set a specific players level to a level. If so override the entity with the requested players. std::string requestedPlayerToSetLevelOf = ""; - if (args.size() > 1) { - requestedPlayerToSetLevelOf = args[1]; + if (splitArgs.size() > 1) { + requestedPlayerToSetLevelOf = splitArgs[1]; auto requestedPlayer = PlayerManager::GetPlayer(requestedPlayerToSetLevelOf); @@ -1324,7 +2034,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit entity = requestedPlayer->GetOwner(); } - const auto requestedLevelOptional = GeneralUtils::TryParse<uint32_t>(args[0]); + const auto requestedLevelOptional = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); uint32_t oldLevel; // first check the level is valid @@ -1370,10 +2080,9 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit u" and UScore to " + GeneralUtils::to_u16string(characterComponent->GetUScore()) + u". Relog to see changes."); } - return; } - if (chatCommand == "pos" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void Pos(Entity* entity, const SystemAddress& sysAddr, const std::string args) { const auto position = entity->GetPosition(); ChatPackets::SendSystemMessage(sysAddr, u"<" + (GeneralUtils::to_u16string(position.x)) + u", " + (GeneralUtils::to_u16string(position.y)) + u", " + (GeneralUtils::to_u16string(position.z)) + u">"); @@ -1381,7 +2090,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit LOG("Position: %f, %f, %f", position.x, position.y, position.z); } - if (chatCommand == "rot" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void Rot(Entity* entity, const SystemAddress& sysAddr, const std::string args) { const auto rotation = entity->GetRotation(); ChatPackets::SendSystemMessage(sysAddr, u"<" + (GeneralUtils::to_u16string(rotation.w)) + u", " + (GeneralUtils::to_u16string(rotation.x)) + u", " + (GeneralUtils::to_u16string(rotation.y)) + u", " + (GeneralUtils::to_u16string(rotation.z)) + u">"); @@ -1389,23 +2098,26 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit LOG("Rotation: %f, %f, %f, %f", rotation.w, rotation.x, rotation.y, rotation.z); } - if (chatCommand == "locrow" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void LocRow(Entity* entity, const SystemAddress& sysAddr, const std::string args) { const auto position = entity->GetPosition(); const auto rotation = entity->GetRotation(); LOG("<location x=\"%f\" y=\"%f\" z=\"%f\" rw=\"%f\" rx=\"%f\" ry=\"%f\" rz=\"%f\" />", position.x, position.y, position.z, rotation.w, rotation.x, rotation.y, rotation.z); } - if (chatCommand == "playlvlfx" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void PlayLvlFx(Entity* entity, const SystemAddress& sysAddr, const std::string args) { GameMessages::SendPlayFXEffect(entity, 7074, u"create", "7074", LWOOBJID_EMPTY, 1.0f, 1.0f, true); } - if (chatCommand == "playrebuildfx" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void PlayRebuildFx(Entity* entity, const SystemAddress& sysAddr, const std::string args) { GameMessages::SendPlayFXEffect(entity, 230, u"rebuild", "230", LWOOBJID_EMPTY, 1.0f, 1.0f, true); } - if ((chatCommand == "freemoney" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) && args.size() == 1) { - const auto money = GeneralUtils::TryParse<int64_t>(args[0]); + void FreeMoney(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto money = GeneralUtils::TryParse<int64_t>(splitArgs[0]); if (!money) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid money."); @@ -1416,8 +2128,11 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ch->SetCoins(ch->GetCoins() + money.value(), eLootSourceType::MODERATION); } - if ((chatCommand == "setcurrency") && args.size() == 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - const auto money = GeneralUtils::TryParse<int64_t>(args[0]); + void SetCurrency(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto money = GeneralUtils::TryParse<int64_t>(splitArgs[0]); if (!money) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid money."); @@ -1428,84 +2143,53 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ch->SetCoins(money.value(), eLootSourceType::MODERATION); } - // Allow for this on even while not a GM, as it sometimes toggles incorrrectly. - if (chatCommand == "gminvis" && entity->GetCharacter()->GetParentUser()->GetMaxGMLevel() >= eGameMasterLevel::DEVELOPER) { - GameMessages::SendToggleGMInvis(entity->GetObjectID(), true, UNASSIGNED_SYSTEM_ADDRESS); + void Buff(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 2) return; - return; - } - - if (chatCommand == "gmimmune" && args.size() >= 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); - - const auto state = GeneralUtils::TryParse<int32_t>(args[0]); - - if (!state) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid state."); - return; - } - - if (destroyableComponent) destroyableComponent->SetIsGMImmune(state.value()); - return; - } - - //Testing basic attack immunity - if (chatCommand == "attackimmune" && args.size() >= 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { - auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); - - const auto state = GeneralUtils::TryParse<int32_t>(args[0]); - - if (!state) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid state."); - return; - } - - if (destroyableComponent) destroyableComponent->SetIsImmune(state.value()); - return; - } - - if (chatCommand == "buff" && args.size() >= 2 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { auto* buffComponent = entity->GetComponent<BuffComponent>(); - const auto id = GeneralUtils::TryParse<int32_t>(args[0]); + const auto id = GeneralUtils::TryParse<int32_t>(splitArgs[0]); if (!id) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid buff id."); return; } - const auto duration = GeneralUtils::TryParse<int32_t>(args[1]); + const auto duration = GeneralUtils::TryParse<int32_t>(splitArgs[1]); if (!duration) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid buff duration."); return; } if (buffComponent) buffComponent->ApplyBuff(id.value(), duration.value(), entity->GetObjectID()); - return; } - if ((chatCommand == "testmap" && args.size() >= 1) && entity->GetGMLevel() >= eGameMasterLevel::FORUM_MODERATOR) { + void TestMap(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + ChatPackets::SendSystemMessage(sysAddr, u"Requesting map change..."); LWOCLONEID cloneId = 0; bool force = false; - const auto reqZoneOptional = GeneralUtils::TryParse<LWOMAPID>(args[0]); + const auto reqZoneOptional = GeneralUtils::TryParse<LWOMAPID>(splitArgs[0]); if (!reqZoneOptional) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid zone."); return; } const LWOMAPID reqZone = reqZoneOptional.value(); - if (args.size() > 1) { + if (splitArgs.size() > 1) { auto index = 1; - if (args[index] == "force") { + if (splitArgs[index] == "force") { index++; force = true; } - if (args.size() > index) { - const auto cloneIdOptional = GeneralUtils::TryParse<LWOCLONEID>(args[index]); + if (splitArgs.size() > index) { + const auto cloneIdOptional = GeneralUtils::TryParse<LWOCLONEID>(splitArgs[index]); if (!cloneIdOptional) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid clone id."); return; @@ -1542,40 +2226,41 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit }); } else { std::string msg = "ZoneID not found or allowed: "; - msg.append(args[0]); // FIXME: unnecessary utf16 re-encoding just for error + msg.append(splitArgs[0]); // FIXME: unnecessary utf16 re-encoding just for error ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::UTF8ToUTF16(msg, msg.size())); } } - if (chatCommand == "createprivate" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 3) { - const auto zone = GeneralUtils::TryParse<uint32_t>(args[0]); + void CreatePrivate(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 3) return; + + const auto zone = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); if (!zone) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid zone."); return; } - const auto clone = GeneralUtils::TryParse<uint32_t>(args[1]); + const auto clone = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); if (!clone) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid clone."); return; } - const auto& password = args[2]; + const auto& password = splitArgs[2]; ZoneInstanceManager::Instance()->CreatePrivateZone(Game::server, zone.value(), clone.value(), password); ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16("Sent request for private zone with password: " + password)); - - return; } - if ((chatCommand == "debugui") && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void DebugUi(Entity* entity, const SystemAddress& sysAddr, const std::string args) { ChatPackets::SendSystemMessage(sysAddr, u"Opening UIDebugger..."); - AMFArrayValue args; - GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, "ToggleUIDebugger;", args); } - if ((chatCommand == "boost") && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void Boost(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + auto* possessorComponent = entity->GetComponent<PossessorComponent>(); if (possessorComponent == nullptr) { @@ -1588,8 +2273,8 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit return; } - if (args.size() >= 1) { - const auto time = GeneralUtils::TryParse<float>(args[0]); + if (splitArgs.size() >= 1) { + const auto time = GeneralUtils::TryParse<float>(splitArgs[0]); if (!time) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid boost time."); @@ -1604,10 +2289,9 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } else { GameMessages::SendVehicleAddPassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); } - } - if ((chatCommand == "unboost") && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void Unboost(Entity* entity, const SystemAddress& sysAddr, const std::string args) { auto* possessorComponent = entity->GetComponent<PossessorComponent>(); if (possessorComponent == nullptr) return; @@ -1617,21 +2301,24 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit GameMessages::SendVehicleRemovePassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); } - if (chatCommand == "activatespawner" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { - auto spawners = Game::zoneManager->GetSpawnersByName(args[0]); + void ActivateSpawner(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + auto spawners = Game::zoneManager->GetSpawnersByName(splitArgs[0]); for (auto* spawner : spawners) { spawner->Activate(); } - spawners = Game::zoneManager->GetSpawnersInGroup(args[0]); + spawners = Game::zoneManager->GetSpawnersInGroup(splitArgs[0]); for (auto* spawner : spawners) { spawner->Activate(); } } - if (chatCommand == "spawnphysicsverts" && entity->GetGMLevel() >= eGameMasterLevel::JUNIOR_DEVELOPER) { + void SpawnPhysicsVerts(Entity* entity, const SystemAddress& sysAddr, const std::string args) { //Go tell physics to spawn all the vertices: auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::PHANTOM_PHYSICS); for (auto en : entities) { @@ -1641,7 +2328,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "reportproxphys" && entity->GetGMLevel() >= eGameMasterLevel::JUNIOR_DEVELOPER) { + void ReportProxPhys(Entity* entity, const SystemAddress& sysAddr, const std::string args) { auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::PROXIMITY_MONITOR); for (auto en : entities) { auto phys = static_cast<ProximityMonitorComponent*>(en->GetComponent(eReplicaComponentType::PROXIMITY_MONITOR)); @@ -1657,25 +2344,31 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "triggerspawner" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { - auto spawners = Game::zoneManager->GetSpawnersByName(args[0]); + void TriggerSpawner(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + auto spawners = Game::zoneManager->GetSpawnersByName(splitArgs[0]); for (auto* spawner : spawners) { spawner->Spawn(); } - spawners = Game::zoneManager->GetSpawnersInGroup(args[0]); + spawners = Game::zoneManager->GetSpawnersInGroup(splitArgs[0]); for (auto* spawner : spawners) { spawner->Spawn(); } } - if (chatCommand == "reforge" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 2) { - const auto baseItem = GeneralUtils::TryParse<LOT>(args[0]); + void Reforge(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 2) return; + + const auto baseItem = GeneralUtils::TryParse<LOT>(splitArgs[0]); if (!baseItem) return; - const auto reforgedItem = GeneralUtils::TryParse<LOT>(args[1]); + const auto reforgedItem = GeneralUtils::TryParse<LOT>(splitArgs[1]); if (!reforgedItem) return; auto* inventoryComponent = entity->GetComponent<InventoryComponent>(); @@ -1687,16 +2380,14 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit inventoryComponent->AddItem(baseItem.value(), 1, eLootSourceType::MODERATION, eInventoryType::INVALID, data); } - if (chatCommand == "crash" && entity->GetGMLevel() >= eGameMasterLevel::OPERATOR) { + void Crash(Entity* entity, const SystemAddress& sysAddr, const std::string args) { ChatPackets::SendSystemMessage(sysAddr, u"Crashing..."); int* badPtr = nullptr; *badPtr = 0; - - return; } - if (chatCommand == "metrics" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void Metrics(Entity* entity, const SystemAddress& sysAddr, const std::string args) { for (const auto variable : Metrics::GetAllMetrics()) { auto* metric = Metrics::GetMetric(variable); @@ -1729,11 +2420,9 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit sysAddr, u"Process ID: " + GeneralUtils::to_u16string(Metrics::GetProcessID()) ); - - return; } - if (chatCommand == "reloadconfig" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void ReloadConfig(Entity* entity, const SystemAddress& sysAddr, const std::string args) { Game::config->ReloadConfig(); VanityUtilities::SpawnVanity(); dpWorld::Reload(); @@ -1749,14 +2438,17 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ChatPackets::SendSystemMessage(sysAddr, u"Successfully reloaded config for world!"); } - if (chatCommand == "rollloot" && entity->GetGMLevel() >= eGameMasterLevel::OPERATOR && args.size() >= 3) { - const auto lootMatrixIndex = GeneralUtils::TryParse<uint32_t>(args[0]); + void RollLoot(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 3) return; + + const auto lootMatrixIndex = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); if (!lootMatrixIndex) return; - const auto targetLot = GeneralUtils::TryParse<uint32_t>(args[1]); + const auto targetLot = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); if (!targetLot) return; - const auto loops = GeneralUtils::TryParse<uint32_t>(args[2]); + const auto loops = GeneralUtils::TryParse<uint32_t>(splitArgs[2]); if (!loops) return; uint64_t totalRuns = 0; @@ -1787,16 +2479,19 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ChatPackets::SendSystemMessage(sysAddr, message); } - if (chatCommand == "deleteinven" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { + void DeleteInven(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + eInventoryType inventoryType = eInventoryType::INVALID; - const auto inventoryTypeOptional = GeneralUtils::TryParse<eInventoryType>(args[0]); + const auto inventoryTypeOptional = GeneralUtils::TryParse<eInventoryType>(splitArgs[0]); if (!inventoryTypeOptional) { // In this case, we treat the input as a string and try to find it in the reflection list - std::transform(args[0].begin(), args[0].end(), args[0].begin(), ::toupper); - LOG("looking for inventory %s", args[0].c_str()); + std::transform(splitArgs[0].begin(), splitArgs[0].end(), splitArgs[0].begin(), ::toupper); + LOG("looking for inventory %s", splitArgs[0].c_str()); for (uint32_t index = 0; index < NUMBER_OF_INVENTORIES; index++) { - if (std::string_view(args[0]) == std::string_view(InventoryType::InventoryTypeToString(static_cast<eInventoryType>(index)))) inventoryType = static_cast<eInventoryType>(index); + if (std::string_view(splitArgs[0]) == std::string_view(InventoryType::InventoryTypeToString(static_cast<eInventoryType>(index)))) inventoryType = static_cast<eInventoryType>(index); } } else { inventoryType = inventoryTypeOptional.value(); @@ -1814,14 +2509,17 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit if (!inventoryToDelete) return; inventoryToDelete->DeleteAllItems(); - LOG("Deleted inventory %s for user %llu", args[0].c_str(), entity->GetObjectID()); - ChatPackets::SendSystemMessage(sysAddr, u"Deleted inventory " + GeneralUtils::UTF8ToUTF16(args[0])); + LOG("Deleted inventory %s for user %llu", splitArgs[0].c_str(), entity->GetObjectID()); + ChatPackets::SendSystemMessage(sysAddr, u"Deleted inventory " + GeneralUtils::UTF8ToUTF16(splitArgs[0])); } - if (chatCommand == "castskill" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { + void CastSkill(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + auto* skillComponent = entity->GetComponent<SkillComponent>(); if (skillComponent) { - const auto skillId = GeneralUtils::TryParse<uint32_t>(args[0]); + const auto skillId = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); if (!skillId) { ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill ID."); @@ -1833,15 +2531,18 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "setskillslot" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 2) { + void SetSkillSlot(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 2) return; + auto* const inventoryComponent = entity->GetComponent<InventoryComponent>(); if (inventoryComponent) { - const auto slot = GeneralUtils::TryParse<BehaviorSlot>(args[0]); + const auto slot = GeneralUtils::TryParse<BehaviorSlot>(splitArgs[0]); if (!slot) { ChatPackets::SendSystemMessage(sysAddr, u"Error getting slot."); return; } else { - const auto skillId = GeneralUtils::TryParse<uint32_t>(args[1]); + const auto skillId = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); if (!skillId) { ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill."); return; @@ -1853,10 +2554,13 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "setfaction" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { + void SetFaction(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); if (destroyableComponent) { - const auto faction = GeneralUtils::TryParse<int32_t>(args[0]); + const auto faction = GeneralUtils::TryParse<int32_t>(splitArgs[0]); if (!faction) { ChatPackets::SendSystemMessage(sysAddr, u"Error getting faction."); @@ -1868,10 +2572,13 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "addfaction" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { + void AddFaction(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); if (destroyableComponent) { - const auto faction = GeneralUtils::TryParse<int32_t>(args[0]); + const auto faction = GeneralUtils::TryParse<int32_t>(splitArgs[0]); if (!faction) { ChatPackets::SendSystemMessage(sysAddr, u"Error getting faction."); @@ -1883,7 +2590,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "getfactions" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { + void GetFactions(Entity* entity, const SystemAddress& sysAddr, const std::string args) { auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); if (destroyableComponent) { ChatPackets::SendSystemMessage(sysAddr, u"Friendly factions:"); @@ -1898,25 +2605,33 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } - if (chatCommand == "setrewardcode" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() == 1) { + void SetRewardCode(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto* character = entity->GetCharacter(); + if (!character) return; + auto* user = character->GetParentUser(); + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; auto* cdrewardCodes = CDClientManager::GetTable<CDRewardCodesTable>(); - auto id = cdrewardCodes->GetCodeID(args[0]); + auto id = cdrewardCodes->GetCodeID(splitArgs[0]); if (id != -1) Database::Get()->InsertRewardCode(user->GetAccountID(), id); } - if (chatCommand == "inspect" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { + void Inspect(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + Entity* closest = nullptr; std::u16string ldf; bool isLDF = false; - auto component = GeneralUtils::TryParse<eReplicaComponentType>(args[0]); + auto component = GeneralUtils::TryParse<eReplicaComponentType>(splitArgs[0]); if (!component) { component = eReplicaComponentType::INVALID; - ldf = GeneralUtils::UTF8ToUTF16(args[0]); + ldf = GeneralUtils::UTF8ToUTF16(splitArgs[0]); isLDF = true; } @@ -1977,11 +2692,11 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(stream.str())); } - if (args.size() >= 2) { - if (args[1] == "-m" && args.size() >= 3) { + if (splitArgs.size() >= 2) { + if (splitArgs[1] == "-m" && splitArgs.size() >= 3) { auto* const movingPlatformComponent = closest->GetComponent<MovingPlatformComponent>(); - const auto mValue = GeneralUtils::TryParse<int32_t>(args[2]); + const auto mValue = GeneralUtils::TryParse<int32_t>(splitArgs[2]); if (!movingPlatformComponent || !mValue) return; @@ -1994,23 +2709,23 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } Game::entityManager->SerializeEntity(closest); - } else if (args[1] == "-a" && args.size() >= 3) { - RenderComponent::PlayAnimation(closest, args.at(2)); - } else if (args[1] == "-s") { + } else if (splitArgs[1] == "-a" && splitArgs.size() >= 3) { + RenderComponent::PlayAnimation(closest, splitArgs.at(2)); + } else if (splitArgs[1] == "-s") { for (auto* entry : closest->GetSettings()) { ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::UTF8ToUTF16(entry->GetString())); } ChatPackets::SendSystemMessage(sysAddr, u"------"); ChatPackets::SendSystemMessage(sysAddr, u"Spawner ID: " + GeneralUtils::to_u16string(closest->GetSpawnerID())); - } else if (args[1] == "-p") { + } else if (splitArgs[1] == "-p") { const auto postion = closest->GetPosition(); ChatPackets::SendSystemMessage( sysAddr, GeneralUtils::ASCIIToUTF16("< " + std::to_string(postion.x) + ", " + std::to_string(postion.y) + ", " + std::to_string(postion.z) + " >") ); - } else if (args[1] == "-f") { + } else if (splitArgs[1] == "-f") { auto* destuctable = closest->GetComponent<DestroyableComponent>(); if (destuctable == nullptr) { @@ -2030,14 +2745,14 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry))); } - if (args.size() >= 3) { - const auto faction = GeneralUtils::TryParse<int32_t>(args[2]); + if (splitArgs.size() >= 3) { + const auto faction = GeneralUtils::TryParse<int32_t>(splitArgs[2]); if (!faction) return; destuctable->SetFaction(-1); destuctable->AddFaction(faction.value(), true); } - } else if (args[1] == "-cf") { + } else if (splitArgs[1] == "-cf") { auto* destuctable = entity->GetComponent<DestroyableComponent>(); if (!destuctable) { ChatPackets::SendSystemMessage(sysAddr, u"No destroyable component on this entity!"); @@ -2045,7 +2760,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } if (destuctable->IsEnemy(closest)) ChatPackets::SendSystemMessage(sysAddr, u"They are our enemy"); else ChatPackets::SendSystemMessage(sysAddr, u"They are NOT our enemy"); - } else if (args[1] == "-t") { + } else if (splitArgs[1] == "-t") { auto* phantomPhysicsComponent = closest->GetComponent<PhantomPhysicsComponent>(); if (phantomPhysicsComponent != nullptr) { @@ -2066,6 +2781,276 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } } } +}; + +namespace GMGreaterThanZeroCommands { + + void Kick(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() == 1) { + auto* player = PlayerManager::GetPlayer(splitArgs[0]); + + std::u16string username = GeneralUtils::UTF8ToUTF16(splitArgs[0]); + if (player == nullptr) { + ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + username); + return; + } + + Game::server->Disconnect(player->GetSystemAddress(), eServerDisconnectIdentifiers::KICK); + + ChatPackets::SendSystemMessage(sysAddr, u"Kicked: " + username); + } else { + ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /kick <username>"); + } + } + + void Ban(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + + if (splitArgs.size() == 1) { + auto* player = PlayerManager::GetPlayer(splitArgs[0]); + + uint32_t accountId = 0; + + if (player == nullptr) { + auto characterInfo = Database::Get()->GetCharacterInfo(splitArgs[0]); + + if (characterInfo) { + accountId = characterInfo->accountId; + } + + if (accountId == 0) { + ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + GeneralUtils::UTF8ToUTF16(splitArgs[0])); + + return; + } + } else { + auto* character = player->GetCharacter(); + auto* user = character != nullptr ? character->GetParentUser() : nullptr; + if (user) accountId = user->GetAccountID(); + } + + if (accountId != 0) Database::Get()->UpdateAccountBan(accountId, true); + + if (player != nullptr) { + Game::server->Disconnect(player->GetSystemAddress(), eServerDisconnectIdentifiers::FREE_TRIAL_EXPIRED); + } + + ChatPackets::SendSystemMessage(sysAddr, u"Banned: " + GeneralUtils::ASCIIToUTF16(splitArgs[0])); + } else { + ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /ban <username>"); + } + } + + void MailItem(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 2) return; + + const auto& playerName = splitArgs[0]; + + auto playerInfo = Database::Get()->GetCharacterInfo(playerName); + + uint32_t receiverID = 0; + if (!playerInfo) { + ChatPackets::SendSystemMessage(sysAddr, u"Failed to find that player"); + + return; + } + + receiverID = playerInfo->id; + + const auto lot = GeneralUtils::TryParse<LOT>(splitArgs.at(1)); + + if (!lot) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid item lot."); + return; + } + + IMail::MailInfo mailInsert; + mailInsert.senderId = entity->GetObjectID(); + mailInsert.senderUsername = "Darkflame Universe"; + mailInsert.receiverId = receiverID; + mailInsert.recipient = playerName; + mailInsert.subject = "Lost item"; + mailInsert.body = "This is a replacement item for one you lost."; + mailInsert.itemID = LWOOBJID_EMPTY; + mailInsert.itemLOT = lot.value(); + mailInsert.itemSubkey = LWOOBJID_EMPTY; + mailInsert.itemCount = 1; + Database::Get()->InsertNewMail(mailInsert); + + ChatPackets::SendSystemMessage(sysAddr, u"Mail sent"); + } + + void ApproveProperty(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (PropertyManagementComponent::Instance() != nullptr) { + PropertyManagementComponent::Instance()->UpdateApprovedStatus(true); + } + } + + void Mute(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + + if (splitArgs.size() >= 1) { + auto* player = PlayerManager::GetPlayer(splitArgs[0]); + + uint32_t accountId = 0; + LWOOBJID characterId = 0; + + if (player == nullptr) { + auto characterInfo = Database::Get()->GetCharacterInfo(splitArgs[0]); + + if (characterInfo) { + accountId = characterInfo->accountId; + characterId = characterInfo->id; + + GeneralUtils::SetBit(characterId, eObjectBits::CHARACTER); + GeneralUtils::SetBit(characterId, eObjectBits::PERSISTENT); + } + + if (accountId == 0) { + ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + GeneralUtils::UTF8ToUTF16(splitArgs[0])); + + return; + } + } else { + auto* character = player->GetCharacter(); + auto* user = character != nullptr ? character->GetParentUser() : nullptr; + if (user) accountId = user->GetAccountID(); + characterId = player->GetObjectID(); + } + + time_t expire = 1; // Default to indefinate mute + + if (splitArgs.size() >= 2) { + const auto days = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); + if (!days) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid days."); + + return; + } + + std::optional<uint32_t> hours; + if (splitArgs.size() >= 3) { + hours = GeneralUtils::TryParse<uint32_t>(splitArgs[2]); + if (!hours) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid hours."); + + return; + } + } + + expire = time(NULL); + expire += 24 * 60 * 60 * days.value(); + expire += 60 * 60 * hours.value_or(0); + } + + if (accountId != 0) Database::Get()->UpdateAccountUnmuteTime(accountId, expire); + + char buffer[32] = "brought up for review.\0"; + + if (expire != 1) { + std::tm* ptm = std::localtime(&expire); + // Format: Mo, 15.06.2009 20:20:00 + std::strftime(buffer, 32, "%a, %d.%m.%Y %H:%M:%S", ptm); + } + + const auto timeStr = GeneralUtils::ASCIIToUTF16(std::string(buffer)); + + ChatPackets::SendSystemMessage(sysAddr, u"Muted: " + GeneralUtils::UTF8ToUTF16(splitArgs[0]) + u" until " + timeStr); + + //Notify chat about it + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::GM_MUTE); + + bitStream.Write(characterId); + bitStream.Write(expire); + + Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); + } else { + ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /mute <username> <days (optional)> <hours (optional)>"); + } + } + + void Fly(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + auto* character = entity->GetCharacter(); + + if (character) { + bool isFlying = character->GetIsFlying(); + + if (isFlying) { + GameMessages::SendSetJetPackMode(entity, false); + + character->SetIsFlying(false); + } else { + float speedScale = 1.0f; + + if (splitArgs.size() >= 1) { + const auto tempScaleStore = GeneralUtils::TryParse<float>(splitArgs.at(0)); + + if (tempScaleStore) { + speedScale = tempScaleStore.value(); + } else { + ChatPackets::SendSystemMessage(sysAddr, u"Failed to parse speed scale argument."); + } + } + + float airSpeed = 20 * speedScale; + float maxAirSpeed = 30 * speedScale; + float verticalVelocity = 1.5 * speedScale; + + GameMessages::SendSetJetPackMode(entity, true, true, false, 167, airSpeed, maxAirSpeed, verticalVelocity); + + character->SetIsFlying(true); + } + } + } + + void AttackImmune(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); + + const auto state = GeneralUtils::TryParse<int32_t>(splitArgs[0]); + + if (!state) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid state."); + return; + } + + if (destroyableComponent) destroyableComponent->SetIsImmune(state.value()); + } + + void GmImmune(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); + + const auto state = GeneralUtils::TryParse<int32_t>(splitArgs[0]); + + if (!state) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid state."); + return; + } + + if (destroyableComponent) destroyableComponent->SetIsGMImmune(state.value()); + } + + void GmInvis(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + GameMessages::SendToggleGMInvis(entity->GetObjectID(), true, UNASSIGNED_SYSTEM_ADDRESS); + } + + void SetName(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(args), UNASSIGNED_SYSTEM_ADDRESS); + } + + void Title(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + std::string name = entity->GetCharacter()->GetName() + " - " + args; + GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(name), UNASSIGNED_SYSTEM_ADDRESS); + } } void SlashCommandHandler::SendAnnouncement(const std::string& title, const std::string& message) { diff --git a/dGame/dUtilities/SlashCommandHandler.h b/dGame/dUtilities/SlashCommandHandler.h index 85b7c697..e986487b 100644 --- a/dGame/dUtilities/SlashCommandHandler.h +++ b/dGame/dUtilities/SlashCommandHandler.h @@ -7,13 +7,128 @@ #define SLASHCOMMANDHANDLER_H #include "RakNetTypes.h" +#include "eGameMasterLevel.h" #include <string> class Entity; -namespace SlashCommandHandler { - void HandleChatCommand(const std::u16string& command, Entity* entity, const SystemAddress& sysAddr); - void SendAnnouncement(const std::string& title, const std::string& message); +struct Command { + std::string help; + std::string info; + std::vector<std::string> aliases; + std::function<void(Entity*, const SystemAddress&,const std::string)> handle; + eGameMasterLevel requiredLevel = eGameMasterLevel::OPERATOR; }; +namespace SlashCommandHandler { + void Startup(); + void HandleChatCommand(const std::u16string& command, Entity* entity, const SystemAddress& sysAddr); + void SendAnnouncement(const std::string& title, const std::string& message); + void RegisterCommand(Command info); +}; + +namespace DEVGMCommands { + void SetGMLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ToggleNameplate(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ToggleSkipCinematics(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Kill(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Metrics(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Announce(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetAnnTitle(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetAnnMsg(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ShutdownUniverse(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetMinifig(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void TestMap(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ReportProxPhys(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SpawnPhysicsVerts(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Teleport(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ActivateSpawner(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void AddMission(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Boost(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Unboost(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Buff(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void BuffMe(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void BuffMed(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ClearFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void CompleteMission(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void CreatePrivate(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void DebugUi(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Dismount(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ReloadConfig(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ForceSave(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Freecam(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void FreeMoney(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void GetNavmeshHeight(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void GiveUScore(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void GmAddItem(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Inspect(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ListSpawns(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void LocRow(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Lookup(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void PlayAnimation(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void PlayEffect(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void PlayLvlFx(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void PlayRebuildFx(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Pos(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void RefillStats(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Reforge(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ResetMission(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Rot(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void RunMacro(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetControlScheme(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetCurrency(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetInventorySize(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetUiState(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Spawn(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SpawnGroup(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SpeedBoost(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void StartCelebration(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void StopEffect(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Toggle(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void TpAll(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void TriggerSpawner(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void UnlockEmote(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetSkillSlot(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetFaction(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void AddFaction(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void GetFactions(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetRewardCode(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Crash(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void RollLoot(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void CastSkill(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void DeleteInven(Entity* entity, const SystemAddress& sysAddr, const std::string args); +} + +namespace GMZeroCommands { + void Help(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Credits(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Info(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Die(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Ping(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Pvp(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void RequestMailCount(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Who(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void FixStats(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Join(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void LeaveZone(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Resurrect(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void InstanceInfo(Entity* entity, const SystemAddress& sysAddr, const std::string args); +} + +namespace GMGreaterThanZeroCommands { + void Kick(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void MailItem(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Ban(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ApproveProperty(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Mute(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Fly(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void AttackImmune(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void GmImmune(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void GmInvis(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetName(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Title(Entity* entity, const SystemAddress& sysAddr, const std::string args); +} + #endif // SLASHCOMMANDHANDLER_H diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 1c85ef22..9f6d63bc 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -79,6 +79,7 @@ #include "PositionUpdate.h" #include "PlayerManager.h" #include "eLoginResponse.h" +#include "SlashCommandHandler.h" namespace Game { Logger* logger = nullptr; @@ -313,6 +314,9 @@ int main(int argc, char** argv) { uint32_t sqlPingTime = 10 * 60 * currentFramerate; // 10 minutes in frames uint32_t emptyShutdownTime = (cloneID == 0 ? 30 : 5) * 60 * currentFramerate; // 30 minutes for main worlds, 5 for all others. + // Register slash commands + SlashCommandHandler::Startup(); + Game::logger->Flush(); // once immediately before the main loop while (true) { Metrics::StartMeasurement(MetricVariable::Frame); diff --git a/tests/dCommonTests/dEnumsTests/MagicEnumTests.cpp b/tests/dCommonTests/dEnumsTests/MagicEnumTests.cpp index 0ca2e2ea..c47eb489 100644 --- a/tests/dCommonTests/dEnumsTests/MagicEnumTests.cpp +++ b/tests/dCommonTests/dEnumsTests/MagicEnumTests.cpp @@ -121,7 +121,7 @@ TEST(MagicEnumTest, eGameMessageTypeTest) { namespace { template <typename T> void AssertEnumArraySorted(const T& eArray) { - for (int i = 0; i < eArray->size(); ++i) { + for (int i = 0; i < eArray->size() - 1; ++i) { const auto entryCurr = eArray->at(i).first; LOG_EARRAY(eArray, i, entryCurr); const auto entryNext = eArray->at(++i).first; From 3260a063cb46d2ad24102c225fa3c138046ac82e Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 8 Apr 2024 13:13:19 -0700 Subject: [PATCH 07/78] ignore whitespace in try parse (#1536) --- dCommon/GeneralUtils.h | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/dCommon/GeneralUtils.h b/dCommon/GeneralUtils.h index d502f55a..f59ec71f 100644 --- a/dCommon/GeneralUtils.h +++ b/dCommon/GeneralUtils.h @@ -156,8 +156,11 @@ namespace GeneralUtils { * @returns An std::optional containing the desired value if it is equivalent to the string */ template <Numeric T> - [[nodiscard]] std::optional<T> TryParse(const std::string_view str) { + [[nodiscard]] std::optional<T> TryParse(std::string_view str) { numeric_parse_t<T> result; + if (!str.empty()) { + while (std::isspace(str.front())) str.remove_prefix(1); + } const char* const strEnd = str.data() + str.size(); const auto [parseEnd, ec] = std::from_chars(str.data(), strEnd, result); @@ -181,8 +184,12 @@ namespace GeneralUtils { * @returns An std::optional containing the desired value if it is equivalent to the string */ template <std::floating_point T> - [[nodiscard]] std::optional<T> TryParse(const std::string_view str) noexcept + [[nodiscard]] std::optional<T> TryParse(std::string_view str) noexcept try { + if (!str.empty()) { + while (std::isspace(str.front())) str.remove_prefix(1); + } + size_t parseNum; const T result = details::_parse<T>(str, parseNum); const bool isParsed = str.length() == parseNum; From be0a2f6f14e5acbd8a30bcbcf70de8b2dbb2c057 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 8 Apr 2024 13:13:31 -0700 Subject: [PATCH 08/78] fix jittering (#1537) --- dGame/Entity.cpp | 4 +--- dGame/dComponents/SkillComponent.cpp | 22 +++++++++++++++++----- dGame/dComponents/SkillComponent.h | 4 ++-- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 90763b4c..97e86758 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -1635,10 +1635,8 @@ void Entity::PickupItem(const LWOOBJID& objectID) { CDObjectSkillsTable* skillsTable = CDClientManager::GetTable<CDObjectSkillsTable>(); std::vector<CDObjectSkills> skills = skillsTable->Query([=](CDObjectSkills entry) {return (entry.objectTemplate == p.second.lot); }); for (CDObjectSkills skill : skills) { - CDSkillBehaviorTable* skillBehTable = CDClientManager::GetTable<CDSkillBehaviorTable>(); - auto* skillComponent = GetComponent<SkillComponent>(); - if (skillComponent) skillComponent->CastSkill(skill.skillID, GetObjectID(), GetObjectID()); + if (skillComponent) skillComponent->CastSkill(skill.skillID, GetObjectID(), GetObjectID(), skill.castOnType, NiQuaternion(0, 0, 0, 0)); auto* missionComponent = GetComponent<MissionComponent>(); diff --git a/dGame/dComponents/SkillComponent.cpp b/dGame/dComponents/SkillComponent.cpp index 2e6edacd..8460032c 100644 --- a/dGame/dComponents/SkillComponent.cpp +++ b/dGame/dComponents/SkillComponent.cpp @@ -227,7 +227,7 @@ void SkillComponent::RegisterCalculatedProjectile(const LWOOBJID projectileId, B this->m_managedProjectiles.push_back(entry); } -bool SkillComponent::CastSkill(const uint32_t skillId, LWOOBJID target, const LWOOBJID optionalOriginatorID) { +bool SkillComponent::CastSkill(const uint32_t skillId, LWOOBJID target, const LWOOBJID optionalOriginatorID, const int32_t castType, const NiQuaternion rotationOverride) { uint32_t behaviorId = -1; // try to find it via the cache const auto& pair = m_skillBehaviorCache.find(skillId); @@ -247,11 +247,19 @@ bool SkillComponent::CastSkill(const uint32_t skillId, LWOOBJID target, const LW return false; } - return CalculateBehavior(skillId, behaviorId, target, false, false, optionalOriginatorID).success; + return CalculateBehavior(skillId, behaviorId, target, false, false, optionalOriginatorID, castType, rotationOverride).success; } -SkillExecutionResult SkillComponent::CalculateBehavior(const uint32_t skillId, const uint32_t behaviorId, const LWOOBJID target, const bool ignoreTarget, const bool clientInitalized, const LWOOBJID originatorOverride) { +SkillExecutionResult SkillComponent::CalculateBehavior( + const uint32_t skillId, + const uint32_t behaviorId, + const LWOOBJID target, + const bool ignoreTarget, + const bool clientInitalized, + const LWOOBJID originatorOverride, + const int32_t castType, + const NiQuaternion rotationOverride) { RakNet::BitStream bitStream{}; auto* behavior = Behavior::CreateBehavior(behaviorId); @@ -283,7 +291,7 @@ SkillExecutionResult SkillComponent::CalculateBehavior(const uint32_t skillId, c // Echo start skill EchoStartSkill start; - start.iCastType = 0; + start.iCastType = castType; start.skillID = skillId; start.uiSkillHandle = context->skillUId; start.optionalOriginatorID = context->originator; @@ -294,6 +302,10 @@ SkillExecutionResult SkillComponent::CalculateBehavior(const uint32_t skillId, c if (originator != nullptr) { start.originatorRot = originator->GetRotation(); } + + if (rotationOverride != NiQuaternionConstant::IDENTITY) { + start.originatorRot = rotationOverride; + } //start.optionalTargetID = target; start.sBitStream.assign(reinterpret_cast<char*>(bitStream.GetData()), bitStream.GetNumberOfBytesUsed()); @@ -464,7 +476,7 @@ void SkillComponent::HandleUnCast(const uint32_t behaviorId, const LWOOBJID targ behavior->UnCast(&context, { target }); } -SkillComponent::SkillComponent(Entity* parent): Component(parent) { +SkillComponent::SkillComponent(Entity* parent) : Component(parent) { this->m_skillUid = 0; } diff --git a/dGame/dComponents/SkillComponent.h b/dGame/dComponents/SkillComponent.h index 2acae5d7..24d92148 100644 --- a/dGame/dComponents/SkillComponent.h +++ b/dGame/dComponents/SkillComponent.h @@ -127,7 +127,7 @@ public: * @param optionalOriginatorID change the originator of the skill * @return if the case succeeded */ - bool CastSkill(const uint32_t skillId, LWOOBJID target = LWOOBJID_EMPTY, const LWOOBJID optionalOriginatorID = LWOOBJID_EMPTY); + bool CastSkill(const uint32_t skillId, LWOOBJID target = LWOOBJID_EMPTY, const LWOOBJID optionalOriginatorID = LWOOBJID_EMPTY, const int32_t castType = 0, const NiQuaternion rotationOverride = NiQuaternionConstant::IDENTITY); /** * Initializes a server-side skill calculation. @@ -139,7 +139,7 @@ public: * @param originatorOverride an override for the originator of the skill calculation * @return the result of the skill calculation */ - SkillExecutionResult CalculateBehavior(uint32_t skillId, uint32_t behaviorId, LWOOBJID target, bool ignoreTarget = false, bool clientInitalized = false, LWOOBJID originatorOverride = LWOOBJID_EMPTY); + SkillExecutionResult CalculateBehavior(uint32_t skillId, uint32_t behaviorId, LWOOBJID target, bool ignoreTarget = false, bool clientInitalized = false, LWOOBJID originatorOverride = LWOOBJID_EMPTY, const int32_t castType = 0, const NiQuaternion rotationOverride = NiQuaternionConstant::IDENTITY); /** * Register a server-side projectile. From 28ce8ac54d00a36dfd53e086792b62e8a4f46d92 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 8 Apr 2024 13:13:49 -0700 Subject: [PATCH 09/78] remove usage of xmldoc as a ptr (#1538) resolves a memory leak in BrickDatabase, adds stability to character save doc. Tested that saving manually via force-save, logout and /crash all saved my position and my removed banana as expected. The doc was always deleted on character destruction and on any updates, so this is just a semantic change (and now we no longer have new'd tinyxml2::documents on the heap) --- dGame/Character.cpp | 52 +++++++------------ dGame/Character.h | 4 +- dGame/Entity.cpp | 5 +- dGame/Entity.h | 2 +- dGame/dComponents/BuffComponent.cpp | 12 ++--- dGame/dComponents/BuffComponent.h | 4 +- dGame/dComponents/CharacterComponent.cpp | 14 ++--- dGame/dComponents/CharacterComponent.h | 4 +- dGame/dComponents/Component.cpp | 4 +- dGame/dComponents/Component.h | 4 +- .../ControllablePhysicsComponent.cpp | 8 +-- .../ControllablePhysicsComponent.h | 4 +- dGame/dComponents/DestroyableComponent.cpp | 8 +-- dGame/dComponents/DestroyableComponent.h | 4 +- dGame/dComponents/InventoryComponent.cpp | 35 +++++++------ dGame/dComponents/InventoryComponent.h | 10 ++-- .../dComponents/LevelProgressionComponent.cpp | 8 +-- dGame/dComponents/LevelProgressionComponent.h | 4 +- dGame/dComponents/MissionComponent.cpp | 28 +++++----- dGame/dComponents/MissionComponent.h | 4 +- dGame/dMission/Mission.cpp | 38 +++++++------- dGame/dMission/Mission.h | 4 +- dGame/dUtilities/BrickDatabase.cpp | 9 ++-- 23 files changed, 125 insertions(+), 144 deletions(-) diff --git a/dGame/Character.cpp b/dGame/Character.cpp index eab7583f..59a67462 100644 --- a/dGame/Character.cpp +++ b/dGame/Character.cpp @@ -27,12 +27,9 @@ Character::Character(uint32_t id, User* parentUser) { m_ID = id; m_ParentUser = parentUser; m_OurEntity = nullptr; - m_Doc = nullptr; } Character::~Character() { - if (m_Doc) delete m_Doc; - m_Doc = nullptr; m_OurEntity = nullptr; m_ParentUser = nullptr; } @@ -55,8 +52,6 @@ void Character::UpdateInfoFromDatabase() { m_ZoneInstanceID = 0; //These values don't really matter, these are only used on the char select screen and seem unused. m_ZoneCloneID = 0; - m_Doc = nullptr; - //Quickly and dirtly parse the xmlData to get the info we need: DoQuickXMLDataParse(); @@ -70,18 +65,13 @@ void Character::UpdateInfoFromDatabase() { } void Character::UpdateFromDatabase() { - if (m_Doc) delete m_Doc; UpdateInfoFromDatabase(); } void Character::DoQuickXMLDataParse() { if (m_XMLData.size() == 0) return; - delete m_Doc; - m_Doc = new tinyxml2::XMLDocument(); - if (!m_Doc) return; - - if (m_Doc->Parse(m_XMLData.c_str(), m_XMLData.size()) == 0) { + if (m_Doc.Parse(m_XMLData.c_str(), m_XMLData.size()) == 0) { LOG("Loaded xmlData for character %s (%i)!", m_Name.c_str(), m_ID); } else { LOG("Failed to load xmlData!"); @@ -89,7 +79,7 @@ void Character::DoQuickXMLDataParse() { return; } - tinyxml2::XMLElement* mf = m_Doc->FirstChildElement("obj")->FirstChildElement("mf"); + tinyxml2::XMLElement* mf = m_Doc.FirstChildElement("obj")->FirstChildElement("mf"); if (!mf) { LOG("Failed to find mf tag!"); return; @@ -108,7 +98,7 @@ void Character::DoQuickXMLDataParse() { mf->QueryAttribute("ess", &m_Eyes); mf->QueryAttribute("ms", &m_Mouth); - tinyxml2::XMLElement* inv = m_Doc->FirstChildElement("obj")->FirstChildElement("inv"); + tinyxml2::XMLElement* inv = m_Doc.FirstChildElement("obj")->FirstChildElement("inv"); if (!inv) { LOG("Char has no inv!"); return; @@ -141,7 +131,7 @@ void Character::DoQuickXMLDataParse() { } - tinyxml2::XMLElement* character = m_Doc->FirstChildElement("obj")->FirstChildElement("char"); + tinyxml2::XMLElement* character = m_Doc.FirstChildElement("obj")->FirstChildElement("char"); if (character) { character->QueryAttribute("cc", &m_Coins); int32_t gm_level = 0; @@ -205,7 +195,7 @@ void Character::DoQuickXMLDataParse() { character->QueryAttribute("lzrw", &m_OriginalRotation.w); } - auto* flags = m_Doc->FirstChildElement("obj")->FirstChildElement("flag"); + auto* flags = m_Doc.FirstChildElement("obj")->FirstChildElement("flag"); if (flags) { auto* currentChild = flags->FirstChildElement(); while (currentChild) { @@ -239,12 +229,10 @@ void Character::SetBuildMode(bool buildMode) { } void Character::SaveXMLToDatabase() { - if (!m_Doc) return; - //For metrics, we'll record the time it took to save: auto start = std::chrono::system_clock::now(); - tinyxml2::XMLElement* character = m_Doc->FirstChildElement("obj")->FirstChildElement("char"); + tinyxml2::XMLElement* character = m_Doc.FirstChildElement("obj")->FirstChildElement("char"); if (character) { character->SetAttribute("gm", static_cast<uint32_t>(m_GMLevel)); character->SetAttribute("cc", m_Coins); @@ -266,11 +254,11 @@ void Character::SaveXMLToDatabase() { } auto emotes = character->FirstChildElement("ue"); - if (!emotes) emotes = m_Doc->NewElement("ue"); + if (!emotes) emotes = m_Doc.NewElement("ue"); emotes->DeleteChildren(); for (int emoteID : m_UnlockedEmotes) { - auto emote = m_Doc->NewElement("e"); + auto emote = m_Doc.NewElement("e"); emote->SetAttribute("id", emoteID); emotes->LinkEndChild(emote); @@ -280,15 +268,15 @@ void Character::SaveXMLToDatabase() { } //Export our flags: - auto* flags = m_Doc->FirstChildElement("obj")->FirstChildElement("flag"); + auto* flags = m_Doc.FirstChildElement("obj")->FirstChildElement("flag"); if (!flags) { - flags = m_Doc->NewElement("flag"); //Create a flags tag if we don't have one - m_Doc->FirstChildElement("obj")->LinkEndChild(flags); //Link it to the obj tag so we can find next time + flags = m_Doc.NewElement("flag"); //Create a flags tag if we don't have one + m_Doc.FirstChildElement("obj")->LinkEndChild(flags); //Link it to the obj tag so we can find next time } flags->DeleteChildren(); //Clear it if we have anything, so that we can fill it up again without dupes for (std::pair<uint32_t, uint64_t> flag : m_PlayerFlags) { - auto* f = m_Doc->NewElement("f"); + auto* f = m_Doc.NewElement("f"); f->SetAttribute("id", flag.first); //Because of the joy that is tinyxml2, it doesn't offer a function to set a uint64 as an attribute. @@ -301,7 +289,7 @@ void Character::SaveXMLToDatabase() { // Prevents the news feed from showing up on world transfers if (GetPlayerFlag(ePlayerFlag::IS_NEWS_SCREEN_VISIBLE)) { - auto* s = m_Doc->NewElement("s"); + auto* s = m_Doc.NewElement("s"); s->SetAttribute("si", ePlayerFlag::IS_NEWS_SCREEN_VISIBLE); flags->LinkEndChild(s); } @@ -326,7 +314,7 @@ void Character::SaveXMLToDatabase() { void Character::SetIsNewLogin() { // If we dont have a flag element, then we cannot have a s element as a child of flag. - auto* flags = m_Doc->FirstChildElement("obj")->FirstChildElement("flag"); + auto* flags = m_Doc.FirstChildElement("obj")->FirstChildElement("flag"); if (!flags) return; auto* currentChild = flags->FirstChildElement(); @@ -344,7 +332,7 @@ void Character::SetIsNewLogin() { void Character::WriteToDatabase() { //Dump our xml into m_XMLData: tinyxml2::XMLPrinter printer(0, true, 0); - m_Doc->Print(&printer); + m_Doc.Print(&printer); //Finally, save to db: Database::Get()->UpdateCharacterXml(m_ID, printer.CStr()); @@ -421,15 +409,15 @@ void Character::SetRetroactiveFlags() { void Character::SaveXmlRespawnCheckpoints() { //Export our respawn points: - auto* points = m_Doc->FirstChildElement("obj")->FirstChildElement("res"); + auto* points = m_Doc.FirstChildElement("obj")->FirstChildElement("res"); if (!points) { - points = m_Doc->NewElement("res"); - m_Doc->FirstChildElement("obj")->LinkEndChild(points); + points = m_Doc.NewElement("res"); + m_Doc.FirstChildElement("obj")->LinkEndChild(points); } points->DeleteChildren(); for (const auto& point : m_WorldRespawnCheckpoints) { - auto* r = m_Doc->NewElement("r"); + auto* r = m_Doc.NewElement("r"); r->SetAttribute("w", point.first); r->SetAttribute("x", point.second.x); @@ -443,7 +431,7 @@ void Character::SaveXmlRespawnCheckpoints() { void Character::LoadXmlRespawnCheckpoints() { m_WorldRespawnCheckpoints.clear(); - auto* points = m_Doc->FirstChildElement("obj")->FirstChildElement("res"); + auto* points = m_Doc.FirstChildElement("obj")->FirstChildElement("res"); if (!points) { return; } diff --git a/dGame/Character.h b/dGame/Character.h index b994fb61..77f286f0 100644 --- a/dGame/Character.h +++ b/dGame/Character.h @@ -37,7 +37,7 @@ public: void LoadXmlRespawnCheckpoints(); const std::string& GetXMLData() const { return m_XMLData; } - tinyxml2::XMLDocument* GetXMLDoc() const { return m_Doc; } + const tinyxml2::XMLDocument& GetXMLDoc() const { return m_Doc; } /** * Out of abundance of safety and clarity of what this saves, this is its own function. @@ -623,7 +623,7 @@ private: /** * The character XML belonging to this character */ - tinyxml2::XMLDocument* m_Doc; + tinyxml2::XMLDocument m_Doc; /** * Title of an announcement this character made (reserved for GMs) diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 97e86758..00ad8471 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -476,8 +476,7 @@ void Entity::Initialize() { } if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::INVENTORY) > 0 || m_Character) { - auto* xmlDoc = m_Character ? m_Character->GetXMLDoc() : nullptr; - AddComponent<InventoryComponent>(xmlDoc); + AddComponent<InventoryComponent>(); } // if this component exists, then we initialize it. it's value is always 0 if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MULTI_ZONE_ENTRANCE, -1) != -1) { @@ -1244,7 +1243,7 @@ void Entity::WriteComponents(RakNet::BitStream& outBitStream, eReplicaPacketType outBitStream.Write0(); } -void Entity::UpdateXMLDoc(tinyxml2::XMLDocument* doc) { +void Entity::UpdateXMLDoc(tinyxml2::XMLDocument& doc) { //This function should only ever be called from within Character, meaning doc should always exist when this is called. //Naturally, we don't include any non-player components in this update function. diff --git a/dGame/Entity.h b/dGame/Entity.h index 740d7c92..ffdcb713 100644 --- a/dGame/Entity.h +++ b/dGame/Entity.h @@ -174,7 +174,7 @@ public: void WriteBaseReplicaData(RakNet::BitStream& outBitStream, eReplicaPacketType packetType); void WriteComponents(RakNet::BitStream& outBitStream, eReplicaPacketType packetType); - void UpdateXMLDoc(tinyxml2::XMLDocument* doc); + void UpdateXMLDoc(tinyxml2::XMLDocument& doc); void Update(float deltaTime); // Events diff --git a/dGame/dComponents/BuffComponent.cpp b/dGame/dComponents/BuffComponent.cpp index 8b76e423..44c88ccb 100644 --- a/dGame/dComponents/BuffComponent.cpp +++ b/dGame/dComponents/BuffComponent.cpp @@ -326,9 +326,9 @@ Entity* BuffComponent::GetParent() const { return m_Parent; } -void BuffComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { +void BuffComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) { // Load buffs - auto* dest = doc->FirstChildElement("obj")->FirstChildElement("dest"); + auto* dest = doc.FirstChildElement("obj")->FirstChildElement("dest"); // Make sure we have a clean buff element. auto* buffElement = dest->FirstChildElement("buff"); @@ -386,15 +386,15 @@ void BuffComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { } } -void BuffComponent::UpdateXml(tinyxml2::XMLDocument* doc) { +void BuffComponent::UpdateXml(tinyxml2::XMLDocument& doc) { // Save buffs - auto* dest = doc->FirstChildElement("obj")->FirstChildElement("dest"); + auto* dest = doc.FirstChildElement("obj")->FirstChildElement("dest"); // Make sure we have a clean buff element. auto* buffElement = dest->FirstChildElement("buff"); if (buffElement == nullptr) { - buffElement = doc->NewElement("buff"); + buffElement = doc.NewElement("buff"); dest->LinkEndChild(buffElement); } else { @@ -402,7 +402,7 @@ void BuffComponent::UpdateXml(tinyxml2::XMLDocument* doc) { } for (const auto& [id, buff] : m_Buffs) { - auto* buffEntry = doc->NewElement("b"); + auto* buffEntry = doc.NewElement("b"); // TODO: change this if to if (buff.cancelOnZone || buff.cancelOnLogout) handling at some point. No current way to differentiate between zone transfer and logout. if (buff.cancelOnZone) continue; diff --git a/dGame/dComponents/BuffComponent.h b/dGame/dComponents/BuffComponent.h index df3c6a78..507e53a0 100644 --- a/dGame/dComponents/BuffComponent.h +++ b/dGame/dComponents/BuffComponent.h @@ -57,9 +57,9 @@ public: Entity* GetParent() const; - void LoadFromXml(tinyxml2::XMLDocument* doc) override; + void LoadFromXml(const tinyxml2::XMLDocument& doc) override; - void UpdateXml(tinyxml2::XMLDocument* doc) override; + void UpdateXml(tinyxml2::XMLDocument& doc) override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; diff --git a/dGame/dComponents/CharacterComponent.cpp b/dGame/dComponents/CharacterComponent.cpp index 3eafd924..d706af9c 100644 --- a/dGame/dComponents/CharacterComponent.cpp +++ b/dGame/dComponents/CharacterComponent.cpp @@ -186,9 +186,9 @@ void CharacterComponent::SetGMLevel(eGameMasterLevel gmlevel) { m_GMLevel = gmlevel; } -void CharacterComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { +void CharacterComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) { - tinyxml2::XMLElement* character = doc->FirstChildElement("obj")->FirstChildElement("char"); + auto* character = doc.FirstChildElement("obj")->FirstChildElement("char"); if (!character) { LOG("Failed to find char tag while loading XML!"); return; @@ -299,8 +299,8 @@ void CharacterComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { } } -void CharacterComponent::UpdateXml(tinyxml2::XMLDocument* doc) { - tinyxml2::XMLElement* minifig = doc->FirstChildElement("obj")->FirstChildElement("mf"); +void CharacterComponent::UpdateXml(tinyxml2::XMLDocument& doc) { + tinyxml2::XMLElement* minifig = doc.FirstChildElement("obj")->FirstChildElement("mf"); if (!minifig) { LOG("Failed to find mf tag while updating XML!"); return; @@ -320,7 +320,7 @@ void CharacterComponent::UpdateXml(tinyxml2::XMLDocument* doc) { // done with minifig - tinyxml2::XMLElement* character = doc->FirstChildElement("obj")->FirstChildElement("char"); + tinyxml2::XMLElement* character = doc.FirstChildElement("obj")->FirstChildElement("char"); if (!character) { LOG("Failed to find char tag while updating XML!"); return; @@ -338,11 +338,11 @@ void CharacterComponent::UpdateXml(tinyxml2::XMLDocument* doc) { // Set the zone statistics of the form <zs><s/> ... <s/></zs> auto zoneStatistics = character->FirstChildElement("zs"); - if (!zoneStatistics) zoneStatistics = doc->NewElement("zs"); + if (!zoneStatistics) zoneStatistics = doc.NewElement("zs"); zoneStatistics->DeleteChildren(); for (auto pair : m_ZoneStatistics) { - auto zoneStatistic = doc->NewElement("s"); + auto zoneStatistic = doc.NewElement("s"); zoneStatistic->SetAttribute("map", pair.first); zoneStatistic->SetAttribute("ac", pair.second.m_AchievementsCollected); diff --git a/dGame/dComponents/CharacterComponent.h b/dGame/dComponents/CharacterComponent.h index aa5c2e29..7e63b0bd 100644 --- a/dGame/dComponents/CharacterComponent.h +++ b/dGame/dComponents/CharacterComponent.h @@ -70,8 +70,8 @@ public: CharacterComponent(Entity* parent, Character* character, const SystemAddress& systemAddress); ~CharacterComponent() override; - void LoadFromXml(tinyxml2::XMLDocument* doc) override; - void UpdateXml(tinyxml2::XMLDocument* doc) override; + void LoadFromXml(const tinyxml2::XMLDocument& doc) override; + void UpdateXml(tinyxml2::XMLDocument& doc) override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; diff --git a/dGame/dComponents/Component.cpp b/dGame/dComponents/Component.cpp index 705c44a7..8f38fb61 100644 --- a/dGame/dComponents/Component.cpp +++ b/dGame/dComponents/Component.cpp @@ -21,11 +21,11 @@ void Component::OnUse(Entity* originator) { } -void Component::UpdateXml(tinyxml2::XMLDocument* doc) { +void Component::UpdateXml(tinyxml2::XMLDocument& doc) { } -void Component::LoadFromXml(tinyxml2::XMLDocument* doc) { +void Component::LoadFromXml(const tinyxml2::XMLDocument& doc) { } diff --git a/dGame/dComponents/Component.h b/dGame/dComponents/Component.h index 062924f7..160565fb 100644 --- a/dGame/dComponents/Component.h +++ b/dGame/dComponents/Component.h @@ -34,13 +34,13 @@ public: * Save data from this componennt to character XML * @param doc the document to write data to */ - virtual void UpdateXml(tinyxml2::XMLDocument* doc); + virtual void UpdateXml(tinyxml2::XMLDocument& doc); /** * Load base data for this component from character XML * @param doc the document to read data from */ - virtual void LoadFromXml(tinyxml2::XMLDocument* doc); + virtual void LoadFromXml(const tinyxml2::XMLDocument& doc); virtual void Serialize(RakNet::BitStream& outBitStream, bool isConstruction); diff --git a/dGame/dComponents/ControllablePhysicsComponent.cpp b/dGame/dComponents/ControllablePhysicsComponent.cpp index dccbe915..18e4b19d 100644 --- a/dGame/dComponents/ControllablePhysicsComponent.cpp +++ b/dGame/dComponents/ControllablePhysicsComponent.cpp @@ -158,8 +158,8 @@ void ControllablePhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bo } } -void ControllablePhysicsComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { - tinyxml2::XMLElement* character = doc->FirstChildElement("obj")->FirstChildElement("char"); +void ControllablePhysicsComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) { + auto* character = doc.FirstChildElement("obj")->FirstChildElement("char"); if (!character) { LOG("Failed to find char tag!"); return; @@ -178,8 +178,8 @@ void ControllablePhysicsComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { m_DirtyPosition = true; } -void ControllablePhysicsComponent::UpdateXml(tinyxml2::XMLDocument* doc) { - tinyxml2::XMLElement* character = doc->FirstChildElement("obj")->FirstChildElement("char"); +void ControllablePhysicsComponent::UpdateXml(tinyxml2::XMLDocument& doc) { + tinyxml2::XMLElement* character = doc.FirstChildElement("obj")->FirstChildElement("char"); if (!character) { LOG("Failed to find char tag while updating XML!"); return; diff --git a/dGame/dComponents/ControllablePhysicsComponent.h b/dGame/dComponents/ControllablePhysicsComponent.h index 8834128a..6309b8fc 100644 --- a/dGame/dComponents/ControllablePhysicsComponent.h +++ b/dGame/dComponents/ControllablePhysicsComponent.h @@ -28,8 +28,8 @@ public: void Update(float deltaTime) override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; - void LoadFromXml(tinyxml2::XMLDocument* doc) override; - void UpdateXml(tinyxml2::XMLDocument* doc) override; + void LoadFromXml(const tinyxml2::XMLDocument& doc) override; + void UpdateXml(tinyxml2::XMLDocument& doc) override; /** * Sets the position of this entity, also ensures this update is serialized next tick. diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 1b9b3285..476235f9 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -185,8 +185,8 @@ void DestroyableComponent::Update(float deltaTime) { m_DamageCooldownTimer -= deltaTime; } -void DestroyableComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { - tinyxml2::XMLElement* dest = doc->FirstChildElement("obj")->FirstChildElement("dest"); +void DestroyableComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) { + auto* dest = doc.FirstChildElement("obj")->FirstChildElement("dest"); if (!dest) { LOG("Failed to find dest tag!"); return; @@ -207,8 +207,8 @@ void DestroyableComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { m_DirtyHealth = true; } -void DestroyableComponent::UpdateXml(tinyxml2::XMLDocument* doc) { - tinyxml2::XMLElement* dest = doc->FirstChildElement("obj")->FirstChildElement("dest"); +void DestroyableComponent::UpdateXml(tinyxml2::XMLDocument& doc) { + tinyxml2::XMLElement* dest = doc.FirstChildElement("obj")->FirstChildElement("dest"); if (!dest) { LOG("Failed to find dest tag!"); return; diff --git a/dGame/dComponents/DestroyableComponent.h b/dGame/dComponents/DestroyableComponent.h index 85a4f941..56f30103 100644 --- a/dGame/dComponents/DestroyableComponent.h +++ b/dGame/dComponents/DestroyableComponent.h @@ -26,8 +26,8 @@ public: void Update(float deltaTime) override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; - void LoadFromXml(tinyxml2::XMLDocument* doc) override; - void UpdateXml(tinyxml2::XMLDocument* doc) override; + void LoadFromXml(const tinyxml2::XMLDocument& doc) override; + void UpdateXml(tinyxml2::XMLDocument& doc) override; /** * Initializes the component using a different LOT diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 161d7b91..8af7fb34 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -38,7 +38,7 @@ #include "CDObjectSkillsTable.h" #include "CDSkillBehaviorTable.h" -InventoryComponent::InventoryComponent(Entity* parent, tinyxml2::XMLDocument* document) : Component(parent) { +InventoryComponent::InventoryComponent(Entity* parent) : Component(parent) { this->m_Dirty = true; this->m_Equipped = {}; this->m_Pushed = {}; @@ -48,7 +48,8 @@ InventoryComponent::InventoryComponent(Entity* parent, tinyxml2::XMLDocument* do const auto lot = parent->GetLOT(); if (lot == 1) { - LoadXml(document); + auto* character = m_Parent->GetCharacter(); + if (character) LoadXml(character->GetXMLDoc()); CheckProxyIntegrity(); @@ -472,10 +473,10 @@ bool InventoryComponent::HasSpaceForLoot(const std::unordered_map<LOT, int32_t>& return true; } -void InventoryComponent::LoadXml(tinyxml2::XMLDocument* document) { +void InventoryComponent::LoadXml(const tinyxml2::XMLDocument& document) { LoadPetXml(document); - auto* inventoryElement = document->FirstChildElement("obj")->FirstChildElement("inv"); + auto* inventoryElement = document.FirstChildElement("obj")->FirstChildElement("inv"); if (inventoryElement == nullptr) { LOG("Failed to find 'inv' xml element!"); @@ -594,10 +595,10 @@ void InventoryComponent::LoadXml(tinyxml2::XMLDocument* document) { } } -void InventoryComponent::UpdateXml(tinyxml2::XMLDocument* document) { +void InventoryComponent::UpdateXml(tinyxml2::XMLDocument& document) { UpdatePetXml(document); - auto* inventoryElement = document->FirstChildElement("obj")->FirstChildElement("inv"); + auto* inventoryElement = document.FirstChildElement("obj")->FirstChildElement("inv"); if (inventoryElement == nullptr) { LOG("Failed to find 'inv' xml element!"); @@ -631,7 +632,7 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument* document) { bags->DeleteChildren(); for (const auto* inventory : inventoriesToSave) { - auto* bag = document->NewElement("b"); + auto* bag = document.NewElement("b"); bag->SetAttribute("t", inventory->GetType()); bag->SetAttribute("m", static_cast<unsigned int>(inventory->GetSize())); @@ -654,14 +655,14 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument* document) { continue; } - auto* bagElement = document->NewElement("in"); + auto* bagElement = document.NewElement("in"); bagElement->SetAttribute("t", inventory->GetType()); for (const auto& pair : inventory->GetItems()) { auto* item = pair.second; - auto* itemElement = document->NewElement("i"); + auto* itemElement = document.NewElement("i"); itemElement->SetAttribute("l", item->GetLot()); itemElement->SetAttribute("id", item->GetId()); @@ -680,7 +681,7 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument* document) { continue; } - auto* extraInfo = document->NewElement("x"); + auto* extraInfo = document.NewElement("x"); extraInfo->SetAttribute("ma", data->GetString(false).c_str()); @@ -1542,8 +1543,8 @@ void InventoryComponent::PurgeProxies(Item* item) { } } -void InventoryComponent::LoadPetXml(tinyxml2::XMLDocument* document) { - auto* petInventoryElement = document->FirstChildElement("obj")->FirstChildElement("pet"); +void InventoryComponent::LoadPetXml(const tinyxml2::XMLDocument& document) { + auto* petInventoryElement = document.FirstChildElement("obj")->FirstChildElement("pet"); if (petInventoryElement == nullptr) { m_Pets.clear(); @@ -1574,19 +1575,19 @@ void InventoryComponent::LoadPetXml(tinyxml2::XMLDocument* document) { } } -void InventoryComponent::UpdatePetXml(tinyxml2::XMLDocument* document) { - auto* petInventoryElement = document->FirstChildElement("obj")->FirstChildElement("pet"); +void InventoryComponent::UpdatePetXml(tinyxml2::XMLDocument& document) { + auto* petInventoryElement = document.FirstChildElement("obj")->FirstChildElement("pet"); if (petInventoryElement == nullptr) { - petInventoryElement = document->NewElement("pet"); + petInventoryElement = document.NewElement("pet"); - document->FirstChildElement("obj")->LinkEndChild(petInventoryElement); + document.FirstChildElement("obj")->LinkEndChild(petInventoryElement); } petInventoryElement->DeleteChildren(); for (const auto& pet : m_Pets) { - auto* petElement = document->NewElement("p"); + auto* petElement = document.NewElement("p"); petElement->SetAttribute("id", pet.first); petElement->SetAttribute("l", pet.second.lot); diff --git a/dGame/dComponents/InventoryComponent.h b/dGame/dComponents/InventoryComponent.h index 8f58a523..a1eb14d1 100644 --- a/dGame/dComponents/InventoryComponent.h +++ b/dGame/dComponents/InventoryComponent.h @@ -38,12 +38,12 @@ enum class eItemType : int32_t; class InventoryComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::INVENTORY; - explicit InventoryComponent(Entity* parent, tinyxml2::XMLDocument* document = nullptr); + InventoryComponent(Entity* parent); void Update(float deltaTime) override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; - void LoadXml(tinyxml2::XMLDocument* document); - void UpdateXml(tinyxml2::XMLDocument* document) override; + void LoadXml(const tinyxml2::XMLDocument& document); + void UpdateXml(tinyxml2::XMLDocument& document) override; /** * Returns an inventory of the specified type, if it exists @@ -470,13 +470,13 @@ private: * Saves all the pet information stored in inventory items to the database * @param document the xml doc to save to */ - void LoadPetXml(tinyxml2::XMLDocument* document); + void LoadPetXml(const tinyxml2::XMLDocument& document); /** * Loads all the pet information from an xml doc into items * @param document the xml doc to load from */ - void UpdatePetXml(tinyxml2::XMLDocument* document); + void UpdatePetXml(tinyxml2::XMLDocument& document); }; #endif diff --git a/dGame/dComponents/LevelProgressionComponent.cpp b/dGame/dComponents/LevelProgressionComponent.cpp index 2d3d5144..a6801a40 100644 --- a/dGame/dComponents/LevelProgressionComponent.cpp +++ b/dGame/dComponents/LevelProgressionComponent.cpp @@ -13,8 +13,8 @@ LevelProgressionComponent::LevelProgressionComponent(Entity* parent) : Component m_CharacterVersion = eCharacterVersion::LIVE; } -void LevelProgressionComponent::UpdateXml(tinyxml2::XMLDocument* doc) { - tinyxml2::XMLElement* level = doc->FirstChildElement("obj")->FirstChildElement("lvl"); +void LevelProgressionComponent::UpdateXml(tinyxml2::XMLDocument& doc) { + tinyxml2::XMLElement* level = doc.FirstChildElement("obj")->FirstChildElement("lvl"); if (!level) { LOG("Failed to find lvl tag while updating XML!"); return; @@ -24,8 +24,8 @@ void LevelProgressionComponent::UpdateXml(tinyxml2::XMLDocument* doc) { level->SetAttribute("cv", static_cast<uint32_t>(m_CharacterVersion)); } -void LevelProgressionComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { - tinyxml2::XMLElement* level = doc->FirstChildElement("obj")->FirstChildElement("lvl"); +void LevelProgressionComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) { + auto* level = doc.FirstChildElement("obj")->FirstChildElement("lvl"); if (!level) { LOG("Failed to find lvl tag while loading XML!"); return; diff --git a/dGame/dComponents/LevelProgressionComponent.h b/dGame/dComponents/LevelProgressionComponent.h index a27039f3..e9981ab6 100644 --- a/dGame/dComponents/LevelProgressionComponent.h +++ b/dGame/dComponents/LevelProgressionComponent.h @@ -27,13 +27,13 @@ public: * Save data from this componennt to character XML * @param doc the document to write data to */ - void UpdateXml(tinyxml2::XMLDocument* doc) override; + void UpdateXml(tinyxml2::XMLDocument& doc) override; /** * Load base data for this component from character XML * @param doc the document to read data from */ - void LoadFromXml(tinyxml2::XMLDocument* doc) override; + void LoadFromXml(const tinyxml2::XMLDocument& doc) override; /** * Gets the current level of the entity diff --git a/dGame/dComponents/MissionComponent.cpp b/dGame/dComponents/MissionComponent.cpp index 151fcf2f..1eb02e57 100644 --- a/dGame/dComponents/MissionComponent.cpp +++ b/dGame/dComponents/MissionComponent.cpp @@ -504,10 +504,8 @@ bool MissionComponent::RequiresItem(const LOT lot) { } -void MissionComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { - if (doc == nullptr) return; - - auto* mis = doc->FirstChildElement("obj")->FirstChildElement("mis"); +void MissionComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) { + auto* mis = doc.FirstChildElement("obj")->FirstChildElement("mis"); if (mis == nullptr) return; @@ -523,7 +521,7 @@ void MissionComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { auto* mission = new Mission(this, missionId); - mission->LoadFromXml(doneM); + mission->LoadFromXml(*doneM); doneM = doneM->NextSiblingElement(); @@ -540,7 +538,7 @@ void MissionComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { auto* mission = new Mission(this, missionId); - mission->LoadFromXml(currentM); + mission->LoadFromXml(*currentM); if (currentM->QueryAttribute("o", &missionOrder) == tinyxml2::XML_SUCCESS && mission->IsMission()) { mission->SetUniqueMissionOrderID(missionOrder); @@ -554,25 +552,23 @@ void MissionComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { } -void MissionComponent::UpdateXml(tinyxml2::XMLDocument* doc) { - if (doc == nullptr) return; - +void MissionComponent::UpdateXml(tinyxml2::XMLDocument& doc) { auto shouldInsertMis = false; - auto* obj = doc->FirstChildElement("obj"); + auto* obj = doc.FirstChildElement("obj"); auto* mis = obj->FirstChildElement("mis"); if (mis == nullptr) { - mis = doc->NewElement("mis"); + mis = doc.NewElement("mis"); shouldInsertMis = true; } mis->DeleteChildren(); - auto* done = doc->NewElement("done"); - auto* cur = doc->NewElement("cur"); + auto* done = doc.NewElement("done"); + auto* cur = doc.NewElement("cur"); for (const auto& pair : m_Missions) { auto* mission = pair.second; @@ -580,10 +576,10 @@ void MissionComponent::UpdateXml(tinyxml2::XMLDocument* doc) { if (mission) { const auto complete = mission->IsComplete(); - auto* m = doc->NewElement("m"); + auto* m = doc.NewElement("m"); if (complete) { - mission->UpdateXml(m); + mission->UpdateXml(*m); done->LinkEndChild(m); @@ -591,7 +587,7 @@ void MissionComponent::UpdateXml(tinyxml2::XMLDocument* doc) { } if (mission->IsMission()) m->SetAttribute("o", mission->GetUniqueMissionOrderID()); - mission->UpdateXml(m); + mission->UpdateXml(*m); cur->LinkEndChild(m); } diff --git a/dGame/dComponents/MissionComponent.h b/dGame/dComponents/MissionComponent.h index 866f1650..a01794f0 100644 --- a/dGame/dComponents/MissionComponent.h +++ b/dGame/dComponents/MissionComponent.h @@ -31,8 +31,8 @@ public: explicit MissionComponent(Entity* parent); ~MissionComponent() override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate, unsigned int& flags); - void LoadFromXml(tinyxml2::XMLDocument* doc) override; - void UpdateXml(tinyxml2::XMLDocument* doc) override; + void LoadFromXml(const tinyxml2::XMLDocument& doc) override; + void UpdateXml(tinyxml2::XMLDocument& doc) override; /** * Returns all the missions for this entity, mapped by mission ID diff --git a/dGame/dMission/Mission.cpp b/dGame/dMission/Mission.cpp index 4ed80bf3..c2ed2a42 100644 --- a/dGame/dMission/Mission.cpp +++ b/dGame/dMission/Mission.cpp @@ -65,24 +65,24 @@ Mission::Mission(MissionComponent* missionComponent, const uint32_t missionId) { } } -void Mission::LoadFromXml(tinyxml2::XMLElement* element) { +void Mission::LoadFromXml(const tinyxml2::XMLElement& element) { // Start custom XML - if (element->Attribute("state") != nullptr) { - m_State = static_cast<eMissionState>(std::stoul(element->Attribute("state"))); + if (element.Attribute("state") != nullptr) { + m_State = static_cast<eMissionState>(std::stoul(element.Attribute("state"))); } // End custom XML - if (element->Attribute("cct") != nullptr) { - m_Completions = std::stoul(element->Attribute("cct")); + if (element.Attribute("cct") != nullptr) { + m_Completions = std::stoul(element.Attribute("cct")); - m_Timestamp = std::stoul(element->Attribute("cts")); + m_Timestamp = std::stoul(element.Attribute("cts")); if (IsComplete()) { return; } } - auto* task = element->FirstChildElement(); + auto* task = element.FirstChildElement(); auto index = 0U; @@ -132,19 +132,19 @@ void Mission::LoadFromXml(tinyxml2::XMLElement* element) { } } -void Mission::UpdateXml(tinyxml2::XMLElement* element) { +void Mission::UpdateXml(tinyxml2::XMLElement& element) { // Start custom XML - element->SetAttribute("state", static_cast<unsigned int>(m_State)); + element.SetAttribute("state", static_cast<unsigned int>(m_State)); // End custom XML - element->DeleteChildren(); + element.DeleteChildren(); - element->SetAttribute("id", static_cast<unsigned int>(info.id)); + element.SetAttribute("id", static_cast<unsigned int>(info.id)); if (m_Completions > 0) { - element->SetAttribute("cct", static_cast<unsigned int>(m_Completions)); + element.SetAttribute("cct", static_cast<unsigned int>(m_Completions)); - element->SetAttribute("cts", static_cast<unsigned int>(m_Timestamp)); + element.SetAttribute("cts", static_cast<unsigned int>(m_Timestamp)); if (IsComplete()) { return; @@ -155,27 +155,27 @@ void Mission::UpdateXml(tinyxml2::XMLElement* element) { if (task->GetType() == eMissionTaskType::COLLECTION || task->GetType() == eMissionTaskType::VISIT_PROPERTY) { - auto* child = element->GetDocument()->NewElement("sv"); + auto* child = element.GetDocument()->NewElement("sv"); child->SetAttribute("v", static_cast<unsigned int>(task->GetProgress())); - element->LinkEndChild(child); + element.LinkEndChild(child); for (auto unique : task->GetUnique()) { - auto* uniqueElement = element->GetDocument()->NewElement("sv"); + auto* uniqueElement = element.GetDocument()->NewElement("sv"); uniqueElement->SetAttribute("v", static_cast<unsigned int>(unique)); - element->LinkEndChild(uniqueElement); + element.LinkEndChild(uniqueElement); } break; } - auto* child = element->GetDocument()->NewElement("sv"); + auto* child = element.GetDocument()->NewElement("sv"); child->SetAttribute("v", static_cast<unsigned int>(task->GetProgress())); - element->LinkEndChild(child); + element.LinkEndChild(child); } } diff --git a/dGame/dMission/Mission.h b/dGame/dMission/Mission.h index d8c104e8..74b8d352 100644 --- a/dGame/dMission/Mission.h +++ b/dGame/dMission/Mission.h @@ -28,8 +28,8 @@ public: Mission(MissionComponent* missionComponent, uint32_t missionId); ~Mission(); - void LoadFromXml(tinyxml2::XMLElement* element); - void UpdateXml(tinyxml2::XMLElement* element); + void LoadFromXml(const tinyxml2::XMLElement& element); + void UpdateXml(tinyxml2::XMLElement& element); /** * Returns the ID of this mission diff --git a/dGame/dUtilities/BrickDatabase.cpp b/dGame/dUtilities/BrickDatabase.cpp index 7b447b84..61a7341d 100644 --- a/dGame/dUtilities/BrickDatabase.cpp +++ b/dGame/dUtilities/BrickDatabase.cpp @@ -29,15 +29,14 @@ const BrickList& BrickDatabase::GetBricks(const LxfmlPath& lxfmlPath) { return emptyCache; } - auto* doc = new tinyxml2::XMLDocument(); - if (doc->Parse(data.str().c_str(), data.str().size()) != 0) { - delete doc; + tinyxml2::XMLDocument doc; + if (doc.Parse(data.str().c_str(), data.str().size()) != 0) { return emptyCache; } BrickList parts; - auto* lxfml = doc->FirstChildElement("LXFML"); + auto* lxfml = doc.FirstChildElement("LXFML"); auto* bricks = lxfml->FirstChildElement("Bricks"); std::string searchTerm = "Brick"; @@ -86,7 +85,5 @@ const BrickList& BrickDatabase::GetBricks(const LxfmlPath& lxfmlPath) { m_Cache[lxfmlPath] = parts; - delete doc; - return m_Cache[lxfmlPath]; } From db192d2cde796823c2ac8670df07389767d3e4dd Mon Sep 17 00:00:00 2001 From: jadebenn <jadebenn@users.noreply.github.com> Date: Mon, 8 Apr 2024 23:50:41 -0500 Subject: [PATCH 10/78] chore: Fix use of uninitialized variable in RemoveItemFromInventory (#1540) --- dGame/dGameMessages/GameMessages.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 3ef9f3b5..74946179 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -4660,7 +4660,7 @@ void GameMessages::HandleBuyFromVendor(RakNet::BitStream& inStream, Entity* enti if (!user) return; Entity* player = Game::entityManager->GetEntity(user->GetLoggedInChar()); if (!player) return; - + // handle buying normal items auto* vendorComponent = entity->GetComponent<VendorComponent>(); if (vendorComponent) { @@ -5290,7 +5290,7 @@ void GameMessages::HandleRemoveItemFromInventory(RakNet::BitStream& inStream, En bool iLootTypeSourceIsDefault = false; LWOOBJID iLootTypeSource = LWOOBJID_EMPTY; bool iObjIDIsDefault = false; - LWOOBJID iObjID; + LWOOBJID iObjID = LWOOBJID_EMPTY; bool iObjTemplateIsDefault = false; LOT iObjTemplate = LOT_NULL; bool iRequestingObjIDIsDefault = false; From 1ee45639afdd6713e9730b5da7f82b5eba2cd373 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 8 Apr 2024 22:20:25 -0700 Subject: [PATCH 11/78] Update GeneralUtils.h (#1541) --- dCommon/GeneralUtils.h | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/dCommon/GeneralUtils.h b/dCommon/GeneralUtils.h index f59ec71f..35c0b895 100644 --- a/dCommon/GeneralUtils.h +++ b/dCommon/GeneralUtils.h @@ -158,9 +158,7 @@ namespace GeneralUtils { template <Numeric T> [[nodiscard]] std::optional<T> TryParse(std::string_view str) { numeric_parse_t<T> result; - if (!str.empty()) { - while (std::isspace(str.front())) str.remove_prefix(1); - } + while (!str.empty() && std::isspace(str.front())) str.remove_prefix(1); const char* const strEnd = str.data() + str.size(); const auto [parseEnd, ec] = std::from_chars(str.data(), strEnd, result); @@ -186,9 +184,7 @@ namespace GeneralUtils { template <std::floating_point T> [[nodiscard]] std::optional<T> TryParse(std::string_view str) noexcept try { - if (!str.empty()) { - while (std::isspace(str.front())) str.remove_prefix(1); - } + while (!str.empty() && std::isspace(str.front())) str.remove_prefix(1); size_t parseNum; const T result = details::_parse<T>(str, parseNum); From d067a8d12f8faf4c6b8c715d1d679b45ea1ecb29 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell <aronwk.aaron@gmail.com> Date: Tue, 9 Apr 2024 20:15:51 -0500 Subject: [PATCH 12/78] chore: split out slash commands into multiple files (#1539) * chore: split out slash commands into multiple files Breakup the monolithic file don't register slashcommands on startup * fix typo --- dGame/dUtilities/CMakeLists.txt | 8 +- dGame/dUtilities/SlashCommandHandler.cpp | 2219 +---------------- dGame/dUtilities/SlashCommandHandler.h | 102 +- dGame/dUtilities/SlashCommands/CMakeLists.txt | 6 + .../SlashCommands/DEVGMCommands.cpp | 1592 ++++++++++++ .../dUtilities/SlashCommands/DEVGMCommands.h | 73 + .../GMGreaterThanZeroCommands.cpp | 290 +++ .../SlashCommands/GMGreaterThanZeroCommands.h | 13 + .../SlashCommands/GMZeroCommands.cpp | 228 ++ .../dUtilities/SlashCommands/GMZeroCommands.h | 15 + dWorldServer/WorldServer.cpp | 4 +- 11 files changed, 2297 insertions(+), 2253 deletions(-) create mode 100644 dGame/dUtilities/SlashCommands/CMakeLists.txt create mode 100644 dGame/dUtilities/SlashCommands/DEVGMCommands.cpp create mode 100644 dGame/dUtilities/SlashCommands/DEVGMCommands.h create mode 100644 dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp create mode 100644 dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h create mode 100644 dGame/dUtilities/SlashCommands/GMZeroCommands.cpp create mode 100644 dGame/dUtilities/SlashCommands/GMZeroCommands.h diff --git a/dGame/dUtilities/CMakeLists.txt b/dGame/dUtilities/CMakeLists.txt index 1e54b59b..2202fddc 100644 --- a/dGame/dUtilities/CMakeLists.txt +++ b/dGame/dUtilities/CMakeLists.txt @@ -8,9 +8,15 @@ set(DGAME_DUTILITIES_SOURCES "BrickDatabase.cpp" "SlashCommandHandler.cpp" "VanityUtilities.cpp") +add_subdirectory(SlashCommands) + +foreach(file ${DGAME_DUTILITIES_SLASHCOMMANDS}) + set(DGAME_DUTILITIES_SOURCES ${DGAME_DUTILITIES_SOURCES} "SlashCommands/${file}") +endforeach() + add_library(dUtilities OBJECT ${DGAME_DUTILITIES_SOURCES}) target_precompile_headers(dUtilities REUSE_FROM dGameBase) -target_include_directories(dUtilities PUBLIC "." +target_include_directories(dUtilities PUBLIC "." "SlashCommands" PRIVATE "${PROJECT_SOURCE_DIR}/dGame/dComponents" "${PROJECT_SOURCE_DIR}/dGame/dInventory" # transitive via PossessableComponent.h diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 34ae013c..bd7d5f28 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -3,83 +3,20 @@ * Copyright 2024 */ + + #include "SlashCommandHandler.h" -#include <sstream> -#include <iostream> -#include <fstream> -#include <exception> -#include "dZoneManager.h" +#include <iomanip> -#include "Metrics.hpp" +#include "DEVGMCommands.h" +#include "GMGreaterThanZeroCommands.h" +#include "GMZeroCommands.h" -#include "User.h" -#include "UserManager.h" -#include "BitStream.h" -#include "dCommonVars.h" -#include "GeneralUtils.h" -#include "Entity.h" -#include "EntityManager.h" -#include "Logger.h" -#include "WorldPackets.h" -#include "GameMessages.h" -#include "CDClientDatabase.h" -#include "ZoneInstanceManager.h" -#include "ControllablePhysicsComponent.h" -#include "NiPoint3.h" -#include "NiQuaternion.h" -#include "ChatPackets.h" -#include "InventoryComponent.h" -#include "Game.h" -#include "CharacterComponent.h" -#include "Database.h" -#include "DestroyableComponent.h" -#include "dServer.h" -#include "MissionComponent.h" -#include "Mail.h" -#include "dpWorld.h" -#include "Item.h" -#include "PropertyManagementComponent.h" -#include "BitStreamUtils.h" -#include "Loot.h" -#include "EntityInfo.h" -#include "LUTriggers.h" -#include "PhantomPhysicsComponent.h" -#include "ProximityMonitorComponent.h" -#include "dpShapeSphere.h" -#include "PossessableComponent.h" -#include "PossessorComponent.h" -#include "HavokVehiclePhysicsComponent.h" -#include "BuffComponent.h" -#include "SkillComponent.h" -#include "VanityUtilities.h" -#include "ScriptedActivityComponent.h" -#include "LevelProgressionComponent.h" -#include "AssetManager.h" -#include "BinaryPathFinder.h" -#include "dConfig.h" -#include "eBubbleType.h" #include "Amf3.h" -#include "MovingPlatformComponent.h" -#include "eMissionState.h" -#include "TriggerComponent.h" -#include "eServerDisconnectIdentifiers.h" -#include "eObjectBits.h" -#include "eGameMasterLevel.h" -#include "eReplicaComponentType.h" -#include "RenderComponent.h" -#include "eControlScheme.h" -#include "eConnectionType.h" +#include "Database.h" #include "eChatMessageType.h" -#include "eMasterMessageType.h" -#include "PlayerManager.h" - -#include "CDRewardCodesTable.h" -#include "CDObjectsTable.h" -#include "CDZoneTableTable.h" -#include "ePlayerFlag.h" -#include "dNavMesh.h" -#include <ranges> +#include "dServer.h" namespace { std::vector<Command> CommandInfos; @@ -137,8 +74,69 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& chat, Entity* } } -void SlashCommandHandler::Startup() { +// This commands in here so we can access the CommandInfos to display info +void GMZeroCommands::Help(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + std::ostringstream feedback; + if (args.empty()) { + feedback << "----- Commands -----"; + for (const auto& command : CommandInfos) { + // TODO: Limit displaying commands based on GM level they require + if (command.requiredLevel > entity->GetGMLevel()) continue; + LOG("Help command: %s", command.aliases[0].c_str()); + feedback << "\n/" << command.aliases[0] << ": " << command.help; + } + } else { + bool foundCommand = false; + for (const auto& command : CommandInfos) { + if (std::ranges::find(command.aliases, args) == command.aliases.end()) continue; + if (entity->GetGMLevel() < command.requiredLevel) break; + foundCommand = true; + feedback << "----- " << command.aliases.at(0) << " -----\n"; + // info can be a localizable string + feedback << command.info; + if (command.aliases.size() == 1) break; + + feedback << "\nAliases: "; + for (size_t i = 0; i < command.aliases.size(); i++) { + if (i > 0) feedback << ", "; + feedback << command.aliases[i]; + } + } + + // Let GameMasters know if the command doesn't exist + if (!foundCommand && entity->GetGMLevel() > eGameMasterLevel::CIVILIAN) feedback << "Command " << std::quoted(args) << " does not exist!"; + } + const auto feedbackStr = feedback.str(); + if (!feedbackStr.empty()) GameMessages::SendSlashCommandFeedbackText(entity, GeneralUtils::ASCIIToUTF16(feedbackStr)); +} + +void SlashCommandHandler::SendAnnouncement(const std::string& title, const std::string& message) { + AMFArrayValue args; + + args.Insert("title", title); + args.Insert("message", message); + + GameMessages::SendUIMessageServerToAllClients("ToggleAnnounce", args); + + //Notify chat about it + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::GM_ANNOUNCE); + + bitStream.Write<uint32_t>(title.size()); + for (auto character : title) { + bitStream.Write<char>(character); + } + + bitStream.Write<uint32_t>(message.size()); + for (auto character : message) { + bitStream.Write<char>(character); + } + + Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); +} + +void SlashCommandHandler::Startup() { // Register Dev Commands Command SetGMLevelCommand{ .help = "Change the GM level of your character", @@ -1000,2080 +998,3 @@ void SlashCommandHandler::Startup() { RegisterCommand(InstanceInfoCommand); } - -namespace GMZeroCommands { - // The star delimiter is to be used for marking the start and end of a localized string. - void Help(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - std::ostringstream feedback; - if (args.empty()) { - feedback << "----- Commands -----"; - for (const auto& command : CommandInfos) { - // TODO: Limit displaying commands based on GM level they require - if (command.requiredLevel > entity->GetGMLevel()) continue; - LOG("Help command: %s", command.aliases[0].c_str()); - feedback << "\n/" << command.aliases[0] << ": " << command.help; - } - } else { - bool foundCommand = false; - for (const auto& command : CommandInfos) { - if (std::ranges::find(command.aliases, args) == command.aliases.end()) continue; - - if (entity->GetGMLevel() < command.requiredLevel) break; - foundCommand = true; - feedback << "----- " << command.aliases.at(0) << " -----\n"; - // info can be a localizable string - feedback << command.info; - if (command.aliases.size() == 1) break; - - feedback << "\nAliases: "; - for (size_t i = 0; i < command.aliases.size(); i++) { - if (i > 0) feedback << ", "; - feedback << command.aliases[i]; - } - } - - // Let GameMasters know if the command doesn't exist - if (!foundCommand && entity->GetGMLevel() > eGameMasterLevel::CIVILIAN) feedback << "Command " << std::quoted(args) << " does not exist!"; - } - const auto feedbackStr = feedback.str(); - if (!feedbackStr.empty()) GameMessages::SendSlashCommandFeedbackText(entity, GeneralUtils::ASCIIToUTF16(feedbackStr)); - } - - void Pvp(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - auto* character = entity->GetComponent<CharacterComponent>(); - - if (character == nullptr) { - LOG("Failed to find character component!"); - return; - } - - character->SetPvpEnabled(!character->GetPvpEnabled()); - Game::entityManager->SerializeEntity(entity); - - std::stringstream message; - message << character->GetName() << " changed their PVP flag to " << std::to_string(character->GetPvpEnabled()) << "!"; - - ChatPackets::SendSystemMessage(UNASSIGNED_SYSTEM_ADDRESS, GeneralUtils::UTF8ToUTF16(message.str()), true); - } - - void Who(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - ChatPackets::SendSystemMessage( - sysAddr, - u"Players in this instance: (" + GeneralUtils::to_u16string(PlayerManager::GetAllPlayers().size()) + u")" - ); - - for (auto* player : PlayerManager::GetAllPlayers()) { - const auto& name = player->GetCharacter()->GetName(); - - ChatPackets::SendSystemMessage( - sysAddr, - GeneralUtils::UTF8ToUTF16(player == entity ? name + " (you)" : name) - ); - } - } - - void Ping(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - if (!args.empty() && args.starts_with("-l")) { - std::stringstream message; - message << "Your latest ping: " << std::to_string(Game::server->GetLatestPing(sysAddr)) << "ms"; - - ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(message.str())); - } else { - std::stringstream message; - message << "Your average ping: " << std::to_string(Game::server->GetPing(sysAddr)) << "ms"; - - ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(message.str())); - } - } - - void FixStats(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - // Reset skill component and buff component - auto* skillComponent = entity->GetComponent<SkillComponent>(); - auto* buffComponent = entity->GetComponent<BuffComponent>(); - auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); - - // If any of the components are nullptr, return - if (skillComponent == nullptr || buffComponent == nullptr || destroyableComponent == nullptr) { - return; - } - - // Reset skill component - skillComponent->Reset(); - - // Reset buff component - buffComponent->Reset(); - - // Fix the destroyable component - destroyableComponent->FixStats(); - } - - void Credits(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto& customText = VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/CREDITS.md").string()); - - { - AMFArrayValue args; - - args.Insert("state", "Story"); - - GameMessages::SendUIMessageServerToSingleClient(entity, entity->GetSystemAddress(), "pushGameState", args); - } - - entity->AddCallbackTimer(0.5f, [customText, entity]() { - AMFArrayValue args; - - args.Insert("visible", true); - args.Insert("text", customText); - - LOG("Sending %s", customText.c_str()); - - GameMessages::SendUIMessageServerToSingleClient(entity, entity->GetSystemAddress(), "ToggleStoryBox", args); - }); - } - - void Info(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto& customText = VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/INFO.md").string()); - - { - AMFArrayValue args; - - args.Insert("state", "Story"); - - GameMessages::SendUIMessageServerToSingleClient(entity, entity->GetSystemAddress(), "pushGameState", args); - } - - entity->AddCallbackTimer(0.5f, [customText, entity]() { - AMFArrayValue args; - - args.Insert("visible", true); - args.Insert("text", customText); - - LOG("Sending %s", customText.c_str()); - - GameMessages::SendUIMessageServerToSingleClient(entity, entity->GetSystemAddress(), "ToggleStoryBox", args); - }); - } - - void LeaveZone(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto currentZone = Game::zoneManager->GetZone()->GetZoneID().GetMapID(); - LWOMAPID newZone = 0; - - if (currentZone == 1001 || currentZone % 100 == 0) { - ChatPackets::SendSystemMessage(sysAddr, u"You are not in an instanced zone."); - return; - } else { - newZone = (currentZone / 100) * 100; - } - // If new zone would be inaccessible, then default to Avant Gardens. - if (!Game::zoneManager->CheckIfAccessibleZone(newZone)) newZone = 1100; - - ChatPackets::SendSystemMessage(sysAddr, u"Leaving zone..."); - - const auto objid = entity->GetObjectID(); - - ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, newZone, 0, false, [objid](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) { - auto* entity = Game::entityManager->GetEntity(objid); - - if (entity == nullptr) { - return; - } - - const auto sysAddr = entity->GetSystemAddress(); - - LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", entity->GetCharacter()->GetName().c_str(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort); - - if (entity->GetCharacter()) { - entity->GetCharacter()->SetZoneID(zoneID); - entity->GetCharacter()->SetZoneInstance(zoneInstance); - entity->GetCharacter()->SetZoneClone(zoneClone); - } - - entity->GetCharacter()->SaveXMLToDatabase(); - - WorldPackets::SendTransferToWorld(sysAddr, serverIP, serverPort, mythranShift); - }); - } - - void Join(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - ChatPackets::SendSystemMessage(sysAddr, u"Requesting private map..."); - const auto& password = splitArgs[0]; - - ZoneInstanceManager::Instance()->RequestPrivateZone(Game::server, false, password, [=](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) { - LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", sysAddr.ToString(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort); - - if (entity->GetCharacter()) { - entity->GetCharacter()->SetZoneID(zoneID); - entity->GetCharacter()->SetZoneInstance(zoneInstance); - entity->GetCharacter()->SetZoneClone(zoneClone); - } - - entity->GetCharacter()->SaveXMLToDatabase(); - - WorldPackets::SendTransferToWorld(sysAddr, serverIP, serverPort, mythranShift); - }); - } - - void Die(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - entity->Smash(entity->GetObjectID()); - } - - void Resurrect(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - ScriptedActivityComponent* scriptedActivityComponent = Game::zoneManager->GetZoneControlObject()->GetComponent<ScriptedActivityComponent>(); - - if (scriptedActivityComponent) { // check if user is in activity world and if so, they can't resurrect - ChatPackets::SendSystemMessage(sysAddr, u"You cannot resurrect in an activity world."); - return; - } - - GameMessages::SendResurrect(entity); - } - - void RequestMailCount(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - Mail::HandleNotificationRequest(entity->GetSystemAddress(), entity->GetObjectID()); - } - - void InstanceInfo(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto zoneId = Game::zoneManager->GetZone()->GetZoneID(); - - ChatPackets::SendSystemMessage(sysAddr, u"Map: " + (GeneralUtils::to_u16string(zoneId.GetMapID())) + u"\nClone: " + (GeneralUtils::to_u16string(zoneId.GetCloneID())) + u"\nInstance: " + (GeneralUtils::to_u16string(zoneId.GetInstanceID()))); - } -}; - -namespace DEVGMCommands { - void SetGMLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - User* user = UserManager::Instance()->GetUser(entity->GetSystemAddress()); - - const auto level_intermed = GeneralUtils::TryParse<uint32_t>(args); - if (!level_intermed) { - GameMessages::SendSlashCommandFeedbackText(entity, u"Invalid GM level."); - return; - } - eGameMasterLevel level = static_cast<eGameMasterLevel>(level_intermed.value()); - -#ifndef DEVELOPER_SERVER - if (user->GetMaxGMLevel() == eGameMasterLevel::JUNIOR_DEVELOPER) { - level = eGameMasterLevel::CIVILIAN; - } -#endif - - if (level > user->GetMaxGMLevel()) level = user->GetMaxGMLevel(); - - if (level == entity->GetGMLevel()) return; - bool success = user->GetMaxGMLevel() >= level; - - if (success) { - WorldPackets::SendGMLevelChange(entity->GetSystemAddress(), success, user->GetMaxGMLevel(), entity->GetGMLevel(), level); - GameMessages::SendChatModeUpdate(entity->GetObjectID(), level); - entity->SetGMLevel(level); - LOG("User %s (%i) has changed their GM level to %i for charID %llu", user->GetUsername().c_str(), user->GetAccountID(), level, entity->GetObjectID()); - } - -#ifndef DEVELOPER_SERVER - if ((entity->GetGMLevel() > user->GetMaxGMLevel()) || (entity->GetGMLevel() > eGameMasterLevel::CIVILIAN && user->GetMaxGMLevel() == eGameMasterLevel::JUNIOR_DEVELOPER)) { - WorldPackets::SendGMLevelChange(entity->GetSystemAddress(), true, user->GetMaxGMLevel(), entity->GetGMLevel(), eGameMasterLevel::CIVILIAN); - GameMessages::SendChatModeUpdate(entity->GetObjectID(), eGameMasterLevel::CIVILIAN); - entity->SetGMLevel(eGameMasterLevel::CIVILIAN); - - GameMessages::SendToggleGMInvis(entity->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS); - - GameMessages::SendSlashCommandFeedbackText(entity, u"Your game master level has been changed, you may not be able to use all commands."); - } -#endif - } - - - void ToggleNameplate(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - if ((Game::config->GetValue("allow_nameplate_off") != "1" && entity->GetGMLevel() < eGameMasterLevel::DEVELOPER)) return; - - auto* character = entity->GetCharacter(); - if (character && character->GetBillboardVisible()) { - character->SetBillboardVisible(false); - GameMessages::SendSlashCommandFeedbackText(entity, u"Your nameplate has been turned off and is not visible to players currently in this zone."); - } else { - character->SetBillboardVisible(true); - GameMessages::SendSlashCommandFeedbackText(entity, u"Your nameplate is now on and visible to all players."); - } - } - - void ToggleSkipCinematics(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - if (Game::config->GetValue("allow_players_to_skip_cinematics") != "1" && entity->GetGMLevel() < eGameMasterLevel::DEVELOPER) return; - auto* character = entity->GetCharacter(); - if (!character) return; - bool current = character->GetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS); - character->SetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS, !current); - if (!current) { - GameMessages::SendSlashCommandFeedbackText(entity, u"You have elected to skip cinematics. Note that not all cinematics can be skipped, but most will be skipped now."); - } else { - GameMessages::SendSlashCommandFeedbackText(entity, u"Cinematics will no longer be skipped."); - } - } - - void ResetMission(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - const auto missionId = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); - if (!missionId) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid mission ID."); - return; - } - - auto* missionComponent = entity->GetComponent<MissionComponent>(); - if (!missionComponent) return; - missionComponent->ResetMission(missionId.value()); - } - - void SetMinifig(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.size() < 2) return; - - const auto minifigItemIdExists = GeneralUtils::TryParse<int32_t>(splitArgs[1]); - if (!minifigItemIdExists) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid Minifig Item Id ID."); - return; - } - const int32_t minifigItemId = minifigItemIdExists.value(); - Game::entityManager->DestructEntity(entity, sysAddr); - auto* charComp = entity->GetComponent<CharacterComponent>(); - std::string lowerName = splitArgs[0]; - if (lowerName.empty()) return; - std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower); - if (lowerName == "eyebrows") { - charComp->m_Character->SetEyebrows(minifigItemId); - } else if (lowerName == "eyes") { - charComp->m_Character->SetEyes(minifigItemId); - } else if (lowerName == "haircolor") { - charComp->m_Character->SetHairColor(minifigItemId); - } else if (lowerName == "hairstyle") { - charComp->m_Character->SetHairStyle(minifigItemId); - } else if (lowerName == "pants") { - charComp->m_Character->SetPantsColor(minifigItemId); - } else if (lowerName == "lefthand") { - charComp->m_Character->SetLeftHand(minifigItemId); - } else if (lowerName == "mouth") { - charComp->m_Character->SetMouth(minifigItemId); - } else if (lowerName == "righthand") { - charComp->m_Character->SetRightHand(minifigItemId); - } else if (lowerName == "shirtcolor") { - charComp->m_Character->SetShirtColor(minifigItemId); - } else if (lowerName == "hands") { - charComp->m_Character->SetLeftHand(minifigItemId); - charComp->m_Character->SetRightHand(minifigItemId); - } else { - Game::entityManager->ConstructEntity(entity); - ChatPackets::SendSystemMessage(sysAddr, u"Invalid Minifig item to change, try one of the following: Eyebrows, Eyes, HairColor, HairStyle, Pants, LeftHand, Mouth, RightHand, Shirt, Hands"); - return; - } - - Game::entityManager->ConstructEntity(entity); - ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(lowerName) + u" set to " + (GeneralUtils::to_u16string(minifigItemId))); - - GameMessages::SendToggleGMInvis(entity->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS); // need to retoggle because it gets reenabled on creation of new character - } - - void PlayAnimation(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - std::u16string anim = GeneralUtils::ASCIIToUTF16(splitArgs[0], splitArgs[0].size()); - RenderComponent::PlayAnimation(entity, anim); - auto* possessorComponent = entity->GetComponent<PossessorComponent>(); - if (possessorComponent) { - auto* possessedComponent = Game::entityManager->GetEntity(possessorComponent->GetPossessable()); - if (possessedComponent) RenderComponent::PlayAnimation(possessedComponent, anim); - } - } - - void ListSpawns(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - for (const auto& pair : Game::entityManager->GetSpawnPointEntities()) { - ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(pair.first)); - } - - ChatPackets::SendSystemMessage(sysAddr, u"Current: " + GeneralUtils::ASCIIToUTF16(entity->GetCharacter()->GetTargetScene())); - } - - void UnlockEmote(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - const auto emoteID = GeneralUtils::TryParse<int32_t>(splitArgs[0]); - - if (!emoteID) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid emote ID."); - return; - } - - entity->GetCharacter()->UnlockEmote(emoteID.value()); - } - - void ForceSave(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - entity->GetCharacter()->SaveXMLToDatabase(); - } - - void Kill(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - ChatPackets::SendSystemMessage(sysAddr, u"Brutally murdering that player, if online on this server."); - - auto* player = PlayerManager::GetPlayer(splitArgs[0]); - if (player) { - player->Smash(entity->GetObjectID()); - ChatPackets::SendSystemMessage(sysAddr, u"It has been done, do you feel good about yourself now?"); - return; - } - - ChatPackets::SendSystemMessage(sysAddr, u"They were saved from your carnage."); - return; - } - - void SpeedBoost(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - const auto boostOptional = GeneralUtils::TryParse<float>(splitArgs[0]); - if (!boostOptional) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid boost."); - return; - } - const float boost = boostOptional.value(); - - auto* controllablePhysicsComponent = entity->GetComponent<ControllablePhysicsComponent>(); - - if (!controllablePhysicsComponent) return; - controllablePhysicsComponent->SetSpeedMultiplier(boost); - - // speedboost possessables - auto possessor = entity->GetComponent<PossessorComponent>(); - if (possessor) { - auto possessedID = possessor->GetPossessable(); - if (possessedID != LWOOBJID_EMPTY) { - auto possessable = Game::entityManager->GetEntity(possessedID); - if (possessable) { - auto* possessControllablePhysicsComponent = possessable->GetComponent<ControllablePhysicsComponent>(); - if (possessControllablePhysicsComponent) { - possessControllablePhysicsComponent->SetSpeedMultiplier(boost); - } - } - } - } - - Game::entityManager->SerializeEntity(entity); - } - - void Freecam(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto state = !entity->GetVar<bool>(u"freecam"); - entity->SetVar<bool>(u"freecam", state); - - GameMessages::SendSetPlayerControlScheme(entity, static_cast<eControlScheme>(state ? 9 : 1)); - - ChatPackets::SendSystemMessage(sysAddr, u"Toggled freecam."); - } - - void SetControlScheme(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - const auto scheme = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); - - if (!scheme) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid control scheme."); - return; - } - - GameMessages::SendSetPlayerControlScheme(entity, static_cast<eControlScheme>(scheme.value())); - - ChatPackets::SendSystemMessage(sysAddr, u"Switched control scheme."); - } - - void SetUiState(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - AMFArrayValue uiState; - - uiState.Insert("state", splitArgs[0]); - - GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, "pushGameState", uiState); - - ChatPackets::SendSystemMessage(sysAddr, u"Switched UI state."); - } - - void Toggle(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - AMFArrayValue amfArgs; - - amfArgs.Insert("visible", true); - - GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, splitArgs[0], amfArgs); - - ChatPackets::SendSystemMessage(sysAddr, u"Toggled UI state."); - } - - void SetInventorySize(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - const auto sizeOptional = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); - if (!sizeOptional) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid size."); - return; - } - const uint32_t size = sizeOptional.value(); - - eInventoryType selectedInventory = eInventoryType::ITEMS; - - // a possible inventory was provided if we got more than 1 argument - if (splitArgs.size() >= 2) { - selectedInventory = GeneralUtils::TryParse<eInventoryType>(splitArgs.at(1)).value_or(eInventoryType::INVALID); - if (selectedInventory == eInventoryType::INVALID) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid inventory."); - return; - } else { - // In this case, we treat the input as a string and try to find it in the reflection list - std::transform(splitArgs.at(1).begin(), splitArgs.at(1).end(), splitArgs.at(1).begin(), ::toupper); - for (uint32_t index = 0; index < NUMBER_OF_INVENTORIES; index++) { - if (std::string_view(splitArgs.at(1)) == std::string_view(InventoryType::InventoryTypeToString(static_cast<eInventoryType>(index)))) selectedInventory = static_cast<eInventoryType>(index); - } - } - - ChatPackets::SendSystemMessage(sysAddr, u"Setting inventory " + - GeneralUtils::ASCIIToUTF16(splitArgs.at(1)) + - u" to size " + - GeneralUtils::to_u16string(size)); - } else ChatPackets::SendSystemMessage(sysAddr, u"Setting inventory ITEMS to size " + GeneralUtils::to_u16string(size)); - - auto* inventoryComponent = entity->GetComponent<InventoryComponent>(); - if (inventoryComponent) { - auto* inventory = inventoryComponent->GetInventory(selectedInventory); - - inventory->SetSize(size); - } - } - - void RunMacro(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - // Only process if input does not contain separator charaters - if (splitArgs[0].find("/") != std::string::npos) return; - if (splitArgs[0].find("\\") != std::string::npos) return; - - auto infile = Game::assetManager->GetFile(("macros/" + splitArgs[0] + ".scm").c_str()); - - if (!infile) { - ChatPackets::SendSystemMessage(sysAddr, u"Unknown macro! Is the filename right?"); - return; - } - - if (infile.good()) { - std::string line; - while (std::getline(infile, line)) { - // Do this in two separate calls to catch both \n and \r\n - line.erase(std::remove(line.begin(), line.end(), '\n'), line.end()); - line.erase(std::remove(line.begin(), line.end(), '\r'), line.end()); - SlashCommandHandler::HandleChatCommand(GeneralUtils::ASCIIToUTF16(line), entity, sysAddr); - } - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Unknown macro! Is the filename right?"); - } - } - - void AddMission(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - const auto missionID = GeneralUtils::TryParse<uint32_t>(splitArgs.at(0)); - - if (!missionID) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid mission id."); - return; - } - - auto comp = static_cast<MissionComponent*>(entity->GetComponent(eReplicaComponentType::MISSION)); - if (comp) comp->AcceptMission(missionID.value(), true); - } - - void CompleteMission(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - const auto missionID = GeneralUtils::TryParse<uint32_t>(splitArgs.at(0)); - - if (!missionID) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid mission id."); - return; - } - - auto comp = static_cast<MissionComponent*>(entity->GetComponent(eReplicaComponentType::MISSION)); - if (comp) comp->CompleteMission(missionID.value(), true); - } - - void SetFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.size() == 1) { - const auto flagId = GeneralUtils::TryParse<int32_t>(splitArgs.at(0)); - - if (!flagId) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id."); - return; - } - - entity->GetCharacter()->SetPlayerFlag(flagId.value(), true); - } else if (splitArgs.size() >= 2) { - const auto flagId = GeneralUtils::TryParse<int32_t>(splitArgs.at(1)); - std::string onOffFlag = splitArgs.at(0); - if (!flagId) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id."); - return; - } - - if (onOffFlag != "off" && onOffFlag != "on") { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag type."); - return; - } - - entity->GetCharacter()->SetPlayerFlag(flagId.value(), onOffFlag == "on"); - } - } - - void ClearFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - const auto flagId = GeneralUtils::TryParse<int32_t>(splitArgs.at(0)); - - if (!flagId) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id."); - return; - } - - entity->GetCharacter()->SetPlayerFlag(flagId.value(), false); - } - - void PlayEffect(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.size() < 3) return; - - const auto effectID = GeneralUtils::TryParse<int32_t>(splitArgs.at(0)); - - if (!effectID) return; - - // FIXME: use fallible ASCIIToUTF16 conversion, because non-ascii isn't valid anyway - GameMessages::SendPlayFXEffect(entity->GetObjectID(), effectID.value(), GeneralUtils::ASCIIToUTF16(splitArgs.at(1)), splitArgs.at(2)); - } - - void StopEffect(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - GameMessages::SendStopFXEffect(entity, true, splitArgs[0]); - } - - void SetAnnTitle(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - entity->GetCharacter()->SetAnnouncementTitle(args); - } - - void SetAnnMsg(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - entity->GetCharacter()->SetAnnouncementMessage(args); - } - - void Announce(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - if (entity->GetCharacter()->GetAnnouncementTitle().empty() || entity->GetCharacter()->GetAnnouncementMessage().empty()) { - ChatPackets::SendSystemMessage(sysAddr, u"Use /setanntitle <title> & /setannmsg <msg> first!"); - return; - } - - SlashCommandHandler::SendAnnouncement(entity->GetCharacter()->GetAnnouncementTitle(), entity->GetCharacter()->GetAnnouncementMessage()); - } - - void ShutdownUniverse(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - //Tell the master server that we're going to be shutting down whole "universe": - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::MASTER, eMasterMessageType::SHUTDOWN_UNIVERSE); - Game::server->SendToMaster(bitStream); - ChatPackets::SendSystemMessage(sysAddr, u"Sent universe shutdown notification to master."); - - //Tell chat to send an announcement to all servers - SlashCommandHandler::SendAnnouncement("Servers Closing Soon!", "DLU servers will close for maintenance in 10 minutes from now."); - } - - void GetNavmeshHeight(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - auto control = static_cast<ControllablePhysicsComponent*>(entity->GetComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS)); - if (!control) return; - - float y = dpWorld::GetNavMesh()->GetHeightAtPoint(control->GetPosition()); - std::u16string msg = u"Navmesh height: " + (GeneralUtils::to_u16string(y)); - ChatPackets::SendSystemMessage(sysAddr, msg); - } - - void GmAddItem(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - - if (splitArgs.size() == 1) { - const auto itemLOT = GeneralUtils::TryParse<uint32_t>(splitArgs.at(0)); - - if (!itemLOT) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid item LOT."); - return; - } - - InventoryComponent* inventory = static_cast<InventoryComponent*>(entity->GetComponent(eReplicaComponentType::INVENTORY)); - - inventory->AddItem(itemLOT.value(), 1, eLootSourceType::MODERATION); - } else if (splitArgs.size() == 2) { - const auto itemLOT = GeneralUtils::TryParse<uint32_t>(splitArgs.at(0)); - if (!itemLOT) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid item LOT."); - return; - } - - const auto count = GeneralUtils::TryParse<uint32_t>(splitArgs.at(1)); - if (!count) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid item count."); - return; - } - - InventoryComponent* inventory = static_cast<InventoryComponent*>(entity->GetComponent(eReplicaComponentType::INVENTORY)); - - inventory->AddItem(itemLOT.value(), count.value(), eLootSourceType::MODERATION); - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /gmadditem <lot>"); - } - } - - void Teleport(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - - NiPoint3 pos{}; - if (splitArgs.size() == 3) { - - const auto x = GeneralUtils::TryParse<float>(splitArgs.at(0)); - if (!x) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid x."); - return; - } - - const auto y = GeneralUtils::TryParse<float>(splitArgs.at(1)); - if (!y) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid y."); - return; - } - - const auto z = GeneralUtils::TryParse<float>(splitArgs.at(2)); - if (!z) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid z."); - return; - } - - pos.SetX(x.value()); - pos.SetY(y.value()); - pos.SetZ(z.value()); - - LOG("Teleporting objectID: %llu to %f, %f, %f", entity->GetObjectID(), pos.x, pos.y, pos.z); - GameMessages::SendTeleport(entity->GetObjectID(), pos, NiQuaternion(), sysAddr); - } else if (splitArgs.size() == 2) { - - const auto x = GeneralUtils::TryParse<float>(splitArgs.at(0)); - if (!x) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid x."); - return; - } - - const auto z = GeneralUtils::TryParse<float>(splitArgs.at(1)); - if (!z) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid z."); - return; - } - - pos.SetX(x.value()); - pos.SetY(0.0f); - pos.SetZ(z.value()); - - LOG("Teleporting objectID: %llu to X: %f, Z: %f", entity->GetObjectID(), pos.x, pos.z); - GameMessages::SendTeleport(entity->GetObjectID(), pos, NiQuaternion(), sysAddr); - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /teleport <x> (<y>) <z> - if no Y given, will teleport to the height of the terrain (or any physics object)."); - } - - auto* possessorComponent = entity->GetComponent<PossessorComponent>(); - if (possessorComponent) { - auto* possassableEntity = Game::entityManager->GetEntity(possessorComponent->GetPossessable()); - - if (possassableEntity != nullptr) { - auto* havokVehiclePhysicsComponent = possassableEntity->GetComponent<HavokVehiclePhysicsComponent>(); - if (havokVehiclePhysicsComponent) { - havokVehiclePhysicsComponent->SetPosition(pos); - Game::entityManager->SerializeEntity(possassableEntity); - } else GameMessages::SendTeleport(possassableEntity->GetObjectID(), pos, NiQuaternion(), sysAddr); - } - } - } - - void TpAll(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto pos = entity->GetPosition(); - - const auto characters = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::CHARACTER); - - for (auto* character : characters) { - GameMessages::SendTeleport(character->GetObjectID(), pos, NiQuaternion(), character->GetSystemAddress()); - } - } - - void Dismount(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - auto* possessorComponent = entity->GetComponent<PossessorComponent>(); - if (possessorComponent) { - auto possessableId = possessorComponent->GetPossessable(); - if (possessableId != LWOOBJID_EMPTY) { - auto* possessableEntity = Game::entityManager->GetEntity(possessableId); - if (possessableEntity) possessorComponent->Dismount(possessableEntity, true); - } - } - } - - void BuffMe(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - auto dest = static_cast<DestroyableComponent*>(entity->GetComponent(eReplicaComponentType::DESTROYABLE)); - if (dest) { - dest->SetHealth(999); - dest->SetMaxHealth(999.0f); - dest->SetArmor(999); - dest->SetMaxArmor(999.0f); - dest->SetImagination(999); - dest->SetMaxImagination(999.0f); - } - Game::entityManager->SerializeEntity(entity); - } - - void StartCelebration(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - const auto celebration = GeneralUtils::TryParse<int32_t>(splitArgs.at(0)); - - if (!celebration) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid celebration."); - return; - } - - GameMessages::SendStartCelebrationEffect(entity, entity->GetSystemAddress(), celebration.value()); - } - - void BuffMed(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - auto dest = static_cast<DestroyableComponent*>(entity->GetComponent(eReplicaComponentType::DESTROYABLE)); - if (dest) { - dest->SetHealth(9); - dest->SetMaxHealth(9.0f); - dest->SetArmor(9); - dest->SetMaxArmor(9.0f); - dest->SetImagination(9); - dest->SetMaxImagination(9.0f); - } - Game::entityManager->SerializeEntity(entity); - } - - void RefillStats(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - auto dest = static_cast<DestroyableComponent*>(entity->GetComponent(eReplicaComponentType::DESTROYABLE)); - if (dest) { - dest->SetHealth(static_cast<int32_t>(dest->GetMaxHealth())); - dest->SetArmor(static_cast<int32_t>(dest->GetMaxArmor())); - dest->SetImagination(static_cast<int32_t>(dest->GetMaxImagination())); - } - - Game::entityManager->SerializeEntity(entity); - } - - void Lookup(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - auto query = CDClientDatabase::CreatePreppedStmt( - "SELECT `id`, `name` FROM `Objects` WHERE `displayName` LIKE ?1 OR `name` LIKE ?1 OR `description` LIKE ?1 LIMIT 50"); - - const std::string query_text = "%" + args + "%"; - query.bind(1, query_text.c_str()); - - auto tables = query.execQuery(); - - while (!tables.eof()) { - std::string message = std::to_string(tables.getIntField(0)) + " - " + tables.getStringField(1); - ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::UTF8ToUTF16(message, message.size())); - tables.nextRow(); - } - } - - void Spawn(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - ControllablePhysicsComponent* comp = static_cast<ControllablePhysicsComponent*>(entity->GetComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS)); - if (!comp) return; - - const auto lot = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); - - if (!lot) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid lot."); - return; - } - - EntityInfo info; - info.lot = lot.value(); - info.pos = comp->GetPosition(); - info.rot = comp->GetRotation(); - info.spawner = nullptr; - info.spawnerID = entity->GetObjectID(); - info.spawnerNodeID = 0; - - Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr); - - if (newEntity == nullptr) { - ChatPackets::SendSystemMessage(sysAddr, u"Failed to spawn entity."); - return; - } - - Game::entityManager->ConstructEntity(newEntity); - } - - void SpawnGroup(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.size() < 3) return; - - const auto lot = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); - if (!lot) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid lot."); - return; - } - - const auto numberToSpawnOptional = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); - if (!numberToSpawnOptional && numberToSpawnOptional.value() > 0) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid number of enemies to spawn."); - return; - } - uint32_t numberToSpawn = numberToSpawnOptional.value(); - - // Must spawn within a radius of at least 0.0f - const auto radiusToSpawnWithinOptional = GeneralUtils::TryParse<float>(splitArgs[2]); - if (!radiusToSpawnWithinOptional && radiusToSpawnWithinOptional.value() < 0.0f) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid radius to spawn within."); - return; - } - const float radiusToSpawnWithin = radiusToSpawnWithinOptional.value(); - - EntityInfo info; - info.lot = lot.value(); - info.spawner = nullptr; - info.spawnerID = entity->GetObjectID(); - info.spawnerNodeID = 0; - - auto playerPosition = entity->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 = Game::entityManager->CreateEntity(info); - if (newEntity == nullptr) { - ChatPackets::SendSystemMessage(sysAddr, u"Failed to spawn entity."); - return; - } - - Game::entityManager->ConstructEntity(newEntity); - numberToSpawn--; - } - } - - void GiveUScore(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - const auto uscoreOptional = GeneralUtils::TryParse<int32_t>(splitArgs[0]); - if (!uscoreOptional) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid uscore."); - return; - } - const int32_t uscore = uscoreOptional.value(); - - CharacterComponent* character = entity->GetComponent<CharacterComponent>(); - if (character) character->SetUScore(character->GetUScore() + uscore); - // MODERATION should work but it doesn't. Relog to see uscore changes - - eLootSourceType lootType = eLootSourceType::MODERATION; - - if (splitArgs.size() >= 2) { - const auto type = GeneralUtils::TryParse<eLootSourceType>(splitArgs[1]); - lootType = type.value_or(lootType); - } - - GameMessages::SendModifyLEGOScore(entity, entity->GetSystemAddress(), uscore, lootType); - } - - void SetLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - // We may be trying to set a specific players level to a level. If so override the entity with the requested players. - std::string requestedPlayerToSetLevelOf = ""; - if (splitArgs.size() > 1) { - requestedPlayerToSetLevelOf = splitArgs[1]; - - auto requestedPlayer = PlayerManager::GetPlayer(requestedPlayerToSetLevelOf); - - if (!requestedPlayer) { - ChatPackets::SendSystemMessage(sysAddr, u"No player found with username: (" + GeneralUtils::UTF8ToUTF16(requestedPlayerToSetLevelOf) + u")."); - return; - } - - if (!requestedPlayer->GetOwner()) { - ChatPackets::SendSystemMessage(sysAddr, u"No entity found with username: (" + GeneralUtils::UTF8ToUTF16(requestedPlayerToSetLevelOf) + u")."); - return; - } - - entity = requestedPlayer->GetOwner(); - } - const auto requestedLevelOptional = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); - uint32_t oldLevel; - - // first check the level is valid - if (!requestedLevelOptional) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid level."); - return; - } - uint32_t requestedLevel = requestedLevelOptional.value(); - // query to set our uscore to the correct value for this level - - auto characterComponent = entity->GetComponent<CharacterComponent>(); - if (!characterComponent) return; - auto levelComponent = entity->GetComponent<LevelProgressionComponent>(); - auto query = CDClientDatabase::CreatePreppedStmt("SELECT requiredUScore from LevelProgressionLookup WHERE id = ?;"); - query.bind(1, static_cast<int>(requestedLevel)); - auto result = query.execQuery(); - - if (result.eof()) return; - - // Set the UScore first - oldLevel = levelComponent->GetLevel(); - characterComponent->SetUScore(result.getIntField(0, characterComponent->GetUScore())); - - // handle level up for each level we have passed if we set our level to be higher than the current one. - if (oldLevel < requestedLevel) { - while (oldLevel < requestedLevel) { - oldLevel += 1; - levelComponent->SetLevel(oldLevel); - levelComponent->HandleLevelUp(); - } - } else { - levelComponent->SetLevel(requestedLevel); - } - - if (requestedPlayerToSetLevelOf != "") { - ChatPackets::SendSystemMessage( - sysAddr, u"Set " + GeneralUtils::UTF8ToUTF16(requestedPlayerToSetLevelOf) + u"'s level to " + GeneralUtils::to_u16string(requestedLevel) + - u" and UScore to " + GeneralUtils::to_u16string(characterComponent->GetUScore()) + - u". Relog to see changes."); - } else { - ChatPackets::SendSystemMessage( - sysAddr, u"Set your level to " + GeneralUtils::to_u16string(requestedLevel) + - u" and UScore to " + GeneralUtils::to_u16string(characterComponent->GetUScore()) + - u". Relog to see changes."); - } - } - - void Pos(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto position = entity->GetPosition(); - - ChatPackets::SendSystemMessage(sysAddr, u"<" + (GeneralUtils::to_u16string(position.x)) + u", " + (GeneralUtils::to_u16string(position.y)) + u", " + (GeneralUtils::to_u16string(position.z)) + u">"); - - LOG("Position: %f, %f, %f", position.x, position.y, position.z); - } - - void Rot(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto rotation = entity->GetRotation(); - - ChatPackets::SendSystemMessage(sysAddr, u"<" + (GeneralUtils::to_u16string(rotation.w)) + u", " + (GeneralUtils::to_u16string(rotation.x)) + u", " + (GeneralUtils::to_u16string(rotation.y)) + u", " + (GeneralUtils::to_u16string(rotation.z)) + u">"); - - LOG("Rotation: %f, %f, %f, %f", rotation.w, rotation.x, rotation.y, rotation.z); - } - - void LocRow(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto position = entity->GetPosition(); - const auto rotation = entity->GetRotation(); - - LOG("<location x=\"%f\" y=\"%f\" z=\"%f\" rw=\"%f\" rx=\"%f\" ry=\"%f\" rz=\"%f\" />", position.x, position.y, position.z, rotation.w, rotation.x, rotation.y, rotation.z); - } - - void PlayLvlFx(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - GameMessages::SendPlayFXEffect(entity, 7074, u"create", "7074", LWOOBJID_EMPTY, 1.0f, 1.0f, true); - } - - void PlayRebuildFx(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - GameMessages::SendPlayFXEffect(entity, 230, u"rebuild", "230", LWOOBJID_EMPTY, 1.0f, 1.0f, true); - } - - void FreeMoney(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - const auto money = GeneralUtils::TryParse<int64_t>(splitArgs[0]); - - if (!money) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid money."); - return; - } - - auto* ch = entity->GetCharacter(); - ch->SetCoins(ch->GetCoins() + money.value(), eLootSourceType::MODERATION); - } - - void SetCurrency(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - const auto money = GeneralUtils::TryParse<int64_t>(splitArgs[0]); - - if (!money) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid money."); - return; - } - - auto* ch = entity->GetCharacter(); - ch->SetCoins(money.value(), eLootSourceType::MODERATION); - } - - void Buff(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.size() < 2) return; - - auto* buffComponent = entity->GetComponent<BuffComponent>(); - - const auto id = GeneralUtils::TryParse<int32_t>(splitArgs[0]); - if (!id) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid buff id."); - return; - } - - const auto duration = GeneralUtils::TryParse<int32_t>(splitArgs[1]); - if (!duration) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid buff duration."); - return; - } - - if (buffComponent) buffComponent->ApplyBuff(id.value(), duration.value(), entity->GetObjectID()); - } - - void TestMap(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - ChatPackets::SendSystemMessage(sysAddr, u"Requesting map change..."); - LWOCLONEID cloneId = 0; - bool force = false; - - const auto reqZoneOptional = GeneralUtils::TryParse<LWOMAPID>(splitArgs[0]); - if (!reqZoneOptional) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid zone."); - return; - } - const LWOMAPID reqZone = reqZoneOptional.value(); - - if (splitArgs.size() > 1) { - auto index = 1; - - if (splitArgs[index] == "force") { - index++; - - force = true; - } - - if (splitArgs.size() > index) { - const auto cloneIdOptional = GeneralUtils::TryParse<LWOCLONEID>(splitArgs[index]); - if (!cloneIdOptional) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid clone id."); - return; - } - cloneId = cloneIdOptional.value(); - } - } - - const auto objid = entity->GetObjectID(); - - if (force || Game::zoneManager->CheckIfAccessibleZone(reqZone)) { // to prevent tomfoolery - - ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, reqZone, cloneId, false, [objid](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) { - - auto* entity = Game::entityManager->GetEntity(objid); - if (!entity) return; - - const auto sysAddr = entity->GetSystemAddress(); - - ChatPackets::SendSystemMessage(sysAddr, u"Transfering map..."); - - LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", sysAddr.ToString(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort); - if (entity->GetCharacter()) { - entity->GetCharacter()->SetZoneID(zoneID); - entity->GetCharacter()->SetZoneInstance(zoneInstance); - entity->GetCharacter()->SetZoneClone(zoneClone); - entity->GetComponent<CharacterComponent>()->SetLastRocketConfig(u""); - } - - entity->GetCharacter()->SaveXMLToDatabase(); - - WorldPackets::SendTransferToWorld(sysAddr, serverIP, serverPort, mythranShift); - return; - }); - } else { - std::string msg = "ZoneID not found or allowed: "; - msg.append(splitArgs[0]); // FIXME: unnecessary utf16 re-encoding just for error - ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::UTF8ToUTF16(msg, msg.size())); - } - } - - void CreatePrivate(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.size() < 3) return; - - const auto zone = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); - if (!zone) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid zone."); - return; - } - - const auto clone = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); - if (!clone) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid clone."); - return; - } - - const auto& password = splitArgs[2]; - - ZoneInstanceManager::Instance()->CreatePrivateZone(Game::server, zone.value(), clone.value(), password); - - ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16("Sent request for private zone with password: " + password)); - } - - void DebugUi(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - ChatPackets::SendSystemMessage(sysAddr, u"Opening UIDebugger..."); - } - - void Boost(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - - auto* possessorComponent = entity->GetComponent<PossessorComponent>(); - - if (possessorComponent == nullptr) { - return; - } - - auto* vehicle = Game::entityManager->GetEntity(possessorComponent->GetPossessable()); - - if (vehicle == nullptr) { - return; - } - - if (splitArgs.size() >= 1) { - const auto time = GeneralUtils::TryParse<float>(splitArgs[0]); - - if (!time) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid boost time."); - return; - } else { - GameMessages::SendVehicleAddPassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); - entity->AddCallbackTimer(time.value(), [vehicle]() { - if (!vehicle) return; - GameMessages::SendVehicleRemovePassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); - }); - } - } else { - GameMessages::SendVehicleAddPassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); - } - } - - void Unboost(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - auto* possessorComponent = entity->GetComponent<PossessorComponent>(); - - if (possessorComponent == nullptr) return; - auto* vehicle = Game::entityManager->GetEntity(possessorComponent->GetPossessable()); - - if (vehicle == nullptr) return; - GameMessages::SendVehicleRemovePassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); - } - - void ActivateSpawner(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - auto spawners = Game::zoneManager->GetSpawnersByName(splitArgs[0]); - - for (auto* spawner : spawners) { - spawner->Activate(); - } - - spawners = Game::zoneManager->GetSpawnersInGroup(splitArgs[0]); - - for (auto* spawner : spawners) { - spawner->Activate(); - } - } - - void SpawnPhysicsVerts(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - //Go tell physics to spawn all the vertices: - auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::PHANTOM_PHYSICS); - for (auto en : entities) { - auto phys = static_cast<PhantomPhysicsComponent*>(en->GetComponent(eReplicaComponentType::PHANTOM_PHYSICS)); - if (phys) - phys->SpawnVertices(); - } - } - - void ReportProxPhys(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::PROXIMITY_MONITOR); - for (auto en : entities) { - auto phys = static_cast<ProximityMonitorComponent*>(en->GetComponent(eReplicaComponentType::PROXIMITY_MONITOR)); - if (phys) { - for (auto prox : phys->GetProximitiesData()) { - if (!prox.second) continue; - - auto sphere = static_cast<dpShapeSphere*>(prox.second->GetShape()); - auto pos = prox.second->GetPosition(); - LOG("Proximity: %s, r: %f, pos: %f, %f, %f", prox.first.c_str(), sphere->GetRadius(), pos.x, pos.y, pos.z); - } - } - } - } - - void TriggerSpawner(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - auto spawners = Game::zoneManager->GetSpawnersByName(splitArgs[0]); - - for (auto* spawner : spawners) { - spawner->Spawn(); - } - - spawners = Game::zoneManager->GetSpawnersInGroup(splitArgs[0]); - - for (auto* spawner : spawners) { - spawner->Spawn(); - } - } - - void Reforge(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.size() < 2) return; - - const auto baseItem = GeneralUtils::TryParse<LOT>(splitArgs[0]); - if (!baseItem) return; - - const auto reforgedItem = GeneralUtils::TryParse<LOT>(splitArgs[1]); - if (!reforgedItem) return; - - auto* inventoryComponent = entity->GetComponent<InventoryComponent>(); - if (!inventoryComponent) return; - - std::vector<LDFBaseData*> data{}; - data.push_back(new LDFData<int32_t>(u"reforgedLOT", reforgedItem.value())); - - inventoryComponent->AddItem(baseItem.value(), 1, eLootSourceType::MODERATION, eInventoryType::INVALID, data); - } - - void Crash(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - ChatPackets::SendSystemMessage(sysAddr, u"Crashing..."); - - int* badPtr = nullptr; - *badPtr = 0; - } - - void Metrics(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - for (const auto variable : Metrics::GetAllMetrics()) { - auto* metric = Metrics::GetMetric(variable); - - if (metric == nullptr) { - continue; - } - - ChatPackets::SendSystemMessage( - sysAddr, - GeneralUtils::ASCIIToUTF16(Metrics::MetricVariableToString(variable)) + - u": " + - GeneralUtils::to_u16string(Metrics::ToMiliseconds(metric->average)) + - u"ms" - ); - } - - ChatPackets::SendSystemMessage( - sysAddr, - u"Peak RSS: " + GeneralUtils::to_u16string(static_cast<float>(static_cast<double>(Metrics::GetPeakRSS()) / 1.024e6)) + - u"MB" - ); - - ChatPackets::SendSystemMessage( - sysAddr, - u"Current RSS: " + GeneralUtils::to_u16string(static_cast<float>(static_cast<double>(Metrics::GetCurrentRSS()) / 1.024e6)) + - u"MB" - ); - - ChatPackets::SendSystemMessage( - sysAddr, - u"Process ID: " + GeneralUtils::to_u16string(Metrics::GetProcessID()) - ); - } - - void ReloadConfig(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - Game::config->ReloadConfig(); - VanityUtilities::SpawnVanity(); - dpWorld::Reload(); - auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::SCRIPTED_ACTIVITY); - for (const auto* const entity : entities) { - auto* const scriptedActivityComponent = entity->GetComponent<ScriptedActivityComponent>(); - if (!scriptedActivityComponent) continue; - - scriptedActivityComponent->ReloadConfig(); - } - Game::server->UpdateMaximumMtuSize(); - Game::server->UpdateBandwidthLimit(); - ChatPackets::SendSystemMessage(sysAddr, u"Successfully reloaded config for world!"); - } - - void RollLoot(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.size() < 3) return; - - const auto lootMatrixIndex = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); - if (!lootMatrixIndex) return; - - const auto targetLot = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); - if (!targetLot) return; - - const auto loops = GeneralUtils::TryParse<uint32_t>(splitArgs[2]); - if (!loops) return; - - uint64_t totalRuns = 0; - - for (uint32_t i = 0; i < loops; i++) { - while (true) { - auto lootRoll = Loot::RollLootMatrix(lootMatrixIndex.value()); - totalRuns += 1; - bool doBreak = false; - for (const auto& kv : lootRoll) { - if (static_cast<uint32_t>(kv.first) == targetLot) { - doBreak = true; - } - } - if (doBreak) break; - } - } - - std::u16string message = u"Ran loot drops looking for " - + GeneralUtils::to_u16string(targetLot.value()) - + u", " - + GeneralUtils::to_u16string(loops.value()) - + u" times. It ran " - + GeneralUtils::to_u16string(totalRuns) - + u" times. Averaging out at " - + GeneralUtils::to_u16string(static_cast<float>(totalRuns) / loops.value()); - - ChatPackets::SendSystemMessage(sysAddr, message); - } - - void DeleteInven(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - eInventoryType inventoryType = eInventoryType::INVALID; - - const auto inventoryTypeOptional = GeneralUtils::TryParse<eInventoryType>(splitArgs[0]); - if (!inventoryTypeOptional) { - // In this case, we treat the input as a string and try to find it in the reflection list - std::transform(splitArgs[0].begin(), splitArgs[0].end(), splitArgs[0].begin(), ::toupper); - LOG("looking for inventory %s", splitArgs[0].c_str()); - for (uint32_t index = 0; index < NUMBER_OF_INVENTORIES; index++) { - if (std::string_view(splitArgs[0]) == std::string_view(InventoryType::InventoryTypeToString(static_cast<eInventoryType>(index)))) inventoryType = static_cast<eInventoryType>(index); - } - } else { - inventoryType = inventoryTypeOptional.value(); - } - - if (inventoryType == eInventoryType::INVALID || inventoryType >= NUMBER_OF_INVENTORIES) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid inventory provided."); - return; - } - - auto* inventoryComponent = entity->GetComponent<InventoryComponent>(); - if (!inventoryComponent) return; - - auto* inventoryToDelete = inventoryComponent->GetInventory(inventoryType); - if (!inventoryToDelete) return; - - inventoryToDelete->DeleteAllItems(); - LOG("Deleted inventory %s for user %llu", splitArgs[0].c_str(), entity->GetObjectID()); - ChatPackets::SendSystemMessage(sysAddr, u"Deleted inventory " + GeneralUtils::UTF8ToUTF16(splitArgs[0])); - } - - void CastSkill(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - auto* skillComponent = entity->GetComponent<SkillComponent>(); - if (skillComponent) { - const auto skillId = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); - - if (!skillId) { - ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill ID."); - return; - } else { - skillComponent->CastSkill(skillId.value(), entity->GetObjectID(), entity->GetObjectID()); - ChatPackets::SendSystemMessage(sysAddr, u"Cast skill"); - } - } - } - - void SetSkillSlot(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.size() < 2) return; - - auto* const inventoryComponent = entity->GetComponent<InventoryComponent>(); - if (inventoryComponent) { - const auto slot = GeneralUtils::TryParse<BehaviorSlot>(splitArgs[0]); - if (!slot) { - ChatPackets::SendSystemMessage(sysAddr, u"Error getting slot."); - return; - } else { - const auto skillId = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); - if (!skillId) { - ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill."); - return; - } else { - if (inventoryComponent->SetSkill(slot.value(), skillId.value())) ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot successfully"); - else ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot failed"); - } - } - } - } - - void SetFaction(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); - if (destroyableComponent) { - const auto faction = GeneralUtils::TryParse<int32_t>(splitArgs[0]); - - if (!faction) { - ChatPackets::SendSystemMessage(sysAddr, u"Error getting faction."); - return; - } else { - destroyableComponent->SetFaction(faction.value()); - ChatPackets::SendSystemMessage(sysAddr, u"Set faction and updated enemies list"); - } - } - } - - void AddFaction(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); - if (destroyableComponent) { - const auto faction = GeneralUtils::TryParse<int32_t>(splitArgs[0]); - - if (!faction) { - ChatPackets::SendSystemMessage(sysAddr, u"Error getting faction."); - return; - } else { - destroyableComponent->AddFaction(faction.value()); - ChatPackets::SendSystemMessage(sysAddr, u"Added faction and updated enemies list"); - } - } - } - - void GetFactions(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); - if (destroyableComponent) { - ChatPackets::SendSystemMessage(sysAddr, u"Friendly factions:"); - for (const auto entry : destroyableComponent->GetFactionIDs()) { - ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry))); - } - - ChatPackets::SendSystemMessage(sysAddr, u"Enemy factions:"); - for (const auto entry : destroyableComponent->GetEnemyFactionsIDs()) { - ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry))); - } - } - } - - void SetRewardCode(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - auto* character = entity->GetCharacter(); - if (!character) return; - auto* user = character->GetParentUser(); - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - auto* cdrewardCodes = CDClientManager::GetTable<CDRewardCodesTable>(); - - auto id = cdrewardCodes->GetCodeID(splitArgs[0]); - if (id != -1) Database::Get()->InsertRewardCode(user->GetAccountID(), id); - } - - void Inspect(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - Entity* closest = nullptr; - - std::u16string ldf; - - bool isLDF = false; - - auto component = GeneralUtils::TryParse<eReplicaComponentType>(splitArgs[0]); - if (!component) { - component = eReplicaComponentType::INVALID; - - ldf = GeneralUtils::UTF8ToUTF16(splitArgs[0]); - - isLDF = true; - } - - auto reference = entity->GetPosition(); - - auto closestDistance = 0.0f; - - const auto candidates = Game::entityManager->GetEntitiesByComponent(component.value()); - - for (auto* candidate : candidates) { - if (candidate->GetLOT() == 1 || candidate->GetLOT() == 8092) { - continue; - } - - if (isLDF && !candidate->HasVar(ldf)) { - continue; - } - - if (!closest) { - closest = candidate; - - closestDistance = NiPoint3::Distance(candidate->GetPosition(), reference); - - continue; - } - - const auto distance = NiPoint3::Distance(candidate->GetPosition(), reference); - - if (distance < closestDistance) { - closest = candidate; - - closestDistance = distance; - } - } - - if (!closest) return; - - Game::entityManager->SerializeEntity(closest); - - auto* table = CDClientManager::GetTable<CDObjectsTable>(); - - const auto& info = table->GetByID(closest->GetLOT()); - - std::stringstream header; - - header << info.name << " [" << std::to_string(info.id) << "]" << " " << std::to_string(closestDistance) << " " << std::to_string(closest->IsSleeping()); - - ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(header.str())); - - for (const auto& pair : closest->GetComponents()) { - auto id = pair.first; - - std::stringstream stream; - - stream << "Component [" << std::to_string(static_cast<uint32_t>(id)) << "]"; - - ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(stream.str())); - } - - if (splitArgs.size() >= 2) { - if (splitArgs[1] == "-m" && splitArgs.size() >= 3) { - auto* const movingPlatformComponent = closest->GetComponent<MovingPlatformComponent>(); - - const auto mValue = GeneralUtils::TryParse<int32_t>(splitArgs[2]); - - if (!movingPlatformComponent || !mValue) return; - - movingPlatformComponent->SetSerialized(true); - - if (mValue == -1) { - movingPlatformComponent->StopPathing(); - } else { - movingPlatformComponent->GotoWaypoint(mValue.value()); - } - - Game::entityManager->SerializeEntity(closest); - } else if (splitArgs[1] == "-a" && splitArgs.size() >= 3) { - RenderComponent::PlayAnimation(closest, splitArgs.at(2)); - } else if (splitArgs[1] == "-s") { - for (auto* entry : closest->GetSettings()) { - ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::UTF8ToUTF16(entry->GetString())); - } - - ChatPackets::SendSystemMessage(sysAddr, u"------"); - ChatPackets::SendSystemMessage(sysAddr, u"Spawner ID: " + GeneralUtils::to_u16string(closest->GetSpawnerID())); - } else if (splitArgs[1] == "-p") { - const auto postion = closest->GetPosition(); - - ChatPackets::SendSystemMessage( - sysAddr, - GeneralUtils::ASCIIToUTF16("< " + std::to_string(postion.x) + ", " + std::to_string(postion.y) + ", " + std::to_string(postion.z) + " >") - ); - } else if (splitArgs[1] == "-f") { - auto* destuctable = closest->GetComponent<DestroyableComponent>(); - - if (destuctable == nullptr) { - ChatPackets::SendSystemMessage(sysAddr, u"No destroyable component on this entity!"); - return; - } - - ChatPackets::SendSystemMessage(sysAddr, u"Smashable: " + (GeneralUtils::to_u16string(destuctable->GetIsSmashable()))); - - ChatPackets::SendSystemMessage(sysAddr, u"Friendly factions:"); - for (const auto entry : destuctable->GetFactionIDs()) { - ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry))); - } - - ChatPackets::SendSystemMessage(sysAddr, u"Enemy factions:"); - for (const auto entry : destuctable->GetEnemyFactionsIDs()) { - ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry))); - } - - if (splitArgs.size() >= 3) { - const auto faction = GeneralUtils::TryParse<int32_t>(splitArgs[2]); - if (!faction) return; - - destuctable->SetFaction(-1); - destuctable->AddFaction(faction.value(), true); - } - } else if (splitArgs[1] == "-cf") { - auto* destuctable = entity->GetComponent<DestroyableComponent>(); - if (!destuctable) { - ChatPackets::SendSystemMessage(sysAddr, u"No destroyable component on this entity!"); - return; - } - if (destuctable->IsEnemy(closest)) ChatPackets::SendSystemMessage(sysAddr, u"They are our enemy"); - else ChatPackets::SendSystemMessage(sysAddr, u"They are NOT our enemy"); - } else if (splitArgs[1] == "-t") { - auto* phantomPhysicsComponent = closest->GetComponent<PhantomPhysicsComponent>(); - - if (phantomPhysicsComponent != nullptr) { - ChatPackets::SendSystemMessage(sysAddr, u"Type: " + (GeneralUtils::to_u16string(static_cast<uint32_t>(phantomPhysicsComponent->GetEffectType())))); - const auto dir = phantomPhysicsComponent->GetDirection(); - ChatPackets::SendSystemMessage(sysAddr, u"Direction: <" + (GeneralUtils::to_u16string(dir.x)) + u", " + (GeneralUtils::to_u16string(dir.y)) + u", " + (GeneralUtils::to_u16string(dir.z)) + u">"); - ChatPackets::SendSystemMessage(sysAddr, u"Multiplier: " + (GeneralUtils::to_u16string(phantomPhysicsComponent->GetDirectionalMultiplier()))); - ChatPackets::SendSystemMessage(sysAddr, u"Active: " + (GeneralUtils::to_u16string(phantomPhysicsComponent->GetPhysicsEffectActive()))); - } - - auto* triggerComponent = closest->GetComponent<TriggerComponent>(); - if (triggerComponent) { - auto trigger = triggerComponent->GetTrigger(); - if (trigger) { - ChatPackets::SendSystemMessage(sysAddr, u"Trigger: " + (GeneralUtils::to_u16string(trigger->id))); - } - } - } - } - } -}; - -namespace GMGreaterThanZeroCommands { - - void Kick(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.size() == 1) { - auto* player = PlayerManager::GetPlayer(splitArgs[0]); - - std::u16string username = GeneralUtils::UTF8ToUTF16(splitArgs[0]); - if (player == nullptr) { - ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + username); - return; - } - - Game::server->Disconnect(player->GetSystemAddress(), eServerDisconnectIdentifiers::KICK); - - ChatPackets::SendSystemMessage(sysAddr, u"Kicked: " + username); - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /kick <username>"); - } - } - - void Ban(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - - if (splitArgs.size() == 1) { - auto* player = PlayerManager::GetPlayer(splitArgs[0]); - - uint32_t accountId = 0; - - if (player == nullptr) { - auto characterInfo = Database::Get()->GetCharacterInfo(splitArgs[0]); - - if (characterInfo) { - accountId = characterInfo->accountId; - } - - if (accountId == 0) { - ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + GeneralUtils::UTF8ToUTF16(splitArgs[0])); - - return; - } - } else { - auto* character = player->GetCharacter(); - auto* user = character != nullptr ? character->GetParentUser() : nullptr; - if (user) accountId = user->GetAccountID(); - } - - if (accountId != 0) Database::Get()->UpdateAccountBan(accountId, true); - - if (player != nullptr) { - Game::server->Disconnect(player->GetSystemAddress(), eServerDisconnectIdentifiers::FREE_TRIAL_EXPIRED); - } - - ChatPackets::SendSystemMessage(sysAddr, u"Banned: " + GeneralUtils::ASCIIToUTF16(splitArgs[0])); - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /ban <username>"); - } - } - - void MailItem(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.size() < 2) return; - - const auto& playerName = splitArgs[0]; - - auto playerInfo = Database::Get()->GetCharacterInfo(playerName); - - uint32_t receiverID = 0; - if (!playerInfo) { - ChatPackets::SendSystemMessage(sysAddr, u"Failed to find that player"); - - return; - } - - receiverID = playerInfo->id; - - const auto lot = GeneralUtils::TryParse<LOT>(splitArgs.at(1)); - - if (!lot) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid item lot."); - return; - } - - IMail::MailInfo mailInsert; - mailInsert.senderId = entity->GetObjectID(); - mailInsert.senderUsername = "Darkflame Universe"; - mailInsert.receiverId = receiverID; - mailInsert.recipient = playerName; - mailInsert.subject = "Lost item"; - mailInsert.body = "This is a replacement item for one you lost."; - mailInsert.itemID = LWOOBJID_EMPTY; - mailInsert.itemLOT = lot.value(); - mailInsert.itemSubkey = LWOOBJID_EMPTY; - mailInsert.itemCount = 1; - Database::Get()->InsertNewMail(mailInsert); - - ChatPackets::SendSystemMessage(sysAddr, u"Mail sent"); - } - - void ApproveProperty(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - if (PropertyManagementComponent::Instance() != nullptr) { - PropertyManagementComponent::Instance()->UpdateApprovedStatus(true); - } - } - - void Mute(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - - if (splitArgs.size() >= 1) { - auto* player = PlayerManager::GetPlayer(splitArgs[0]); - - uint32_t accountId = 0; - LWOOBJID characterId = 0; - - if (player == nullptr) { - auto characterInfo = Database::Get()->GetCharacterInfo(splitArgs[0]); - - if (characterInfo) { - accountId = characterInfo->accountId; - characterId = characterInfo->id; - - GeneralUtils::SetBit(characterId, eObjectBits::CHARACTER); - GeneralUtils::SetBit(characterId, eObjectBits::PERSISTENT); - } - - if (accountId == 0) { - ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + GeneralUtils::UTF8ToUTF16(splitArgs[0])); - - return; - } - } else { - auto* character = player->GetCharacter(); - auto* user = character != nullptr ? character->GetParentUser() : nullptr; - if (user) accountId = user->GetAccountID(); - characterId = player->GetObjectID(); - } - - time_t expire = 1; // Default to indefinate mute - - if (splitArgs.size() >= 2) { - const auto days = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); - if (!days) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid days."); - - return; - } - - std::optional<uint32_t> hours; - if (splitArgs.size() >= 3) { - hours = GeneralUtils::TryParse<uint32_t>(splitArgs[2]); - if (!hours) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid hours."); - - return; - } - } - - expire = time(NULL); - expire += 24 * 60 * 60 * days.value(); - expire += 60 * 60 * hours.value_or(0); - } - - if (accountId != 0) Database::Get()->UpdateAccountUnmuteTime(accountId, expire); - - char buffer[32] = "brought up for review.\0"; - - if (expire != 1) { - std::tm* ptm = std::localtime(&expire); - // Format: Mo, 15.06.2009 20:20:00 - std::strftime(buffer, 32, "%a, %d.%m.%Y %H:%M:%S", ptm); - } - - const auto timeStr = GeneralUtils::ASCIIToUTF16(std::string(buffer)); - - ChatPackets::SendSystemMessage(sysAddr, u"Muted: " + GeneralUtils::UTF8ToUTF16(splitArgs[0]) + u" until " + timeStr); - - //Notify chat about it - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::GM_MUTE); - - bitStream.Write(characterId); - bitStream.Write(expire); - - Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /mute <username> <days (optional)> <hours (optional)>"); - } - } - - void Fly(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - auto* character = entity->GetCharacter(); - - if (character) { - bool isFlying = character->GetIsFlying(); - - if (isFlying) { - GameMessages::SendSetJetPackMode(entity, false); - - character->SetIsFlying(false); - } else { - float speedScale = 1.0f; - - if (splitArgs.size() >= 1) { - const auto tempScaleStore = GeneralUtils::TryParse<float>(splitArgs.at(0)); - - if (tempScaleStore) { - speedScale = tempScaleStore.value(); - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Failed to parse speed scale argument."); - } - } - - float airSpeed = 20 * speedScale; - float maxAirSpeed = 30 * speedScale; - float verticalVelocity = 1.5 * speedScale; - - GameMessages::SendSetJetPackMode(entity, true, true, false, 167, airSpeed, maxAirSpeed, verticalVelocity); - - character->SetIsFlying(true); - } - } - } - - void AttackImmune(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); - - const auto state = GeneralUtils::TryParse<int32_t>(splitArgs[0]); - - if (!state) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid state."); - return; - } - - if (destroyableComponent) destroyableComponent->SetIsImmune(state.value()); - } - - void GmImmune(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; - - auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); - - const auto state = GeneralUtils::TryParse<int32_t>(splitArgs[0]); - - if (!state) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid state."); - return; - } - - if (destroyableComponent) destroyableComponent->SetIsGMImmune(state.value()); - } - - void GmInvis(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - GameMessages::SendToggleGMInvis(entity->GetObjectID(), true, UNASSIGNED_SYSTEM_ADDRESS); - } - - void SetName(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(args), UNASSIGNED_SYSTEM_ADDRESS); - } - - void Title(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - std::string name = entity->GetCharacter()->GetName() + " - " + args; - GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(name), UNASSIGNED_SYSTEM_ADDRESS); - } -} - -void SlashCommandHandler::SendAnnouncement(const std::string& title, const std::string& message) { - AMFArrayValue args; - - args.Insert("title", title); - args.Insert("message", message); - - GameMessages::SendUIMessageServerToAllClients("ToggleAnnounce", args); - - //Notify chat about it - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::GM_ANNOUNCE); - - bitStream.Write<uint32_t>(title.size()); - for (auto character : title) { - bitStream.Write<char>(character); - } - - bitStream.Write<uint32_t>(message.size()); - for (auto character : message) { - bitStream.Write<char>(character); - } - - Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); -} diff --git a/dGame/dUtilities/SlashCommandHandler.h b/dGame/dUtilities/SlashCommandHandler.h index e986487b..6a94dd3b 100644 --- a/dGame/dUtilities/SlashCommandHandler.h +++ b/dGame/dUtilities/SlashCommandHandler.h @@ -21,114 +21,14 @@ struct Command { }; namespace SlashCommandHandler { - void Startup(); void HandleChatCommand(const std::u16string& command, Entity* entity, const SystemAddress& sysAddr); void SendAnnouncement(const std::string& title, const std::string& message); void RegisterCommand(Command info); + void Startup(); }; -namespace DEVGMCommands { - void SetGMLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void ToggleNameplate(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void ToggleSkipCinematics(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Kill(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Metrics(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Announce(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void SetAnnTitle(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void SetAnnMsg(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void ShutdownUniverse(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void SetMinifig(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void TestMap(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void ReportProxPhys(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void SpawnPhysicsVerts(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Teleport(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void ActivateSpawner(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void AddMission(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Boost(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Unboost(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Buff(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void BuffMe(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void BuffMed(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void ClearFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void CompleteMission(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void CreatePrivate(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void DebugUi(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Dismount(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void ReloadConfig(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void ForceSave(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Freecam(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void FreeMoney(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void GetNavmeshHeight(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void GiveUScore(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void GmAddItem(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Inspect(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void ListSpawns(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void LocRow(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Lookup(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void PlayAnimation(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void PlayEffect(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void PlayLvlFx(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void PlayRebuildFx(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Pos(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void RefillStats(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Reforge(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void ResetMission(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Rot(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void RunMacro(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void SetControlScheme(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void SetCurrency(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void SetFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void SetInventorySize(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void SetUiState(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Spawn(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void SpawnGroup(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void SpeedBoost(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void StartCelebration(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void StopEffect(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Toggle(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void TpAll(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void TriggerSpawner(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void UnlockEmote(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void SetLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void SetSkillSlot(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void SetFaction(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void AddFaction(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void GetFactions(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void SetRewardCode(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Crash(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void RollLoot(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void CastSkill(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void DeleteInven(Entity* entity, const SystemAddress& sysAddr, const std::string args); -} - namespace GMZeroCommands { void Help(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Credits(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Info(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Die(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Ping(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Pvp(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void RequestMailCount(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Who(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void FixStats(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Join(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void LeaveZone(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Resurrect(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void InstanceInfo(Entity* entity, const SystemAddress& sysAddr, const std::string args); -} - -namespace GMGreaterThanZeroCommands { - void Kick(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void MailItem(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Ban(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void ApproveProperty(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Mute(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Fly(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void AttackImmune(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void GmImmune(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void GmInvis(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void SetName(Entity* entity, const SystemAddress& sysAddr, const std::string args); - void Title(Entity* entity, const SystemAddress& sysAddr, const std::string args); } #endif // SLASHCOMMANDHANDLER_H diff --git a/dGame/dUtilities/SlashCommands/CMakeLists.txt b/dGame/dUtilities/SlashCommands/CMakeLists.txt new file mode 100644 index 00000000..999cd0ec --- /dev/null +++ b/dGame/dUtilities/SlashCommands/CMakeLists.txt @@ -0,0 +1,6 @@ +set(DGAME_DUTILITIES_SLASHCOMMANDS + "DEVGMCommands.cpp" + "GMGreaterThanZeroCommands.cpp" + "GMZeroCommands.cpp" + PARENT_SCOPE +) diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp new file mode 100644 index 00000000..b8d6b9f3 --- /dev/null +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp @@ -0,0 +1,1592 @@ +#include "DEVGMCommands.h" + +// Classes +#include "AssetManager.h" +#include "Character.h" +#include "ChatPackets.h" +#include "dConfig.h" +#include "dNavMesh.h" +#include "dpWorld.h" +#include "dServer.h" +#include "dpShapeSphere.h" +#include "dZoneManager.h" +#include "EntityInfo.h" +#include "Metrics.hpp" +#include "PlayerManager.h" +#include "SlashCommandHandler.h" +#include "UserManager.h" +#include "User.h" +#include "VanityUtilities.h" +#include "WorldPackets.h" +#include "ZoneInstanceManager.h" + +// Database +#include "Database.h" +#include "CDObjectsTable.h" +#include "CDRewardCodesTable.h" + +// Components +#include "BuffComponent.h" +#include "CharacterComponent.h" +#include "ControllablePhysicsComponent.h" +#include "DestroyableComponent.h" +#include "HavokVehiclePhysicsComponent.h" +#include "InventoryComponent.h" +#include "LevelProgressionComponent.h" +#include "MissionComponent.h" +#include "MovingPlatformComponent.h" +#include "PossessorComponent.h" +#include "ProximityMonitorComponent.h" +#include "RenderComponent.h" +#include "ScriptedActivityComponent.h" +#include "SkillComponent.h" +#include "TriggerComponent.h" + +// Enums +#include "eGameMasterLevel.h" +#include "eMasterMessageType.h" +#include "eInventoryType.h" +#include "ePlayerFlag.h" + + +namespace DEVGMCommands { + void SetGMLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + User* user = UserManager::Instance()->GetUser(entity->GetSystemAddress()); + + const auto level_intermed = GeneralUtils::TryParse<uint32_t>(args); + if (!level_intermed) { + GameMessages::SendSlashCommandFeedbackText(entity, u"Invalid GM level."); + return; + } + eGameMasterLevel level = static_cast<eGameMasterLevel>(level_intermed.value()); + +#ifndef DEVELOPER_SERVER + if (user->GetMaxGMLevel() == eGameMasterLevel::JUNIOR_DEVELOPER) { + level = eGameMasterLevel::CIVILIAN; + } +#endif + + if (level > user->GetMaxGMLevel()) level = user->GetMaxGMLevel(); + + if (level == entity->GetGMLevel()) return; + bool success = user->GetMaxGMLevel() >= level; + + if (success) { + WorldPackets::SendGMLevelChange(entity->GetSystemAddress(), success, user->GetMaxGMLevel(), entity->GetGMLevel(), level); + GameMessages::SendChatModeUpdate(entity->GetObjectID(), level); + entity->SetGMLevel(level); + LOG("User %s (%i) has changed their GM level to %i for charID %llu", user->GetUsername().c_str(), user->GetAccountID(), level, entity->GetObjectID()); + } + +#ifndef DEVELOPER_SERVER + if ((entity->GetGMLevel() > user->GetMaxGMLevel()) || (entity->GetGMLevel() > eGameMasterLevel::CIVILIAN && user->GetMaxGMLevel() == eGameMasterLevel::JUNIOR_DEVELOPER)) { + WorldPackets::SendGMLevelChange(entity->GetSystemAddress(), true, user->GetMaxGMLevel(), entity->GetGMLevel(), eGameMasterLevel::CIVILIAN); + GameMessages::SendChatModeUpdate(entity->GetObjectID(), eGameMasterLevel::CIVILIAN); + entity->SetGMLevel(eGameMasterLevel::CIVILIAN); + + GameMessages::SendToggleGMInvis(entity->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS); + + GameMessages::SendSlashCommandFeedbackText(entity, u"Your game master level has been changed, you may not be able to use all commands."); + } +#endif + } + + + void ToggleNameplate(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if ((Game::config->GetValue("allow_nameplate_off") != "1" && entity->GetGMLevel() < eGameMasterLevel::DEVELOPER)) return; + + auto* character = entity->GetCharacter(); + if (character && character->GetBillboardVisible()) { + character->SetBillboardVisible(false); + GameMessages::SendSlashCommandFeedbackText(entity, u"Your nameplate has been turned off and is not visible to players currently in this zone."); + } else { + character->SetBillboardVisible(true); + GameMessages::SendSlashCommandFeedbackText(entity, u"Your nameplate is now on and visible to all players."); + } + } + + void ToggleSkipCinematics(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (Game::config->GetValue("allow_players_to_skip_cinematics") != "1" && entity->GetGMLevel() < eGameMasterLevel::DEVELOPER) return; + auto* character = entity->GetCharacter(); + if (!character) return; + bool current = character->GetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS); + character->SetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS, !current); + if (!current) { + GameMessages::SendSlashCommandFeedbackText(entity, u"You have elected to skip cinematics. Note that not all cinematics can be skipped, but most will be skipped now."); + } else { + GameMessages::SendSlashCommandFeedbackText(entity, u"Cinematics will no longer be skipped."); + } + } + + void ResetMission(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto missionId = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); + if (!missionId) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid mission ID."); + return; + } + + auto* missionComponent = entity->GetComponent<MissionComponent>(); + if (!missionComponent) return; + missionComponent->ResetMission(missionId.value()); + } + + void SetMinifig(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 2) return; + + const auto minifigItemIdExists = GeneralUtils::TryParse<int32_t>(splitArgs[1]); + if (!minifigItemIdExists) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid Minifig Item Id ID."); + return; + } + const int32_t minifigItemId = minifigItemIdExists.value(); + Game::entityManager->DestructEntity(entity, sysAddr); + auto* charComp = entity->GetComponent<CharacterComponent>(); + std::string lowerName = splitArgs[0]; + if (lowerName.empty()) return; + std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower); + if (lowerName == "eyebrows") { + charComp->m_Character->SetEyebrows(minifigItemId); + } else if (lowerName == "eyes") { + charComp->m_Character->SetEyes(minifigItemId); + } else if (lowerName == "haircolor") { + charComp->m_Character->SetHairColor(minifigItemId); + } else if (lowerName == "hairstyle") { + charComp->m_Character->SetHairStyle(minifigItemId); + } else if (lowerName == "pants") { + charComp->m_Character->SetPantsColor(minifigItemId); + } else if (lowerName == "lefthand") { + charComp->m_Character->SetLeftHand(minifigItemId); + } else if (lowerName == "mouth") { + charComp->m_Character->SetMouth(minifigItemId); + } else if (lowerName == "righthand") { + charComp->m_Character->SetRightHand(minifigItemId); + } else if (lowerName == "shirtcolor") { + charComp->m_Character->SetShirtColor(minifigItemId); + } else if (lowerName == "hands") { + charComp->m_Character->SetLeftHand(minifigItemId); + charComp->m_Character->SetRightHand(minifigItemId); + } else { + Game::entityManager->ConstructEntity(entity); + ChatPackets::SendSystemMessage(sysAddr, u"Invalid Minifig item to change, try one of the following: Eyebrows, Eyes, HairColor, HairStyle, Pants, LeftHand, Mouth, RightHand, Shirt, Hands"); + return; + } + + Game::entityManager->ConstructEntity(entity); + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(lowerName) + u" set to " + (GeneralUtils::to_u16string(minifigItemId))); + + GameMessages::SendToggleGMInvis(entity->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS); // need to retoggle because it gets reenabled on creation of new character + } + + void PlayAnimation(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + std::u16string anim = GeneralUtils::ASCIIToUTF16(splitArgs[0], splitArgs[0].size()); + RenderComponent::PlayAnimation(entity, anim); + auto* possessorComponent = entity->GetComponent<PossessorComponent>(); + if (possessorComponent) { + auto* possessedComponent = Game::entityManager->GetEntity(possessorComponent->GetPossessable()); + if (possessedComponent) RenderComponent::PlayAnimation(possessedComponent, anim); + } + } + + void ListSpawns(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + for (const auto& pair : Game::entityManager->GetSpawnPointEntities()) { + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(pair.first)); + } + + ChatPackets::SendSystemMessage(sysAddr, u"Current: " + GeneralUtils::ASCIIToUTF16(entity->GetCharacter()->GetTargetScene())); + } + + void UnlockEmote(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto emoteID = GeneralUtils::TryParse<int32_t>(splitArgs[0]); + + if (!emoteID) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid emote ID."); + return; + } + + entity->GetCharacter()->UnlockEmote(emoteID.value()); + } + + void ForceSave(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + entity->GetCharacter()->SaveXMLToDatabase(); + } + + void Kill(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + ChatPackets::SendSystemMessage(sysAddr, u"Brutally murdering that player, if online on this server."); + + auto* player = PlayerManager::GetPlayer(splitArgs[0]); + if (player) { + player->Smash(entity->GetObjectID()); + ChatPackets::SendSystemMessage(sysAddr, u"It has been done, do you feel good about yourself now?"); + return; + } + + ChatPackets::SendSystemMessage(sysAddr, u"They were saved from your carnage."); + return; + } + + void SpeedBoost(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto boostOptional = GeneralUtils::TryParse<float>(splitArgs[0]); + if (!boostOptional) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid boost."); + return; + } + const float boost = boostOptional.value(); + + auto* controllablePhysicsComponent = entity->GetComponent<ControllablePhysicsComponent>(); + + if (!controllablePhysicsComponent) return; + controllablePhysicsComponent->SetSpeedMultiplier(boost); + + // speedboost possessables + auto possessor = entity->GetComponent<PossessorComponent>(); + if (possessor) { + auto possessedID = possessor->GetPossessable(); + if (possessedID != LWOOBJID_EMPTY) { + auto possessable = Game::entityManager->GetEntity(possessedID); + if (possessable) { + auto* possessControllablePhysicsComponent = possessable->GetComponent<ControllablePhysicsComponent>(); + if (possessControllablePhysicsComponent) { + possessControllablePhysicsComponent->SetSpeedMultiplier(boost); + } + } + } + } + + Game::entityManager->SerializeEntity(entity); + } + + void Freecam(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto state = !entity->GetVar<bool>(u"freecam"); + entity->SetVar<bool>(u"freecam", state); + + GameMessages::SendSetPlayerControlScheme(entity, static_cast<eControlScheme>(state ? 9 : 1)); + + ChatPackets::SendSystemMessage(sysAddr, u"Toggled freecam."); + } + + void SetControlScheme(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto scheme = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); + + if (!scheme) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid control scheme."); + return; + } + + GameMessages::SendSetPlayerControlScheme(entity, static_cast<eControlScheme>(scheme.value())); + + ChatPackets::SendSystemMessage(sysAddr, u"Switched control scheme."); + } + + void SetUiState(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + AMFArrayValue uiState; + + uiState.Insert("state", splitArgs[0]); + + GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, "pushGameState", uiState); + + ChatPackets::SendSystemMessage(sysAddr, u"Switched UI state."); + } + + void Toggle(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + AMFArrayValue amfArgs; + + amfArgs.Insert("visible", true); + + GameMessages::SendUIMessageServerToSingleClient(entity, sysAddr, splitArgs[0], amfArgs); + + ChatPackets::SendSystemMessage(sysAddr, u"Toggled UI state."); + } + + void SetInventorySize(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto sizeOptional = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); + if (!sizeOptional) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid size."); + return; + } + const uint32_t size = sizeOptional.value(); + + eInventoryType selectedInventory = eInventoryType::ITEMS; + + // a possible inventory was provided if we got more than 1 argument + if (splitArgs.size() >= 2) { + selectedInventory = GeneralUtils::TryParse<eInventoryType>(splitArgs.at(1)).value_or(eInventoryType::INVALID); + if (selectedInventory == eInventoryType::INVALID) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid inventory."); + return; + } else { + // In this case, we treat the input as a string and try to find it in the reflection list + std::transform(splitArgs.at(1).begin(), splitArgs.at(1).end(), splitArgs.at(1).begin(), ::toupper); + for (uint32_t index = 0; index < NUMBER_OF_INVENTORIES; index++) { + if (std::string_view(splitArgs.at(1)) == std::string_view(InventoryType::InventoryTypeToString(static_cast<eInventoryType>(index)))) selectedInventory = static_cast<eInventoryType>(index); + } + } + + ChatPackets::SendSystemMessage(sysAddr, u"Setting inventory " + + GeneralUtils::ASCIIToUTF16(splitArgs.at(1)) + + u" to size " + + GeneralUtils::to_u16string(size)); + } else ChatPackets::SendSystemMessage(sysAddr, u"Setting inventory ITEMS to size " + GeneralUtils::to_u16string(size)); + + auto* inventoryComponent = entity->GetComponent<InventoryComponent>(); + if (inventoryComponent) { + auto* inventory = inventoryComponent->GetInventory(selectedInventory); + + inventory->SetSize(size); + } + } + + void RunMacro(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + // Only process if input does not contain separator charaters + if (splitArgs[0].find("/") != std::string::npos) return; + if (splitArgs[0].find("\\") != std::string::npos) return; + + auto infile = Game::assetManager->GetFile(("macros/" + splitArgs[0] + ".scm").c_str()); + + if (!infile) { + ChatPackets::SendSystemMessage(sysAddr, u"Unknown macro! Is the filename right?"); + return; + } + + if (infile.good()) { + std::string line; + while (std::getline(infile, line)) { + // Do this in two separate calls to catch both \n and \r\n + line.erase(std::remove(line.begin(), line.end(), '\n'), line.end()); + line.erase(std::remove(line.begin(), line.end(), '\r'), line.end()); + SlashCommandHandler::HandleChatCommand(GeneralUtils::ASCIIToUTF16(line), entity, sysAddr); + } + } else { + ChatPackets::SendSystemMessage(sysAddr, u"Unknown macro! Is the filename right?"); + } + } + + void AddMission(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto missionID = GeneralUtils::TryParse<uint32_t>(splitArgs.at(0)); + + if (!missionID) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid mission id."); + return; + } + + auto comp = static_cast<MissionComponent*>(entity->GetComponent(eReplicaComponentType::MISSION)); + if (comp) comp->AcceptMission(missionID.value(), true); + } + + void CompleteMission(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto missionID = GeneralUtils::TryParse<uint32_t>(splitArgs.at(0)); + + if (!missionID) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid mission id."); + return; + } + + auto comp = static_cast<MissionComponent*>(entity->GetComponent(eReplicaComponentType::MISSION)); + if (comp) comp->CompleteMission(missionID.value(), true); + } + + void SetFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() == 1) { + const auto flagId = GeneralUtils::TryParse<int32_t>(splitArgs.at(0)); + + if (!flagId) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id."); + return; + } + + entity->GetCharacter()->SetPlayerFlag(flagId.value(), true); + } else if (splitArgs.size() >= 2) { + const auto flagId = GeneralUtils::TryParse<int32_t>(splitArgs.at(1)); + std::string onOffFlag = splitArgs.at(0); + if (!flagId) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id."); + return; + } + + if (onOffFlag != "off" && onOffFlag != "on") { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag type."); + return; + } + + entity->GetCharacter()->SetPlayerFlag(flagId.value(), onOffFlag == "on"); + } + } + + void ClearFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto flagId = GeneralUtils::TryParse<int32_t>(splitArgs.at(0)); + + if (!flagId) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id."); + return; + } + + entity->GetCharacter()->SetPlayerFlag(flagId.value(), false); + } + + void PlayEffect(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 3) return; + + const auto effectID = GeneralUtils::TryParse<int32_t>(splitArgs.at(0)); + + if (!effectID) return; + + // FIXME: use fallible ASCIIToUTF16 conversion, because non-ascii isn't valid anyway + GameMessages::SendPlayFXEffect(entity->GetObjectID(), effectID.value(), GeneralUtils::ASCIIToUTF16(splitArgs.at(1)), splitArgs.at(2)); + } + + void StopEffect(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + GameMessages::SendStopFXEffect(entity, true, splitArgs[0]); + } + + void SetAnnTitle(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + entity->GetCharacter()->SetAnnouncementTitle(args); + } + + void SetAnnMsg(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + entity->GetCharacter()->SetAnnouncementMessage(args); + } + + void Announce(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (entity->GetCharacter()->GetAnnouncementTitle().empty() || entity->GetCharacter()->GetAnnouncementMessage().empty()) { + ChatPackets::SendSystemMessage(sysAddr, u"Use /setanntitle <title> & /setannmsg <msg> first!"); + return; + } + + SlashCommandHandler::SendAnnouncement(entity->GetCharacter()->GetAnnouncementTitle(), entity->GetCharacter()->GetAnnouncementMessage()); + } + + void ShutdownUniverse(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + //Tell the master server that we're going to be shutting down whole "universe": + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::MASTER, eMasterMessageType::SHUTDOWN_UNIVERSE); + Game::server->SendToMaster(bitStream); + ChatPackets::SendSystemMessage(sysAddr, u"Sent universe shutdown notification to master."); + + //Tell chat to send an announcement to all servers + SlashCommandHandler::SendAnnouncement("Servers Closing Soon!", "DLU servers will close for maintenance in 10 minutes from now."); + } + + void GetNavmeshHeight(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto control = static_cast<ControllablePhysicsComponent*>(entity->GetComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS)); + if (!control) return; + + float y = dpWorld::GetNavMesh()->GetHeightAtPoint(control->GetPosition()); + std::u16string msg = u"Navmesh height: " + (GeneralUtils::to_u16string(y)); + ChatPackets::SendSystemMessage(sysAddr, msg); + } + + void GmAddItem(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + + if (splitArgs.size() == 1) { + const auto itemLOT = GeneralUtils::TryParse<uint32_t>(splitArgs.at(0)); + + if (!itemLOT) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid item LOT."); + return; + } + + InventoryComponent* inventory = static_cast<InventoryComponent*>(entity->GetComponent(eReplicaComponentType::INVENTORY)); + + inventory->AddItem(itemLOT.value(), 1, eLootSourceType::MODERATION); + } else if (splitArgs.size() == 2) { + const auto itemLOT = GeneralUtils::TryParse<uint32_t>(splitArgs.at(0)); + if (!itemLOT) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid item LOT."); + return; + } + + const auto count = GeneralUtils::TryParse<uint32_t>(splitArgs.at(1)); + if (!count) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid item count."); + return; + } + + InventoryComponent* inventory = static_cast<InventoryComponent*>(entity->GetComponent(eReplicaComponentType::INVENTORY)); + + inventory->AddItem(itemLOT.value(), count.value(), eLootSourceType::MODERATION); + } else { + ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /gmadditem <lot>"); + } + } + + void Teleport(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + + NiPoint3 pos{}; + if (splitArgs.size() == 3) { + + const auto x = GeneralUtils::TryParse<float>(splitArgs.at(0)); + if (!x) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid x."); + return; + } + + const auto y = GeneralUtils::TryParse<float>(splitArgs.at(1)); + if (!y) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid y."); + return; + } + + const auto z = GeneralUtils::TryParse<float>(splitArgs.at(2)); + if (!z) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid z."); + return; + } + + pos.SetX(x.value()); + pos.SetY(y.value()); + pos.SetZ(z.value()); + + LOG("Teleporting objectID: %llu to %f, %f, %f", entity->GetObjectID(), pos.x, pos.y, pos.z); + GameMessages::SendTeleport(entity->GetObjectID(), pos, NiQuaternion(), sysAddr); + } else if (splitArgs.size() == 2) { + + const auto x = GeneralUtils::TryParse<float>(splitArgs.at(0)); + if (!x) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid x."); + return; + } + + const auto z = GeneralUtils::TryParse<float>(splitArgs.at(1)); + if (!z) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid z."); + return; + } + + pos.SetX(x.value()); + pos.SetY(0.0f); + pos.SetZ(z.value()); + + LOG("Teleporting objectID: %llu to X: %f, Z: %f", entity->GetObjectID(), pos.x, pos.z); + GameMessages::SendTeleport(entity->GetObjectID(), pos, NiQuaternion(), sysAddr); + } else { + ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /teleport <x> (<y>) <z> - if no Y given, will teleport to the height of the terrain (or any physics object)."); + } + + auto* possessorComponent = entity->GetComponent<PossessorComponent>(); + if (possessorComponent) { + auto* possassableEntity = Game::entityManager->GetEntity(possessorComponent->GetPossessable()); + + if (possassableEntity != nullptr) { + auto* havokVehiclePhysicsComponent = possassableEntity->GetComponent<HavokVehiclePhysicsComponent>(); + if (havokVehiclePhysicsComponent) { + havokVehiclePhysicsComponent->SetPosition(pos); + Game::entityManager->SerializeEntity(possassableEntity); + } else GameMessages::SendTeleport(possassableEntity->GetObjectID(), pos, NiQuaternion(), sysAddr); + } + } + } + + void TpAll(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto pos = entity->GetPosition(); + + const auto characters = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::CHARACTER); + + for (auto* character : characters) { + GameMessages::SendTeleport(character->GetObjectID(), pos, NiQuaternion(), character->GetSystemAddress()); + } + } + + void Dismount(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto* possessorComponent = entity->GetComponent<PossessorComponent>(); + if (possessorComponent) { + auto possessableId = possessorComponent->GetPossessable(); + if (possessableId != LWOOBJID_EMPTY) { + auto* possessableEntity = Game::entityManager->GetEntity(possessableId); + if (possessableEntity) possessorComponent->Dismount(possessableEntity, true); + } + } + } + + void BuffMe(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto dest = static_cast<DestroyableComponent*>(entity->GetComponent(eReplicaComponentType::DESTROYABLE)); + if (dest) { + dest->SetHealth(999); + dest->SetMaxHealth(999.0f); + dest->SetArmor(999); + dest->SetMaxArmor(999.0f); + dest->SetImagination(999); + dest->SetMaxImagination(999.0f); + } + Game::entityManager->SerializeEntity(entity); + } + + void StartCelebration(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto celebration = GeneralUtils::TryParse<int32_t>(splitArgs.at(0)); + + if (!celebration) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid celebration."); + return; + } + + GameMessages::SendStartCelebrationEffect(entity, entity->GetSystemAddress(), celebration.value()); + } + + void BuffMed(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto dest = static_cast<DestroyableComponent*>(entity->GetComponent(eReplicaComponentType::DESTROYABLE)); + if (dest) { + dest->SetHealth(9); + dest->SetMaxHealth(9.0f); + dest->SetArmor(9); + dest->SetMaxArmor(9.0f); + dest->SetImagination(9); + dest->SetMaxImagination(9.0f); + } + Game::entityManager->SerializeEntity(entity); + } + + void RefillStats(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto dest = static_cast<DestroyableComponent*>(entity->GetComponent(eReplicaComponentType::DESTROYABLE)); + if (dest) { + dest->SetHealth(static_cast<int32_t>(dest->GetMaxHealth())); + dest->SetArmor(static_cast<int32_t>(dest->GetMaxArmor())); + dest->SetImagination(static_cast<int32_t>(dest->GetMaxImagination())); + } + + Game::entityManager->SerializeEntity(entity); + } + + void Lookup(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto query = CDClientDatabase::CreatePreppedStmt( + "SELECT `id`, `name` FROM `Objects` WHERE `displayName` LIKE ?1 OR `name` LIKE ?1 OR `description` LIKE ?1 LIMIT 50"); + + const std::string query_text = "%" + args + "%"; + query.bind(1, query_text.c_str()); + + auto tables = query.execQuery(); + + while (!tables.eof()) { + std::string message = std::to_string(tables.getIntField(0)) + " - " + tables.getStringField(1); + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::UTF8ToUTF16(message, message.size())); + tables.nextRow(); + } + } + + void Spawn(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + ControllablePhysicsComponent* comp = static_cast<ControllablePhysicsComponent*>(entity->GetComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS)); + if (!comp) return; + + const auto lot = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); + + if (!lot) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid lot."); + return; + } + + EntityInfo info; + info.lot = lot.value(); + info.pos = comp->GetPosition(); + info.rot = comp->GetRotation(); + info.spawner = nullptr; + info.spawnerID = entity->GetObjectID(); + info.spawnerNodeID = 0; + + Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr); + + if (newEntity == nullptr) { + ChatPackets::SendSystemMessage(sysAddr, u"Failed to spawn entity."); + return; + } + + Game::entityManager->ConstructEntity(newEntity); + } + + void SpawnGroup(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 3) return; + + const auto lot = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); + if (!lot) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid lot."); + return; + } + + const auto numberToSpawnOptional = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); + if (!numberToSpawnOptional && numberToSpawnOptional.value() > 0) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid number of enemies to spawn."); + return; + } + uint32_t numberToSpawn = numberToSpawnOptional.value(); + + // Must spawn within a radius of at least 0.0f + const auto radiusToSpawnWithinOptional = GeneralUtils::TryParse<float>(splitArgs[2]); + if (!radiusToSpawnWithinOptional && radiusToSpawnWithinOptional.value() < 0.0f) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid radius to spawn within."); + return; + } + const float radiusToSpawnWithin = radiusToSpawnWithinOptional.value(); + + EntityInfo info; + info.lot = lot.value(); + info.spawner = nullptr; + info.spawnerID = entity->GetObjectID(); + info.spawnerNodeID = 0; + + auto playerPosition = entity->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 = Game::entityManager->CreateEntity(info); + if (newEntity == nullptr) { + ChatPackets::SendSystemMessage(sysAddr, u"Failed to spawn entity."); + return; + } + + Game::entityManager->ConstructEntity(newEntity); + numberToSpawn--; + } + } + + void GiveUScore(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto uscoreOptional = GeneralUtils::TryParse<int32_t>(splitArgs[0]); + if (!uscoreOptional) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid uscore."); + return; + } + const int32_t uscore = uscoreOptional.value(); + + CharacterComponent* character = entity->GetComponent<CharacterComponent>(); + if (character) character->SetUScore(character->GetUScore() + uscore); + // MODERATION should work but it doesn't. Relog to see uscore changes + + eLootSourceType lootType = eLootSourceType::MODERATION; + + if (splitArgs.size() >= 2) { + const auto type = GeneralUtils::TryParse<eLootSourceType>(splitArgs[1]); + lootType = type.value_or(lootType); + } + + GameMessages::SendModifyLEGOScore(entity, entity->GetSystemAddress(), uscore, lootType); + } + + void SetLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + // We may be trying to set a specific players level to a level. If so override the entity with the requested players. + std::string requestedPlayerToSetLevelOf = ""; + if (splitArgs.size() > 1) { + requestedPlayerToSetLevelOf = splitArgs[1]; + + auto requestedPlayer = PlayerManager::GetPlayer(requestedPlayerToSetLevelOf); + + if (!requestedPlayer) { + ChatPackets::SendSystemMessage(sysAddr, u"No player found with username: (" + GeneralUtils::UTF8ToUTF16(requestedPlayerToSetLevelOf) + u")."); + return; + } + + if (!requestedPlayer->GetOwner()) { + ChatPackets::SendSystemMessage(sysAddr, u"No entity found with username: (" + GeneralUtils::UTF8ToUTF16(requestedPlayerToSetLevelOf) + u")."); + return; + } + + entity = requestedPlayer->GetOwner(); + } + const auto requestedLevelOptional = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); + uint32_t oldLevel; + + // first check the level is valid + if (!requestedLevelOptional) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid level."); + return; + } + uint32_t requestedLevel = requestedLevelOptional.value(); + // query to set our uscore to the correct value for this level + + auto characterComponent = entity->GetComponent<CharacterComponent>(); + if (!characterComponent) return; + auto levelComponent = entity->GetComponent<LevelProgressionComponent>(); + auto query = CDClientDatabase::CreatePreppedStmt("SELECT requiredUScore from LevelProgressionLookup WHERE id = ?;"); + query.bind(1, static_cast<int>(requestedLevel)); + auto result = query.execQuery(); + + if (result.eof()) return; + + // Set the UScore first + oldLevel = levelComponent->GetLevel(); + characterComponent->SetUScore(result.getIntField(0, characterComponent->GetUScore())); + + // handle level up for each level we have passed if we set our level to be higher than the current one. + if (oldLevel < requestedLevel) { + while (oldLevel < requestedLevel) { + oldLevel += 1; + levelComponent->SetLevel(oldLevel); + levelComponent->HandleLevelUp(); + } + } else { + levelComponent->SetLevel(requestedLevel); + } + + if (requestedPlayerToSetLevelOf != "") { + ChatPackets::SendSystemMessage( + sysAddr, u"Set " + GeneralUtils::UTF8ToUTF16(requestedPlayerToSetLevelOf) + u"'s level to " + GeneralUtils::to_u16string(requestedLevel) + + u" and UScore to " + GeneralUtils::to_u16string(characterComponent->GetUScore()) + + u". Relog to see changes."); + } else { + ChatPackets::SendSystemMessage( + sysAddr, u"Set your level to " + GeneralUtils::to_u16string(requestedLevel) + + u" and UScore to " + GeneralUtils::to_u16string(characterComponent->GetUScore()) + + u". Relog to see changes."); + } + } + + void Pos(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto position = entity->GetPosition(); + + ChatPackets::SendSystemMessage(sysAddr, u"<" + (GeneralUtils::to_u16string(position.x)) + u", " + (GeneralUtils::to_u16string(position.y)) + u", " + (GeneralUtils::to_u16string(position.z)) + u">"); + + LOG("Position: %f, %f, %f", position.x, position.y, position.z); + } + + void Rot(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto rotation = entity->GetRotation(); + + ChatPackets::SendSystemMessage(sysAddr, u"<" + (GeneralUtils::to_u16string(rotation.w)) + u", " + (GeneralUtils::to_u16string(rotation.x)) + u", " + (GeneralUtils::to_u16string(rotation.y)) + u", " + (GeneralUtils::to_u16string(rotation.z)) + u">"); + + LOG("Rotation: %f, %f, %f, %f", rotation.w, rotation.x, rotation.y, rotation.z); + } + + void LocRow(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto position = entity->GetPosition(); + const auto rotation = entity->GetRotation(); + + LOG("<location x=\"%f\" y=\"%f\" z=\"%f\" rw=\"%f\" rx=\"%f\" ry=\"%f\" rz=\"%f\" />", position.x, position.y, position.z, rotation.w, rotation.x, rotation.y, rotation.z); + } + + void PlayLvlFx(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + GameMessages::SendPlayFXEffect(entity, 7074, u"create", "7074", LWOOBJID_EMPTY, 1.0f, 1.0f, true); + } + + void PlayRebuildFx(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + GameMessages::SendPlayFXEffect(entity, 230, u"rebuild", "230", LWOOBJID_EMPTY, 1.0f, 1.0f, true); + } + + void FreeMoney(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto money = GeneralUtils::TryParse<int64_t>(splitArgs[0]); + + if (!money) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid money."); + return; + } + + auto* ch = entity->GetCharacter(); + ch->SetCoins(ch->GetCoins() + money.value(), eLootSourceType::MODERATION); + } + + void SetCurrency(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + const auto money = GeneralUtils::TryParse<int64_t>(splitArgs[0]); + + if (!money) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid money."); + return; + } + + auto* ch = entity->GetCharacter(); + ch->SetCoins(money.value(), eLootSourceType::MODERATION); + } + + void Buff(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 2) return; + + auto* buffComponent = entity->GetComponent<BuffComponent>(); + + const auto id = GeneralUtils::TryParse<int32_t>(splitArgs[0]); + if (!id) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid buff id."); + return; + } + + const auto duration = GeneralUtils::TryParse<int32_t>(splitArgs[1]); + if (!duration) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid buff duration."); + return; + } + + if (buffComponent) buffComponent->ApplyBuff(id.value(), duration.value(), entity->GetObjectID()); + } + + void TestMap(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + ChatPackets::SendSystemMessage(sysAddr, u"Requesting map change..."); + LWOCLONEID cloneId = 0; + bool force = false; + + const auto reqZoneOptional = GeneralUtils::TryParse<LWOMAPID>(splitArgs[0]); + if (!reqZoneOptional) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid zone."); + return; + } + const LWOMAPID reqZone = reqZoneOptional.value(); + + if (splitArgs.size() > 1) { + auto index = 1; + + if (splitArgs[index] == "force") { + index++; + + force = true; + } + + if (splitArgs.size() > index) { + const auto cloneIdOptional = GeneralUtils::TryParse<LWOCLONEID>(splitArgs[index]); + if (!cloneIdOptional) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid clone id."); + return; + } + cloneId = cloneIdOptional.value(); + } + } + + const auto objid = entity->GetObjectID(); + + if (force || Game::zoneManager->CheckIfAccessibleZone(reqZone)) { // to prevent tomfoolery + + ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, reqZone, cloneId, false, [objid](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) { + + auto* entity = Game::entityManager->GetEntity(objid); + if (!entity) return; + + const auto sysAddr = entity->GetSystemAddress(); + + ChatPackets::SendSystemMessage(sysAddr, u"Transfering map..."); + + LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", sysAddr.ToString(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort); + if (entity->GetCharacter()) { + entity->GetCharacter()->SetZoneID(zoneID); + entity->GetCharacter()->SetZoneInstance(zoneInstance); + entity->GetCharacter()->SetZoneClone(zoneClone); + entity->GetComponent<CharacterComponent>()->SetLastRocketConfig(u""); + } + + entity->GetCharacter()->SaveXMLToDatabase(); + + WorldPackets::SendTransferToWorld(sysAddr, serverIP, serverPort, mythranShift); + return; + }); + } else { + std::string msg = "ZoneID not found or allowed: "; + msg.append(splitArgs[0]); // FIXME: unnecessary utf16 re-encoding just for error + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::UTF8ToUTF16(msg, msg.size())); + } + } + + void CreatePrivate(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 3) return; + + const auto zone = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); + if (!zone) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid zone."); + return; + } + + const auto clone = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); + if (!clone) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid clone."); + return; + } + + const auto& password = splitArgs[2]; + + ZoneInstanceManager::Instance()->CreatePrivateZone(Game::server, zone.value(), clone.value(), password); + + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16("Sent request for private zone with password: " + password)); + } + + void DebugUi(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + ChatPackets::SendSystemMessage(sysAddr, u"Opening UIDebugger..."); + } + + void Boost(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + + auto* possessorComponent = entity->GetComponent<PossessorComponent>(); + + if (possessorComponent == nullptr) { + return; + } + + auto* vehicle = Game::entityManager->GetEntity(possessorComponent->GetPossessable()); + + if (vehicle == nullptr) { + return; + } + + if (splitArgs.size() >= 1) { + const auto time = GeneralUtils::TryParse<float>(splitArgs[0]); + + if (!time) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid boost time."); + return; + } else { + GameMessages::SendVehicleAddPassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + entity->AddCallbackTimer(time.value(), [vehicle]() { + if (!vehicle) return; + GameMessages::SendVehicleRemovePassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + }); + } + } else { + GameMessages::SendVehicleAddPassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + } + } + + void Unboost(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto* possessorComponent = entity->GetComponent<PossessorComponent>(); + + if (possessorComponent == nullptr) return; + auto* vehicle = Game::entityManager->GetEntity(possessorComponent->GetPossessable()); + + if (vehicle == nullptr) return; + GameMessages::SendVehicleRemovePassiveBoostAction(vehicle->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); + } + + void ActivateSpawner(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + auto spawners = Game::zoneManager->GetSpawnersByName(splitArgs[0]); + + for (auto* spawner : spawners) { + spawner->Activate(); + } + + spawners = Game::zoneManager->GetSpawnersInGroup(splitArgs[0]); + + for (auto* spawner : spawners) { + spawner->Activate(); + } + } + + void SpawnPhysicsVerts(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + //Go tell physics to spawn all the vertices: + auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::PHANTOM_PHYSICS); + for (auto en : entities) { + auto phys = static_cast<PhantomPhysicsComponent*>(en->GetComponent(eReplicaComponentType::PHANTOM_PHYSICS)); + if (phys) + phys->SpawnVertices(); + } + } + + void ReportProxPhys(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::PROXIMITY_MONITOR); + for (auto en : entities) { + auto phys = static_cast<ProximityMonitorComponent*>(en->GetComponent(eReplicaComponentType::PROXIMITY_MONITOR)); + if (phys) { + for (auto prox : phys->GetProximitiesData()) { + if (!prox.second) continue; + + auto sphere = static_cast<dpShapeSphere*>(prox.second->GetShape()); + auto pos = prox.second->GetPosition(); + LOG("Proximity: %s, r: %f, pos: %f, %f, %f", prox.first.c_str(), sphere->GetRadius(), pos.x, pos.y, pos.z); + } + } + } + } + + void TriggerSpawner(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + auto spawners = Game::zoneManager->GetSpawnersByName(splitArgs[0]); + + for (auto* spawner : spawners) { + spawner->Spawn(); + } + + spawners = Game::zoneManager->GetSpawnersInGroup(splitArgs[0]); + + for (auto* spawner : spawners) { + spawner->Spawn(); + } + } + + void Reforge(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 2) return; + + const auto baseItem = GeneralUtils::TryParse<LOT>(splitArgs[0]); + if (!baseItem) return; + + const auto reforgedItem = GeneralUtils::TryParse<LOT>(splitArgs[1]); + if (!reforgedItem) return; + + auto* inventoryComponent = entity->GetComponent<InventoryComponent>(); + if (!inventoryComponent) return; + + std::vector<LDFBaseData*> data{}; + data.push_back(new LDFData<int32_t>(u"reforgedLOT", reforgedItem.value())); + + inventoryComponent->AddItem(baseItem.value(), 1, eLootSourceType::MODERATION, eInventoryType::INVALID, data); + } + + void Crash(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + ChatPackets::SendSystemMessage(sysAddr, u"Crashing..."); + + int* badPtr = nullptr; + *badPtr = 0; + } + + void Metrics(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + for (const auto variable : Metrics::GetAllMetrics()) { + auto* metric = Metrics::GetMetric(variable); + + if (metric == nullptr) { + continue; + } + + ChatPackets::SendSystemMessage( + sysAddr, + GeneralUtils::ASCIIToUTF16(Metrics::MetricVariableToString(variable)) + + u": " + + GeneralUtils::to_u16string(Metrics::ToMiliseconds(metric->average)) + + u"ms" + ); + } + + ChatPackets::SendSystemMessage( + sysAddr, + u"Peak RSS: " + GeneralUtils::to_u16string(static_cast<float>(static_cast<double>(Metrics::GetPeakRSS()) / 1.024e6)) + + u"MB" + ); + + ChatPackets::SendSystemMessage( + sysAddr, + u"Current RSS: " + GeneralUtils::to_u16string(static_cast<float>(static_cast<double>(Metrics::GetCurrentRSS()) / 1.024e6)) + + u"MB" + ); + + ChatPackets::SendSystemMessage( + sysAddr, + u"Process ID: " + GeneralUtils::to_u16string(Metrics::GetProcessID()) + ); + } + + void ReloadConfig(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + Game::config->ReloadConfig(); + VanityUtilities::SpawnVanity(); + dpWorld::Reload(); + auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::SCRIPTED_ACTIVITY); + for (const auto* const entity : entities) { + auto* const scriptedActivityComponent = entity->GetComponent<ScriptedActivityComponent>(); + if (!scriptedActivityComponent) continue; + + scriptedActivityComponent->ReloadConfig(); + } + Game::server->UpdateMaximumMtuSize(); + Game::server->UpdateBandwidthLimit(); + ChatPackets::SendSystemMessage(sysAddr, u"Successfully reloaded config for world!"); + } + + void RollLoot(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 3) return; + + const auto lootMatrixIndex = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); + if (!lootMatrixIndex) return; + + const auto targetLot = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); + if (!targetLot) return; + + const auto loops = GeneralUtils::TryParse<uint32_t>(splitArgs[2]); + if (!loops) return; + + uint64_t totalRuns = 0; + + for (uint32_t i = 0; i < loops; i++) { + while (true) { + auto lootRoll = Loot::RollLootMatrix(lootMatrixIndex.value()); + totalRuns += 1; + bool doBreak = false; + for (const auto& kv : lootRoll) { + if (static_cast<uint32_t>(kv.first) == targetLot) { + doBreak = true; + } + } + if (doBreak) break; + } + } + + std::u16string message = u"Ran loot drops looking for " + + GeneralUtils::to_u16string(targetLot.value()) + + u", " + + GeneralUtils::to_u16string(loops.value()) + + u" times. It ran " + + GeneralUtils::to_u16string(totalRuns) + + u" times. Averaging out at " + + GeneralUtils::to_u16string(static_cast<float>(totalRuns) / loops.value()); + + ChatPackets::SendSystemMessage(sysAddr, message); + } + + void DeleteInven(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + eInventoryType inventoryType = eInventoryType::INVALID; + + const auto inventoryTypeOptional = GeneralUtils::TryParse<eInventoryType>(splitArgs[0]); + if (!inventoryTypeOptional) { + // In this case, we treat the input as a string and try to find it in the reflection list + std::transform(splitArgs[0].begin(), splitArgs[0].end(), splitArgs[0].begin(), ::toupper); + LOG("looking for inventory %s", splitArgs[0].c_str()); + for (uint32_t index = 0; index < NUMBER_OF_INVENTORIES; index++) { + if (std::string_view(splitArgs[0]) == std::string_view(InventoryType::InventoryTypeToString(static_cast<eInventoryType>(index)))) inventoryType = static_cast<eInventoryType>(index); + } + } else { + inventoryType = inventoryTypeOptional.value(); + } + + if (inventoryType == eInventoryType::INVALID || inventoryType >= NUMBER_OF_INVENTORIES) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid inventory provided."); + return; + } + + auto* inventoryComponent = entity->GetComponent<InventoryComponent>(); + if (!inventoryComponent) return; + + auto* inventoryToDelete = inventoryComponent->GetInventory(inventoryType); + if (!inventoryToDelete) return; + + inventoryToDelete->DeleteAllItems(); + LOG("Deleted inventory %s for user %llu", splitArgs[0].c_str(), entity->GetObjectID()); + ChatPackets::SendSystemMessage(sysAddr, u"Deleted inventory " + GeneralUtils::UTF8ToUTF16(splitArgs[0])); + } + + void CastSkill(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + auto* skillComponent = entity->GetComponent<SkillComponent>(); + if (skillComponent) { + const auto skillId = GeneralUtils::TryParse<uint32_t>(splitArgs[0]); + + if (!skillId) { + ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill ID."); + return; + } else { + skillComponent->CastSkill(skillId.value(), entity->GetObjectID(), entity->GetObjectID()); + ChatPackets::SendSystemMessage(sysAddr, u"Cast skill"); + } + } + } + + void SetSkillSlot(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 2) return; + + auto* const inventoryComponent = entity->GetComponent<InventoryComponent>(); + if (inventoryComponent) { + const auto slot = GeneralUtils::TryParse<BehaviorSlot>(splitArgs[0]); + if (!slot) { + ChatPackets::SendSystemMessage(sysAddr, u"Error getting slot."); + return; + } else { + const auto skillId = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); + if (!skillId) { + ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill."); + return; + } else { + if (inventoryComponent->SetSkill(slot.value(), skillId.value())) ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot successfully"); + else ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot failed"); + } + } + } + } + + void SetFaction(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); + if (destroyableComponent) { + const auto faction = GeneralUtils::TryParse<int32_t>(splitArgs[0]); + + if (!faction) { + ChatPackets::SendSystemMessage(sysAddr, u"Error getting faction."); + return; + } else { + destroyableComponent->SetFaction(faction.value()); + ChatPackets::SendSystemMessage(sysAddr, u"Set faction and updated enemies list"); + } + } + } + + void AddFaction(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); + if (destroyableComponent) { + const auto faction = GeneralUtils::TryParse<int32_t>(splitArgs[0]); + + if (!faction) { + ChatPackets::SendSystemMessage(sysAddr, u"Error getting faction."); + return; + } else { + destroyableComponent->AddFaction(faction.value()); + ChatPackets::SendSystemMessage(sysAddr, u"Added faction and updated enemies list"); + } + } + } + + void GetFactions(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); + if (destroyableComponent) { + ChatPackets::SendSystemMessage(sysAddr, u"Friendly factions:"); + for (const auto entry : destroyableComponent->GetFactionIDs()) { + ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry))); + } + + ChatPackets::SendSystemMessage(sysAddr, u"Enemy factions:"); + for (const auto entry : destroyableComponent->GetEnemyFactionsIDs()) { + ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry))); + } + } + } + + void SetRewardCode(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto* character = entity->GetCharacter(); + if (!character) return; + auto* user = character->GetParentUser(); + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + auto* cdrewardCodes = CDClientManager::GetTable<CDRewardCodesTable>(); + + auto id = cdrewardCodes->GetCodeID(splitArgs[0]); + if (id != -1) Database::Get()->InsertRewardCode(user->GetAccountID(), id); + } + + void Inspect(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + Entity* closest = nullptr; + + std::u16string ldf; + + bool isLDF = false; + + auto component = GeneralUtils::TryParse<eReplicaComponentType>(splitArgs[0]); + if (!component) { + component = eReplicaComponentType::INVALID; + + ldf = GeneralUtils::UTF8ToUTF16(splitArgs[0]); + + isLDF = true; + } + + auto reference = entity->GetPosition(); + + auto closestDistance = 0.0f; + + const auto candidates = Game::entityManager->GetEntitiesByComponent(component.value()); + + for (auto* candidate : candidates) { + if (candidate->GetLOT() == 1 || candidate->GetLOT() == 8092) { + continue; + } + + if (isLDF && !candidate->HasVar(ldf)) { + continue; + } + + if (!closest) { + closest = candidate; + + closestDistance = NiPoint3::Distance(candidate->GetPosition(), reference); + + continue; + } + + const auto distance = NiPoint3::Distance(candidate->GetPosition(), reference); + + if (distance < closestDistance) { + closest = candidate; + + closestDistance = distance; + } + } + + if (!closest) return; + + Game::entityManager->SerializeEntity(closest); + + auto* table = CDClientManager::GetTable<CDObjectsTable>(); + + const auto& info = table->GetByID(closest->GetLOT()); + + std::stringstream header; + + header << info.name << " [" << std::to_string(info.id) << "]" << " " << std::to_string(closestDistance) << " " << std::to_string(closest->IsSleeping()); + + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(header.str())); + + for (const auto& pair : closest->GetComponents()) { + auto id = pair.first; + + std::stringstream stream; + + stream << "Component [" << std::to_string(static_cast<uint32_t>(id)) << "]"; + + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(stream.str())); + } + + if (splitArgs.size() >= 2) { + if (splitArgs[1] == "-m" && splitArgs.size() >= 3) { + auto* const movingPlatformComponent = closest->GetComponent<MovingPlatformComponent>(); + + const auto mValue = GeneralUtils::TryParse<int32_t>(splitArgs[2]); + + if (!movingPlatformComponent || !mValue) return; + + movingPlatformComponent->SetSerialized(true); + + if (mValue == -1) { + movingPlatformComponent->StopPathing(); + } else { + movingPlatformComponent->GotoWaypoint(mValue.value()); + } + + Game::entityManager->SerializeEntity(closest); + } else if (splitArgs[1] == "-a" && splitArgs.size() >= 3) { + RenderComponent::PlayAnimation(closest, splitArgs.at(2)); + } else if (splitArgs[1] == "-s") { + for (auto* entry : closest->GetSettings()) { + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::UTF8ToUTF16(entry->GetString())); + } + + ChatPackets::SendSystemMessage(sysAddr, u"------"); + ChatPackets::SendSystemMessage(sysAddr, u"Spawner ID: " + GeneralUtils::to_u16string(closest->GetSpawnerID())); + } else if (splitArgs[1] == "-p") { + const auto postion = closest->GetPosition(); + + ChatPackets::SendSystemMessage( + sysAddr, + GeneralUtils::ASCIIToUTF16("< " + std::to_string(postion.x) + ", " + std::to_string(postion.y) + ", " + std::to_string(postion.z) + " >") + ); + } else if (splitArgs[1] == "-f") { + auto* destuctable = closest->GetComponent<DestroyableComponent>(); + + if (destuctable == nullptr) { + ChatPackets::SendSystemMessage(sysAddr, u"No destroyable component on this entity!"); + return; + } + + ChatPackets::SendSystemMessage(sysAddr, u"Smashable: " + (GeneralUtils::to_u16string(destuctable->GetIsSmashable()))); + + ChatPackets::SendSystemMessage(sysAddr, u"Friendly factions:"); + for (const auto entry : destuctable->GetFactionIDs()) { + ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry))); + } + + ChatPackets::SendSystemMessage(sysAddr, u"Enemy factions:"); + for (const auto entry : destuctable->GetEnemyFactionsIDs()) { + ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry))); + } + + if (splitArgs.size() >= 3) { + const auto faction = GeneralUtils::TryParse<int32_t>(splitArgs[2]); + if (!faction) return; + + destuctable->SetFaction(-1); + destuctable->AddFaction(faction.value(), true); + } + } else if (splitArgs[1] == "-cf") { + auto* destuctable = entity->GetComponent<DestroyableComponent>(); + if (!destuctable) { + ChatPackets::SendSystemMessage(sysAddr, u"No destroyable component on this entity!"); + return; + } + if (destuctable->IsEnemy(closest)) ChatPackets::SendSystemMessage(sysAddr, u"They are our enemy"); + else ChatPackets::SendSystemMessage(sysAddr, u"They are NOT our enemy"); + } else if (splitArgs[1] == "-t") { + auto* phantomPhysicsComponent = closest->GetComponent<PhantomPhysicsComponent>(); + + if (phantomPhysicsComponent != nullptr) { + ChatPackets::SendSystemMessage(sysAddr, u"Type: " + (GeneralUtils::to_u16string(static_cast<uint32_t>(phantomPhysicsComponent->GetEffectType())))); + const auto dir = phantomPhysicsComponent->GetDirection(); + ChatPackets::SendSystemMessage(sysAddr, u"Direction: <" + (GeneralUtils::to_u16string(dir.x)) + u", " + (GeneralUtils::to_u16string(dir.y)) + u", " + (GeneralUtils::to_u16string(dir.z)) + u">"); + ChatPackets::SendSystemMessage(sysAddr, u"Multiplier: " + (GeneralUtils::to_u16string(phantomPhysicsComponent->GetDirectionalMultiplier()))); + ChatPackets::SendSystemMessage(sysAddr, u"Active: " + (GeneralUtils::to_u16string(phantomPhysicsComponent->GetPhysicsEffectActive()))); + } + + auto* triggerComponent = closest->GetComponent<TriggerComponent>(); + if (triggerComponent) { + auto trigger = triggerComponent->GetTrigger(); + if (trigger) { + ChatPackets::SendSystemMessage(sysAddr, u"Trigger: " + (GeneralUtils::to_u16string(trigger->id))); + } + } + } + } + } +}; diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.h b/dGame/dUtilities/SlashCommands/DEVGMCommands.h new file mode 100644 index 00000000..149348a1 --- /dev/null +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.h @@ -0,0 +1,73 @@ +namespace DEVGMCommands { + void SetGMLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ToggleNameplate(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ToggleSkipCinematics(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Kill(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Metrics(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Announce(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetAnnTitle(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetAnnMsg(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ShutdownUniverse(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetMinifig(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void TestMap(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ReportProxPhys(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SpawnPhysicsVerts(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Teleport(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ActivateSpawner(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void AddMission(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Boost(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Unboost(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Buff(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void BuffMe(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void BuffMed(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ClearFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void CompleteMission(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void CreatePrivate(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void DebugUi(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Dismount(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ReloadConfig(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ForceSave(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Freecam(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void FreeMoney(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void GetNavmeshHeight(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void GiveUScore(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void GmAddItem(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Inspect(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ListSpawns(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void LocRow(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Lookup(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void PlayAnimation(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void PlayEffect(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void PlayLvlFx(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void PlayRebuildFx(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Pos(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void RefillStats(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Reforge(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ResetMission(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Rot(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void RunMacro(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetControlScheme(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetCurrency(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetInventorySize(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetUiState(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Spawn(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SpawnGroup(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SpeedBoost(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void StartCelebration(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void StopEffect(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Toggle(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void TpAll(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void TriggerSpawner(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void UnlockEmote(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetSkillSlot(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetFaction(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void AddFaction(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void GetFactions(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetRewardCode(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Crash(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void RollLoot(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void CastSkill(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void DeleteInven(Entity* entity, const SystemAddress& sysAddr, const std::string args); +} \ No newline at end of file diff --git a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp new file mode 100644 index 00000000..d0aa66aa --- /dev/null +++ b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp @@ -0,0 +1,290 @@ +#include "GMGreaterThanZeroCommands.h" + +// Classes +#include "Character.h" +#include "ChatPackets.h" +#include "dServer.h" +#include "PlayerManager.h" +#include "User.h" + +// Database +#include "Database.h" + +// Components +#include "DestroyableComponent.h" +#include "PropertyManagementComponent.h" + +// Enums +#include "eChatMessageType.h" +#include "eServerDisconnectIdentifiers.h" +#include "eObjectBits.h" + +namespace GMGreaterThanZeroCommands { + + void Kick(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() == 1) { + auto* player = PlayerManager::GetPlayer(splitArgs[0]); + + std::u16string username = GeneralUtils::UTF8ToUTF16(splitArgs[0]); + if (player == nullptr) { + ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + username); + return; + } + + Game::server->Disconnect(player->GetSystemAddress(), eServerDisconnectIdentifiers::KICK); + + ChatPackets::SendSystemMessage(sysAddr, u"Kicked: " + username); + } else { + ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /kick <username>"); + } + } + + void Ban(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + + if (splitArgs.size() == 1) { + auto* player = PlayerManager::GetPlayer(splitArgs[0]); + + uint32_t accountId = 0; + + if (player == nullptr) { + auto characterInfo = Database::Get()->GetCharacterInfo(splitArgs[0]); + + if (characterInfo) { + accountId = characterInfo->accountId; + } + + if (accountId == 0) { + ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + GeneralUtils::UTF8ToUTF16(splitArgs[0])); + + return; + } + } else { + auto* character = player->GetCharacter(); + auto* user = character != nullptr ? character->GetParentUser() : nullptr; + if (user) accountId = user->GetAccountID(); + } + + if (accountId != 0) Database::Get()->UpdateAccountBan(accountId, true); + + if (player != nullptr) { + Game::server->Disconnect(player->GetSystemAddress(), eServerDisconnectIdentifiers::FREE_TRIAL_EXPIRED); + } + + ChatPackets::SendSystemMessage(sysAddr, u"Banned: " + GeneralUtils::ASCIIToUTF16(splitArgs[0])); + } else { + ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /ban <username>"); + } + } + + void MailItem(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.size() < 2) return; + + const auto& playerName = splitArgs[0]; + + auto playerInfo = Database::Get()->GetCharacterInfo(playerName); + + uint32_t receiverID = 0; + if (!playerInfo) { + ChatPackets::SendSystemMessage(sysAddr, u"Failed to find that player"); + + return; + } + + receiverID = playerInfo->id; + + const auto lot = GeneralUtils::TryParse<LOT>(splitArgs.at(1)); + + if (!lot) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid item lot."); + return; + } + + IMail::MailInfo mailInsert; + mailInsert.senderId = entity->GetObjectID(); + mailInsert.senderUsername = "Darkflame Universe"; + mailInsert.receiverId = receiverID; + mailInsert.recipient = playerName; + mailInsert.subject = "Lost item"; + mailInsert.body = "This is a replacement item for one you lost."; + mailInsert.itemID = LWOOBJID_EMPTY; + mailInsert.itemLOT = lot.value(); + mailInsert.itemSubkey = LWOOBJID_EMPTY; + mailInsert.itemCount = 1; + Database::Get()->InsertNewMail(mailInsert); + + ChatPackets::SendSystemMessage(sysAddr, u"Mail sent"); + } + + void ApproveProperty(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (PropertyManagementComponent::Instance() != nullptr) { + PropertyManagementComponent::Instance()->UpdateApprovedStatus(true); + } + } + + void Mute(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + + if (splitArgs.size() >= 1) { + auto* player = PlayerManager::GetPlayer(splitArgs[0]); + + uint32_t accountId = 0; + LWOOBJID characterId = 0; + + if (player == nullptr) { + auto characterInfo = Database::Get()->GetCharacterInfo(splitArgs[0]); + + if (characterInfo) { + accountId = characterInfo->accountId; + characterId = characterInfo->id; + + GeneralUtils::SetBit(characterId, eObjectBits::CHARACTER); + GeneralUtils::SetBit(characterId, eObjectBits::PERSISTENT); + } + + if (accountId == 0) { + ChatPackets::SendSystemMessage(sysAddr, u"Count not find player of name: " + GeneralUtils::UTF8ToUTF16(splitArgs[0])); + + return; + } + } else { + auto* character = player->GetCharacter(); + auto* user = character != nullptr ? character->GetParentUser() : nullptr; + if (user) accountId = user->GetAccountID(); + characterId = player->GetObjectID(); + } + + time_t expire = 1; // Default to indefinate mute + + if (splitArgs.size() >= 2) { + const auto days = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); + if (!days) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid days."); + + return; + } + + std::optional<uint32_t> hours; + if (splitArgs.size() >= 3) { + hours = GeneralUtils::TryParse<uint32_t>(splitArgs[2]); + if (!hours) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid hours."); + + return; + } + } + + expire = time(NULL); + expire += 24 * 60 * 60 * days.value(); + expire += 60 * 60 * hours.value_or(0); + } + + if (accountId != 0) Database::Get()->UpdateAccountUnmuteTime(accountId, expire); + + char buffer[32] = "brought up for review.\0"; + + if (expire != 1) { + std::tm* ptm = std::localtime(&expire); + // Format: Mo, 15.06.2009 20:20:00 + std::strftime(buffer, 32, "%a, %d.%m.%Y %H:%M:%S", ptm); + } + + const auto timeStr = GeneralUtils::ASCIIToUTF16(std::string(buffer)); + + ChatPackets::SendSystemMessage(sysAddr, u"Muted: " + GeneralUtils::UTF8ToUTF16(splitArgs[0]) + u" until " + timeStr); + + //Notify chat about it + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::GM_MUTE); + + bitStream.Write(characterId); + bitStream.Write(expire); + + Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); + } else { + ChatPackets::SendSystemMessage(sysAddr, u"Correct usage: /mute <username> <days (optional)> <hours (optional)>"); + } + } + + void Fly(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + auto* character = entity->GetCharacter(); + + if (character) { + bool isFlying = character->GetIsFlying(); + + if (isFlying) { + GameMessages::SendSetJetPackMode(entity, false); + + character->SetIsFlying(false); + } else { + float speedScale = 1.0f; + + if (splitArgs.size() >= 1) { + const auto tempScaleStore = GeneralUtils::TryParse<float>(splitArgs.at(0)); + + if (tempScaleStore) { + speedScale = tempScaleStore.value(); + } else { + ChatPackets::SendSystemMessage(sysAddr, u"Failed to parse speed scale argument."); + } + } + + float airSpeed = 20 * speedScale; + float maxAirSpeed = 30 * speedScale; + float verticalVelocity = 1.5 * speedScale; + + GameMessages::SendSetJetPackMode(entity, true, true, false, 167, airSpeed, maxAirSpeed, verticalVelocity); + + character->SetIsFlying(true); + } + } + } + + void AttackImmune(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); + + const auto state = GeneralUtils::TryParse<int32_t>(splitArgs[0]); + + if (!state) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid state."); + return; + } + + if (destroyableComponent) destroyableComponent->SetIsImmune(state.value()); + } + + void GmImmune(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); + + const auto state = GeneralUtils::TryParse<int32_t>(splitArgs[0]); + + if (!state) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid state."); + return; + } + + if (destroyableComponent) destroyableComponent->SetIsGMImmune(state.value()); + } + + void GmInvis(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + GameMessages::SendToggleGMInvis(entity->GetObjectID(), true, UNASSIGNED_SYSTEM_ADDRESS); + } + + void SetName(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(args), UNASSIGNED_SYSTEM_ADDRESS); + } + + void Title(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + std::string name = entity->GetCharacter()->GetName() + " - " + args; + GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(name), UNASSIGNED_SYSTEM_ADDRESS); + } +} diff --git a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h new file mode 100644 index 00000000..4b03441f --- /dev/null +++ b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h @@ -0,0 +1,13 @@ +namespace GMGreaterThanZeroCommands { + void Kick(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void MailItem(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Ban(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ApproveProperty(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Mute(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Fly(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void AttackImmune(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void GmImmune(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void GmInvis(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SetName(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Title(Entity* entity, const SystemAddress& sysAddr, const std::string args); +} \ No newline at end of file diff --git a/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp b/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp new file mode 100644 index 00000000..f183d5ea --- /dev/null +++ b/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp @@ -0,0 +1,228 @@ +#include "GMZeroCommands.h" + +// Classes +#include "Amf3.h" +#include "BinaryPathFinder.h" +#include "ChatPackets.h" +#include "dServer.h" +#include "dZoneManager.h" +#include "Mail.h" +#include "PlayerManager.h" +#include "SlashCommandHandler.h" +#include "VanityUtilities.h" +#include "WorldPackets.h" +#include "ZoneInstanceManager.h" + +// Components +#include "BuffComponent.h" +#include "CharacterComponent.h" +#include "DestroyableComponent.h" +#include "ScriptedActivityComponent.h" +#include "SkillComponent.h" + +// Emuns +#include "eGameMasterLevel.h" + +namespace GMZeroCommands { + void Pvp(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto* character = entity->GetComponent<CharacterComponent>(); + + if (character == nullptr) { + LOG("Failed to find character component!"); + return; + } + + character->SetPvpEnabled(!character->GetPvpEnabled()); + Game::entityManager->SerializeEntity(entity); + + std::stringstream message; + message << character->GetName() << " changed their PVP flag to " << std::to_string(character->GetPvpEnabled()) << "!"; + + ChatPackets::SendSystemMessage(UNASSIGNED_SYSTEM_ADDRESS, GeneralUtils::UTF8ToUTF16(message.str()), true); + } + + void Who(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + ChatPackets::SendSystemMessage( + sysAddr, + u"Players in this instance: (" + GeneralUtils::to_u16string(PlayerManager::GetAllPlayers().size()) + u")" + ); + + for (auto* player : PlayerManager::GetAllPlayers()) { + const auto& name = player->GetCharacter()->GetName(); + + ChatPackets::SendSystemMessage( + sysAddr, + GeneralUtils::UTF8ToUTF16(player == entity ? name + " (you)" : name) + ); + } + } + + void Ping(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (!args.empty() && args.starts_with("-l")) { + std::stringstream message; + message << "Your latest ping: " << std::to_string(Game::server->GetLatestPing(sysAddr)) << "ms"; + + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(message.str())); + } else { + std::stringstream message; + message << "Your average ping: " << std::to_string(Game::server->GetPing(sysAddr)) << "ms"; + + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(message.str())); + } + } + + void FixStats(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + // Reset skill component and buff component + auto* skillComponent = entity->GetComponent<SkillComponent>(); + auto* buffComponent = entity->GetComponent<BuffComponent>(); + auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); + + // If any of the components are nullptr, return + if (skillComponent == nullptr || buffComponent == nullptr || destroyableComponent == nullptr) { + return; + } + + // Reset skill component + skillComponent->Reset(); + + // Reset buff component + buffComponent->Reset(); + + // Fix the destroyable component + destroyableComponent->FixStats(); + } + + void Credits(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto& customText = VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/CREDITS.md").string()); + + { + AMFArrayValue args; + + args.Insert("state", "Story"); + + GameMessages::SendUIMessageServerToSingleClient(entity, entity->GetSystemAddress(), "pushGameState", args); + } + + entity->AddCallbackTimer(0.5f, [customText, entity]() { + AMFArrayValue args; + + args.Insert("visible", true); + args.Insert("text", customText); + + LOG("Sending %s", customText.c_str()); + + GameMessages::SendUIMessageServerToSingleClient(entity, entity->GetSystemAddress(), "ToggleStoryBox", args); + }); + } + + void Info(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto& customText = VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/INFO.md").string()); + + { + AMFArrayValue args; + + args.Insert("state", "Story"); + + GameMessages::SendUIMessageServerToSingleClient(entity, entity->GetSystemAddress(), "pushGameState", args); + } + + entity->AddCallbackTimer(0.5f, [customText, entity]() { + AMFArrayValue args; + + args.Insert("visible", true); + args.Insert("text", customText); + + LOG("Sending %s", customText.c_str()); + + GameMessages::SendUIMessageServerToSingleClient(entity, entity->GetSystemAddress(), "ToggleStoryBox", args); + }); + } + + void LeaveZone(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto currentZone = Game::zoneManager->GetZone()->GetZoneID().GetMapID(); + LWOMAPID newZone = 0; + + if (currentZone == 1001 || currentZone % 100 == 0) { + ChatPackets::SendSystemMessage(sysAddr, u"You are not in an instanced zone."); + return; + } else { + newZone = (currentZone / 100) * 100; + } + // If new zone would be inaccessible, then default to Avant Gardens. + if (!Game::zoneManager->CheckIfAccessibleZone(newZone)) newZone = 1100; + + ChatPackets::SendSystemMessage(sysAddr, u"Leaving zone..."); + + const auto objid = entity->GetObjectID(); + + ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, newZone, 0, false, [objid](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) { + auto* entity = Game::entityManager->GetEntity(objid); + + if (entity == nullptr) { + return; + } + + const auto sysAddr = entity->GetSystemAddress(); + + LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", entity->GetCharacter()->GetName().c_str(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort); + + if (entity->GetCharacter()) { + entity->GetCharacter()->SetZoneID(zoneID); + entity->GetCharacter()->SetZoneInstance(zoneInstance); + entity->GetCharacter()->SetZoneClone(zoneClone); + } + + entity->GetCharacter()->SaveXMLToDatabase(); + + WorldPackets::SendTransferToWorld(sysAddr, serverIP, serverPort, mythranShift); + }); + } + + void Join(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto splitArgs = GeneralUtils::SplitString(args, ' '); + if (splitArgs.empty()) return; + + ChatPackets::SendSystemMessage(sysAddr, u"Requesting private map..."); + const auto& password = splitArgs[0]; + + ZoneInstanceManager::Instance()->RequestPrivateZone(Game::server, false, password, [=](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) { + LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", sysAddr.ToString(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort); + + if (entity->GetCharacter()) { + entity->GetCharacter()->SetZoneID(zoneID); + entity->GetCharacter()->SetZoneInstance(zoneInstance); + entity->GetCharacter()->SetZoneClone(zoneClone); + } + + entity->GetCharacter()->SaveXMLToDatabase(); + + WorldPackets::SendTransferToWorld(sysAddr, serverIP, serverPort, mythranShift); + }); + } + + void Die(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + entity->Smash(entity->GetObjectID()); + } + + void Resurrect(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + ScriptedActivityComponent* scriptedActivityComponent = Game::zoneManager->GetZoneControlObject()->GetComponent<ScriptedActivityComponent>(); + + if (scriptedActivityComponent) { // check if user is in activity world and if so, they can't resurrect + ChatPackets::SendSystemMessage(sysAddr, u"You cannot resurrect in an activity world."); + return; + } + + GameMessages::SendResurrect(entity); + } + + void RequestMailCount(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + Mail::HandleNotificationRequest(entity->GetSystemAddress(), entity->GetObjectID()); + } + + void InstanceInfo(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto zoneId = Game::zoneManager->GetZone()->GetZoneID(); + + ChatPackets::SendSystemMessage(sysAddr, u"Map: " + (GeneralUtils::to_u16string(zoneId.GetMapID())) + u"\nClone: " + (GeneralUtils::to_u16string(zoneId.GetCloneID())) + u"\nInstance: " + (GeneralUtils::to_u16string(zoneId.GetInstanceID()))); + } +}; + diff --git a/dGame/dUtilities/SlashCommands/GMZeroCommands.h b/dGame/dUtilities/SlashCommands/GMZeroCommands.h new file mode 100644 index 00000000..29aa8f36 --- /dev/null +++ b/dGame/dUtilities/SlashCommands/GMZeroCommands.h @@ -0,0 +1,15 @@ +namespace GMZeroCommands { + void Help(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Credits(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Info(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Die(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Ping(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Pvp(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void RequestMailCount(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Who(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void FixStats(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Join(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void LeaveZone(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Resurrect(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void InstanceInfo(Entity* entity, const SystemAddress& sysAddr, const std::string args); +} diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 9f6d63bc..b6954ca2 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -314,8 +314,8 @@ int main(int argc, char** argv) { uint32_t sqlPingTime = 10 * 60 * currentFramerate; // 10 minutes in frames uint32_t emptyShutdownTime = (cloneID == 0 ? 30 : 5) * 60 * currentFramerate; // 30 minutes for main worlds, 5 for all others. - // Register slash commands - SlashCommandHandler::Startup(); + // Register slash commands if not in zone 0 + if (zoneID != 0) SlashCommandHandler::Startup(); Game::logger->Flush(); // once immediately before the main loop while (true) { From b8b2b687e24617e131e85d1f4171840cc0d268c5 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Wed, 10 Apr 2024 05:32:54 -0700 Subject: [PATCH 13/78] inherit exception for CppSQLite3Exception (#1544) catch any exception just in case exception isnt inherited from --- dCommon/Diagnostics.cpp | 2 ++ thirdparty/SQLite/CppSQLite3.h | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/dCommon/Diagnostics.cpp b/dCommon/Diagnostics.cpp index 46c17e43..2ed27fef 100644 --- a/dCommon/Diagnostics.cpp +++ b/dCommon/Diagnostics.cpp @@ -120,6 +120,8 @@ void CatchUnhandled(int sig) { if (eptr) std::rethrow_exception(eptr); } catch(const std::exception& e) { LOG("Caught exception: '%s'", e.what()); + } catch (...) { + LOG("Caught unknown exception."); } #ifndef INCLUDE_BACKTRACE diff --git a/thirdparty/SQLite/CppSQLite3.h b/thirdparty/SQLite/CppSQLite3.h index 7ae8a8b7..70c4b8e8 100644 --- a/thirdparty/SQLite/CppSQLite3.h +++ b/thirdparty/SQLite/CppSQLite3.h @@ -36,10 +36,11 @@ #include "sqlite3.h" #include <cstdio> #include <cstring> +#include <exception> #define CPPSQLITE_ERROR 1000 -class CppSQLite3Exception +class CppSQLite3Exception : public std::exception { public: @@ -54,6 +55,8 @@ public: const int errorCode() { return mnErrCode; } const char* errorMessage() { return mpszErrMess; } + + const char* what() const noexcept override { return mpszErrMess; } static const char* errorCodeAsString(int nErrCode); From 3a6123fe36d6c16aba08615077ea8203ae08daf8 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 11 Apr 2024 08:29:49 -0700 Subject: [PATCH 14/78] fix console sound (#1547) --- dScripts/02_server/Map/General/TokenConsoleServer.cpp | 4 +++- dScripts/02_server/Map/NS/NsTokenConsoleServer.cpp | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dScripts/02_server/Map/General/TokenConsoleServer.cpp b/dScripts/02_server/Map/General/TokenConsoleServer.cpp index e13011cb..0a1f679c 100644 --- a/dScripts/02_server/Map/General/TokenConsoleServer.cpp +++ b/dScripts/02_server/Map/General/TokenConsoleServer.cpp @@ -17,7 +17,9 @@ void TokenConsoleServer::OnUse(Entity* self, Entity* user) { inv->RemoveItem(6194, bricksToTake); //play sound - GameMessages::SendPlayNDAudioEmitter(self, user->GetSystemAddress(), "947d0d52-c7f8-4516-8dee-e1593a7fd1d1"); + if (self->HasVar(u"sound1")) { + GameMessages::SendPlayNDAudioEmitter(self, user->GetSystemAddress(), self->GetVarAsString(u"sound1")); + } //figure out which faction the player belongs to: auto character = user->GetCharacter(); diff --git a/dScripts/02_server/Map/NS/NsTokenConsoleServer.cpp b/dScripts/02_server/Map/NS/NsTokenConsoleServer.cpp index 74542f9f..7d825828 100644 --- a/dScripts/02_server/Map/NS/NsTokenConsoleServer.cpp +++ b/dScripts/02_server/Map/NS/NsTokenConsoleServer.cpp @@ -39,7 +39,7 @@ void NsTokenConsoleServer::OnUse(Entity* self, Entity* user) { const auto useSound = self->GetVar<std::string>(u"sound1"); if (!useSound.empty()) { - GameMessages::SendPlayNDAudioEmitter(self, UNASSIGNED_SYSTEM_ADDRESS, useSound); + GameMessages::SendPlayNDAudioEmitter(self, user->GetSystemAddress(), useSound); } // Player must be in faction to interact with this entity. From 5049f215bad44f3e5bfdf0e77c469d7fc75cf334 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sat, 13 Apr 2024 21:41:51 -0700 Subject: [PATCH 15/78] chore: Use string to access SQLite columns (#1535) * use string to access field name * Update DEVGMCommands.cpp * corrected column name * constexpr array include <array> Revert "constexpr array" This reverts commit 1492e8b1773ed5fbbe767c74466ca263178ecdd4. Revert "include <array>" This reverts commit 2b7a67e89ad673d420f496be97f9bc51fd2d5e59. include <array> constexpr array --------- Co-authored-by: jadebenn <jonahbenn@yahoo.com> --- dGame/UserManager.cpp | 8 +++--- dGame/dBehaviors/Behavior.cpp | 4 +-- dGame/dBehaviors/SwitchMultipleBehavior.cpp | 4 +-- dGame/dComponents/BaseCombatAIComponent.cpp | 26 +++++++++---------- dGame/dComponents/BuffComponent.cpp | 2 +- dGame/dComponents/DestroyableComponent.cpp | 4 +-- dGame/dComponents/InventoryComponent.cpp | 2 +- dGame/dComponents/MissionComponent.cpp | 4 +-- dGame/dComponents/PetComponent.cpp | 12 ++++----- dGame/dComponents/PossessableComponent.cpp | 4 +-- .../PropertyManagementComponent.cpp | 6 ++--- dGame/dComponents/RenderComponent.cpp | 4 +-- .../RocketLaunchpadControlComponent.cpp | 12 ++++----- dGame/dComponents/SkillComponent.cpp | 4 +-- dGame/dInventory/Item.cpp | 6 ++--- dGame/dInventory/ItemSet.cpp | 17 +++++++----- dGame/dUtilities/Preconditions.cpp | 8 +++--- .../SlashCommands/DEVGMCommands.cpp | 2 +- 18 files changed, 67 insertions(+), 62 deletions(-) diff --git a/dGame/UserManager.cpp b/dGame/UserManager.cpp index da7e9e23..8579d882 100644 --- a/dGame/UserManager.cpp +++ b/dGame/UserManager.cpp @@ -536,13 +536,13 @@ void UserManager::LoginCharacter(const SystemAddress& sysAddr, uint32_t playerID uint32_t FindCharShirtID(uint32_t shirtColor, uint32_t shirtStyle) { try { auto stmt = CDClientDatabase::CreatePreppedStmt( - "select obj.id from Objects as obj JOIN (select * from ComponentsRegistry as cr JOIN ItemComponent as ic on ic.id = cr.component_id where cr.component_type == 11) as icc on icc.id = obj.id where lower(obj._internalNotes) == ? AND icc.color1 == ? AND icc.decal == ?" + "select obj.id as objectId from Objects as obj JOIN (select * from ComponentsRegistry as cr JOIN ItemComponent as ic on ic.id = cr.component_id where cr.component_type == 11) as icc on icc.id = obj.id where lower(obj._internalNotes) == ? AND icc.color1 == ? AND icc.decal == ?" ); stmt.bind(1, "character create shirt"); stmt.bind(2, static_cast<int>(shirtColor)); stmt.bind(3, static_cast<int>(shirtStyle)); auto tableData = stmt.execQuery(); - auto shirtLOT = tableData.getIntField(0, 4069); + auto shirtLOT = tableData.getIntField("objectId", 4069); tableData.finalize(); return shirtLOT; } catch (const std::exception& ex) { @@ -555,12 +555,12 @@ uint32_t FindCharShirtID(uint32_t shirtColor, uint32_t shirtStyle) { uint32_t FindCharPantsID(uint32_t pantsColor) { try { auto stmt = CDClientDatabase::CreatePreppedStmt( - "select obj.id from Objects as obj JOIN (select * from ComponentsRegistry as cr JOIN ItemComponent as ic on ic.id = cr.component_id where cr.component_type == 11) as icc on icc.id = obj.id where lower(obj._internalNotes) == ? AND icc.color1 == ?" + "select obj.id as objectId from Objects as obj JOIN (select * from ComponentsRegistry as cr JOIN ItemComponent as ic on ic.id = cr.component_id where cr.component_type == 11) as icc on icc.id = obj.id where lower(obj._internalNotes) == ? AND icc.color1 == ?" ); stmt.bind(1, "cc pants"); stmt.bind(2, static_cast<int>(pantsColor)); auto tableData = stmt.execQuery(); - auto pantsLOT = tableData.getIntField(0, 2508); + auto pantsLOT = tableData.getIntField("objectId", 2508); tableData.finalize(); return pantsLOT; } catch (const std::exception& ex) { diff --git a/dGame/dBehaviors/Behavior.cpp b/dGame/dBehaviors/Behavior.cpp index 4d57a9df..7cefd4dd 100644 --- a/dGame/dBehaviors/Behavior.cpp +++ b/dGame/dBehaviors/Behavior.cpp @@ -377,10 +377,10 @@ void Behavior::PlayFx(std::u16string type, const LWOOBJID target, const LWOOBJID return; } - const auto name = std::string(result.getStringField(0)); + const auto name = std::string(result.getStringField("effectName")); if (type.empty()) { - const auto typeResult = result.getStringField(1); + const auto typeResult = result.getStringField("effectType"); type = GeneralUtils::ASCIIToUTF16(typeResult); diff --git a/dGame/dBehaviors/SwitchMultipleBehavior.cpp b/dGame/dBehaviors/SwitchMultipleBehavior.cpp index 92c9a8de..00d639d5 100644 --- a/dGame/dBehaviors/SwitchMultipleBehavior.cpp +++ b/dGame/dBehaviors/SwitchMultipleBehavior.cpp @@ -47,11 +47,11 @@ void SwitchMultipleBehavior::Load() { auto result = query.execQuery(); while (!result.eof()) { - const auto behavior_id = static_cast<uint32_t>(result.getFloatField(1)); + const auto behavior_id = static_cast<uint32_t>(result.getFloatField("behavior")); auto* behavior = CreateBehavior(behavior_id); - auto value = result.getFloatField(2); + auto value = result.getFloatField("value"); this->m_behaviors.emplace_back(value, behavior); diff --git a/dGame/dComponents/BaseCombatAIComponent.cpp b/dGame/dComponents/BaseCombatAIComponent.cpp index 0c2a796c..60dceeef 100644 --- a/dGame/dComponents/BaseCombatAIComponent.cpp +++ b/dGame/dComponents/BaseCombatAIComponent.cpp @@ -45,20 +45,20 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id): auto componentResult = componentQuery.execQuery(); if (!componentResult.eof()) { - if (!componentResult.fieldIsNull(0)) - m_AggroRadius = componentResult.getFloatField(0); + if (!componentResult.fieldIsNull("aggroRadius")) + m_AggroRadius = componentResult.getFloatField("aggroRadius"); - if (!componentResult.fieldIsNull(1)) - m_TetherSpeed = componentResult.getFloatField(1); + if (!componentResult.fieldIsNull("tetherSpeed")) + m_TetherSpeed = componentResult.getFloatField("tetherSpeed"); - if (!componentResult.fieldIsNull(2)) - m_PursuitSpeed = componentResult.getFloatField(2); + if (!componentResult.fieldIsNull("pursuitSpeed")) + m_PursuitSpeed = componentResult.getFloatField("pursuitSpeed"); - if (!componentResult.fieldIsNull(3)) - m_SoftTetherRadius = componentResult.getFloatField(3); + if (!componentResult.fieldIsNull("softTetherRadius")) + m_SoftTetherRadius = componentResult.getFloatField("softTetherRadius"); - if (!componentResult.fieldIsNull(4)) - m_HardTetherRadius = componentResult.getFloatField(4); + if (!componentResult.fieldIsNull("hardTetherRadius")) + m_HardTetherRadius = componentResult.getFloatField("hardTetherRadius"); } componentResult.finalize(); @@ -82,11 +82,11 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id): auto result = skillQuery.execQuery(); while (!result.eof()) { - const auto skillId = static_cast<uint32_t>(result.getIntField(0)); + const auto skillId = static_cast<uint32_t>(result.getIntField("skillID")); - const auto abilityCooldown = static_cast<float>(result.getFloatField(1)); + const auto abilityCooldown = static_cast<float>(result.getFloatField("cooldown")); - const auto behaviorId = static_cast<uint32_t>(result.getIntField(2)); + const auto behaviorId = static_cast<uint32_t>(result.getIntField("behaviorID")); auto* behavior = Behavior::CreateBehavior(behaviorId); diff --git a/dGame/dComponents/BuffComponent.cpp b/dGame/dComponents/BuffComponent.cpp index 44c88ccb..2c940647 100644 --- a/dGame/dComponents/BuffComponent.cpp +++ b/dGame/dComponents/BuffComponent.cpp @@ -450,7 +450,7 @@ const std::vector<BuffParameter>& BuffComponent::GetBuffParameters(int32_t buffI param.value = result.getFloatField("NumberValue"); param.effectId = result.getIntField("EffectID"); - if (!result.fieldIsNull(3)) { + if (!result.fieldIsNull("StringValue")) { std::istringstream stream(result.getStringField("StringValue")); std::string token; diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 476235f9..8ec1e4c9 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -389,9 +389,9 @@ void DestroyableComponent::AddFaction(const int32_t factionID, const bool ignore if (result.eof()) return; - if (result.fieldIsNull(0)) return; + if (result.fieldIsNull("enemyList")) return; - const auto* list_string = result.getStringField(0); + const auto* list_string = result.getStringField("enemyList"); std::stringstream ss(list_string); std::string token; diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 8af7fb34..60dd071c 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -1094,7 +1094,7 @@ void InventoryComponent::CheckItemSet(const LOT lot) { auto result = query.execQuery(); while (!result.eof()) { - const auto id = result.getIntField(0); + const auto id = result.getIntField("setID"); bool found = false; diff --git a/dGame/dComponents/MissionComponent.cpp b/dGame/dComponents/MissionComponent.cpp index 1eb02e57..0760d8e4 100644 --- a/dGame/dComponents/MissionComponent.cpp +++ b/dGame/dComponents/MissionComponent.cpp @@ -466,8 +466,8 @@ bool MissionComponent::RequiresItem(const LOT lot) { return false; } - if (!result.fieldIsNull(0)) { - const auto type = std::string(result.getStringField(0)); + if (!result.fieldIsNull("type")) { + const auto type = std::string(result.getStringField("type")); result.finalize(); diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index a6bb00fb..6b809ec0 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -188,20 +188,20 @@ void PetComponent::OnUse(Entity* originator) { return; } - if (result.fieldIsNull(0)) { + if (result.fieldIsNull("ValidPiecesLXF")) { result.finalize(); return; } - buildFile = std::string(result.getStringField(0)); + buildFile = std::string(result.getStringField("ValidPiecesLXF")); PetPuzzleData data; data.buildFile = buildFile; - data.puzzleModelLot = result.getIntField(1); - data.timeLimit = result.getFloatField(2); - data.numValidPieces = result.getIntField(3); - data.imaginationCost = result.getIntField(4); + data.puzzleModelLot = result.getIntField("PuzzleModelLot"); + data.timeLimit = result.getFloatField("Timelimit"); + data.numValidPieces = result.getIntField("NumValidPieces"); + data.imaginationCost = result.getIntField("imagCostPerBuild"); if (data.timeLimit <= 0) data.timeLimit = 60; imaginationCost = data.imaginationCost; diff --git a/dGame/dComponents/PossessableComponent.cpp b/dGame/dComponents/PossessableComponent.cpp index ae5b05b3..e0e8c8ad 100644 --- a/dGame/dComponents/PossessableComponent.cpp +++ b/dGame/dComponents/PossessableComponent.cpp @@ -18,8 +18,8 @@ PossessableComponent::PossessableComponent(Entity* parent, uint32_t componentId) // Should a result not exist for this default to attached visible if (!result.eof()) { - m_PossessionType = static_cast<ePossessionType>(result.getIntField(0, 1)); // Default to Attached Visible - m_DepossessOnHit = static_cast<bool>(result.getIntField(1, 0)); + m_PossessionType = static_cast<ePossessionType>(result.getIntField("possessionType", 1)); // Default to Attached Visible + m_DepossessOnHit = static_cast<bool>(result.getIntField("depossessOnHit", 0)); } else { m_PossessionType = ePossessionType::ATTACHED_VISIBLE; m_DepossessOnHit = false; diff --git a/dGame/dComponents/PropertyManagementComponent.cpp b/dGame/dComponents/PropertyManagementComponent.cpp index 95a6f3e0..ad96097b 100644 --- a/dGame/dComponents/PropertyManagementComponent.cpp +++ b/dGame/dComponents/PropertyManagementComponent.cpp @@ -49,11 +49,11 @@ PropertyManagementComponent::PropertyManagementComponent(Entity* parent) : Compo auto result = query.execQuery(); - if (result.eof() || result.fieldIsNull(0)) { + if (result.eof() || result.fieldIsNull("id")) { return; } - templateId = result.getIntField(0); + templateId = result.getIntField("id"); auto propertyInfo = Database::Get()->GetPropertyInfo(zoneId, cloneId); @@ -105,7 +105,7 @@ std::vector<NiPoint3> PropertyManagementComponent::GetPaths() const { std::vector<float> points; - std::istringstream stream(result.getStringField(0)); + std::istringstream stream(result.getStringField("path")); std::string token; while (std::getline(stream, token, ' ')) { diff --git a/dGame/dComponents/RenderComponent.cpp b/dGame/dComponents/RenderComponent.cpp index 2067ef2a..cc5974a1 100644 --- a/dGame/dComponents/RenderComponent.cpp +++ b/dGame/dComponents/RenderComponent.cpp @@ -117,7 +117,7 @@ void RenderComponent::PlayEffect(const int32_t effectId, const std::u16string& e auto result = query.execQuery(); - if (result.eof() || result.fieldIsNull(0)) { + if (result.eof() || result.fieldIsNull("animation_length")) { result.finalize(); m_DurationCache[effectId] = 0; @@ -127,7 +127,7 @@ void RenderComponent::PlayEffect(const int32_t effectId, const std::u16string& e return; } - effect.time = static_cast<float>(result.getFloatField(0)); + effect.time = static_cast<float>(result.getFloatField("animation_length")); result.finalize(); diff --git a/dGame/dComponents/RocketLaunchpadControlComponent.cpp b/dGame/dComponents/RocketLaunchpadControlComponent.cpp index 2bc4deec..c0c90581 100644 --- a/dGame/dComponents/RocketLaunchpadControlComponent.cpp +++ b/dGame/dComponents/RocketLaunchpadControlComponent.cpp @@ -27,12 +27,12 @@ RocketLaunchpadControlComponent::RocketLaunchpadControlComponent(Entity* parent, auto result = query.execQuery(); - if (!result.eof() && !result.fieldIsNull(0)) { - m_TargetZone = result.getIntField(0); - m_DefaultZone = result.getIntField(1); - m_TargetScene = result.getStringField(2); - m_AltPrecondition = new PreconditionExpression(result.getStringField(3)); - m_AltLandingScene = result.getStringField(4); + if (!result.eof() && !result.fieldIsNull("targetZone")) { + m_TargetZone = result.getIntField("targetZone"); + m_DefaultZone = result.getIntField("defaultZoneID"); + m_TargetScene = result.getStringField("targetScene"); + m_AltPrecondition = new PreconditionExpression(result.getStringField("altLandingPrecondition")); + m_AltLandingScene = result.getStringField("altLandingSpawnPointName"); } result.finalize(); diff --git a/dGame/dComponents/SkillComponent.cpp b/dGame/dComponents/SkillComponent.cpp index 8460032c..d926ff2c 100644 --- a/dGame/dComponents/SkillComponent.cpp +++ b/dGame/dComponents/SkillComponent.cpp @@ -99,7 +99,7 @@ void SkillComponent::SyncPlayerProjectile(const LWOOBJID projectileId, RakNet::B return; } - const auto behavior_id = static_cast<uint32_t>(result.getIntField(0)); + const auto behavior_id = static_cast<uint32_t>(result.getIntField("behaviorID")); result.finalize(); @@ -425,7 +425,7 @@ void SkillComponent::SyncProjectileCalculation(const ProjectileSyncEntry& entry) return; } - const auto behaviorId = static_cast<uint32_t>(result.getIntField(0)); + const auto behaviorId = static_cast<uint32_t>(result.getIntField("behaviorID")); result.finalize(); diff --git a/dGame/dInventory/Item.cpp b/dGame/dInventory/Item.cpp index d3f15315..b6193692 100644 --- a/dGame/dInventory/Item.cpp +++ b/dGame/dInventory/Item.cpp @@ -405,18 +405,18 @@ void Item::DisassembleModel(uint32_t numToDismantle) { auto result = query.execQuery(); - if (result.eof() || result.fieldIsNull(0)) { + if (result.eof() || result.fieldIsNull("render_asset")) { return; } - std::string renderAsset = std::string(result.getStringField(0)); + std::string renderAsset = std::string(result.getStringField("render_asset")); // normalize path slashes for (auto& c : renderAsset) { if (c == '\\') c = '/'; } - std::string lxfmlFolderName = std::string(result.getStringField(1)); + std::string lxfmlFolderName = std::string(result.getStringField("LXFMLFolder")); if (!lxfmlFolderName.empty()) lxfmlFolderName.insert(0, "/"); std::vector<std::string> renderAssetSplit = GeneralUtils::SplitString(renderAsset, '/'); diff --git a/dGame/dInventory/ItemSet.cpp b/dGame/dInventory/ItemSet.cpp index 1d086786..d8d320ed 100644 --- a/dGame/dInventory/ItemSet.cpp +++ b/dGame/dInventory/ItemSet.cpp @@ -8,10 +8,13 @@ #include "MissionComponent.h" #include "eMissionTaskType.h" #include <algorithm> +#include <array> #include "CDSkillBehaviorTable.h" ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent) { + using namespace std::string_view_literals; + this->m_ID = id; this->m_InventoryComponent = inventoryComponent; @@ -27,14 +30,16 @@ ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent) { return; } - for (auto i = 0; i < 5; ++i) { - if (result.fieldIsNull(i)) { + constexpr std::array rowNames = { "skillSetWith2"sv, "skillSetWith3"sv, "skillSetWith4"sv, "skillSetWith5"sv, "skillSetWith6"sv }; + for (auto i = 0; i < rowNames.size(); ++i) { + const auto rowName = rowNames[i]; + if (result.fieldIsNull(rowName.data())) { continue; } auto skillQuery = CDClientDatabase::CreatePreppedStmt( "SELECT SkillID FROM ItemSetSkills WHERE SkillSetID = ?;"); - skillQuery.bind(1, result.getIntField(i)); + skillQuery.bind(1, result.getIntField(rowName.data())); auto skillResult = skillQuery.execQuery(); @@ -43,13 +48,13 @@ ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent) { } while (!skillResult.eof()) { - if (skillResult.fieldIsNull(0)) { + if (skillResult.fieldIsNull("SkillID")) { skillResult.nextRow(); continue; } - const auto skillId = skillResult.getIntField(0); + const auto skillId = skillResult.getIntField("SkillID"); switch (i) { case 0: @@ -75,7 +80,7 @@ ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent) { } } - std::string ids = result.getStringField(5); + std::string ids = result.getStringField("itemIDs"); ids.erase(std::remove_if(ids.begin(), ids.end(), ::isspace), ids.end()); diff --git a/dGame/dUtilities/Preconditions.cpp b/dGame/dUtilities/Preconditions.cpp index bd855962..25e8b773 100644 --- a/dGame/dUtilities/Preconditions.cpp +++ b/dGame/dUtilities/Preconditions.cpp @@ -33,10 +33,10 @@ Precondition::Precondition(const uint32_t condition) { return; } - this->type = static_cast<PreconditionType>(result.fieldIsNull(0) ? 0 : result.getIntField(0)); + this->type = static_cast<PreconditionType>(result.fieldIsNull("type") ? 0 : result.getIntField("type")); - if (!result.fieldIsNull(1)) { - std::istringstream stream(result.getStringField(1)); + if (!result.fieldIsNull("targetLOT")) { + std::istringstream stream(result.getStringField("targetLOT")); std::string token; while (std::getline(stream, token, ',')) { @@ -45,7 +45,7 @@ Precondition::Precondition(const uint32_t condition) { } } - this->count = result.fieldIsNull(2) ? 1 : result.getIntField(2); + this->count = result.fieldIsNull("targetCount") ? 1 : result.getIntField("targetCount"); result.finalize(); } diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp index b8d6b9f3..bc008ab2 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp @@ -704,7 +704,7 @@ namespace DEVGMCommands { auto tables = query.execQuery(); while (!tables.eof()) { - std::string message = std::to_string(tables.getIntField(0)) + " - " + tables.getStringField(1); + std::string message = std::to_string(tables.getIntField("id")) + " - " + tables.getStringField("name"); ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::UTF8ToUTF16(message, message.size())); tables.nextRow(); } From fafe2aefadc9844429f66e6dc20478e41a419f14 Mon Sep 17 00:00:00 2001 From: jadebenn <jadebenn@users.noreply.github.com> Date: Mon, 15 Apr 2024 01:14:54 -0500 Subject: [PATCH 16/78] chore: Nitpicking-utils (#1549) * nit * GeneralUtils const-correctness and minor fixes * use copy instead of reference for char iteration loops * fix typo and reorganize some functions --- dCommon/GeneralUtils.cpp | 119 ++++++++++++++-------------- dCommon/GeneralUtils.h | 77 +++++++++--------- tests/dCommonTests/TestEncoding.cpp | 48 +++++------ 3 files changed, 122 insertions(+), 122 deletions(-) diff --git a/dCommon/GeneralUtils.cpp b/dCommon/GeneralUtils.cpp index 159cc127..52287904 100644 --- a/dCommon/GeneralUtils.cpp +++ b/dCommon/GeneralUtils.cpp @@ -8,23 +8,23 @@ #include <map> template <typename T> -inline size_t MinSize(size_t size, const std::basic_string_view<T>& string) { - if (size == size_t(-1) || size > string.size()) { +static inline size_t MinSize(const size_t size, const std::basic_string_view<T> string) { + if (size == SIZE_MAX || size > string.size()) { return string.size(); } else { return size; } } -inline bool IsLeadSurrogate(char16_t c) { +inline bool IsLeadSurrogate(const char16_t c) { return (0xD800 <= c) && (c <= 0xDBFF); } -inline bool IsTrailSurrogate(char16_t c) { +inline bool IsTrailSurrogate(const char16_t c) { return (0xDC00 <= c) && (c <= 0xDFFF); } -inline void PushUTF8CodePoint(std::string& ret, char32_t cp) { +inline void PushUTF8CodePoint(std::string& ret, const char32_t cp) { if (cp <= 0x007F) { ret.push_back(static_cast<uint8_t>(cp)); } else if (cp <= 0x07FF) { @@ -46,16 +46,16 @@ inline void PushUTF8CodePoint(std::string& ret, char32_t cp) { constexpr const char16_t REPLACEMENT_CHARACTER = 0xFFFD; -bool _IsSuffixChar(uint8_t c) { +bool static _IsSuffixChar(const uint8_t c) { return (c & 0xC0) == 0x80; } -bool GeneralUtils::_NextUTF8Char(std::string_view& slice, uint32_t& out) { - size_t rem = slice.length(); +bool GeneralUtils::details::_NextUTF8Char(std::string_view& slice, uint32_t& out) { + const size_t rem = slice.length(); if (slice.empty()) return false; const uint8_t* bytes = reinterpret_cast<const uint8_t*>(&slice.front()); if (rem > 0) { - uint8_t first = bytes[0]; + const uint8_t first = bytes[0]; if (first < 0x80) { // 1 byte character out = static_cast<uint32_t>(first & 0x7F); slice.remove_prefix(1); @@ -64,7 +64,7 @@ bool GeneralUtils::_NextUTF8Char(std::string_view& slice, uint32_t& out) { // middle byte, not valid at start, fall through } else if (first < 0xE0) { // two byte character if (rem > 1) { - uint8_t second = bytes[1]; + const uint8_t second = bytes[1]; if (_IsSuffixChar(second)) { out = (static_cast<uint32_t>(first & 0x1F) << 6) + static_cast<uint32_t>(second & 0x3F); @@ -74,8 +74,8 @@ bool GeneralUtils::_NextUTF8Char(std::string_view& slice, uint32_t& out) { } } else if (first < 0xF0) { // three byte character if (rem > 2) { - uint8_t second = bytes[1]; - uint8_t third = bytes[2]; + const uint8_t second = bytes[1]; + const uint8_t third = bytes[2]; if (_IsSuffixChar(second) && _IsSuffixChar(third)) { out = (static_cast<uint32_t>(first & 0x0F) << 12) + (static_cast<uint32_t>(second & 0x3F) << 6) @@ -86,9 +86,9 @@ bool GeneralUtils::_NextUTF8Char(std::string_view& slice, uint32_t& out) { } } else if (first < 0xF8) { // four byte character if (rem > 3) { - uint8_t second = bytes[1]; - uint8_t third = bytes[2]; - uint8_t fourth = bytes[3]; + const uint8_t second = bytes[1]; + const uint8_t third = bytes[2]; + const uint8_t fourth = bytes[3]; if (_IsSuffixChar(second) && _IsSuffixChar(third) && _IsSuffixChar(fourth)) { out = (static_cast<uint32_t>(first & 0x07) << 18) + (static_cast<uint32_t>(second & 0x3F) << 12) @@ -107,7 +107,7 @@ bool GeneralUtils::_NextUTF8Char(std::string_view& slice, uint32_t& out) { } /// See <https://www.ietf.org/rfc/rfc2781.html#section-2.1> -bool PushUTF16CodePoint(std::u16string& output, uint32_t U, size_t size) { +bool PushUTF16CodePoint(std::u16string& output, const uint32_t U, const size_t size) { if (output.length() >= size) return false; if (U < 0x10000) { // If U < 0x10000, encode U as a 16-bit unsigned integer and terminate. @@ -120,7 +120,7 @@ bool PushUTF16CodePoint(std::u16string& output, uint32_t U, size_t size) { // Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF, // U' must be less than or equal to 0xFFFFF. That is, U' can be // represented in 20 bits. - uint32_t Ut = U - 0x10000; + const uint32_t Ut = U - 0x10000; // Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and // 0xDC00, respectively. These integers each have 10 bits free to @@ -141,25 +141,25 @@ bool PushUTF16CodePoint(std::u16string& output, uint32_t U, size_t size) { } else return false; } -std::u16string GeneralUtils::UTF8ToUTF16(const std::string_view& string, size_t size) { - size_t newSize = MinSize(size, string); +std::u16string GeneralUtils::UTF8ToUTF16(const std::string_view string, const size_t size) { + const size_t newSize = MinSize(size, string); std::u16string output; output.reserve(newSize); std::string_view iterator = string; uint32_t c; - while (_NextUTF8Char(iterator, c) && PushUTF16CodePoint(output, c, size)) {} + while (details::_NextUTF8Char(iterator, c) && PushUTF16CodePoint(output, c, size)) {} return output; } //! Converts an std::string (ASCII) to UCS-2 / UTF-16 -std::u16string GeneralUtils::ASCIIToUTF16(const std::string_view& string, size_t size) { - size_t newSize = MinSize(size, string); +std::u16string GeneralUtils::ASCIIToUTF16(const std::string_view string, const size_t size) { + const size_t newSize = MinSize(size, string); std::u16string ret; ret.reserve(newSize); - for (size_t i = 0; i < newSize; i++) { - char c = string[i]; + for (size_t i = 0; i < newSize; ++i) { + const char c = string[i]; // Note: both 7-bit ascii characters and REPLACEMENT_CHARACTER fit in one char16_t ret.push_back((c > 0 && c <= 127) ? static_cast<char16_t>(c) : REPLACEMENT_CHARACTER); } @@ -169,18 +169,18 @@ std::u16string GeneralUtils::ASCIIToUTF16(const std::string_view& string, size_t //! Converts a (potentially-ill-formed) UTF-16 string to UTF-8 //! See: <http://simonsapin.github.io/wtf-8/#decoding-ill-formed-utf-16> -std::string GeneralUtils::UTF16ToWTF8(const std::u16string_view& string, size_t size) { - size_t newSize = MinSize(size, string); +std::string GeneralUtils::UTF16ToWTF8(const std::u16string_view string, const size_t size) { + const size_t newSize = MinSize(size, string); std::string ret; ret.reserve(newSize); - for (size_t i = 0; i < newSize; i++) { - char16_t u = string[i]; + for (size_t i = 0; i < newSize; ++i) { + const char16_t u = string[i]; if (IsLeadSurrogate(u) && (i + 1) < newSize) { - char16_t next = string[i + 1]; + const char16_t next = string[i + 1]; if (IsTrailSurrogate(next)) { i += 1; - char32_t cp = 0x10000 + const char32_t cp = 0x10000 + ((static_cast<char32_t>(u) - 0xD800) << 10) + (static_cast<char32_t>(next) - 0xDC00); PushUTF8CodePoint(ret, cp); @@ -195,40 +195,40 @@ std::string GeneralUtils::UTF16ToWTF8(const std::u16string_view& string, size_t return ret; } -bool GeneralUtils::CaseInsensitiveStringCompare(const std::string& a, const std::string& b) { +bool GeneralUtils::CaseInsensitiveStringCompare(const std::string_view a, const std::string_view b) { return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char a, char b) { return tolower(a) == tolower(b); }); } // MARK: Bits //! Sets a specific bit in a signed 64-bit integer -int64_t GeneralUtils::SetBit(int64_t value, uint32_t index) { +int64_t GeneralUtils::SetBit(int64_t value, const uint32_t index) { return value |= 1ULL << index; } //! Clears a specific bit in a signed 64-bit integer -int64_t GeneralUtils::ClearBit(int64_t value, uint32_t index) { +int64_t GeneralUtils::ClearBit(int64_t value, const uint32_t index) { return value &= ~(1ULL << index); } //! Checks a specific bit in a signed 64-bit integer -bool GeneralUtils::CheckBit(int64_t value, uint32_t index) { +bool GeneralUtils::CheckBit(int64_t value, const uint32_t index) { return value & (1ULL << index); } -bool GeneralUtils::ReplaceInString(std::string& str, const std::string& from, const std::string& to) { - size_t start_pos = str.find(from); +bool GeneralUtils::ReplaceInString(std::string& str, const std::string_view from, const std::string_view to) { + const size_t start_pos = str.find(from); if (start_pos == std::string::npos) return false; str.replace(start_pos, from.length(), to); return true; } -std::vector<std::wstring> GeneralUtils::SplitString(std::wstring& str, wchar_t delimiter) { +std::vector<std::wstring> GeneralUtils::SplitString(const std::wstring_view str, const wchar_t delimiter) { std::vector<std::wstring> vector = std::vector<std::wstring>(); std::wstring current; - for (const auto& c : str) { + for (const wchar_t c : str) { if (c == delimiter) { vector.push_back(current); current = L""; @@ -237,15 +237,15 @@ std::vector<std::wstring> GeneralUtils::SplitString(std::wstring& str, wchar_t d } } - vector.push_back(current); + vector.push_back(std::move(current)); return vector; } -std::vector<std::u16string> GeneralUtils::SplitString(const std::u16string& str, char16_t delimiter) { +std::vector<std::u16string> GeneralUtils::SplitString(const std::u16string_view str, const char16_t delimiter) { std::vector<std::u16string> vector = std::vector<std::u16string>(); std::u16string current; - for (const auto& c : str) { + for (const char16_t c : str) { if (c == delimiter) { vector.push_back(current); current = u""; @@ -254,17 +254,15 @@ std::vector<std::u16string> GeneralUtils::SplitString(const std::u16string& str, } } - vector.push_back(current); + vector.push_back(std::move(current)); return vector; } -std::vector<std::string> GeneralUtils::SplitString(const std::string& str, char delimiter) { +std::vector<std::string> GeneralUtils::SplitString(const std::string_view str, const char delimiter) { std::vector<std::string> vector = std::vector<std::string>(); std::string current = ""; - for (size_t i = 0; i < str.length(); i++) { - char c = str[i]; - + for (const char c : str) { if (c == delimiter) { vector.push_back(current); current = ""; @@ -273,8 +271,7 @@ std::vector<std::string> GeneralUtils::SplitString(const std::string& str, char } } - vector.push_back(current); - + vector.push_back(std::move(current)); return vector; } @@ -283,7 +280,7 @@ std::u16string GeneralUtils::ReadWString(RakNet::BitStream& inStream) { inStream.Read<uint32_t>(length); std::u16string string; - for (auto i = 0; i < length; i++) { + for (uint32_t i = 0; i < length; ++i) { uint16_t c; inStream.Read(c); string.push_back(c); @@ -292,29 +289,29 @@ std::u16string GeneralUtils::ReadWString(RakNet::BitStream& inStream) { return string; } -std::vector<std::string> GeneralUtils::GetSqlFileNamesFromFolder(const std::string& folder) { +std::vector<std::string> GeneralUtils::GetSqlFileNamesFromFolder(const std::string_view folder) { // Because we dont know how large the initial number before the first _ is we need to make it a map like so. - std::map<uint32_t, std::string> filenames{}; - for (auto& t : std::filesystem::directory_iterator(folder)) { - auto filename = t.path().filename().string(); - auto index = std::stoi(GeneralUtils::SplitString(filename, '_').at(0)); - filenames.insert(std::make_pair(index, filename)); + std::map<uint32_t, std::string> filenames{}; + for (const auto& t : std::filesystem::directory_iterator(folder)) { + auto filename = t.path().filename().string(); + const auto index = std::stoi(GeneralUtils::SplitString(filename, '_').at(0)); + filenames.emplace(index, std::move(filename)); } // Now sort the map by the oldest migration. std::vector<std::string> sortedFiles{}; - auto fileIterator = filenames.begin(); - std::map<uint32_t, std::string>::iterator oldest = filenames.begin(); + auto fileIterator = filenames.cbegin(); + auto oldest = filenames.cbegin(); while (!filenames.empty()) { - if (fileIterator == filenames.end()) { + if (fileIterator == filenames.cend()) { sortedFiles.push_back(oldest->second); filenames.erase(oldest); - fileIterator = filenames.begin(); - oldest = filenames.begin(); + fileIterator = filenames.cbegin(); + oldest = filenames.cbegin(); continue; } if (oldest->first > fileIterator->first) oldest = fileIterator; - fileIterator++; + ++fileIterator; } return sortedFiles; diff --git a/dCommon/GeneralUtils.h b/dCommon/GeneralUtils.h index 35c0b895..0a8d15c1 100644 --- a/dCommon/GeneralUtils.h +++ b/dCommon/GeneralUtils.h @@ -3,17 +3,18 @@ // C++ #include <charconv> #include <cstdint> -#include <random> #include <ctime> +#include <functional> +#include <optional> +#include <random> +#include <span> +#include <stdexcept> #include <string> #include <string_view> -#include <optional> -#include <functional> #include <type_traits> -#include <stdexcept> + #include "BitStream.h" #include "NiPoint3.h" - #include "dPlatforms.h" #include "Game.h" #include "Logger.h" @@ -32,29 +33,31 @@ namespace GeneralUtils { //! Converts a plain ASCII string to a UTF-16 string /*! \param string The string to convert - \param size A size to trim the string to. Default is -1 (No trimming) + \param size A size to trim the string to. Default is SIZE_MAX (No trimming) \return An UTF-16 representation of the string */ - std::u16string ASCIIToUTF16(const std::string_view& string, size_t size = -1); + std::u16string ASCIIToUTF16(const std::string_view string, const size_t size = SIZE_MAX); //! Converts a UTF-8 String to a UTF-16 string /*! \param string The string to convert - \param size A size to trim the string to. Default is -1 (No trimming) + \param size A size to trim the string to. Default is SIZE_MAX (No trimming) \return An UTF-16 representation of the string */ - std::u16string UTF8ToUTF16(const std::string_view& string, size_t size = -1); + std::u16string UTF8ToUTF16(const std::string_view string, const size_t size = SIZE_MAX); - //! Internal, do not use - bool _NextUTF8Char(std::string_view& slice, uint32_t& out); + namespace details { + //! Internal, do not use + bool _NextUTF8Char(std::string_view& slice, uint32_t& out); + } //! Converts a UTF-16 string to a UTF-8 string /*! \param string The string to convert - \param size A size to trim the string to. Default is -1 (No trimming) + \param size A size to trim the string to. Default is SIZE_MAX (No trimming) \return An UTF-8 representation of the string */ - std::string UTF16ToWTF8(const std::u16string_view& string, size_t size = -1); + std::string UTF16ToWTF8(const std::u16string_view string, const size_t size = SIZE_MAX); /** * Compares two basic strings but does so ignoring case sensitivity @@ -62,7 +65,7 @@ namespace GeneralUtils { * \param b the second string to compare against the first string * @return if the two strings are equal */ - bool CaseInsensitiveStringCompare(const std::string& a, const std::string& b); + bool CaseInsensitiveStringCompare(const std::string_view a, const std::string_view b); // MARK: Bits @@ -70,9 +73,9 @@ namespace GeneralUtils { //! Sets a bit on a numerical value template <typename T> - inline void SetBit(T& value, eObjectBits bits) { + inline void SetBit(T& value, const eObjectBits bits) { static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type"); - auto index = static_cast<size_t>(bits); + const auto index = static_cast<size_t>(bits); if (index > (sizeof(T) * 8) - 1) { return; } @@ -82,9 +85,9 @@ namespace GeneralUtils { //! Clears a bit on a numerical value template <typename T> - inline void ClearBit(T& value, eObjectBits bits) { + inline void ClearBit(T& value, const eObjectBits bits) { static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type"); - auto index = static_cast<size_t>(bits); + const auto index = static_cast<size_t>(bits); if (index > (sizeof(T) * 8 - 1)) { return; } @@ -97,14 +100,14 @@ namespace GeneralUtils { \param value The value to set the bit for \param index The index of the bit */ - int64_t SetBit(int64_t value, uint32_t index); + int64_t SetBit(int64_t value, const uint32_t index); //! Clears a specific bit in a signed 64-bit integer /*! \param value The value to clear the bit from \param index The index of the bit */ - int64_t ClearBit(int64_t value, uint32_t index); + int64_t ClearBit(int64_t value, const uint32_t index); //! Checks a specific bit in a signed 64-bit integer /*! @@ -112,19 +115,19 @@ namespace GeneralUtils { \param index The index of the bit \return Whether or not the bit is set */ - bool CheckBit(int64_t value, uint32_t index); + bool CheckBit(int64_t value, const uint32_t index); - bool ReplaceInString(std::string& str, const std::string& from, const std::string& to); + bool ReplaceInString(std::string& str, const std::string_view from, const std::string_view to); std::u16string ReadWString(RakNet::BitStream& inStream); - std::vector<std::wstring> SplitString(std::wstring& str, wchar_t delimiter); + std::vector<std::wstring> SplitString(const std::wstring_view str, const wchar_t delimiter); - std::vector<std::u16string> SplitString(const std::u16string& str, char16_t delimiter); + std::vector<std::u16string> SplitString(const std::u16string_view str, const char16_t delimiter); - std::vector<std::string> SplitString(const std::string& str, char delimiter); + std::vector<std::string> SplitString(const std::string_view str, const char delimiter); - std::vector<std::string> GetSqlFileNamesFromFolder(const std::string& folder); + std::vector<std::string> GetSqlFileNamesFromFolder(const std::string_view folder); // Concept constraining to enum types template <typename T> @@ -144,7 +147,7 @@ namespace GeneralUtils { // If a boolean, present an alias to an intermediate integral type for parsing template <Numeric T> requires std::same_as<T, bool> - struct numeric_parse<T> { using type = uint32_t; }; + struct numeric_parse<T> { using type = uint8_t; }; // Shorthand type alias template <Numeric T> @@ -205,7 +208,7 @@ namespace GeneralUtils { * @returns An std::optional containing the desired NiPoint3 if it can be constructed from the string parameters */ template <typename T> - [[nodiscard]] std::optional<NiPoint3> TryParse(const std::string& strX, const std::string& strY, const std::string& strZ) { + [[nodiscard]] std::optional<NiPoint3> TryParse(const std::string_view strX, const std::string_view strY, const std::string_view strZ) { const auto x = TryParse<float>(strX); if (!x) return std::nullopt; @@ -217,17 +220,17 @@ namespace GeneralUtils { } /** - * The TryParse overload for handling NiPoint3 by passingn a reference to a vector of three strings - * @param str The string vector representing the X, Y, and Xcoordinates + * The TryParse overload for handling NiPoint3 by passing a span of three strings + * @param str The string vector representing the X, Y, and Z coordinates * @returns An std::optional containing the desired NiPoint3 if it can be constructed from the string parameters */ template <typename T> - [[nodiscard]] std::optional<NiPoint3> TryParse(const std::vector<std::string>& str) { + [[nodiscard]] std::optional<NiPoint3> TryParse(const std::span<const std::string> str) { return (str.size() == 3) ? TryParse<NiPoint3>(str[0], str[1], str[2]) : std::nullopt; } template <typename T> - std::u16string to_u16string(T value) { + std::u16string to_u16string(const T value) { return GeneralUtils::ASCIIToUTF16(std::to_string(value)); } @@ -246,7 +249,7 @@ namespace GeneralUtils { \param max The maximum to generate to */ template <typename T> - inline T GenerateRandomNumber(std::size_t min, std::size_t max) { + inline T GenerateRandomNumber(const std::size_t min, const std::size_t max) { // Make sure it is a numeric type static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type"); @@ -273,10 +276,10 @@ namespace GeneralUtils { // on Windows we need to undef these or else they conflict with our numeric limits calls // DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS - #ifdef _WIN32 - #undef min - #undef max - #endif +#ifdef _WIN32 +#undef min +#undef max +#endif template <typename T> inline T GenerateRandomNumber() { diff --git a/tests/dCommonTests/TestEncoding.cpp b/tests/dCommonTests/TestEncoding.cpp index 54ae03d3..0286d284 100644 --- a/tests/dCommonTests/TestEncoding.cpp +++ b/tests/dCommonTests/TestEncoding.cpp @@ -15,12 +15,12 @@ TEST_F(EncodingTest, TestEncodingHello) { originalWord = "Hello World!"; originalWordSv = originalWord; - GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'H'); - GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'e'); - GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'l'); - GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'l'); - GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'o'); - EXPECT_EQ(GeneralUtils::_NextUTF8Char(originalWordSv, out), true); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'H'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'e'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'l'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'l'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'o'); + EXPECT_EQ(GeneralUtils::details::_NextUTF8Char(originalWordSv, out), true); EXPECT_EQ(GeneralUtils::UTF8ToUTF16("Hello World!"), u"Hello World!"); }; @@ -29,15 +29,15 @@ TEST_F(EncodingTest, TestEncodingUmlaut) { originalWord = reinterpret_cast<const char*>(u8"Frühling"); originalWordSv = originalWord; - GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'F'); - GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'r'); - GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'ü'); - GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'h'); - GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'l'); - GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'i'); - GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'n'); - GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'g'); - EXPECT_EQ(GeneralUtils::_NextUTF8Char(originalWordSv, out), false); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'F'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'r'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'ü'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'h'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'l'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'i'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'n'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'g'); + EXPECT_EQ(GeneralUtils::details::_NextUTF8Char(originalWordSv, out), false); EXPECT_EQ(GeneralUtils::UTF8ToUTF16("Frühling"), u"Frühling"); }; @@ -46,10 +46,10 @@ TEST_F(EncodingTest, TestEncodingChinese) { originalWord = "中文字"; originalWordSv = originalWord; - GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'中'); - GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'文'); - GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'字'); - EXPECT_EQ(GeneralUtils::_NextUTF8Char(originalWordSv, out), false); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'中'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'文'); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'字'); + EXPECT_EQ(GeneralUtils::details::_NextUTF8Char(originalWordSv, out), false); EXPECT_EQ(GeneralUtils::UTF8ToUTF16("中文字"), u"中文字"); }; @@ -58,11 +58,11 @@ TEST_F(EncodingTest, TestEncodingEmoji) { originalWord = "👨‍⚖️"; originalWordSv = originalWord; - GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0x1F468); - GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0x200D); - GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0x2696); - GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0xFE0F); - EXPECT_EQ(GeneralUtils::_NextUTF8Char(originalWordSv, out), false); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0x1F468); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0x200D); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0x2696); + GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0xFE0F); + EXPECT_EQ(GeneralUtils::details::_NextUTF8Char(originalWordSv, out), false); EXPECT_EQ(GeneralUtils::UTF8ToUTF16("👨‍⚖️"), u"👨‍⚖️"); }; From 99e7349f6ccf02967eb0fbb7aaf57a3dc0f46934 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell <aronwk.aaron@gmail.com> Date: Wed, 17 Apr 2024 21:47:28 -0500 Subject: [PATCH 17/78] feat: slashcommands for showall, findplayer, get/openhttpmoninfo, and debug world packet (#1545) * feat: showall, findplayer, get/openhttpmoninfo http monitor info is planned to be used later, just putting in info that i've since reverse engineered and don't want lost Additionally add debug world packet for duture dev use Tested all new commands and variation of command arguments * fix missing newline at eofs * address most feedback * Compormise and use struct with (de)serialize * remove httpmoninfo commands --- dChatServer/ChatPacketHandler.cpp | 62 +++++++++++++++++++ dChatServer/ChatPacketHandler.h | 2 + dChatServer/ChatServer.cpp | 4 ++ dChatServer/PlayerContainer.cpp | 2 + dChatServer/PlayerContainer.h | 5 ++ dGame/dUtilities/SlashCommandHandler.cpp | 18 ++++++ .../dUtilities/SlashCommands/DEVGMCommands.h | 2 +- .../GMGreaterThanZeroCommands.cpp | 35 +++++++++++ .../SlashCommands/GMGreaterThanZeroCommands.h | 4 +- dNet/ChatPackets.cpp | 24 +++++++ dNet/ChatPackets.h | 15 +++++ dNet/WorldPackets.cpp | 24 +++++++ dNet/WorldPackets.h | 15 +++++ 13 files changed, 210 insertions(+), 2 deletions(-) diff --git a/dChatServer/ChatPacketHandler.cpp b/dChatServer/ChatPacketHandler.cpp index d37777b6..82cea018 100644 --- a/dChatServer/ChatPacketHandler.cpp +++ b/dChatServer/ChatPacketHandler.cpp @@ -18,6 +18,7 @@ #include "eGameMessageType.h" #include "StringifiedEnum.h" #include "eGameMasterLevel.h" +#include "ChatPackets.h" void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) { //Get from the packet which player we want to do something with: @@ -354,6 +355,67 @@ void ChatPacketHandler::HandleGMLevelUpdate(Packet* packet) { inStream.Read(player.gmLevel); } + +void ChatPacketHandler::HandleWho(Packet* packet) { + CINSTREAM_SKIP_HEADER; + FindPlayerRequest request; + request.Deserialize(inStream); + + const auto& sender = Game::playerContainer.GetPlayerData(request.requestor); + if (!sender) return; + + const auto& player = Game::playerContainer.GetPlayerData(request.playerName.GetAsString()); + bool online = player; + + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET); + bitStream.Write(request.requestor); + + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::WHO_RESPONSE); + bitStream.Write<uint8_t>(online); + bitStream.Write(player.zoneID.GetMapID()); + bitStream.Write(player.zoneID.GetInstanceID()); + bitStream.Write(player.zoneID.GetCloneID()); + bitStream.Write(request.playerName); + + SystemAddress sysAddr = sender.sysAddr; + SEND_PACKET; +} + +void ChatPacketHandler::HandleShowAll(Packet* packet) { + CINSTREAM_SKIP_HEADER; + ShowAllRequest request; + request.Deserialize(inStream); + + const auto& sender = Game::playerContainer.GetPlayerData(request.requestor); + if (!sender) return; + + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET); + bitStream.Write(request.requestor); + + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::SHOW_ALL_RESPONSE); + bitStream.Write<uint8_t>(!request.displayZoneData && !request.displayIndividualPlayers); + bitStream.Write(Game::playerContainer.GetPlayerCount()); + bitStream.Write(Game::playerContainer.GetSimCount()); + bitStream.Write<uint8_t>(request.displayIndividualPlayers); + bitStream.Write<uint8_t>(request.displayZoneData); + if (request.displayZoneData || request.displayIndividualPlayers){ + for (auto& [playerID, playerData ]: Game::playerContainer.GetAllPlayers()){ + if (!playerData) continue; + bitStream.Write<uint8_t>(0); // structure packing + if (request.displayIndividualPlayers) bitStream.Write(LUWString(playerData.playerName)); + if (request.displayZoneData) { + bitStream.Write(playerData.zoneID.GetMapID()); + bitStream.Write(playerData.zoneID.GetInstanceID()); + bitStream.Write(playerData.zoneID.GetCloneID()); + } + } + } + SystemAddress sysAddr = sender.sysAddr; + SEND_PACKET; +} + // the structure the client uses to send this packet is shared in many chat messages // that are sent to the server. Because of this, there are large gaps of unused data in chat messages void ChatPacketHandler::HandleChatMessage(Packet* packet) { diff --git a/dChatServer/ChatPacketHandler.h b/dChatServer/ChatPacketHandler.h index 847fc899..def9c9b9 100644 --- a/dChatServer/ChatPacketHandler.h +++ b/dChatServer/ChatPacketHandler.h @@ -50,6 +50,8 @@ namespace ChatPacketHandler { void HandleFriendResponse(Packet* packet); void HandleRemoveFriend(Packet* packet); void HandleGMLevelUpdate(Packet* packet); + void HandleWho(Packet* packet); + void HandleShowAll(Packet* packet); void HandleChatMessage(Packet* packet); void HandlePrivateChatMessage(Packet* packet); diff --git a/dChatServer/ChatServer.cpp b/dChatServer/ChatServer.cpp index cc938c3c..81b6ddef 100644 --- a/dChatServer/ChatServer.cpp +++ b/dChatServer/ChatServer.cpp @@ -289,7 +289,11 @@ void HandlePacket(Packet* packet) { Game::playerContainer.RemovePlayer(packet); break; case eChatMessageType::WHO: + ChatPacketHandler::HandleWho(packet); + break; case eChatMessageType::SHOW_ALL: + ChatPacketHandler::HandleShowAll(packet); + break; case eChatMessageType::USER_CHANNEL_CHAT_MESSAGE: case eChatMessageType::WORLD_DISCONNECT_REQUEST: case eChatMessageType::WORLD_PROXIMITY_RESPONSE: diff --git a/dChatServer/PlayerContainer.cpp b/dChatServer/PlayerContainer.cpp index 57b3f233..17e2cd1a 100644 --- a/dChatServer/PlayerContainer.cpp +++ b/dChatServer/PlayerContainer.cpp @@ -49,6 +49,7 @@ void PlayerContainer::InsertPlayer(Packet* packet) { data.sysAddr = packet->systemAddress; m_Names[data.playerID] = GeneralUtils::UTF8ToUTF16(data.playerName); + m_PlayerCount++; LOG("Added user: %s (%llu), zone: %i", data.playerName.c_str(), data.playerID, data.zoneID.GetMapID()); @@ -87,6 +88,7 @@ void PlayerContainer::RemovePlayer(Packet* packet) { } } + m_PlayerCount--; LOG("Removed user: %llu", playerID); m_Players.erase(playerID); diff --git a/dChatServer/PlayerContainer.h b/dChatServer/PlayerContainer.h index 3f2d783a..9a17f927 100644 --- a/dChatServer/PlayerContainer.h +++ b/dChatServer/PlayerContainer.h @@ -71,6 +71,9 @@ public: const PlayerData& GetPlayerData(const std::string& playerName); PlayerData& GetPlayerDataMutable(const LWOOBJID& playerID); PlayerData& GetPlayerDataMutable(const std::string& playerName); + uint32_t GetPlayerCount() { return m_PlayerCount; }; + uint32_t GetSimCount() { return m_SimCount; }; + const std::map<LWOOBJID, PlayerData>& GetAllPlayers() { return m_Players; }; TeamData* CreateLocalTeam(std::vector<LWOOBJID> members); TeamData* CreateTeam(LWOOBJID leader, bool local = false); @@ -93,5 +96,7 @@ private: std::unordered_map<LWOOBJID, std::u16string> m_Names; uint32_t m_MaxNumberOfBestFriends = 5; uint32_t m_MaxNumberOfFriends = 50; + uint32_t m_PlayerCount = 0; + uint32_t m_SimCount = 0; }; diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index bd7d5f28..bf7e9eb4 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -878,8 +878,26 @@ void SlashCommandHandler::Startup() { }; RegisterCommand(TitleCommand); + Command ShowAllCommand{ + .help = "Show all online players across World Servers", + .info = "Usage: /showall (displayZoneData: Default 1) (displayIndividualPlayers: Default 1)", + .aliases = { "showall" }, + .handle = GMGreaterThanZeroCommands::ShowAll, + .requiredLevel = eGameMasterLevel::JUNIOR_MODERATOR + }; + RegisterCommand(ShowAllCommand); + + Command FindPlayerCommand{ + .help = "Find the World Server a player is in if they are online", + .info = "Find the World Server a player is in if they are online", + .aliases = { "findplayer" }, + .handle = GMGreaterThanZeroCommands::FindPlayer, + .requiredLevel = eGameMasterLevel::JUNIOR_MODERATOR + }; + RegisterCommand(FindPlayerCommand); // Register GM Zero Commands + Command HelpCommand{ .help = "Display command info", .info = "If a command is given, display detailed info on that command. Otherwise display a list of commands with short desctiptions.", diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.h b/dGame/dUtilities/SlashCommands/DEVGMCommands.h index 149348a1..5e78ed80 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.h +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.h @@ -70,4 +70,4 @@ namespace DEVGMCommands { void RollLoot(Entity* entity, const SystemAddress& sysAddr, const std::string args); void CastSkill(Entity* entity, const SystemAddress& sysAddr, const std::string args); void DeleteInven(Entity* entity, const SystemAddress& sysAddr, const std::string args); -} \ No newline at end of file +} diff --git a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp index d0aa66aa..ea33aa03 100644 --- a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp +++ b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp @@ -287,4 +287,39 @@ namespace GMGreaterThanZeroCommands { std::string name = entity->GetCharacter()->GetName() + " - " + args; GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(name), UNASSIGNED_SYSTEM_ADDRESS); } + + void ShowAll(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + bool displayZoneData = true; + bool displayIndividualPlayers = true; + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + + if (!splitArgs.empty() && !splitArgs.at(0).empty()) displayZoneData = splitArgs.at(0) == "1"; + if (splitArgs.size() > 1) displayIndividualPlayers = splitArgs.at(1) == "1"; + + ShowAllRequest request { + .requestor = entity->GetObjectID(), + .displayZoneData = displayZoneData, + .displayIndividualPlayers = displayIndividualPlayers + }; + + CBITSTREAM; + request.Serialize(bitStream); + Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); + } + + void FindPlayer(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (args.empty()) { + GameMessages::SendSlashCommandFeedbackText(entity, u"No player Given"); + return; + } + + FindPlayerRequest request { + .requestor = entity->GetObjectID(), + .playerName = LUWString(args) + }; + + CBITSTREAM; + request.Serialize(bitStream); + Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); + } } diff --git a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h index 4b03441f..e8d60383 100644 --- a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h +++ b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h @@ -10,4 +10,6 @@ namespace GMGreaterThanZeroCommands { void GmInvis(Entity* entity, const SystemAddress& sysAddr, const std::string args); void SetName(Entity* entity, const SystemAddress& sysAddr, const std::string args); void Title(Entity* entity, const SystemAddress& sysAddr, const std::string args); -} \ No newline at end of file + void ShowAll(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void FindPlayer(Entity* entity, const SystemAddress& sysAddr, const std::string args); +} diff --git a/dNet/ChatPackets.cpp b/dNet/ChatPackets.cpp index d0354659..6c9b36b7 100644 --- a/dNet/ChatPackets.cpp +++ b/dNet/ChatPackets.cpp @@ -12,6 +12,30 @@ #include "eConnectionType.h" #include "eChatMessageType.h" +void ShowAllRequest::Serialize(RakNet::BitStream& bitStream) { + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::SHOW_ALL); + bitStream.Write(this->requestor); + bitStream.Write(this->displayZoneData); + bitStream.Write(this->displayIndividualPlayers); +} + +void ShowAllRequest::Deserialize(RakNet::BitStream& inStream) { + inStream.Read(this->requestor); + inStream.Read(this->displayZoneData); + inStream.Read(this->displayIndividualPlayers); +} + +void FindPlayerRequest::Serialize(RakNet::BitStream& bitStream) { + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WHO); + bitStream.Write(this->requestor); + bitStream.Write(this->playerName); +} + +void FindPlayerRequest::Deserialize(RakNet::BitStream& inStream) { + inStream.Read(this->requestor); + inStream.Read(this->playerName); +} + void ChatPackets::SendChatMessage(const SystemAddress& sysAddr, char chatChannel, const std::string& senderName, LWOOBJID playerObjectID, bool senderMythran, const std::u16string& message) { CBITSTREAM; BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::GENERAL_CHAT_MESSAGE); diff --git a/dNet/ChatPackets.h b/dNet/ChatPackets.h index 8f6de175..c33d00dd 100644 --- a/dNet/ChatPackets.h +++ b/dNet/ChatPackets.h @@ -11,6 +11,21 @@ struct SystemAddress; #include <string> #include "dCommonVars.h" +struct ShowAllRequest{ + LWOOBJID requestor = LWOOBJID_EMPTY; + bool displayZoneData = true; + bool displayIndividualPlayers = true; + void Serialize(RakNet::BitStream& bitStream); + void Deserialize(RakNet::BitStream& inStream); +}; + +struct FindPlayerRequest{ + LWOOBJID requestor = LWOOBJID_EMPTY; + LUWString playerName; + void Serialize(RakNet::BitStream& bitStream); + void Deserialize(RakNet::BitStream& inStream); +}; + namespace ChatPackets { void SendChatMessage(const SystemAddress& sysAddr, char chatChannel, const std::string& senderName, LWOOBJID playerObjectID, bool senderMythran, const std::u16string& message); void SendSystemMessage(const SystemAddress& sysAddr, const std::u16string& message, bool broadcast = false); diff --git a/dNet/WorldPackets.cpp b/dNet/WorldPackets.cpp index 1c2b8dec..44dd687e 100644 --- a/dNet/WorldPackets.cpp +++ b/dNet/WorldPackets.cpp @@ -12,6 +12,15 @@ #include <iostream> +void HTTPMonitorInfo::Serialize(RakNet::BitStream &bitStream) const { + bitStream.Write(port); + bitStream.Write<uint8_t>(openWeb); + bitStream.Write<uint8_t>(supportsSum); + bitStream.Write<uint8_t>(supportsDetail); + bitStream.Write<uint8_t>(supportsWho); + bitStream.Write<uint8_t>(supportsObjects); +} + void WorldPackets::SendLoadStaticZone(const SystemAddress& sysAddr, float x, float y, float z, uint32_t checksum, LWOZONEID zone) { RakNet::BitStream bitStream; BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::LOAD_STATIC_ZONE); @@ -160,3 +169,18 @@ void WorldPackets::SendGMLevelChange(const SystemAddress& sysAddr, bool success, SEND_PACKET; } + +void WorldPackets::SendHTTPMonitorInfo(const SystemAddress& sysAddr, const HTTPMonitorInfo& info) { + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::HTTP_MONITOR_INFO_RESPONSE); + info.Serialize(bitStream); + SEND_PACKET; +} + +void WorldPackets::SendDebugOuput(const SystemAddress& sysAddr, const std::string& data){ + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::DEBUG_OUTPUT); + bitStream.Write<uint32_t>(data.size()); + bitStream.Write(data); + SEND_PACKET; +} diff --git a/dNet/WorldPackets.h b/dNet/WorldPackets.h index 0d5de079..0081623e 100644 --- a/dNet/WorldPackets.h +++ b/dNet/WorldPackets.h @@ -10,6 +10,19 @@ struct SystemAddress; enum class eGameMasterLevel : uint8_t; enum class eCharacterCreationResponse : uint8_t; enum class eRenameResponse : uint8_t; +namespace RakNet { + class BitStream; +}; + +struct HTTPMonitorInfo { + uint16_t port = 80; + bool openWeb = false; + bool supportsSum = false; + bool supportsDetail = false; + bool supportsWho = false; + bool supportsObjects = false; + void Serialize(RakNet::BitStream &bitstream) const; +}; namespace WorldPackets { void SendLoadStaticZone(const SystemAddress& sysAddr, float x, float y, float z, uint32_t checksum, LWOZONEID zone); @@ -21,6 +34,8 @@ namespace WorldPackets { void SendCreateCharacter(const SystemAddress& sysAddr, int64_t reputation, LWOOBJID player, const std::string& xmlData, const std::u16string& username, eGameMasterLevel gm); void SendChatModerationResponse(const SystemAddress& sysAddr, bool requestAccepted, uint32_t requestID, const std::string& receiver, std::vector<std::pair<uint8_t, uint8_t>> unacceptedItems); void SendGMLevelChange(const SystemAddress& sysAddr, bool success, eGameMasterLevel highestLevel, eGameMasterLevel prevLevel, eGameMasterLevel newLevel); + void SendHTTPMonitorInfo(const SystemAddress& sysAddr, const HTTPMonitorInfo& info); + void SendDebugOuput(const SystemAddress& sysAddr, const std::string& data); } #endif // WORLDPACKETS_H From 8fdc212cda523c8a1377e245fd0a57c63bb1e96b Mon Sep 17 00:00:00 2001 From: jadebenn <jadebenn@users.noreply.github.com> Date: Wed, 24 Apr 2024 10:09:04 -0500 Subject: [PATCH 18/78] chore: Convert heap allocation to optional (#1553) * chore: Convert heap allocation to optional * Update dGame/dComponents/PetComponent.h Default-initialize --- dGame/dComponents/PetComponent.cpp | 9 ++++----- dGame/dComponents/PetComponent.h | 6 +++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index 6b809ec0..68c29a6d 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -40,7 +40,7 @@ std::unordered_map<LWOOBJID, LWOOBJID> PetComponent::activePets{}; * Maps all the pet lots to a flag indicating that the player has caught it. All basic pets have been guessed by ObjID * while the faction ones could be checked using their respective missions. */ -std::map<LOT, int32_t> PetComponent::petFlags = { +const std::map<LOT, int32_t> PetComponent::petFlags{ { 3050, 801 }, // Elephant { 3054, 803 }, // Cat { 3195, 806 }, // Triceratops @@ -87,7 +87,6 @@ PetComponent::PetComponent(Entity* parentEntity, uint32_t componentId) : Compone m_StartPosition = NiPoint3Constant::ZERO; m_MovementAI = nullptr; m_TresureTime = 0; - m_Preconditions = nullptr; std::string checkPreconditions = GeneralUtils::UTF16ToWTF8(parentEntity->GetVar<std::u16string>(u"CheckPrecondition")); @@ -158,7 +157,7 @@ void PetComponent::OnUse(Entity* originator) { return; } - if (m_Preconditions != nullptr && !m_Preconditions->Check(originator, true)) { + if (m_Preconditions.has_value() && !m_Preconditions->Check(originator, true)) { return; } @@ -1086,6 +1085,6 @@ void PetComponent::LoadPetNameFromModeration() { } } -void PetComponent::SetPreconditions(std::string& preconditions) { - m_Preconditions = new PreconditionExpression(preconditions); +void PetComponent::SetPreconditions(const std::string& preconditions) { + m_Preconditions = std::make_optional<PreconditionExpression>(preconditions); } diff --git a/dGame/dComponents/PetComponent.h b/dGame/dComponents/PetComponent.h index f4198cae..1abdf155 100644 --- a/dGame/dComponents/PetComponent.h +++ b/dGame/dComponents/PetComponent.h @@ -165,7 +165,7 @@ public: * Sets preconditions for the pet that need to be met before it can be tamed * @param conditions the preconditions to set */ - void SetPreconditions(std::string& conditions); + void SetPreconditions(const std::string& conditions); /** * Returns the entity that this component belongs to @@ -258,7 +258,7 @@ private: /** * Flags that indicate that a player has tamed a pet, indexed by the LOT of the pet */ - static std::map<LOT, int32_t> petFlags; + static const std::map<LOT, int32_t> petFlags; /** * The ID of the component in the pet component table @@ -349,7 +349,7 @@ private: /** * Preconditions that need to be met before an entity can tame this pet */ - PreconditionExpression* m_Preconditions; + std::optional<PreconditionExpression> m_Preconditions{}; /** * Pet information loaded from the CDClientDatabase From 0367c67c85f864ac4b49d68de5ff183d53aa8ae8 Mon Sep 17 00:00:00 2001 From: jadebenn <jadebenn@users.noreply.github.com> Date: Wed, 24 Apr 2024 10:09:15 -0500 Subject: [PATCH 19/78] chore: move the pet minigame table loading logic out of petcomponent (#1551) * move the pet minigame table loading logic out of petcomponent * misc fixes * actually, using paths is dumb here when they're already char strings. why bother? silly me. * removed unga bunga reference-casting * add back in puzzle not found error message * pre-allocate unordered map and make getter const-correct * Update dDatabase/CDClientDatabase/CDClientTables/CDTamingBuildPuzzleTable.cpp Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> --------- Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> --- .../CDClientDatabase/CDClientManager.cpp | 16 ++- .../CDClientTables/CDPetComponentTable.cpp | 2 +- .../CDTamingBuildPuzzleTable.cpp | 35 +++++++ .../CDClientTables/CDTamingBuildPuzzleTable.h | 60 ++++++++++++ .../CDClientTables/CMakeLists.txt | 1 + dGame/dComponents/PetComponent.cpp | 98 +++++-------------- dGame/dComponents/PetComponent.h | 5 - 7 files changed, 127 insertions(+), 90 deletions(-) create mode 100644 dDatabase/CDClientDatabase/CDClientTables/CDTamingBuildPuzzleTable.cpp create mode 100644 dDatabase/CDClientDatabase/CDClientTables/CDTamingBuildPuzzleTable.h diff --git a/dDatabase/CDClientDatabase/CDClientManager.cpp b/dDatabase/CDClientDatabase/CDClientManager.cpp index 0e05c0b8..6ecfb0ad 100644 --- a/dDatabase/CDClientDatabase/CDClientManager.cpp +++ b/dDatabase/CDClientDatabase/CDClientManager.cpp @@ -25,6 +25,7 @@ #include "CDScriptComponentTable.h" #include "CDSkillBehaviorTable.h" #include "CDZoneTableTable.h" +#include "CDTamingBuildPuzzleTable.h" #include "CDVendorComponentTable.h" #include "CDActivitiesTable.h" #include "CDPackageComponentTable.h" @@ -41,8 +42,6 @@ #include "CDRewardCodesTable.h" #include "CDPetComponentTable.h" -#include <exception> - #ifndef CDCLIENT_CACHE_ALL // Uncomment this to cache the full cdclient database into memory. This will make the server load faster, but will use more memory. // A vanilla CDClient takes about 46MB of memory + the regular world data. @@ -55,13 +54,6 @@ #define CDCLIENT_DONT_CACHE_TABLE(x) #endif -class CDClientConnectionException : public std::exception { -public: - virtual const char* what() const throw() { - return "CDClientDatabase is not connected!"; - } -}; - // Using a macro to reduce repetitive code and issues from copy and paste. // As a note, ## in a macro is used to concatenate two tokens together. @@ -108,11 +100,14 @@ DEFINE_TABLE_STORAGE(CDRewardCodesTable); DEFINE_TABLE_STORAGE(CDRewardsTable); DEFINE_TABLE_STORAGE(CDScriptComponentTable); DEFINE_TABLE_STORAGE(CDSkillBehaviorTable); +DEFINE_TABLE_STORAGE(CDTamingBuildPuzzleTable); DEFINE_TABLE_STORAGE(CDVendorComponentTable); DEFINE_TABLE_STORAGE(CDZoneTableTable); void CDClientManager::LoadValuesFromDatabase() { - if (!CDClientDatabase::isConnected) throw CDClientConnectionException(); + if (!CDClientDatabase::isConnected) { + throw std::runtime_error{ "CDClientDatabase is not connected!" }; + } CDActivityRewardsTable::Instance().LoadValuesFromDatabase(); CDActivitiesTable::Instance().LoadValuesFromDatabase(); @@ -152,6 +147,7 @@ void CDClientManager::LoadValuesFromDatabase() { CDRewardsTable::Instance().LoadValuesFromDatabase(); CDScriptComponentTable::Instance().LoadValuesFromDatabase(); CDSkillBehaviorTable::Instance().LoadValuesFromDatabase(); + CDTamingBuildPuzzleTable::Instance().LoadValuesFromDatabase(); CDVendorComponentTable::Instance().LoadValuesFromDatabase(); CDZoneTableTable::Instance().LoadValuesFromDatabase(); } diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDPetComponentTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDPetComponentTable.cpp index f3371ecb..80c10112 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CDPetComponentTable.cpp +++ b/dDatabase/CDClientDatabase/CDClientTables/CDPetComponentTable.cpp @@ -50,7 +50,7 @@ void CDPetComponentTable::LoadValuesFromDatabase() { } void CDPetComponentTable::LoadValuesFromDefaults() { - GetEntriesMutable().insert(std::make_pair(defaultEntry.id, defaultEntry)); + GetEntriesMutable().emplace(defaultEntry.id, defaultEntry); } CDPetComponent& CDPetComponentTable::GetByID(const uint32_t componentID) { diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDTamingBuildPuzzleTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDTamingBuildPuzzleTable.cpp new file mode 100644 index 00000000..c2301b33 --- /dev/null +++ b/dDatabase/CDClientDatabase/CDClientTables/CDTamingBuildPuzzleTable.cpp @@ -0,0 +1,35 @@ +#include "CDTamingBuildPuzzleTable.h" + +void CDTamingBuildPuzzleTable::LoadValuesFromDatabase() { + // First, get the size of the table + uint32_t size = 0; + auto tableSize = CDClientDatabase::ExecuteQuery("SELECT COUNT(*) FROM TamingBuildPuzzles"); + while (!tableSize.eof()) { + size = tableSize.getIntField(0, 0); + tableSize.nextRow(); + } + + // Reserve the size + auto& entries = GetEntriesMutable(); + entries.reserve(size); + + // Now get the data + auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM TamingBuildPuzzles"); + while (!tableData.eof()) { + const auto lot = static_cast<LOT>(tableData.getIntField("NPCLot", LOT_NULL)); + entries.emplace(lot, CDTamingBuildPuzzle{ + .puzzleModelLot = lot, + .validPieces{ tableData.getStringField("ValidPiecesLXF") }, + .timeLimit = static_cast<float>(tableData.getFloatField("Timelimit", 30.0f)), + .numValidPieces = tableData.getIntField("NumValidPieces", 6), + .imaginationCost = tableData.getIntField("imagCostPerBuild", 10) + }); + tableData.nextRow(); + } +} + +const CDTamingBuildPuzzle* CDTamingBuildPuzzleTable::GetByLOT(const LOT lot) const { + const auto& entries = GetEntries(); + const auto itr = entries.find(lot); + return itr != entries.cend() ? &itr->second : nullptr; +} diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDTamingBuildPuzzleTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDTamingBuildPuzzleTable.h new file mode 100644 index 00000000..acbd65bf --- /dev/null +++ b/dDatabase/CDClientDatabase/CDClientTables/CDTamingBuildPuzzleTable.h @@ -0,0 +1,60 @@ +#pragma once +#include "CDTable.h" + +/** + * Information for the minigame to be completed + */ +struct CDTamingBuildPuzzle { + UNUSED_COLUMN(uint32_t id = 0;) + + // The LOT of the object that is to be created + LOT puzzleModelLot = LOT_NULL; + + // The LOT of the NPC + UNUSED_COLUMN(LOT npcLot = LOT_NULL;) + + // The .lxfml file that contains the bricks required to build the model + std::string validPieces{}; + + // The .lxfml file that contains the bricks NOT required to build the model + UNUSED_COLUMN(std::string invalidPieces{};) + + // Difficulty value + UNUSED_COLUMN(int32_t difficulty = 1;) + + // The time limit to complete the build + float timeLimit = 30.0f; + + // The number of pieces required to complete the minigame + int32_t numValidPieces = 6; + + // Number of valid pieces + UNUSED_COLUMN(int32_t totalNumPieces = 16;) + + // Model name + UNUSED_COLUMN(std::string modelName{};) + + // The .lxfml file that contains the full model + UNUSED_COLUMN(std::string fullModel{};) + + // The duration of the pet taming minigame + UNUSED_COLUMN(float duration = 45.0f;) + + // The imagination cost for the tamer to start the minigame + int32_t imaginationCost = 10; +}; + +class CDTamingBuildPuzzleTable : public CDTable<CDTamingBuildPuzzleTable, std::unordered_map<LOT, CDTamingBuildPuzzle>> { +public: + /** + * Load values from the CD client database + */ + void LoadValuesFromDatabase(); + + /** + * Gets the pet ability table corresponding to the pet LOT + * @returns A pointer to the corresponding table, or nullptr if one cannot be found + */ + [[nodiscard]] + const CDTamingBuildPuzzle* GetByLOT(const LOT lot) const; +}; diff --git a/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt b/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt index af401db2..f4551646 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt +++ b/dDatabase/CDClientDatabase/CDClientTables/CMakeLists.txt @@ -36,5 +36,6 @@ set(DDATABASE_CDCLIENTDATABASE_CDCLIENTTABLES_SOURCES "CDActivitiesTable.cpp" "CDRewardsTable.cpp" "CDScriptComponentTable.cpp" "CDSkillBehaviorTable.cpp" + "CDTamingBuildPuzzleTable.cpp" "CDVendorComponentTable.cpp" "CDZoneTableTable.cpp" PARENT_SCOPE) diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index 68c29a6d..debe0bd8 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -2,6 +2,7 @@ #include "GameMessages.h" #include "BrickDatabase.h" #include "CDClientDatabase.h" +#include "CDTamingBuildPuzzleTable.h" #include "ChatPackets.h" #include "EntityManager.h" #include "Character.h" @@ -32,7 +33,6 @@ #include "eMissionState.h" #include "dNavMesh.h" -std::unordered_map<LOT, PetComponent::PetPuzzleData> PetComponent::buildCache{}; std::unordered_map<LWOOBJID, LWOOBJID> PetComponent::currentActivities{}; std::unordered_map<LWOOBJID, LWOOBJID> PetComponent::activePets{}; @@ -151,8 +151,7 @@ void PetComponent::OnUse(Entity* originator) { m_Tamer = LWOOBJID_EMPTY; } - auto* inventoryComponent = originator->GetComponent<InventoryComponent>(); - + auto* const inventoryComponent = originator->GetComponent<InventoryComponent>(); if (inventoryComponent == nullptr) { return; } @@ -161,86 +160,44 @@ void PetComponent::OnUse(Entity* originator) { return; } - auto* movementAIComponent = m_Parent->GetComponent<MovementAIComponent>(); - + auto* const movementAIComponent = m_Parent->GetComponent<MovementAIComponent>(); if (movementAIComponent != nullptr) { movementAIComponent->Stop(); } inventoryComponent->DespawnPet(); - const auto& cached = buildCache.find(m_Parent->GetLOT()); - int32_t imaginationCost = 0; - - std::string buildFile; - - if (cached == buildCache.end()) { - auto query = CDClientDatabase::CreatePreppedStmt( - "SELECT ValidPiecesLXF, PuzzleModelLot, Timelimit, NumValidPieces, imagCostPerBuild FROM TamingBuildPuzzles WHERE NPCLot = ?;"); - query.bind(1, static_cast<int>(m_Parent->GetLOT())); - - auto result = query.execQuery(); - - if (result.eof()) { - ChatPackets::SendSystemMessage(originator->GetSystemAddress(), u"Failed to find the puzzle minigame for this pet."); - - return; - } - - if (result.fieldIsNull("ValidPiecesLXF")) { - result.finalize(); - - return; - } - - buildFile = std::string(result.getStringField("ValidPiecesLXF")); - - PetPuzzleData data; - data.buildFile = buildFile; - data.puzzleModelLot = result.getIntField("PuzzleModelLot"); - data.timeLimit = result.getFloatField("Timelimit"); - data.numValidPieces = result.getIntField("NumValidPieces"); - data.imaginationCost = result.getIntField("imagCostPerBuild"); - if (data.timeLimit <= 0) data.timeLimit = 60; - imaginationCost = data.imaginationCost; - - buildCache[m_Parent->GetLOT()] = data; - - result.finalize(); - } else { - buildFile = cached->second.buildFile; - imaginationCost = cached->second.imaginationCost; + const auto* const entry = CDClientManager::GetTable<CDTamingBuildPuzzleTable>()->GetByLOT(m_Parent->GetLOT()); + if (!entry) { + ChatPackets::SendSystemMessage(originator->GetSystemAddress(), u"Failed to find the puzzle minigame for this pet."); + return; } - auto* destroyableComponent = originator->GetComponent<DestroyableComponent>(); - + const auto* const destroyableComponent = originator->GetComponent<DestroyableComponent>(); if (destroyableComponent == nullptr) { return; } - auto imagination = destroyableComponent->GetImagination(); - - if (imagination < imaginationCost) { + const auto imagination = destroyableComponent->GetImagination(); + if (imagination < entry->imaginationCost) { return; } - const auto& bricks = BrickDatabase::GetBricks(buildFile); - + const auto& bricks = BrickDatabase::GetBricks(entry->validPieces); if (bricks.empty()) { ChatPackets::SendSystemMessage(originator->GetSystemAddress(), u"Failed to load the puzzle minigame for this pet."); - LOG("Couldn't find %s for minigame!", buildFile.c_str()); + LOG("Couldn't find %s for minigame!", entry->validPieces.c_str()); return; } - auto petPosition = m_Parent->GetPosition(); + const auto petPosition = m_Parent->GetPosition(); - auto originatorPosition = originator->GetPosition(); + const auto originatorPosition = originator->GetPosition(); m_Parent->SetRotation(NiQuaternion::LookAt(petPosition, originatorPosition)); float interactionDistance = m_Parent->GetVar<float>(u"interaction_distance"); - if (interactionDistance <= 0) { interactionDistance = 15; } @@ -476,9 +433,8 @@ void PetComponent::TryBuild(uint32_t numBricks, bool clientFailed) { return; } - const auto& cached = buildCache.find(m_Parent->GetLOT()); - - if (cached == buildCache.end()) return; + const auto* const entry = CDClientManager::GetTable<CDTamingBuildPuzzleTable>()->GetByLOT(m_Parent->GetLOT()); + if (!entry) return; auto* destroyableComponent = tamer->GetComponent<DestroyableComponent>(); @@ -486,14 +442,14 @@ void PetComponent::TryBuild(uint32_t numBricks, bool clientFailed) { auto imagination = destroyableComponent->GetImagination(); - imagination -= cached->second.imaginationCost; + imagination -= entry->imaginationCost; destroyableComponent->SetImagination(imagination); Game::entityManager->SerializeEntity(tamer); if (clientFailed) { - if (imagination < cached->second.imaginationCost) { + if (imagination < entry->imaginationCost) { ClientFailTamingMinigame(); } } else { @@ -516,17 +472,14 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) { return; } - const auto& cached = buildCache.find(m_Parent->GetLOT()); - - if (cached == buildCache.end()) { - return; - } + const auto* const entry = CDClientManager::GetTable<CDTamingBuildPuzzleTable>()->GetByLOT(m_Parent->GetLOT()); + if (!entry) return; GameMessages::SendPlayFXEffect(tamer, -1, u"petceleb", "", LWOOBJID_EMPTY, 1, 1, true); RenderComponent::PlayAnimation(tamer, u"rebuild-celebrate"); EntityInfo info{}; - info.lot = cached->second.puzzleModelLot; + info.lot = entry->puzzleModelLot; info.pos = position; info.rot = NiQuaternionConstant::IDENTITY; info.spawnerID = tamer->GetObjectID(); @@ -730,13 +683,10 @@ void PetComponent::ClientExitTamingMinigame(bool voluntaryExit) { } void PetComponent::StartTimer() { - const auto& cached = buildCache.find(m_Parent->GetLOT()); + const auto* const entry = CDClientManager::GetTable<CDTamingBuildPuzzleTable>()->GetByLOT(m_Parent->GetLOT()); + if (!entry) return; - if (cached == buildCache.end()) { - return; - } - - m_Timer = cached->second.timeLimit; + m_Timer = entry->timeLimit; } void PetComponent::ClientFailTamingMinigame() { diff --git a/dGame/dComponents/PetComponent.h b/dGame/dComponents/PetComponent.h index 1abdf155..323330d2 100644 --- a/dGame/dComponents/PetComponent.h +++ b/dGame/dComponents/PetComponent.h @@ -250,11 +250,6 @@ private: */ static std::unordered_map<LWOOBJID, LWOOBJID> currentActivities; - /** - * Cache of all the minigames and their information from the database - */ - static std::unordered_map<LOT, PetComponent::PetPuzzleData> buildCache; - /** * Flags that indicate that a player has tamed a pet, indexed by the LOT of the pet */ From 3801a9772262c648600e16c8e08930345841675d Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell <aronwk.aaron@gmail.com> Date: Wed, 24 Apr 2024 21:35:45 -0500 Subject: [PATCH 20/78] feat: add nlohmann/json lib (#1552) * feat: add nlohmann/json lib * remove build test off --- CMakeLists.txt | 3 ++- cmake/FindJSON.cmake | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 cmake/FindJSON.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index aa517182..ed1a46f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,6 +96,7 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) find_package(MariaDB) +find_package(JSON) # Create a /resServer directory make_directory(${CMAKE_BINARY_DIR}/resServer) @@ -284,7 +285,7 @@ add_subdirectory(dPhysics) add_subdirectory(dServer) # Create a list of common libraries shared between all binaries -set(COMMON_LIBRARIES "dCommon" "dDatabase" "dNet" "raknet" "MariaDB::ConnCpp" "magic_enum") +set(COMMON_LIBRARIES "dCommon" "dDatabase" "dNet" "raknet" "MariaDB::ConnCpp" "magic_enum" "nlohmann_json::nlohmann_json") # Add platform specific common libraries if(UNIX) diff --git a/cmake/FindJSON.cmake b/cmake/FindJSON.cmake new file mode 100644 index 00000000..291ebcd3 --- /dev/null +++ b/cmake/FindJSON.cmake @@ -0,0 +1,20 @@ +include(FetchContent) + +message(STATUS "Fetching json...") + +FetchContent_Declare( + json + GIT_REPOSITORY https://github.com/nlohmann/json + GIT_TAG v3.11.3 +) + +FetchContent_GetProperties(json) +if(NOT json_POPULATED) + FetchContent_Populate(json) + add_subdirectory(${json_SOURCE_DIR} ${json_BINARY_DIR} EXCLUDE_FROM_ALL) +endif() + +FetchContent_MakeAvailable(json) + +message(STATUS "json fetched and is now ready.") +set(JSON_FOUND TRUE) From 35c463656df4cd3eaf673088a8b5c39278213a34 Mon Sep 17 00:00:00 2001 From: Daniel Seiler <me@xiphoseer.de> Date: Mon, 29 Apr 2024 22:51:13 +0200 Subject: [PATCH 21/78] Use volume for mariadb persistence (#1555) I initially used the bind mount because it's arguably easier to back up and move around than a volume, but turns out with https://github.com/DarkflameUniverse/NexusDashboard/issues/92 it's nothing we can recommend for Docker Desktop on WSL, which unfortunately is the primary setup newcomers will try this with. So changing the default to be a volume should address that (presumably by hosting the volume within the WSL Docker VM, as opposed to the host NTFS filesystem) --- docker-compose.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index a7954718..8f5a3d09 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: - darkflame image: mariadb:latest volumes: - - ${DB_DATA_DIR:-./db/data}:/var/lib/mysql + - ${DB_DATA_DIR:-db-data}:/var/lib/mysql environment: - MARIADB_RANDOM_ROOT_PASSWORD=1 - MARIADB_USER=${MARIADB_USER:-darkflame} @@ -79,3 +79,6 @@ services: networks: darkflame: + +volumes: + db-data: From 58cc569c75e99f87ecd86ebf51623f174bf7621c Mon Sep 17 00:00:00 2001 From: David Markowitz <EmosewaMC@gmail.com> Date: Tue, 30 Apr 2024 23:09:35 -0700 Subject: [PATCH 22/78] fix out of order physics updates fixes an issue where physics entities were not given a chance to be marked as sleeping, causing a initial sleeping calls to be missed and causing objects that collided with one another to not register new collisions since they were sleeping at the time the new collision fired off. Tested that Brick Fury now corectly aggros the _first_ spawn of enemies near by to him. Tested that the turrets in crux prime now correctly shoot the _first_ wave of enemies that spawn. --- dWorldServer/WorldServer.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index b6954ca2..ae671e6e 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -388,14 +388,14 @@ int main(int argc, char** argv) { //In world we'd update our other systems here. if (zoneID != 0 && deltaTime > 0.0f) { - Metrics::StartMeasurement(MetricVariable::Physics); - dpWorld::StepWorld(deltaTime); - Metrics::EndMeasurement(MetricVariable::Physics); - Metrics::StartMeasurement(MetricVariable::UpdateEntities); Game::entityManager->UpdateEntities(deltaTime); Metrics::EndMeasurement(MetricVariable::UpdateEntities); + Metrics::StartMeasurement(MetricVariable::Physics); + dpWorld::StepWorld(deltaTime); + Metrics::EndMeasurement(MetricVariable::Physics); + Metrics::StartMeasurement(MetricVariable::Ghosting); if (std::chrono::duration<float>(currentTime - ghostingLastTime).count() >= 1.0f) { Game::entityManager->UpdateGhosting(); From 794b254fe7706d2ededbe326a3cd884edaedeb9d Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 2 May 2024 04:35:44 -0700 Subject: [PATCH 23/78] remove md5 new (#1560) tested that sqlite hash is still calculated --- dWorldServer/WorldServer.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index ae671e6e..9e55d8b7 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -282,24 +282,22 @@ int main(int argc, char** argv) { } const int32_t bufferSize = 1024; - MD5* md5 = new MD5(); + MD5 md5; char fileStreamBuffer[1024] = {}; while (!fileStream.eof()) { memset(fileStreamBuffer, 0, bufferSize); fileStream.read(fileStreamBuffer, bufferSize); - md5->update(fileStreamBuffer, fileStream.gcount()); + md5.update(fileStreamBuffer, fileStream.gcount()); } fileStream.close(); const char* nullTerminateBuffer = "\0"; - md5->update(nullTerminateBuffer, 1); // null terminate the data - md5->finalize(); - databaseChecksum = md5->hexdigest(); - - delete md5; + md5.update(nullTerminateBuffer, 1); // null terminate the data + md5.finalize(); + databaseChecksum = md5.hexdigest(); LOG("FDB Checksum calculated as: %s", databaseChecksum.c_str()); } From 07cb19cc30c8f0dc623bfbff77d7a63039d0faee Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell <aronwk.aaron@gmail.com> Date: Thu, 2 May 2024 06:35:55 -0500 Subject: [PATCH 24/78] chore: remove json (#1561) we can't use it currently due to threadsafety issues, so just going to remove it until we actually need it and will re-add it as a vendored file later due to cmake issues pulling in things --- CMakeLists.txt | 3 +-- cmake/FindJSON.cmake | 20 -------------------- 2 files changed, 1 insertion(+), 22 deletions(-) delete mode 100644 cmake/FindJSON.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index ed1a46f1..aa517182 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,7 +96,6 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) find_package(MariaDB) -find_package(JSON) # Create a /resServer directory make_directory(${CMAKE_BINARY_DIR}/resServer) @@ -285,7 +284,7 @@ add_subdirectory(dPhysics) add_subdirectory(dServer) # Create a list of common libraries shared between all binaries -set(COMMON_LIBRARIES "dCommon" "dDatabase" "dNet" "raknet" "MariaDB::ConnCpp" "magic_enum" "nlohmann_json::nlohmann_json") +set(COMMON_LIBRARIES "dCommon" "dDatabase" "dNet" "raknet" "MariaDB::ConnCpp" "magic_enum") # Add platform specific common libraries if(UNIX) diff --git a/cmake/FindJSON.cmake b/cmake/FindJSON.cmake deleted file mode 100644 index 291ebcd3..00000000 --- a/cmake/FindJSON.cmake +++ /dev/null @@ -1,20 +0,0 @@ -include(FetchContent) - -message(STATUS "Fetching json...") - -FetchContent_Declare( - json - GIT_REPOSITORY https://github.com/nlohmann/json - GIT_TAG v3.11.3 -) - -FetchContent_GetProperties(json) -if(NOT json_POPULATED) - FetchContent_Populate(json) - add_subdirectory(${json_SOURCE_DIR} ${json_BINARY_DIR} EXCLUDE_FROM_ALL) -endif() - -FetchContent_MakeAvailable(json) - -message(STATUS "json fetched and is now ready.") -set(JSON_FOUND TRUE) From 2ca61c3e57229ceea198d4bf17b528a529e8101d Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Fri, 10 May 2024 07:22:26 -0700 Subject: [PATCH 25/78] feat: Dragonmaw (#1562) * rigid as heck * abstract physics creation to separate function * loading Update FvRacePillarDServer.cpp consolidate abcd pillar logic modularization Update SimplePhysicsComponent.cpp Update EntityManager.cpp Update MovingPlatformComponent.cpp still need another pass * geiser works * columns working finally * consolidate logic * constiness * Update PhantomPhysicsComponent.cpp * Update PhysicsComponent.cpp * revert testing code * add versions info --------- Co-authored-by: Aaron Kimbre <aronwk.aaron@gmail.com> --- CMakeVariables.txt | 4 +- dGame/Entity.cpp | 24 +++ dGame/dComponents/PhantomPhysicsComponent.cpp | 193 ++--------------- dGame/dComponents/PhantomPhysicsComponent.h | 13 +- dGame/dComponents/PhysicsComponent.cpp | 201 ++++++++++++++++++ dGame/dComponents/PhysicsComponent.h | 10 + dGame/dComponents/RacingControlComponent.cpp | 6 +- .../RigidbodyPhantomPhysicsComponent.cpp | 49 ++++- .../RigidbodyPhantomPhysicsComponent.h | 22 +- dGame/dGameMessages/GameMessages.cpp | 4 +- .../SlashCommands/DEVGMCommands.cpp | 10 +- .../02_server/Map/FV/Racing/CMakeLists.txt | 3 + .../Map/FV/Racing/FvRacingColumns.cpp | 15 ++ .../02_server/Map/FV/Racing/FvRacingColumns.h | 6 + .../02_server/Map/FV/Racing/RaceFireballs.cpp | 15 ++ .../02_server/Map/FV/Racing/RaceFireballs.h | 9 + .../FV/Racing/RaceShipLapColumnsServer.cpp | 47 ++++ .../Map/FV/Racing/RaceShipLapColumnsServer.h | 8 + dScripts/CppScripts.cpp | 28 ++- dScripts/ai/RACING/OBJECTS/CMakeLists.txt | 4 + dScripts/ai/RACING/OBJECTS/FvRaceDragon.cpp | 30 +++ dScripts/ai/RACING/OBJECTS/FvRaceDragon.h | 15 ++ .../RACING/OBJECTS/FvRacePillarABCServer.cpp | 34 +++ .../ai/RACING/OBJECTS/FvRacePillarABCServer.h | 13 ++ .../ai/RACING/OBJECTS/FvRacePillarDServer.cpp | 21 ++ .../ai/RACING/OBJECTS/FvRacePillarDServer.h | 10 + .../ai/RACING/OBJECTS/FvRacePillarServer.cpp | 15 ++ .../ai/RACING/OBJECTS/FvRacePillarServer.h | 12 ++ versions.txt | 4 +- 29 files changed, 611 insertions(+), 214 deletions(-) create mode 100644 dScripts/02_server/Map/FV/Racing/FvRacingColumns.cpp create mode 100644 dScripts/02_server/Map/FV/Racing/FvRacingColumns.h create mode 100644 dScripts/02_server/Map/FV/Racing/RaceFireballs.cpp create mode 100644 dScripts/02_server/Map/FV/Racing/RaceFireballs.h create mode 100644 dScripts/02_server/Map/FV/Racing/RaceShipLapColumnsServer.cpp create mode 100644 dScripts/02_server/Map/FV/Racing/RaceShipLapColumnsServer.h create mode 100644 dScripts/ai/RACING/OBJECTS/FvRaceDragon.cpp create mode 100644 dScripts/ai/RACING/OBJECTS/FvRaceDragon.h create mode 100644 dScripts/ai/RACING/OBJECTS/FvRacePillarABCServer.cpp create mode 100644 dScripts/ai/RACING/OBJECTS/FvRacePillarABCServer.h create mode 100644 dScripts/ai/RACING/OBJECTS/FvRacePillarDServer.cpp create mode 100644 dScripts/ai/RACING/OBJECTS/FvRacePillarDServer.h create mode 100644 dScripts/ai/RACING/OBJECTS/FvRacePillarServer.cpp create mode 100644 dScripts/ai/RACING/OBJECTS/FvRacePillarServer.h diff --git a/CMakeVariables.txt b/CMakeVariables.txt index d9430d9d..31482ce4 100644 --- a/CMakeVariables.txt +++ b/CMakeVariables.txt @@ -1,6 +1,6 @@ PROJECT_VERSION_MAJOR=1 -PROJECT_VERSION_MINOR=1 -PROJECT_VERSION_PATCH=1 +PROJECT_VERSION_MINOR=2 +PROJECT_VERSION_PATCH=0 # Debugging # Set DYNAMIC to 1 to enable the -rdynamic flag for the linker, yielding some symbols in crashlogs. diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 00ad8471..59b583d3 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -1840,6 +1840,12 @@ const NiPoint3& Entity::GetPosition() const { return vehicel->GetPosition(); } + auto* rigidBodyPhantomPhysicsComponent = GetComponent<RigidbodyPhantomPhysicsComponent>(); + + if (rigidBodyPhantomPhysicsComponent != nullptr) { + return rigidBodyPhantomPhysicsComponent->GetPosition(); + } + return NiPoint3Constant::ZERO; } @@ -1868,6 +1874,12 @@ const NiQuaternion& Entity::GetRotation() const { return vehicel->GetRotation(); } + auto* rigidBodyPhantomPhysicsComponent = GetComponent<RigidbodyPhantomPhysicsComponent>(); + + if (rigidBodyPhantomPhysicsComponent != nullptr) { + return rigidBodyPhantomPhysicsComponent->GetRotation(); + } + return NiQuaternionConstant::IDENTITY; } @@ -1896,6 +1908,12 @@ void Entity::SetPosition(const NiPoint3& position) { vehicel->SetPosition(position); } + auto* rigidBodyPhantomPhysicsComponent = GetComponent<RigidbodyPhantomPhysicsComponent>(); + + if (rigidBodyPhantomPhysicsComponent != nullptr) { + rigidBodyPhantomPhysicsComponent->SetPosition(position); + } + Game::entityManager->SerializeEntity(this); } @@ -1924,6 +1942,12 @@ void Entity::SetRotation(const NiQuaternion& rotation) { vehicel->SetRotation(rotation); } + auto* rigidBodyPhantomPhysicsComponent = GetComponent<RigidbodyPhantomPhysicsComponent>(); + + if (rigidBodyPhantomPhysicsComponent != nullptr) { + rigidBodyPhantomPhysicsComponent->SetRotation(rotation); + } + Game::entityManager->SerializeEntity(this); } diff --git a/dGame/dComponents/PhantomPhysicsComponent.cpp b/dGame/dComponents/PhantomPhysicsComponent.cpp index ba0c2495..95fed36e 100644 --- a/dGame/dComponents/PhantomPhysicsComponent.cpp +++ b/dGame/dComponents/PhantomPhysicsComponent.cpp @@ -47,7 +47,7 @@ PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent) : PhysicsCompon m_Direction = NiPoint3(); // * m_DirectionalMultiplier if (m_Parent->GetVar<bool>(u"create_physics")) { - CreatePhysics(); + m_dpEntity = CreatePhysicsLnv(m_Scale, ComponentType); } if (m_Parent->GetVar<bool>(u"respawnVol")) { @@ -89,105 +89,9 @@ PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent) : PhysicsCompon m_RespawnRot = m_Rotation; } - /* - for (LDFBaseData* data : settings) { - if (data) { - if (data->GetKey() == u"create_physics") { - if (bool(std::stoi(data->GetValueAsString()))) { - CreatePhysics(settings); - } - } - - if (data->GetKey() == u"respawnVol") { - if (bool(std::stoi(data->GetValueAsString()))) { - m_IsRespawnVolume = true; - } - } - - if (m_IsRespawnVolume) { - if (data->GetKey() == u"rspPos") { - //Joy, we get to split strings! - std::stringstream test(data->GetValueAsString()); - std::string segment; - std::vector<std::string> seglist; - - while (std::getline(test, segment, '\x1f')) { - seglist.push_back(segment); - } - - m_RespawnPos = NiPoint3(std::stof(seglist[0]), std::stof(seglist[1]), std::stof(seglist[2])); - } - - if (data->GetKey() == u"rspRot") { - //Joy, we get to split strings! - std::stringstream test(data->GetValueAsString()); - std::string segment; - std::vector<std::string> seglist; - - while (std::getline(test, segment, '\x1f')) { - seglist.push_back(segment); - } - - m_RespawnRot = NiQuaternion(std::stof(seglist[0]), std::stof(seglist[1]), std::stof(seglist[2]), std::stof(seglist[3])); - } - } - - if (m_Parent->GetLOT() == 4945) // HF - RespawnPoints - { - m_IsRespawnVolume = true; - m_RespawnPos = m_Position; - m_RespawnRot = m_Rotation; - } - } - } - */ - - if (!m_HasCreatedPhysics) { - CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>(); - auto componentID = compRegistryTable->GetByIDAndType(m_Parent->GetLOT(), eReplicaComponentType::PHANTOM_PHYSICS); - - CDPhysicsComponentTable* physComp = CDClientManager::GetTable<CDPhysicsComponentTable>(); - - if (physComp == nullptr) return; - - auto* info = physComp->GetByID(componentID); - if (info == nullptr || info->physicsAsset == "" || info->physicsAsset == "NO_PHYSICS") return; - - //temp test - if (info->physicsAsset == "miscellaneous\\misc_phys_10x1x5.hkx") { - m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 10.0f, 5.0f, 1.0f); - } else if (info->physicsAsset == "miscellaneous\\misc_phys_640x640.hkx") { - // TODO Fix physics simulation to do simulation at high velocities due to bullet through paper problem... - m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 1638.4f, 13.521004f * 2.0f, 1638.4f); - - // Move this down by 13.521004 units so it is still effectively at the same height as before - m_Position = m_Position - NiPoint3Constant::UNIT_Y * 13.521004f; - } else if (info->physicsAsset == "env\\trigger_wall_tall.hkx") { - m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 10.0f, 25.0f, 1.0f); - } else if (info->physicsAsset == "env\\env_gen_placeholderphysics.hkx") { - m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 20.0f, 20.0f, 20.0f); - } else if (info->physicsAsset == "env\\POI_trigger_wall.hkx") { - m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 1.0f, 12.5f, 20.0f); // Not sure what the real size is - } else if (info->physicsAsset == "env\\NG_NinjaGo\\env_ng_gen_gate_chamber_puzzle_ceiling_tile_falling_phantom.hkx") { - m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 18.0f, 5.0f, 15.0f); - m_Position += m_Rotation.GetForwardVector() * 7.5f; - } else if (info->physicsAsset == "env\\NG_NinjaGo\\ng_flamejet_brick_phantom.HKX") { - m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 1.0f, 1.0f, 12.0f); - m_Position += m_Rotation.GetForwardVector() * 6.0f; - } else if (info->physicsAsset == "env\\Ring_Trigger.hkx") { - m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 6.0f, 6.0f, 6.0f); - } else if (info->physicsAsset == "env\\vfx_propertyImaginationBall.hkx") { - m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 4.5f); - } else if (info->physicsAsset == "env\\env_won_fv_gas-blocking-volume.hkx") { - m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 390.496826f, 111.467964f, 600.821534f, true); - m_Position.y -= (111.467964f * m_Scale) / 2; - } else { - // LOG_DEBUG("This one is supposed to have %s", info->physicsAsset.c_str()); - - //add fallback cube: - m_dpEntity = new dpEntity(m_Parent->GetObjectID(), 2.0f, 2.0f, 2.0f); - } - + if (!m_dpEntity) { + m_dpEntity = CreatePhysicsEntity(ComponentType); + if (!m_dpEntity) return; m_dpEntity->SetScale(m_Scale); m_dpEntity->SetRotation(m_Rotation); m_dpEntity->SetPosition(m_Position); @@ -201,69 +105,6 @@ PhantomPhysicsComponent::~PhantomPhysicsComponent() { } } -void PhantomPhysicsComponent::CreatePhysics() { - unsigned char alpha; - unsigned char red; - unsigned char green; - unsigned char blue; - int type = -1; - float x = 0.0f; - float y = 0.0f; - float z = 0.0f; - float width = 0.0f; //aka "radius" - float height = 0.0f; - - if (m_Parent->HasVar(u"primitiveModelType")) { - type = m_Parent->GetVar<int32_t>(u"primitiveModelType"); - x = m_Parent->GetVar<float>(u"primitiveModelValueX"); - y = m_Parent->GetVar<float>(u"primitiveModelValueY"); - z = m_Parent->GetVar<float>(u"primitiveModelValueZ"); - } else { - CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>(); - auto componentID = compRegistryTable->GetByIDAndType(m_Parent->GetLOT(), eReplicaComponentType::PHANTOM_PHYSICS); - - CDPhysicsComponentTable* physComp = CDClientManager::GetTable<CDPhysicsComponentTable>(); - - if (physComp == nullptr) return; - - auto info = physComp->GetByID(componentID); - - if (info == nullptr) return; - - type = info->pcShapeType; - width = info->playerRadius; - height = info->playerHeight; - } - - switch (type) { - case 1: { //Make a new box shape - NiPoint3 boxSize(x, y, z); - if (x == 0.0f) { - //LU has some weird values, so I think it's best to scale them down a bit - if (height < 0.5f) height = 2.0f; - if (width < 0.5f) width = 2.0f; - - //Scale them: - width = width * m_Scale; - height = height * m_Scale; - - boxSize = NiPoint3(width, height, width); - } - - m_dpEntity = new dpEntity(m_Parent->GetObjectID(), boxSize); - break; - } - } - - if (!m_dpEntity) return; - - m_dpEntity->SetPosition({ m_Position.x, m_Position.y - (height / 2), m_Position.z }); - - dpWorld::AddEntity(m_dpEntity); - - m_HasCreatedPhysics = true; -} - void PhantomPhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { PhysicsComponent::Serialize(outBitStream, bIsInitialUpdate); @@ -308,8 +149,9 @@ void ApplyCollisionEffect(const LWOOBJID& target, const ePhysicsEffectType effec controllablePhysicsComponent->SetGravityScale(effectScale); GameMessages::SendSetGravityScale(target, effectScale, targetEntity->GetSystemAddress()); } + break; } - // The other types are not handled by the server + case ePhysicsEffectType::ATTRACT: case ePhysicsEffectType::FRICTION: case ePhysicsEffectType::PUSH: @@ -317,6 +159,7 @@ void ApplyCollisionEffect(const LWOOBJID& target, const ePhysicsEffectType effec default: break; } + // The other types are not handled by the server and are here to handle all cases of the enum. } void PhantomPhysicsComponent::Update(float deltaTime) { @@ -356,24 +199,12 @@ void PhantomPhysicsComponent::SetDirection(const NiPoint3& pos) { m_IsDirectional = true; } -void PhantomPhysicsComponent::SpawnVertices() { - if (!m_dpEntity) return; - - LOG("%llu", m_Parent->GetObjectID()); - auto box = static_cast<dpShapeBox*>(m_dpEntity->GetShape()); - for (auto vert : box->GetVertices()) { - LOG("%f, %f, %f", vert.x, vert.y, vert.z); - - EntityInfo info; - info.lot = 33; - info.pos = vert; - info.spawner = nullptr; - info.spawnerID = m_Parent->GetObjectID(); - info.spawnerNodeID = 0; - - Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr); - Game::entityManager->ConstructEntity(newEntity); +void PhantomPhysicsComponent::SpawnVertices() const { + if (!m_dpEntity) { + LOG("No dpEntity to spawn vertices for %llu:%i", m_Parent->GetObjectID(), m_Parent->GetLOT()); + return; } + PhysicsComponent::SpawnVertices(m_dpEntity); } void PhantomPhysicsComponent::SetDirectionalMultiplier(float mul) { diff --git a/dGame/dComponents/PhantomPhysicsComponent.h b/dGame/dComponents/PhantomPhysicsComponent.h index 1aae9527..89cfb857 100644 --- a/dGame/dComponents/PhantomPhysicsComponent.h +++ b/dGame/dComponents/PhantomPhysicsComponent.h @@ -18,6 +18,7 @@ class LDFBaseData; class Entity; class dpEntity; enum class ePhysicsEffectType : uint32_t ; +enum class eReplicaComponentType : uint32_t; /** * Allows the creation of phantom physics for an entity: a physics object that is generally invisible but can be @@ -34,11 +35,6 @@ public: void Update(float deltaTime) override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; - /** - * Creates the physics shape for this entity based on LDF data - */ - void CreatePhysics(); - /** * Sets the direction this physics object is pointed at * @param pos the direction to set @@ -109,7 +105,7 @@ public: /** * Spawns an object at each of the vertices for debugging purposes */ - void SpawnVertices(); + void SpawnVertices() const; /** * Legacy stuff no clue what this does @@ -166,11 +162,6 @@ private: */ dpEntity* m_dpEntity; - /** - * Whether or not the physics object has been created yet - */ - bool m_HasCreatedPhysics = false; - /** * Whether or not this physics object represents an object that updates the respawn pos of an entity that crosses it */ diff --git a/dGame/dComponents/PhysicsComponent.cpp b/dGame/dComponents/PhysicsComponent.cpp index 3a84c4ce..4a250a6a 100644 --- a/dGame/dComponents/PhysicsComponent.cpp +++ b/dGame/dComponents/PhysicsComponent.cpp @@ -1,5 +1,19 @@ #include "PhysicsComponent.h" +#include "eReplicaComponentType.h" +#include "NiPoint3.h" +#include "NiQuaternion.h" + +#include "CDComponentsRegistryTable.h" +#include "CDPhysicsComponentTable.h" + +#include "dpEntity.h" +#include "dpWorld.h" +#include "dpShapeBox.h" +#include "dpShapeSphere.h" + +#include "EntityInfo.h" + PhysicsComponent::PhysicsComponent(Entity* parent) : Component(parent) { m_Position = NiPoint3Constant::ZERO; m_Rotation = NiQuaternionConstant::IDENTITY; @@ -19,3 +33,190 @@ void PhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitia if (!bIsInitialUpdate) m_DirtyPosition = false; } } + +dpEntity* PhysicsComponent::CreatePhysicsEntity(eReplicaComponentType type) { + CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>(); + auto componentID = compRegistryTable->GetByIDAndType(m_Parent->GetLOT(), type); + + CDPhysicsComponentTable* physComp = CDClientManager::GetTable<CDPhysicsComponentTable>(); + + if (physComp == nullptr) return nullptr; + + auto* info = physComp->GetByID(componentID); + if (info == nullptr || info->physicsAsset == "" || info->physicsAsset == "NO_PHYSICS") return nullptr; + + dpEntity* toReturn; + if (info->physicsAsset == "miscellaneous\\misc_phys_10x1x5.hkx") { + toReturn = new dpEntity(m_Parent->GetObjectID(), 10.0f, 5.0f, 1.0f); + } else if (info->physicsAsset == "miscellaneous\\misc_phys_640x640.hkx") { + // TODO Fix physics simulation to do simulation at high velocities due to bullet through paper problem... + toReturn = new dpEntity(m_Parent->GetObjectID(), 1638.4f, 13.521004f * 2.0f, 1638.4f); + + // Move this down by 13.521004 units so it is still effectively at the same height as before + m_Position = m_Position - NiPoint3Constant::UNIT_Y * 13.521004f; + } else if (info->physicsAsset == "env\\trigger_wall_tall.hkx") { + toReturn = new dpEntity(m_Parent->GetObjectID(), 10.0f, 25.0f, 1.0f); + } else if (info->physicsAsset == "env\\env_gen_placeholderphysics.hkx") { + toReturn = new dpEntity(m_Parent->GetObjectID(), 20.0f, 20.0f, 20.0f); + } else if (info->physicsAsset == "env\\POI_trigger_wall.hkx") { + toReturn = new dpEntity(m_Parent->GetObjectID(), 1.0f, 12.5f, 20.0f); // Not sure what the real size is + } else if (info->physicsAsset == "env\\NG_NinjaGo\\env_ng_gen_gate_chamber_puzzle_ceiling_tile_falling_phantom.hkx") { + toReturn = new dpEntity(m_Parent->GetObjectID(), 18.0f, 5.0f, 15.0f); + m_Position += m_Rotation.GetForwardVector() * 7.5f; + } else if (info->physicsAsset == "env\\NG_NinjaGo\\ng_flamejet_brick_phantom.HKX") { + toReturn = new dpEntity(m_Parent->GetObjectID(), 1.0f, 1.0f, 12.0f); + m_Position += m_Rotation.GetForwardVector() * 6.0f; + } else if (info->physicsAsset == "env\\Ring_Trigger.hkx") { + toReturn = new dpEntity(m_Parent->GetObjectID(), 6.0f, 6.0f, 6.0f); + } else if (info->physicsAsset == "env\\vfx_propertyImaginationBall.hkx") { + toReturn = new dpEntity(m_Parent->GetObjectID(), 4.5f); + } else if (info->physicsAsset == "env\\env_won_fv_gas-blocking-volume.hkx") { + toReturn = new dpEntity(m_Parent->GetObjectID(), 390.496826f, 111.467964f, 600.821534f, true); + m_Position.y -= (111.467964f * m_Parent->GetDefaultScale()) / 2; + } else { + // LOG_DEBUG("This one is supposed to have %s", info->physicsAsset.c_str()); + + //add fallback cube: + toReturn = new dpEntity(m_Parent->GetObjectID(), 2.0f, 2.0f, 2.0f); + } + return toReturn; +} + +dpEntity* PhysicsComponent::CreatePhysicsLnv(const float scale, const eReplicaComponentType type) const { + int pcShapeType = -1; + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + float width = 0.0f; //aka "radius" + float height = 0.0f; + dpEntity* toReturn = nullptr; + + if (m_Parent->HasVar(u"primitiveModelType")) { + pcShapeType = m_Parent->GetVar<int32_t>(u"primitiveModelType"); + x = m_Parent->GetVar<float>(u"primitiveModelValueX"); + y = m_Parent->GetVar<float>(u"primitiveModelValueY"); + z = m_Parent->GetVar<float>(u"primitiveModelValueZ"); + } else { + CDComponentsRegistryTable* compRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>(); + auto componentID = compRegistryTable->GetByIDAndType(m_Parent->GetLOT(), type); + + CDPhysicsComponentTable* physComp = CDClientManager::GetTable<CDPhysicsComponentTable>(); + + if (physComp == nullptr) return nullptr; + + auto info = physComp->GetByID(componentID); + + if (info == nullptr) return nullptr; + + pcShapeType = info->pcShapeType; + width = info->playerRadius; + height = info->playerHeight; + } + + switch (pcShapeType) { + case 0: { // HKX type + break; + } + case 1: { //Make a new box shape + NiPoint3 boxSize(x, y, z); + if (x == 0.0f) { + //LU has some weird values, so I think it's best to scale them down a bit + if (height < 0.5f) height = 2.0f; + if (width < 0.5f) width = 2.0f; + + //Scale them: + width = width * scale; + height = height * scale; + + boxSize = NiPoint3(width, height, width); + } + + toReturn = new dpEntity(m_Parent->GetObjectID(), boxSize); + + toReturn->SetPosition({ m_Position.x, m_Position.y - (height / 2), m_Position.z }); + break; + } + case 2: { //Make a new cylinder shape + break; + } + case 3: { //Make a new sphere shape + auto [x, y, z] = m_Position; + toReturn = new dpEntity(m_Parent->GetObjectID(), width); + toReturn->SetPosition({ x, y, z }); + break; + } + case 4: { //Make a new capsule shape + break; + } + } + + if (toReturn) dpWorld::AddEntity(toReturn); + + return toReturn; +} + +void PhysicsComponent::SpawnVertices(dpEntity* entity) const { + if (!entity) return; + + LOG("Spawning vertices for %llu", m_Parent->GetObjectID()); + EntityInfo info; + info.lot = 33; + info.spawner = nullptr; + info.spawnerID = m_Parent->GetObjectID(); + info.spawnerNodeID = 0; + + // These don't use overloaded methods as dPhysics does not link with dGame at the moment. + auto box = dynamic_cast<dpShapeBox*>(entity->GetShape()); + if (box) { + for (auto vert : box->GetVertices()) { + LOG("Vertex at %f, %f, %f", vert.x, vert.y, vert.z); + + info.pos = vert; + Entity* newEntity = Game::entityManager->CreateEntity(info); + Game::entityManager->ConstructEntity(newEntity); + } + } + auto sphere = dynamic_cast<dpShapeSphere*>(entity->GetShape()); + if (sphere) { + auto [x, y, z] = entity->GetPosition(); // Use shapes position instead of the parent's position in case it's different + float plusX = x + sphere->GetRadius(); + float minusX = x - sphere->GetRadius(); + float plusY = y + sphere->GetRadius(); + float minusY = y - sphere->GetRadius(); + float plusZ = z + sphere->GetRadius(); + float minusZ = z - sphere->GetRadius(); + + auto radius = sphere->GetRadius(); + LOG("Radius: %f", radius); + LOG("Plus Vertices %f %f %f", plusX, plusY, plusZ); + LOG("Minus Vertices %f %f %f", minusX, minusY, minusZ); + + info.pos = NiPoint3{ x, plusY, z }; + Entity* newEntity = Game::entityManager->CreateEntity(info); + Game::entityManager->ConstructEntity(newEntity); + + info.pos = NiPoint3{ x, minusY, z }; + newEntity = Game::entityManager->CreateEntity(info); + Game::entityManager->ConstructEntity(newEntity); + + info.pos = NiPoint3{ plusX, y, z }; + newEntity = Game::entityManager->CreateEntity(info); + Game::entityManager->ConstructEntity(newEntity); + + info.pos = NiPoint3{ minusX, y, z }; + newEntity = Game::entityManager->CreateEntity(info); + Game::entityManager->ConstructEntity(newEntity); + + info.pos = NiPoint3{ x, y, plusZ }; + newEntity = Game::entityManager->CreateEntity(info); + Game::entityManager->ConstructEntity(newEntity); + + info.pos = NiPoint3{ x, y, minusZ }; + newEntity = Game::entityManager->CreateEntity(info); + Game::entityManager->ConstructEntity(newEntity); + + info.pos = NiPoint3{ x, y, z }; + newEntity = Game::entityManager->CreateEntity(info); + Game::entityManager->ConstructEntity(newEntity); + } +} diff --git a/dGame/dComponents/PhysicsComponent.h b/dGame/dComponents/PhysicsComponent.h index 71f52e54..4bf0828a 100644 --- a/dGame/dComponents/PhysicsComponent.h +++ b/dGame/dComponents/PhysicsComponent.h @@ -9,6 +9,10 @@ namespace Raknet { class BitStream; }; +enum class eReplicaComponentType : uint32_t; + +class dpEntity; + class PhysicsComponent : public Component { public: PhysicsComponent(Entity* parent); @@ -22,6 +26,12 @@ public: const NiQuaternion& GetRotation() const { return m_Rotation; } virtual void SetRotation(const NiQuaternion& rot) { if (m_Rotation == rot) return; m_Rotation = rot; m_DirtyPosition = true; } protected: + dpEntity* CreatePhysicsEntity(eReplicaComponentType type); + + dpEntity* CreatePhysicsLnv(const float scale, const eReplicaComponentType type) const; + + void SpawnVertices(dpEntity* entity) const; + NiPoint3 m_Position; NiQuaternion m_Rotation; diff --git a/dGame/dComponents/RacingControlComponent.cpp b/dGame/dComponents/RacingControlComponent.cpp index d7e01f94..21d39249 100644 --- a/dGame/dComponents/RacingControlComponent.cpp +++ b/dGame/dComponents/RacingControlComponent.cpp @@ -817,8 +817,10 @@ void RacingControlComponent::Update(float deltaTime) { // Some offset up to make they don't fall through the terrain on a // respawn, seems to fix itself to the track anyhow - player.respawnPosition = position + NiPoint3Constant::UNIT_Y * 5; - player.respawnRotation = vehicle->GetRotation(); + if (waypoint.racing.isResetNode) { + player.respawnPosition = position + NiPoint3Constant::UNIT_Y * 5; + player.respawnRotation = vehicle->GetRotation(); + } player.respawnIndex = respawnIndex; // Reached the start point, lapped diff --git a/dGame/dComponents/RigidbodyPhantomPhysicsComponent.cpp b/dGame/dComponents/RigidbodyPhantomPhysicsComponent.cpp index 30faa688..df81aab3 100644 --- a/dGame/dComponents/RigidbodyPhantomPhysicsComponent.cpp +++ b/dGame/dComponents/RigidbodyPhantomPhysicsComponent.cpp @@ -1,16 +1,57 @@ -/* - * Darkflame Universe - * Copyright 2023 - */ +// Darkflame Universe +// Copyright 2024 #include "RigidbodyPhantomPhysicsComponent.h" #include "Entity.h" +#include "dpEntity.h" +#include "CDComponentsRegistryTable.h" +#include "CDPhysicsComponentTable.h" +#include "dpWorld.h" +#include "dpShapeBox.h" +#include "dpShapeSphere.h" +#include"EntityInfo.h" + RigidbodyPhantomPhysicsComponent::RigidbodyPhantomPhysicsComponent(Entity* parent) : PhysicsComponent(parent) { m_Position = m_Parent->GetDefaultPosition(); m_Rotation = m_Parent->GetDefaultRotation(); + m_Scale = m_Parent->GetDefaultScale(); + + if (m_Parent->GetVar<bool>(u"create_physics")) { + m_dpEntity = CreatePhysicsLnv(m_Scale, ComponentType); + if (!m_dpEntity) { + m_dpEntity = CreatePhysicsEntity(ComponentType); + if (!m_dpEntity) return; + m_dpEntity->SetScale(m_Scale); + m_dpEntity->SetRotation(m_Rotation); + m_dpEntity->SetPosition(m_Position); + dpWorld::AddEntity(m_dpEntity); + } + } } void RigidbodyPhantomPhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { PhysicsComponent::Serialize(outBitStream, bIsInitialUpdate); } + +void RigidbodyPhantomPhysicsComponent::Update(const float deltaTime) { + if (!m_dpEntity) return; + + //Process enter events + for (const auto id : m_dpEntity->GetNewObjects()) { + m_Parent->OnCollisionPhantom(id); + } + + //Process exit events + for (const auto id : m_dpEntity->GetRemovedObjects()) { + m_Parent->OnCollisionLeavePhantom(id); + } +} + +void RigidbodyPhantomPhysicsComponent::SpawnVertices() const { + if (!m_dpEntity) { + LOG("No dpEntity to spawn vertices for %llu:%i", m_Parent->GetObjectID(), m_Parent->GetLOT()); + return; + } + PhysicsComponent::SpawnVertices(m_dpEntity); +} diff --git a/dGame/dComponents/RigidbodyPhantomPhysicsComponent.h b/dGame/dComponents/RigidbodyPhantomPhysicsComponent.h index 09820f8e..11595ec0 100644 --- a/dGame/dComponents/RigidbodyPhantomPhysicsComponent.h +++ b/dGame/dComponents/RigidbodyPhantomPhysicsComponent.h @@ -1,10 +1,8 @@ -/* - * Darkflame Universe - * Copyright 2023 - */ +// Darkflame Universe +// Copyright 2024 -#ifndef __RIGIDBODYPHANTOMPHYSICS_H__ -#define __RIGIDBODYPHANTOMPHYSICS_H__ +#ifndef RIGIDBODYPHANTOMPHYSICS_H +#define RIGIDBODYPHANTOMPHYSICS_H #include "BitStream.h" #include "dCommonVars.h" @@ -13,6 +11,8 @@ #include "PhysicsComponent.h" #include "eReplicaComponentType.h" +class dpEntity; + /** * Component that handles rigid bodies that can be interacted with, mostly client-side rendered. An example is the * bananas that fall from trees in GF. @@ -23,7 +23,15 @@ public: RigidbodyPhantomPhysicsComponent(Entity* parent); + void Update(const float deltaTime) override; + void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; + + void SpawnVertices() const; +private: + float m_Scale{}; + + dpEntity* m_dpEntity{}; }; -#endif // __RIGIDBODYPHANTOMPHYSICS_H__ +#endif // RIGIDBODYPHANTOMPHYSICS_H diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 74946179..614d544c 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -369,8 +369,8 @@ void GameMessages::SendPlatformResync(Entity* entity, const SystemAddress& sysAd const auto lot = entity->GetLOT(); - if (lot == 12341 || lot == 5027 || lot == 5028 || lot == 14335 || lot == 14447 || lot == 14449) { - iDesiredWaypointIndex = 0; + if (lot == 12341 || lot == 5027 || lot == 5028 || lot == 14335 || lot == 14447 || lot == 14449 || lot == 11306 || lot == 11308) { + iDesiredWaypointIndex = (lot == 11306 || lot == 11308) ? 1 : 0; iIndex = 0; nextIndex = 0; bStopAtDesiredWaypoint = true; diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp index bc008ab2..00add608 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp @@ -41,6 +41,7 @@ #include "ScriptedActivityComponent.h" #include "SkillComponent.h" #include "TriggerComponent.h" +#include "RigidbodyPhantomPhysicsComponent.h" // Enums #include "eGameMasterLevel.h" @@ -1129,8 +1130,13 @@ namespace DEVGMCommands { void SpawnPhysicsVerts(Entity* entity, const SystemAddress& sysAddr, const std::string args) { //Go tell physics to spawn all the vertices: auto entities = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::PHANTOM_PHYSICS); - for (auto en : entities) { - auto phys = static_cast<PhantomPhysicsComponent*>(en->GetComponent(eReplicaComponentType::PHANTOM_PHYSICS)); + for (const auto* en : entities) { + const auto* phys = static_cast<PhantomPhysicsComponent*>(en->GetComponent(eReplicaComponentType::PHANTOM_PHYSICS)); + if (phys) + phys->SpawnVertices(); + } + for (const auto* en : Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS)) { + const auto* phys = en->GetComponent<RigidbodyPhantomPhysicsComponent>(); if (phys) phys->SpawnVertices(); } diff --git a/dScripts/02_server/Map/FV/Racing/CMakeLists.txt b/dScripts/02_server/Map/FV/Racing/CMakeLists.txt index 89536b67..9b2108c8 100644 --- a/dScripts/02_server/Map/FV/Racing/CMakeLists.txt +++ b/dScripts/02_server/Map/FV/Racing/CMakeLists.txt @@ -1,3 +1,6 @@ set(DSCRIPTS_SOURCES_02_SERVER_MAP_FV_RACING + "RaceFireballs.cpp" "RaceMaelstromGeiser.cpp" + "RaceShipLapColumnsServer.cpp" + "FvRacingColumns.cpp" PARENT_SCOPE) diff --git a/dScripts/02_server/Map/FV/Racing/FvRacingColumns.cpp b/dScripts/02_server/Map/FV/Racing/FvRacingColumns.cpp new file mode 100644 index 00000000..a3e3dd4a --- /dev/null +++ b/dScripts/02_server/Map/FV/Racing/FvRacingColumns.cpp @@ -0,0 +1,15 @@ +#include "FvRacingColumns.h" +#include "MovingPlatformComponent.h" + +void FvRacingColumns::OnStartup(Entity* self) { + auto* movingPlatformComponent = self->GetComponent<MovingPlatformComponent>(); + if (!movingPlatformComponent) return; + + movingPlatformComponent->StopPathing(); + movingPlatformComponent->SetSerialized(true); + int32_t pathStart = 0; + if (self->HasVar(u"attached_path_start")) { + pathStart = self->GetVar<uint32_t>(u"attached_path_start"); + } + movingPlatformComponent->WarpToWaypoint(pathStart); +} diff --git a/dScripts/02_server/Map/FV/Racing/FvRacingColumns.h b/dScripts/02_server/Map/FV/Racing/FvRacingColumns.h new file mode 100644 index 00000000..f4555693 --- /dev/null +++ b/dScripts/02_server/Map/FV/Racing/FvRacingColumns.h @@ -0,0 +1,6 @@ +#include "CppScripts.h" + +class FvRacingColumns : public CppScripts::Script { +public: + void OnStartup(Entity* self) override; +}; diff --git a/dScripts/02_server/Map/FV/Racing/RaceFireballs.cpp b/dScripts/02_server/Map/FV/Racing/RaceFireballs.cpp new file mode 100644 index 00000000..fb0f1272 --- /dev/null +++ b/dScripts/02_server/Map/FV/Racing/RaceFireballs.cpp @@ -0,0 +1,15 @@ +#include "RaceFireballs.h" +#include "SkillComponent.h" + +void RaceFireballs::OnStartup(Entity* self) { + self->AddTimer("fire", GeneralUtils::GenerateRandomNumber<float>(3.0f, 10.0f)); +} + +void RaceFireballs::OnTimerDone(Entity* self, std::string timerName) { + if (timerName == "fire") { + auto* skillComponent = self->GetComponent<SkillComponent>(); + if (skillComponent) skillComponent->CastSkill(894); + self->AddTimer("fire", GeneralUtils::GenerateRandomNumber<float>(3.0f, 10.0f)); + + } +} diff --git a/dScripts/02_server/Map/FV/Racing/RaceFireballs.h b/dScripts/02_server/Map/FV/Racing/RaceFireballs.h new file mode 100644 index 00000000..e96286ae --- /dev/null +++ b/dScripts/02_server/Map/FV/Racing/RaceFireballs.h @@ -0,0 +1,9 @@ +#pragma once +#include "CppScripts.h" + +class RaceFireballs : public CppScripts::Script +{ +public: + void OnStartup(Entity* self) override; + void OnTimerDone(Entity* self, std::string timerName) override; +}; diff --git a/dScripts/02_server/Map/FV/Racing/RaceShipLapColumnsServer.cpp b/dScripts/02_server/Map/FV/Racing/RaceShipLapColumnsServer.cpp new file mode 100644 index 00000000..c0112b6a --- /dev/null +++ b/dScripts/02_server/Map/FV/Racing/RaceShipLapColumnsServer.cpp @@ -0,0 +1,47 @@ +#include "RaceShipLapColumnsServer.h" + +#include "RacingControlComponent.h" +#include "MovingPlatformComponent.h" + +void RaceShipLapColumnsServer::OnStartup(Entity* self) { + self->SetVar(u"Lap2Complete", false); + self->SetVar(u"Lap3Complete", false); +} + +void SetMovingToWaypoint(const int32_t waypointIndex, const std::string group) { + const auto entities = Game::entityManager->GetEntitiesInGroup(group); + if (entities.empty()) return; + + auto* entity = entities[0]; + entity->SetIsGhostingCandidate(false); + + auto* movingPlatfromComponent = entity->GetComponent<MovingPlatformComponent>(); + if (!movingPlatfromComponent) return; + + movingPlatfromComponent->SetSerialized(true); + movingPlatfromComponent->GotoWaypoint(waypointIndex); + Game::entityManager->SerializeEntity(entity); +} + +void RaceShipLapColumnsServer::OnCollisionPhantom(Entity* self, Entity* target) { + if (!target) return; + + const auto racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL); + if (racingControllers.empty()) return; + + auto* racingControlComponent = racingControllers[0]->GetComponent<RacingControlComponent>(); + if (!racingControlComponent) return; + + const auto* player = racingControlComponent->GetPlayerData(target->GetObjectID()); + if (!player) return; + + if (player->lap == 1 && !self->GetVar<bool>(u"Lap2Complete")) { + self->SetVar(u"Lap2Complete", true); + SetMovingToWaypoint(1, "Lap2Column"); + SetMovingToWaypoint(0, "Lap2Ramp"); + } else if (player->lap == 2 && !self->GetVar<bool>(u"Lap3Complete")) { + self->SetVar(u"Lap3Complete", true); + SetMovingToWaypoint(1, "Lap3Column"); + SetMovingToWaypoint(0, "Lap3Ramp"); + } +} diff --git a/dScripts/02_server/Map/FV/Racing/RaceShipLapColumnsServer.h b/dScripts/02_server/Map/FV/Racing/RaceShipLapColumnsServer.h new file mode 100644 index 00000000..b8a26825 --- /dev/null +++ b/dScripts/02_server/Map/FV/Racing/RaceShipLapColumnsServer.h @@ -0,0 +1,8 @@ +#pragma once +#include "CppScripts.h" + +class RaceShipLapColumnsServer : public CppScripts::Script { +public: + void OnStartup(Entity* self) override; + void OnCollisionPhantom(Entity* self, Entity* target) override; +}; diff --git a/dScripts/CppScripts.cpp b/dScripts/CppScripts.cpp index 9018c3f4..12c730fa 100644 --- a/dScripts/CppScripts.cpp +++ b/dScripts/CppScripts.cpp @@ -154,6 +154,11 @@ #include "FvBounceOverWall.h" #include "FvFong.h" #include "FvMaelstromGeyser.h" +#include "FvRaceDragon.h" +#include "FvRacePillarABCServer.h" +#include "FvRacePillarDServer.h" +#include "RaceFireballs.h" +#include "RaceShipLapColumnsServer.h" // FB Scripts #include "AgJetEffectServer.h" @@ -179,6 +184,7 @@ #include "RaceMaelstromGeiser.h" #include "FvRaceSmashEggImagineServer.h" #include "RaceSmashServer.h" +#include "FvRacingColumns.h" // NT Scripts #include "NtSentinelWalkwayServer.h" @@ -622,9 +628,25 @@ CppScripts::Script* const CppScripts::GetScript(Entity* parent, const std::strin script = new FvBounceOverWall(); else if (scriptName == "scripts\\02_server\\Map\\FV\\L_NPC_FONG.lua") script = new FvFong(); - else if (scriptName == "scripts\\ai\\FV\\L_FV_MAELSTROM_GEYSER.lua") { + else if (scriptName == "scripts\\ai\\FV\\L_FV_MAELSTROM_GEYSER.lua") script = new FvMaelstromGeyser(); - } + else if (scriptName == "scripts\\02_server\\Map\\FV\\Racing\\RACE_SHIP_LAP_COLUMNS_SERVER.lua") + script = new RaceShipLapColumnsServer(); + + // yes we know the lap numbers dont match the file name or anim. thats what they desgined it as. + else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\FV_RACE_DRAGON_LAP1_SERVER.lua") + script = new FvRaceDragon("lap_01", 2); + else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\FV_RACE_DRAGON_LAP2_SERVER.lua") + script = new FvRaceDragon("lap_02", 0); + else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\FV_RACE_DRAGON_LAP3_SERVER.lua") + script = new FvRaceDragon("lap_03", 1); + else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\FV_RACE_PILLAR_ABC_SERVER.lua") + script = new FvRacePillarABCServer(); + else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\FV_RACE_PILLAR_D_SERVER.lua") + script = new FvRacePillarDServer(); + else if (scriptName == "scripts\\02_server\\Map\\FV\\Racing\\RACE_FIREBALLS.lua") + script = new RaceFireballs(); + //Misc: if (scriptName == "scripts\\02_server\\Map\\General\\L_EXPLODING_ASSET.lua") @@ -661,6 +683,8 @@ CppScripts::Script* const CppScripts::GetScript(Entity* parent, const std::strin script = new RaceMaelstromGeiser(); else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\FV_RACE_SMASH_EGG_IMAGINE_SERVER.lua") script = new FvRaceSmashEggImagineServer(); + else if (scriptName == "scripts\\02_server\\Map\\FV\\Racing\\FV_RACING_COLUMNS.lua") + script = new FvRacingColumns(); else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\RACE_SMASH_SERVER.lua") script = new RaceSmashServer(); diff --git a/dScripts/ai/RACING/OBJECTS/CMakeLists.txt b/dScripts/ai/RACING/OBJECTS/CMakeLists.txt index 4ef427d5..83f4b8b3 100644 --- a/dScripts/ai/RACING/OBJECTS/CMakeLists.txt +++ b/dScripts/ai/RACING/OBJECTS/CMakeLists.txt @@ -1,6 +1,10 @@ set(DSCRIPTS_SOURCES_AI_RACING_OBJECTS "RaceImagineCrateServer.cpp" "RaceImaginePowerup.cpp" + "FvRaceDragon.cpp" + "FvRacePillarServer.cpp" + "FvRacePillarABCServer.cpp" + "FvRacePillarDServer.cpp" "FvRaceSmashEggImagineServer.cpp" "RaceSmashServer.cpp" PARENT_SCOPE) diff --git a/dScripts/ai/RACING/OBJECTS/FvRaceDragon.cpp b/dScripts/ai/RACING/OBJECTS/FvRaceDragon.cpp new file mode 100644 index 00000000..cde7b809 --- /dev/null +++ b/dScripts/ai/RACING/OBJECTS/FvRaceDragon.cpp @@ -0,0 +1,30 @@ +#include "FvRaceDragon.h" +#include "RenderComponent.h" +#include "RacingControlComponent.h" + +void FvRaceDragon::OnCollisionPhantom(Entity* self, Entity* target) { + if (!target) return; + + const auto racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL); + if (racingControllers.empty()) return; + + auto* racingControlComponent = racingControllers[0]->GetComponent<RacingControlComponent>(); + if (!racingControlComponent) return; + + const auto* player = racingControlComponent->GetPlayerData(target->GetObjectID()); + if (!player) return; + + if (player->lap != m_Lap) return; + + const auto dragons = Game::entityManager->GetEntitiesInGroup("dragon"); + for (const auto& dragon : dragons) { + if (!dragon || dragon->GetLOT() != this->m_Dragon) continue; + + auto* renderComponent = dragon->GetComponent<RenderComponent>(); + if (!renderComponent) continue; + + renderComponent->PlayAnimation(dragon, m_LapAnimName); + + } + Game::entityManager->DestroyEntity(self); +} diff --git a/dScripts/ai/RACING/OBJECTS/FvRaceDragon.h b/dScripts/ai/RACING/OBJECTS/FvRaceDragon.h new file mode 100644 index 00000000..0a0320c3 --- /dev/null +++ b/dScripts/ai/RACING/OBJECTS/FvRaceDragon.h @@ -0,0 +1,15 @@ +#pragma once +#include "CppScripts.h" + +#include <string> +#include <string_view> + +class FvRaceDragon : public CppScripts::Script { +public: + FvRaceDragon(const std::string_view lapAnimName, const int32_t lap) : m_LapAnimName(lapAnimName), m_Lap(lap) {} +private: + void OnCollisionPhantom(Entity* self, Entity* target) override; + const std::string m_LapAnimName; + const int32_t m_Lap; + const LOT m_Dragon = 11898; +}; diff --git a/dScripts/ai/RACING/OBJECTS/FvRacePillarABCServer.cpp b/dScripts/ai/RACING/OBJECTS/FvRacePillarABCServer.cpp new file mode 100644 index 00000000..7023fecc --- /dev/null +++ b/dScripts/ai/RACING/OBJECTS/FvRacePillarABCServer.cpp @@ -0,0 +1,34 @@ +#include "FvRacePillarABCServer.h" +#include "RenderComponent.h" +#include "RacingControlComponent.h" + +void FvRacePillarABCServer::OnCollisionPhantom(Entity* self, Entity* target) { + if (!target) return; + + const auto racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL); + if (racingControllers.empty()) return; + + auto* racingControlComponent = racingControllers[0]->GetComponent<RacingControlComponent>(); + if (!racingControlComponent) return; + + const auto* player = racingControlComponent->GetPlayerData(target->GetObjectID()); + if (!player || player->lap != 1) return; + + PlayAnimation("crumble", "pillars", m_PillarA); + PlayAnimation("roar", "dragon", m_Dragon); + + self->AddTimer("PillarBFall", 2.5f); + self->AddTimer("PillarCFall", 3.7f); + self->AddTimer("DeleteObject", 3.8f); +} + +void FvRacePillarABCServer::OnTimerDone(Entity* self, std::string timerName) { + if (timerName == "PillarBFall") { + PlayAnimation("crumble", "pillars", m_PillarB); + } else if (timerName == "PillarCFall") { + PlayAnimation("crumble", "pillars", m_PillarC); + } else if (timerName == "DeleteObject") { + Game::entityManager->DestroyEntity(self); + } +} + diff --git a/dScripts/ai/RACING/OBJECTS/FvRacePillarABCServer.h b/dScripts/ai/RACING/OBJECTS/FvRacePillarABCServer.h new file mode 100644 index 00000000..9059fbeb --- /dev/null +++ b/dScripts/ai/RACING/OBJECTS/FvRacePillarABCServer.h @@ -0,0 +1,13 @@ +#pragma once +#include "CppScripts.h" +#include "FvRacePillarServer.h" + +class FvRacePillarABCServer : public FvRacePillarServer { + void OnCollisionPhantom(Entity* self, Entity* target) override; + void OnTimerDone(Entity* self, std::string timerName) override; +private: + const LOT m_PillarA = 11946; + const LOT m_PillarB = 11947; + const LOT m_PillarC = 11948; + const LOT m_Dragon = 11898; +}; diff --git a/dScripts/ai/RACING/OBJECTS/FvRacePillarDServer.cpp b/dScripts/ai/RACING/OBJECTS/FvRacePillarDServer.cpp new file mode 100644 index 00000000..b119352e --- /dev/null +++ b/dScripts/ai/RACING/OBJECTS/FvRacePillarDServer.cpp @@ -0,0 +1,21 @@ +#include "FvRacePillarDServer.h" +#include "RenderComponent.h" +#include "RacingControlComponent.h" + +void FvRacePillarDServer::OnCollisionPhantom(Entity* self, Entity* target) { + if (!target) return; + + const auto racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL); + if (racingControllers.empty()) return; + + auto* racingControlComponent = racingControllers[0]->GetComponent<RacingControlComponent>(); + if (!racingControlComponent) return; + + const auto* player = racingControlComponent->GetPlayerData(target->GetObjectID()); + if (!player) return; + + if (player->lap == 2) { + PlayAnimation("crumble", "pillars", m_PillarD); + PlayAnimation("roar", "dragon", m_Dragon); + } +} diff --git a/dScripts/ai/RACING/OBJECTS/FvRacePillarDServer.h b/dScripts/ai/RACING/OBJECTS/FvRacePillarDServer.h new file mode 100644 index 00000000..e8d21567 --- /dev/null +++ b/dScripts/ai/RACING/OBJECTS/FvRacePillarDServer.h @@ -0,0 +1,10 @@ +#pragma once +#include "CppScripts.h" +#include "FvRacePillarServer.h" + +class FvRacePillarDServer : public FvRacePillarServer { + void OnCollisionPhantom(Entity* self, Entity* target) override; +private: + const LOT m_PillarD = 11949; + const LOT m_Dragon = 11898; +}; diff --git a/dScripts/ai/RACING/OBJECTS/FvRacePillarServer.cpp b/dScripts/ai/RACING/OBJECTS/FvRacePillarServer.cpp new file mode 100644 index 00000000..c89cf2a2 --- /dev/null +++ b/dScripts/ai/RACING/OBJECTS/FvRacePillarServer.cpp @@ -0,0 +1,15 @@ +#include "FvRacePillarServer.h" + +#include "Game.h" +#include "EntityManager.h" +#include "RenderComponent.h" + +void FvRacePillarServer::PlayAnimation(const std::string animName, const std::string group, const LOT lot) { + const auto entities = Game::entityManager->GetEntitiesInGroup(group); + for (const auto& entity : entities) { + if (!entity || entity->GetLOT() != lot) continue; + auto* renderComponent = entity->GetComponent<RenderComponent>(); + if (!renderComponent) continue; + renderComponent->PlayAnimation(entity, animName); + } +} diff --git a/dScripts/ai/RACING/OBJECTS/FvRacePillarServer.h b/dScripts/ai/RACING/OBJECTS/FvRacePillarServer.h new file mode 100644 index 00000000..9249177a --- /dev/null +++ b/dScripts/ai/RACING/OBJECTS/FvRacePillarServer.h @@ -0,0 +1,12 @@ +#ifndef FVRACEPILLARSERVER__H +#define FVRACEPILLARSERVER__H + +#include "CppScripts.h" + +class FvRacePillarServer : public virtual CppScripts::Script { +protected: + // Plays an animation on all entities in a group with a specific LOT + void PlayAnimation(const std::string animName, const std::string group, const LOT lot); +}; + +#endif // FVRACEPILLARSERVER__H diff --git a/versions.txt b/versions.txt index 1dc48d9c..0b77997f 100644 --- a/versions.txt +++ b/versions.txt @@ -1,3 +1,5 @@ +1.2 - Dragonmaw functional +1.1 - Whole lot of fixed bugs and implemented features 1.0 - Final cleanup and bug fixing for public release 0.9 - Includes BBB without the need for a UGC server, cannon cove minigame, and bug fixes. 0.8 - Added Ninjago! and it's various features + frakjaw minigame. AG survival now works. @@ -7,4 +9,4 @@ 0.4 - Added Havok to replace Bullet, Instancing, Quickbuilds, rockets, and a ton more fixes and additions. 0.3 - FrostBurgh, Snowdrift and Snowman's Land testing version. Includes bodged systems. 0.2 - Transfer to VS2019 & Bullet -0.1 - Initial transfer from NixLU, up until BehaviorManager inclusion \ No newline at end of file +0.1 - Initial transfer from NixLU, up until BehaviorManager inclusion From 9e36510c6b9d449a04b0e3a67ea894baa50a51ba Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell <aronwk.aaron@gmail.com> Date: Fri, 10 May 2024 15:21:10 -0500 Subject: [PATCH 26/78] chore: Bump verion to 2.3.0 (#1564) fix versions.txt, update cmake version --- CMakeVariables.txt | 4 ++-- dNet/AuthPackets.cpp | 2 +- versions.txt | 6 ++++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CMakeVariables.txt b/CMakeVariables.txt index 31482ce4..4ded5f59 100644 --- a/CMakeVariables.txt +++ b/CMakeVariables.txt @@ -1,5 +1,5 @@ -PROJECT_VERSION_MAJOR=1 -PROJECT_VERSION_MINOR=2 +PROJECT_VERSION_MAJOR=2 +PROJECT_VERSION_MINOR=3 PROJECT_VERSION_PATCH=0 # Debugging diff --git a/dNet/AuthPackets.cpp b/dNet/AuthPackets.cpp index a2bf731c..715188e8 100644 --- a/dNet/AuthPackets.cpp +++ b/dNet/AuthPackets.cpp @@ -82,7 +82,7 @@ void AuthPackets::SendHandshake(dServer* server, const SystemAddress& sysAddr, c if (serverType == ServerType::Auth) bitStream.Write(ServiceId::Auth); else if (serverType == ServerType::World) bitStream.Write(ServiceId::World); else bitStream.Write(ServiceId::General); - bitStream.Write<uint64_t>(215523405360); + bitStream.Write<uint64_t>(215523470896); server->Send(bitStream, sysAddr, false); } diff --git a/versions.txt b/versions.txt index 0b77997f..fa7ea86c 100644 --- a/versions.txt +++ b/versions.txt @@ -1,5 +1,7 @@ -1.2 - Dragonmaw functional -1.1 - Whole lot of fixed bugs and implemented features +2.3 - Dragonmaw functional, new slash command system, vanity system overhaul +2.2 - Code cleanup and QoL fixes +2.1 - Bug and crash fixes +2.0 - Whole lot of fixed bugs and implemented features 1.0 - Final cleanup and bug fixing for public release 0.9 - Includes BBB without the need for a UGC server, cannon cove minigame, and bug fixes. 0.8 - Added Ninjago! and it's various features + frakjaw minigame. AG survival now works. From 4d1395e522b1ad47b4d6695345947980119933f9 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Fri, 10 May 2024 14:20:42 -0700 Subject: [PATCH 27/78] Update CheatDetection.cpp (#1559) --- dGame/dUtilities/CheatDetection.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/dGame/dUtilities/CheatDetection.cpp b/dGame/dUtilities/CheatDetection.cpp index a87157a1..504b3d53 100644 --- a/dGame/dUtilities/CheatDetection.cpp +++ b/dGame/dUtilities/CheatDetection.cpp @@ -130,12 +130,6 @@ bool CheatDetection::VerifyLwoobjidIsSender(const LWOOBJID& id, const SystemAddr // This will be true if the player does not possess the entity they are trying to send a packet as. // or if the user does not own the character they are trying to send a packet as. - if (invalidPacket) { - va_list args; - va_start(args, messageIfNotSender); - LogAndSaveFailedAntiCheatCheck(id, sysAddr, checkType, messageIfNotSender, args); - va_end(args); - } return !invalidPacket; } From 9f382aca424972ca8ec07a5d5ac7217b5f5880b1 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 12 May 2024 05:30:03 -0700 Subject: [PATCH 28/78] fix: use after free in mission progression after removing item from inventory (#1567) that method is cursed. no longer has ub when deleting an item from the inventory --- dGame/dGameMessages/GameMessages.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 614d544c..93b10cec 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -5354,14 +5354,14 @@ void GameMessages::HandleRemoveItemFromInventory(RakNet::BitStream& inStream, En if (eInvType == eInventoryType::MODELS) { item->DisassembleModel(iStackCount); } - + auto lot = item->GetLot(); item->SetCount(item->GetCount() - iStackCount, true); Game::entityManager->SerializeEntity(entity); auto* missionComponent = entity->GetComponent<MissionComponent>(); if (missionComponent != nullptr) { - missionComponent->Progress(eMissionTaskType::GATHER, item->GetLot(), LWOOBJID_EMPTY, "", -iStackCount); + missionComponent->Progress(eMissionTaskType::GATHER, lot, LWOOBJID_EMPTY, "", -iStackCount); } } } From e3b108e00e1e49941a941b163cb776e5c6481df5 Mon Sep 17 00:00:00 2001 From: Terrev <21133460+Terrev@users.noreply.github.com> Date: Mon, 13 May 2024 07:18:27 -0400 Subject: [PATCH 29/78] fv race place atm (#1570) --- vanity/atm.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vanity/atm.xml b/vanity/atm.xml index 863498ed..c03a59ce 100644 --- a/vanity/atm.xml +++ b/vanity/atm.xml @@ -27,6 +27,8 @@ <location zone="1300" x="51.848" y="329.0" z="561.114" rw="-0.277656" rx="0.00" ry="0.960681" rz="0.00" /> <!--GF Pirate Camp--> <location zone="1300" x="363.259" y="259.367" z="-210.834" rw="0.961918" rx="0.00" ry="-0.273340" rz="0.00" /> + <!--FV Race Place--> + <location zone="1400" x="746.784" y="237.957" z="495.015" rw="-0.036648" rx="0.00" ry="0.999328" rz="0.00" /> <!--FV Great Tree--> <location zone="1400" x="-194.288" y="381.275" z="-93.292" rw="0.935135" rx="0.00" ry="0.354292" rz="0.00" /> <!--FV Paradox Refinery--> From 09a8c99f3e2cec81249113a8a91710a8020e39de Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 16 May 2024 02:29:48 -0700 Subject: [PATCH 30/78] fix: mail crash from underflow and document variables (#1582) * fix mail crash and document variables * const --- dGame/dUtilities/Mail.cpp | 84 ++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 50 deletions(-) diff --git a/dGame/dUtilities/Mail.cpp b/dGame/dUtilities/Mail.cpp index a610c3a7..677728a6 100644 --- a/dGame/dUtilities/Mail.cpp +++ b/dGame/dUtilities/Mail.cpp @@ -94,35 +94,6 @@ void Mail::SendMail(const LWOOBJID sender, const std::string& senderName, LWOOBJ SendNotification(sysAddr, 1); //Show the "one new mail" message } -//Because we need it: -std::string ReadWStringAsString(RakNet::BitStream& bitStream, uint32_t size) { - std::string toReturn = ""; - uint8_t buffer; - bool isFinishedReading = false; - - for (uint32_t i = 0; i < size; ++i) { - bitStream.Read(buffer); - if (!isFinishedReading) toReturn.push_back(buffer); - if (buffer == '\0') isFinishedReading = true; //so we don't continue to read garbage as part of the string. - bitStream.Read(buffer); //Read the null term - } - - return toReturn; -} - -void WriteStringAsWString(RakNet::BitStream& bitStream, std::string str, uint32_t size) { - uint32_t sizeToFill = size - str.size(); - - for (uint32_t i = 0; i < str.size(); ++i) { - bitStream.Write(str[i]); - bitStream.Write(uint8_t(0)); - } - - for (uint32_t i = 0; i < sizeToFill; ++i) { - bitStream.Write(uint16_t(0)); - } -} - void Mail::HandleMailStuff(RakNet::BitStream& packet, const SystemAddress& sysAddr, Entity* entity) { int mailStuffID = 0; packet.Read(mailStuffID); @@ -176,11 +147,20 @@ void Mail::HandleSendMail(RakNet::BitStream& packet, const SystemAddress& sysAdd return; } - std::string subject = ReadWStringAsString(packet, 50); - std::string body = ReadWStringAsString(packet, 400); - std::string recipient = ReadWStringAsString(packet, 32); + LUWString subjectRead(50); + packet.Read(subjectRead); + + LUWString bodyRead(400); + packet.Read(bodyRead); + + LUWString recipientRead(32); + packet.Read(recipientRead); + + const std::string subject = subjectRead.GetAsString(); + const std::string body = bodyRead.GetAsString(); + //Cleanse recipient: - recipient = std::regex_replace(recipient, std::regex("[^0-9a-zA-Z]+"), ""); + const std::string recipient = std::regex_replace(recipientRead.GetAsString(), std::regex("[^0-9a-zA-Z]+"), ""); uint64_t unknown64 = 0; LWOOBJID attachmentID; @@ -267,40 +247,44 @@ void Mail::HandleDataRequest(RakNet::BitStream& packet, const SystemAddress& sys RakNet::BitStream bitStream; BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::MAIL); bitStream.Write(int(MailMessageID::MailData)); - bitStream.Write(int(0)); + bitStream.Write(int(0)); // throttled - bitStream.Write<uint16_t>(playerMail.size()); + bitStream.Write<uint16_t>(playerMail.size()); // size bitStream.Write<uint16_t>(0); for (const auto& mail : playerMail) { bitStream.Write(mail.id); //MailID - WriteStringAsWString(bitStream, mail.subject.c_str(), 50); //subject - WriteStringAsWString(bitStream, mail.body.c_str(), 400); //body - WriteStringAsWString(bitStream, mail.senderUsername.c_str(), 32); //sender + const LUWString subject(mail.subject, 50); + bitStream.Write(subject); //subject + const LUWString body(mail.body, 400); + bitStream.Write(body); //body + const LUWString sender(mail.senderUsername, 32); + bitStream.Write(sender); //sender + bitStream.Write(uint32_t(0)); // packing - bitStream.Write(uint32_t(0)); - bitStream.Write(uint64_t(0)); + bitStream.Write(uint64_t(0)); // attachedCurrency bitStream.Write(mail.itemID); //Attachment ID LOT lot = mail.itemLOT; if (lot <= 0) bitStream.Write(LOT(-1)); else bitStream.Write(lot); - bitStream.Write(uint32_t(0)); + bitStream.Write(uint32_t(0)); // packing - bitStream.Write(mail.itemSubkey); //Attachment subKey - bitStream.Write<uint16_t>(mail.itemCount); //Attachment count + bitStream.Write(mail.itemSubkey); // Attachment subKey - bitStream.Write(uint32_t(0)); - bitStream.Write(uint16_t(0)); + bitStream.Write<uint16_t>(mail.itemCount); // Attachment count + bitStream.Write(uint8_t(0)); // subject type (used for auction) + bitStream.Write(uint8_t(0)); // packing + bitStream.Write(uint32_t(0)); // packing - bitStream.Write<uint64_t>(mail.timeSent); //time sent (twice?) - bitStream.Write<uint64_t>(mail.timeSent); + bitStream.Write<uint64_t>(mail.timeSent); // expiration date + bitStream.Write<uint64_t>(mail.timeSent);// send date bitStream.Write<uint8_t>(mail.wasRead); //was read - bitStream.Write(uint8_t(0)); - bitStream.Write(uint16_t(0)); - bitStream.Write(uint32_t(0)); + bitStream.Write(uint8_t(0)); // isLocalized + bitStream.Write(uint16_t(0)); // packing + bitStream.Write(uint32_t(0)); // packing } Game::server->Send(bitStream, sysAddr, false); From 8837b110abd59f35b233884ede8dbfc90416638e Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 16 May 2024 02:30:00 -0700 Subject: [PATCH 31/78] add include guards (#1569) --- dGame/dUtilities/SlashCommands/DEVGMCommands.h | 5 +++++ dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h | 5 +++++ dGame/dUtilities/SlashCommands/GMZeroCommands.h | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.h b/dGame/dUtilities/SlashCommands/DEVGMCommands.h index 5e78ed80..e03fd4de 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.h +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.h @@ -1,3 +1,6 @@ +#ifndef DEVGMCOMMANDS_H +#define DEVGMCOMMANDS_H + namespace DEVGMCommands { void SetGMLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args); void ToggleNameplate(Entity* entity, const SystemAddress& sysAddr, const std::string args); @@ -71,3 +74,5 @@ namespace DEVGMCommands { void CastSkill(Entity* entity, const SystemAddress& sysAddr, const std::string args); void DeleteInven(Entity* entity, const SystemAddress& sysAddr, const std::string args); } + +#endif //!DEVGMCOMMANDS_H diff --git a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h index e8d60383..7cb3d8d7 100644 --- a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h +++ b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h @@ -1,3 +1,6 @@ +#ifndef GMGREATERTHANZEROCOMMANDS_H +#define GMGREATERTHANZEROCOMMANDS_H + namespace GMGreaterThanZeroCommands { void Kick(Entity* entity, const SystemAddress& sysAddr, const std::string args); void MailItem(Entity* entity, const SystemAddress& sysAddr, const std::string args); @@ -13,3 +16,5 @@ namespace GMGreaterThanZeroCommands { void ShowAll(Entity* entity, const SystemAddress& sysAddr, const std::string args); void FindPlayer(Entity* entity, const SystemAddress& sysAddr, const std::string args); } + +#endif //!GMGREATERTHANZEROCOMMANDS_H diff --git a/dGame/dUtilities/SlashCommands/GMZeroCommands.h b/dGame/dUtilities/SlashCommands/GMZeroCommands.h index 29aa8f36..3b2389b5 100644 --- a/dGame/dUtilities/SlashCommands/GMZeroCommands.h +++ b/dGame/dUtilities/SlashCommands/GMZeroCommands.h @@ -1,3 +1,6 @@ +#ifndef GMZEROCOMMANDS_H +#define GMZEROCOMMANDS_H + namespace GMZeroCommands { void Help(Entity* entity, const SystemAddress& sysAddr, const std::string args); void Credits(Entity* entity, const SystemAddress& sysAddr, const std::string args); @@ -13,3 +16,5 @@ namespace GMZeroCommands { void Resurrect(Entity* entity, const SystemAddress& sysAddr, const std::string args); void InstanceInfo(Entity* entity, const SystemAddress& sysAddr, const std::string args); } + +#endif //!GMZEROCOMMANDS_H From 35321b22d920dea30f640c9f18299a4cd78c731d Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 16 May 2024 02:30:32 -0700 Subject: [PATCH 32/78] script fixes (#1577) fixes an issue where the sirens would not be destroyed correctly fixes undefined behavior in buff station ok for real this time actual fix for mermaids and for general death_behavior 0 skill stuff --- dGame/Entity.cpp | 2 +- dGame/dBehaviors/BehaviorContext.cpp | 26 +++++++++++++------ .../Objects/AgSurvivalBuffStation.cpp | 4 --- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 59b583d3..2210403c 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -1534,7 +1534,7 @@ void Entity::Kill(Entity* murderer, const eKillType killType) { bool waitForDeathAnimation = false; if (destroyableComponent) { - waitForDeathAnimation = destroyableComponent->GetDeathBehavior() == 0 && killType != eKillType::SILENT; + waitForDeathAnimation = !destroyableComponent->GetIsSmashable() && destroyableComponent->GetDeathBehavior() == 0 && killType != eKillType::SILENT; } // Live waited a hard coded 12 seconds for death animations of type 0 before networking destruction! diff --git a/dGame/dBehaviors/BehaviorContext.cpp b/dGame/dBehaviors/BehaviorContext.cpp index 5ca335b1..a3721d8f 100644 --- a/dGame/dBehaviors/BehaviorContext.cpp +++ b/dGame/dBehaviors/BehaviorContext.cpp @@ -224,6 +224,16 @@ bool BehaviorContext::CalculateUpdate(const float deltaTime) { for (auto i = 0u; i < this->syncEntries.size(); ++i) { auto entry = this->syncEntries.at(i); + if (entry.behavior->m_templateId == BehaviorTemplate::ATTACK_DELAY) { + auto* self = Game::entityManager->GetEntity(originator); + if (self) { + auto* destroyableComponent = self->GetComponent<DestroyableComponent>(); + if (destroyableComponent && destroyableComponent->GetHealth() <= 0) { + continue; + } + } + } + if (entry.time > 0) { entry.time -= deltaTime; @@ -333,7 +343,7 @@ void BehaviorContext::FilterTargets(std::vector<Entity*>& targets, std::forward_ } // handle targeting the caster - if (candidate == caster){ + if (candidate == caster) { // if we aren't targeting self, erase, otherise increment and continue if (!targetSelf) index = targets.erase(index); else index++; @@ -356,24 +366,24 @@ void BehaviorContext::FilterTargets(std::vector<Entity*>& targets, std::forward_ } // if they are dead, then earse and continue - if (candidateDestroyableComponent->GetIsDead()){ + if (candidateDestroyableComponent->GetIsDead()) { index = targets.erase(index); continue; } // if their faction is explicitly included, increment and continue auto candidateFactions = candidateDestroyableComponent->GetFactionIDs(); - if (CheckFactionList(includeFactionList, candidateFactions)){ + if (CheckFactionList(includeFactionList, candidateFactions)) { index++; continue; } // check if they are a team member - if (targetTeam){ + if (targetTeam) { auto* team = TeamManager::Instance()->GetTeam(this->caster); - if (team){ + if (team) { // if we find a team member keep it and continue to skip enemy checks - if(std::find(team->members.begin(), team->members.end(), candidate->GetObjectID()) != team->members.end()){ + if (std::find(team->members.begin(), team->members.end(), candidate->GetObjectID()) != team->members.end()) { index++; continue; } @@ -419,8 +429,8 @@ bool BehaviorContext::CheckTargetingRequirements(const Entity* target) const { // returns true if any of the object factions are in the faction list bool BehaviorContext::CheckFactionList(std::forward_list<int32_t>& factionList, std::vector<int32_t>& objectsFactions) const { if (factionList.empty() || objectsFactions.empty()) return false; - for (auto faction : factionList){ - if(std::find(objectsFactions.begin(), objectsFactions.end(), faction) != objectsFactions.end()) return true; + for (auto faction : factionList) { + if (std::find(objectsFactions.begin(), objectsFactions.end(), faction) != objectsFactions.end()) return true; } return false; } diff --git a/dScripts/02_server/Objects/AgSurvivalBuffStation.cpp b/dScripts/02_server/Objects/AgSurvivalBuffStation.cpp index 04d9711c..1a7cac11 100644 --- a/dScripts/02_server/Objects/AgSurvivalBuffStation.cpp +++ b/dScripts/02_server/Objects/AgSurvivalBuffStation.cpp @@ -56,10 +56,6 @@ void AgSurvivalBuffStation::OnTimerDone(Entity* self, std::string timerName) { auto member = Game::entityManager->GetEntity(memberID); if (member != nullptr && !member->GetIsDead()) { GameMessages::SendDropClientLoot(member, self->GetObjectID(), powerupToDrop, 0, self->GetPosition()); - } else { - // If player left the team or left early erase them from the team variable. - team.erase(std::find(team.begin(), team.end(), memberID)); - self->SetVar<std::vector<LWOOBJID>>(u"BuilderTeam", team); } } } From d0a56782901710ca0d528f5c3ce45fe6ffc36ae8 Mon Sep 17 00:00:00 2001 From: TAHuntling <38479763+TAHuntling@users.noreply.github.com> Date: Thu, 16 May 2024 04:50:18 -0500 Subject: [PATCH 33/78] chore: CppScripts refactor (#1579) * Updating CppScripts Rewrote file to use a lambda map rather than the massive if else chain. Kept the original comments alongside each of the different scripts they were by before. * add script tests * Update names * More Changes to Scripts * Update CppScripts.cpp * Removing Unneeded Files * Update CppScripts.cpp * Delete tests/dGameTests/dScriptsTests/CMakeLists.txt * Delete tests/dGameTests/dScriptsTests/dScriptsTests.cpp * Delete tests/dGameTests/dScriptsTests/CppScriptsOld.cpp * Delete tests/dGameTests/dScriptsTests/CppScriptsOld.h * Update CMakeLists.txt * finishing up --------- Co-authored-by: David Markowitz <EmosewaMC@gmail.com> --- dScripts/CppScripts.cpp | 1009 ++++++++++++++------------------------- 1 file changed, 364 insertions(+), 645 deletions(-) diff --git a/dScripts/CppScripts.cpp b/dScripts/CppScripts.cpp index 12c730fa..784edbdb 100644 --- a/dScripts/CppScripts.cpp +++ b/dScripts/CppScripts.cpp @@ -327,668 +327,387 @@ #include "LupGenericInteract.h" #include "WblRobotCitizen.h" +#include <map> +#include <string> +#include <functional> + namespace { - // This is in the translation unit instead of the header to prevent wierd linker errors - InvalidScript* const InvalidToReturn = new InvalidScript(); - std::map<std::string, CppScripts::Script*> m_Scripts; + // This is in the translation unit instead of the header to prevent weird linker errors + InvalidScript InvalidToReturn; + std::map<std::string, CppScripts::Script*> g_Scripts; + std::map<std::string, std::function<CppScripts::Script* ()>> scriptLoader = { + + //VE / AG + { "scripts\\ai\\AG\\L_AG_SHIP_PLAYER_DEATH_TRIGGER.lua", []() { return new AgShipPlayerDeathTrigger(); } }, + {"scripts\\ai\\NP\\L_NPC_NP_SPACEMAN_BOB.lua", []() { return new NpcNpSpacemanBob(); } }, + {"scripts\\ai\\AG\\L_AG_SPACE_STUFF.lua", []() { return new AgSpaceStuff();} }, + {"scripts\\ai\\AG\\L_AG_SHIP_PLAYER_SHOCK_SERVER.lua", []() { return new AgShipPlayerShockServer();} }, + {"scripts\\ai\\AG\\L_AG_IMAG_SMASHABLE.lua", []() { return new AgImagSmashable();} }, + {"scripts\\02_server\\Map\\General\\L_STORY_BOX_INTERACT_SERVER.lua", []() { return new StoryBoxInteractServer();} }, + {"scripts\\02_server\\Map\\General\\L_BINOCULARS.lua", []() { return new Binoculars();} }, + {"scripts\\ai\\WILD\\L_ALL_CRATE_CHICKEN.lua", []() { return new AllCrateChicken();} }, + // Broken? (below) + {"scripts\\ai\\NS\\WH\\L_ROCKHYDRANT_SMASHABLE.lua", []() { return new RockHydrantSmashable();} }, + {"scripts\\02_server\\Map\\SS\\L_SS_MODULAR_BUILD_SERVER.lua", []() { return new SsModularBuildServer();} }, + {"scripts\\02_server\\Map\\Property\\AG_Small\\L_ZONE_AG_PROPERTY.lua", []() { return new ZoneAgProperty();} }, + // this is done in Entity.cpp, not needed for our implementation (below) + {"scripts\\02_server\\Map\\General\\L_POI_MISSION.lua", []() { return new InvalidScript();} }, + {"scripts\\02_server\\Map\\General\\L_TOUCH_MISSION_UPDATE_SERVER.lua", []() { return new TouchMissionUpdateServer();} }, + {"scripts\\ai\\AG\\L_ACT_SHARK_PLAYER_DEATH_TRIGGER.lua", []() { return new ActSharkPlayerDeathTrigger();} }, + {"scripts\\02_server\\Enemy\\General\\L_BASE_ENEMY_MECH.lua", []() { return new BaseEnemyMech();} }, + {"scripts\\zone\\AG\\L_ZONE_AG_SURVIVAL.lua", []() { return new ZoneAgSurvival();} }, + {"scripts\\02_server\\Objects\\L_BUFF_STATION_SERVER.lua", []() { return new AgSurvivalBuffStation();} }, + {"scripts\\ai\\AG\\L_AG_BUS_DOOR.lua", []() { return new AgBusDoor();} }, + {"scripts\\02_server\\Equipment\\L_MAESTROM_EXTRACTICATOR_SERVER.lua", []() { return new MaestromExtracticatorServer();} }, + {"scripts\\02_server\\Map\\AG\\L_AG_CAGED_BRICKS_SERVER.lua", []() { return new AgCagedBricksServer();} }, + {"scripts\\02_server\\Map\\AG\\L_NPC_WISP_SERVER.lua", []() { return new NpcWispServer();} }, + {"scripts\\02_server\\Map\\AG\\L_NPC_EPSILON_SERVER.lua", []() { return new NpcEpsilonServer();} }, + {"scripts\\ai\\AG\\L_AG_TURRET.lua", []() {return new AgTurret();}}, + {"scripts\\ai\\AG\\L_AG_TURRET_FOR_SHIP.lua", []() { return new AgTurret();}}, + {"scripts\\02_server\\Map\\AG\\L_AG_LASER_SENSOR_SERVER.lua", []() {return new AgLaserSensorServer();}}, + {"scripts\\02_server\\Map\\AG\\L_AG_MONUMENT_LASER_SERVER.lua", []() {return new AgMonumentLaserServer();}}, + {"scripts\\ai\\AG\\L_AG_FANS.lua", []() {return new AgFans();}}, + {"scripts\\02_server\\Map\\AG\\L_AG_MONUMENT_BIRDS.lua", []() {return new AgMonumentBirds();}}, + {"scripts\\02_server\\Map\\AG\\L_REMOVE_RENTAL_GEAR.lua", []() {return new RemoveRentalGear();}}, + {"scripts\\02_server\\Map\\AG\\L_NPC_NJ_ASSISTANT_SERVER.lua", []() {return new NpcNjAssistantServer();}}, + {"scripts\\ai\\AG\\L_AG_SALUTING_NPCS.lua", []() {return new AgSalutingNpcs();}}, + {"scripts\\ai\\AG\\L_AG_JET_EFFECT_SERVER.lua", []() {return new AgJetEffectServer();}}, + {"scripts\\02_server\\Enemy\\AG\\L_BOSS_SPIDER_QUEEN_ENEMY_SERVER.lua", []() {return new BossSpiderQueenEnemyServer();}}, + {"scripts\\02_server\\Map\\Property\\AG_Small\\L_ENEMY_SPIDER_SPAWNER.lua", []() {return new EnemySpiderSpawner();}}, + {"scripts/02_server/Map/Property/AG_Small/L_ENEMY_SPIDER_SPAWNER.lua", []() {return new EnemySpiderSpawner();}}, + {"scripts\\ai\\AG\\L_AG_QB_Elevator.lua", []() {return new AgQbElevator();}}, + {"scripts\\ai\\PROPERTY\\AG\\L_AG_PROP_GUARD.lua", []() {return new AgPropGuard();}}, + {"scripts\\02_server\\Map\\AG\\L_AG_BUGSPRAYER.lua", []() {return new AgBugsprayer();}}, + {"scripts\\02_server\\Map\\AG\\L_NPC_AG_COURSE_STARTER.lua", []() {return new NpcAgCourseStarter();}}, + {"scripts\\02_server\\Map\\AG\\L__AG_MONUMENT_RACE_GOAL.lua", []() {return new AgMonumentRaceGoal();}}, + {"scripts\\02_server\\Map\\AG\\L__AG_MONUMENT_RACE_CANCEL.lua", []() {return new AgMonumentRaceCancel();}}, + {"scripts\\02_server\\Map\\AG_Spider_Queen\\L_ZONE_AG_SPIDER_QUEEN.lua", []() {return new ZoneAgSpiderQueen();}}, + {"scripts\\02_server\\Map\\AG_Spider_Queen\\L_SPIDER_BOSS_TREASURE_CHEST_SERVER.lua", []() {return new SpiderBossTreasureChestServer();}}, + {"scripts\\02_server\\Map\\AG\\L_NPC_COWBOY_SERVER.lua", []() {return new NpcCowboyServer();}}, + {"scripts\\02_server\\Map\\Property\\AG_Med\\L_ZONE_AG_MED_PROPERTY.lua", []() {return new ZoneAgMedProperty();}}, + {"scripts\\ai\\AG\\L_AG_STROMBIE_PROPERTY.lua", []() {return new AgStromlingProperty();}}, + {"scripts\\ai\\AG\\L_AG_DARKLING_MECH.lua", []() {return new BaseEnemyMech();}}, + {"scripts\\ai\\AG\\L_AG_DARK_SPIDERLING.lua", []() {return new AgDarkSpiderling();}}, + {"scripts\\ai\\PROPERTY\\L_PROP_GUARDS.lua", []() {return new AgPropguards();}}, + {"scripts\\ai\\PROPERTY\\L_PROPERTY_FX_DAMAGE.lua", []() {return new PropertyFXDamage();}}, + {"scripts\\02_server\\Map\\AG\\L_NPC_PIRATE_SERVER.lua", []() {return new NpcPirateServer();}}, + {"scripts\\ai\\AG\\L_AG_PICNIC_BLANKET.lua", []() {return new AgPicnicBlanket();}}, + {"scripts\\02_server\\Map\\Property\\L_PROPERTY_BANK_INTERACT_SERVER.lua", []() {return new PropertyBankInteract();}}, + {"scripts\\02_server\\Enemy\\VE\\L_VE_MECH.lua", []() {return new VeMech();}}, + {"scripts\\02_server\\Map\\VE\\L_MISSION_CONSOLE_SERVER.lua", []() {return new VeMissionConsole();}}, + {"scripts\\02_server\\Map\\VE\\L_EPSILON_SERVER.lua", []() {return new VeEpsilonServer();}}, + + //NS + {"scripts\\ai\\NS\\L_NS_MODULAR_BUILD.lua", []() {return new NsModularBuild();}}, + {"scripts\\ai\\NS\\L_NS_GET_FACTION_MISSION_SERVER.lua", []() {return new NsGetFactionMissionServer();}}, + {"scripts\\ai\\NS\\L_NS_QB_IMAGINATION_STATUE.lua", []() {return new NsQbImaginationStatue();}}, + {"scripts\\02_server\\Map\\NS\\CONCERT_CHOICEBUILD_MANAGER_SERVER.lua", []() {return new NsConcertChoiceBuildManager();}}, + {"scripts\\ai\\NS\\L_NS_CONCERT_CHOICEBUILD.lua", []() {return new NsConcertChoiceBuild();}}, + {"scripts\\ai\\NS\\L_NS_CONCERT_QUICKBUILD.lua", []() {return new NsConcertQuickBuild();}}, + {"scripts\\ai\\AG\\L_AG_STAGE_PLATFORMS.lua", []() {return new AgStagePlatforms();}}, + {"scripts\\ai\\NS\\L_NS_CONCERT_INSTRUMENT_QB.lua", []() {return new NsConcertInstrument();}}, + {"scripts\\ai\\NS\\L_NS_JONNY_FLAG_MISSION_SERVER.lua", []() {return new NsJohnnyMissionServer();}}, + {"scripts\\02_server\\Objects\\L_STINKY_FISH_TARGET.lua", []() {return new StinkyFishTarget();}}, + {"scripts\\zone\\PROPERTY\\NS\\L_ZONE_NS_PROPERTY.lua", []() {return new ZoneNsProperty();}}, + {"scripts\\02_server\\Map\\Property\\NS_Med\\L_ZONE_NS_MED_PROPERTY.lua", []() {return new ZoneNsMedProperty();}}, + {"scripts\\02_server\\Map\\NS\\L_NS_TOKEN_CONSOLE_SERVER.lua", []() {return new NsTokenConsoleServer();}}, + {"scripts\\02_server\\Map\\NS\\L_NS_LUP_TELEPORT.lua", []() {return new NsLupTeleport();}}, + {"scripts\\02_server\\Map\\NS\\Waves\\L_ZONE_NS_WAVES.lua", []() {return new ZoneNsWaves();}}, + {"scripts\\02_server\\Enemy\\Waves\\L_WAVES_BOSS_HAMMERLING_ENEMY_SERVER.lua", []() {return new WaveBossHammerling();}}, + {"scripts\\02_server\\Enemy\\Waves\\L_WAVES_BOSS_APE_ENEMY_SERVER.lua", []() {return new WaveBossApe();}}, + {"scripts\\02_server\\Enemy\\Waves\\L_WAVES_BOSS_DARK_SPIDERLING_ENEMY_SERVER.lua", []() {return new WaveBossSpiderling();}}, + {"scripts\\02_server\\Enemy\\Waves\\L_WAVES_BOSS_HORESEMEN_ENEMY_SERVER.lua", []() {return new WaveBossHorsemen();}}, + {"scripts\\02_server\\Minigame\\General\\L_MINIGAME_TREASURE_CHEST_SERVER.lua", []() {return new MinigameTreasureChestServer();}}, + {"scripts\\02_server\\Map\\NS\\L_NS_LEGO_CLUB_DOOR.lua", []() {return new NsLegoClubDoor();}}, + {"scripts/ai/NS/L_CL_RING.lua", []() {return new ClRing();}}, + {"scripts\\ai\\WILD\\L_WILD_AMBIENTS.lua", []() {return new WildAmbients();}}, + {"scripts\\ai\\NS\\NS_PP_01\\L_NS_PP_01_TELEPORT.lua", []() {return new PropertyDeathPlane();}}, + {"scripts\\02_server\\Map\\General\\L_QB_SPAWNER.lua", []() {return new QbSpawner();}}, + {"scripts\\ai\\AG\\L_AG_QB_Wall.lua", []() {return new AgQbWall();}}, + + //GF + {"scripts\\02_server\\Map\\GF\\L_GF_TORCH.lua", []() {return new GfTikiTorch();}}, + {"scripts\\ai\\GF\\L_SPECIAL_FIREPIT.lua", []() {return new GfCampfire();}}, + {"scripts\\ai\\GF\\L_GF_ORGAN.lua", []() {return new GfOrgan();}}, + {"scripts\\ai\\GF\\L_GF_BANANA.lua", []() {return new GfBanana();}}, + {"scripts\\ai\\GF\\L_GF_BANANA_CLUSTER.lua", []() {return new GfBananaCluster();}}, + {"scripts/ai/GF/L_GF_JAILKEEP_MISSION.lua", []() {return new GfJailkeepMission();}}, + {"scripts\\ai\\GF\\L_TRIGGER_AMBUSH.lua", []() {return new TriggerAmbush();}}, + {"scripts\\02_server\\Map\\GF\\L_GF_CAPTAINS_CANNON.lua", []() {return new GfCaptainsCannon();}}, + {"scripts\\02_server\\Map\\GF\\L_MAST_TELEPORT.lua", []() {return new MastTeleport();}}, + {"scripts\\ai\\GF\\L_GF_JAIL_WALLS.lua", []() {return new GfJailWalls();}}, + {"scripts\\02_server\\Map\\General\\L_QB_ENEMY_STUNNER.lua", []() {return new QbEnemyStunner();}}, + //Technically also used once in AG (below) + {"scripts\\ai\\GF\\L_GF_PET_DIG_BUILD.lua", []() {return new PetDigBuild();}}, + {"scripts\\02_server\\Map\\GF\\L_SPAWN_LION_SERVER.lua", []() {return new SpawnLionServer();}}, + {"scripts\\02_server\\Enemy\\General\\L_BASE_ENEMY_APE.lua", []() {return new BaseEnemyApe();}}, + {"scripts\\02_server\\Enemy\\General\\L_GF_APE_SMASHING_QB.lua", []() {return new GfApeSmashingQB();}}, + {"scripts\\zone\\PROPERTY\\GF\\L_ZONE_GF_PROPERTY.lua", []() {return new ZoneGfProperty();}}, + {"scripts\\ai\\GF\\L_GF_ARCHWAY.lua", []() {return new GfArchway();}}, + {"scripts\\ai\\GF\\L_GF_MAELSTROM_GEYSER.lua", []() {return new GfMaelstromGeyser();}}, + {"scripts\\ai\\GF\\L_PIRATE_REP.lua", []() {return new PirateRep();}}, + {"scripts\\ai\\GF\\L_GF_PARROT_CRASH.lua", []() {return new GfParrotCrash();}}, + + //SG + {"scripts\\ai\\MINIGAME\\SG_GF\\SERVER\\SG_CANNON.lua", []() {return new SGCannon();}}, + {"scripts\\ai\\MINIGAME\\SG_GF\\L_ZONE_SG_SERVER.lua", []() {return new ZoneSGServer();}}, + + //PR + {"scripts\\client\\ai\\PR\\L_PR_WHISTLE.lua", []() {return new PrWhistle();}}, + {"scripts\\02_server\\Map\\PR\\L_PR_SEAGULL_FLY.lua", []() {return new PrSeagullFly();}}, + {"scripts\\ai\\PETS\\L_HYDRANT_SMASHABLE.lua", []() {return new HydrantSmashable();}}, + {"scripts\\02_server\\map\\PR\\L_HYDRANT_BROKEN.lua", []() {return new HydrantBroken();}}, + {"scripts\\02_server\\Map\\General\\PET_DIG_SERVER.lua", []() {return new PetDigServer();}}, + {"scripts\\02_server\\Map\\AM\\L_SKELETON_DRAGON_PET_DIG_SERVER.lua", []() {return new PetDigServer();}}, + //{"scripts\\02_server\\Map\\AM\\L_SKELETON_DRAGON_PET_DIG_SERVER.lua", [](){return new PetDigServer();}}, + {"scripts\\client\\ai\\PR\\L_CRAB_SERVER.lua", []() {return new CrabServer();}}, + {"scripts\\02_server\\Pets\\L_PET_FROM_DIG_SERVER.lua", []() {return new PetFromDigServer();}}, + {"scripts\\02_server\\Pets\\L_PET_FROM_OBJECT_SERVER.lua", []() {return new PetFromObjectServer();}}, + {"scripts\\02_server\\Pets\\L_DAMAGING_PET.lua", []() {return new DamagingPets();}}, + {"scripts\\02_server\\Map\\PR\\L_SPAWN_GRYPHON_SERVER.lua", []() {return new SpawnGryphonServer();}}, + + //FV + {"scripts\\02_server\\Map\\FV\\L_ACT_CANDLE.lua", []() {return new FvCandle();}}, + {"scripts\\02_server\\Map\\FV\\L_ENEMY_RONIN_SPAWNER.lua", []() {return new EnemyRoninSpawner();}}, + {"scripts\\02_server\\Enemy\\FV\\L_FV_MAELSTROM_CAVALRY.lua", []() {return new FvMaelstromCavalry();}}, + {"scripts\\ai\\FV\\L_ACT_NINJA_TURRET_1.lua", []() {return new ActNinjaTurret();}}, + {"scripts\\02_server\\Map\\FV\\L_FV_HORSEMEN_TRIGGER.lua", []() {return new FvHorsemenTrigger();}}, + {"scripts\\ai\\FV\\L_FV_FLYING_CREVICE_DRAGON.lua", []() {return new FvFlyingCreviceDragon();}}, + {"scripts\\02_server\\Enemy\\FV\\L_FV_MAELSTROM_DRAGON.lua", []() {return new FvMaelstromDragon();}}, + {"scripts\\ai\\FV\\L_FV_DRAGON_SMASHING_GOLEM_QB.lua", []() {return new FvDragonSmashingGolemQb();}}, + {"scripts\\02_server\\Enemy\\General\\L_TREASURE_CHEST_DRAGON_SERVER.lua", []() {return new TreasureChestDragonServer();}}, + {"scripts\\ai\\GENERAL\\L_INSTANCE_EXIT_TRANSFER_PLAYER_TO_LAST_NON_INSTANCE.lua", []() {return new InstanceExitTransferPlayerToLastNonInstance();}}, + {"scripts\\ai\\FV\\L_NPC_FREE_GF_NINJAS.lua", []() {return new FvFreeGfNinjas();}}, + {"scripts\\ai\\FV\\L_FV_PANDA_SPAWNER_SERVER.lua", []() {return new FvPandaSpawnerServer();}}, + {"scripts\\ai\\FV\\L_FV_PANDA_SERVER.lua", []() {return new FvPandaServer();}}, + {"scripts\\zone\\PROPERTY\\FV\\L_ZONE_FV_PROPERTY.lua", []() {return new ZoneFvProperty();}}, + {"scripts\\ai\\FV\\L_FV_BRICK_PUZZLE_SERVER.lua", []() {return new FvBrickPuzzleServer();}}, + {"scripts\\ai\\FV\\L_FV_CONSOLE_LEFT_QUICKBUILD.lua", []() {return new FvConsoleLeftQuickbuild();}}, + {"scripts\\ai\\FV\\L_FV_CONSOLE_RIGHT_QUICKBUILD.lua", []() {return new FvConsoleRightQuickbuild();}}, + {"scripts\\ai\\FV\\L_FV_FACILITY_BRICK.lua", []() {return new FvFacilityBrick();}}, + {"scripts\\ai\\FV\\L_FV_FACILITY_PIPES.lua", []() {return new FvFacilityPipes();}}, + {"scripts\\02_server\\Map\\FV\\L_IMG_BRICK_CONSOLE_QB.lua", []() {return new ImgBrickConsoleQB();}}, + {"scripts\\ai\\FV\\L_ACT_PARADOX_PIPE_FIX.lua", []() {return new ActParadoxPipeFix();}}, + {"scripts\\ai\\FV\\L_FV_NINJA_GUARDS.lua", []() {return new FvNinjaGuard();}}, + {"scripts\\ai\\FV\\L_ACT_PASS_THROUGH_WALL.lua", []() {return new FvPassThroughWall();}}, + {"scripts\\ai\\FV\\L_ACT_BOUNCE_OVER_WALL.lua", []() {return new FvBounceOverWall();}}, + {"scripts\\02_server\\Map\\FV\\L_NPC_FONG.lua", []() {return new FvFong();}}, + {"scripts\\ai\\FV\\L_FV_MAELSTROM_GEYSER.lua", []() {return new FvMaelstromGeyser();}}, + {"scripts\\02_server\\Map\\FV\\Racing\\RACE_SHIP_LAP_COLUMNS_SERVER.lua", []() {return new RaceShipLapColumnsServer();}}, + + //yes we know the lap numbers dont match the file name or anim. Thats what they desgined it as. + {"scripts\\ai\\RACING\\OBJECTS\\FV_RACE_DRAGON_LAP1_SERVER.lua", []() {return new FvRaceDragon("lap_01", 2);}}, + {"scripts\\ai\\RACING\\OBJECTS\\FV_RACE_DRAGON_LAP2_SERVER.lua", []() {return new FvRaceDragon("lap_02", 0);}}, + {"scripts\\ai\\RACING\\OBJECTS\\FV_RACE_DRAGON_LAP3_SERVER.lua", []() {return new FvRaceDragon("lap_03", 1);}}, + {"scripts\\ai\\RACING\\OBJECTS\\FV_RACE_PILLAR_ABC_SERVER.lua", []() {return new FvRacePillarABCServer();}}, + {"scripts\\ai\\RACING\\OBJECTS\\FV_RACE_PILLAR_D_SERVER.lua", []() {return new FvRacePillarDServer();}}, + {"scripts\\02_server\\Map\\FV\\Racing\\RACE_FIREBALLS.lua", []() {return new RaceFireballs();}}, + + //Misc. + {"scripts\\02_server\\Map\\General\\L_EXPLODING_ASSET.lua", []() {return new ExplodingAsset();}}, + {"scripts\\02_server\\Map\\General\\L_WISHING_WELL_SERVER.lua", []() {return new WishingWellServer();}}, + {"scripts\\ai\\ACT\\L_ACT_PLAYER_DEATH_TRIGGER.lua", []() {return new ActPlayerDeathTrigger();}}, + {"scripts\\02_server\\Map\\General\\L_GROWING_FLOWER_SERVER.lua", []() {return new GrowingFlower();}}, + {"scripts\\02_server\\Map\\General\\L_TOKEN_CONSOLE_SERVER.lua", []() {return new TokenConsoleServer();}}, + {"scripts\\ai\\ACT\\FootRace\\L_ACT_BASE_FOOT_RACE.lua", []() {return new BaseFootRaceManager();}}, + {"scripts\\02_server\\Map\\General\\L_PROP_PLATFORM.lua", []() {return new PropertyPlatform();}}, + {"scripts\\02_server\\Map\\VE\\L_VE_BRICKSAMPLE_SERVER.lua", []() {return new VeBricksampleServer();}}, + {"scripts\\02_server\\Map\\General\\L_MAIL_BOX_SERVER.lua", []() {return new MailBoxServer();}}, + {"scripts\\ai\\ACT\\L_ACT_MINE.lua", []() {return new ActMine();}}, + {"scripts\\02_server\\Map\\AM\\L_WANDERING_VENDOR.lua", []() {return new WanderingVendor();}}, + + //Racing + {"scripts\\ai\\RACING\\OBJECTS\\RACE_IMAGINE_CRATE_SERVER.lua", []() {return new RaceImagineCrateServer();}}, + {"scripts\\ai\\ACT\\L_ACT_VEHICLE_DEATH_TRIGGER.lua", []() {return new ActVehicleDeathTrigger();}}, + {"scripts\\ai\\RACING\\OBJECTS\\RACE_IMAGINE_POWERUP.lua", []() {return new RaceImaginePowerup();}}, + {"scripts\\02_server\\Map\\FV\\Racing\\RACE_MAELSTROM_GEISER.lua", []() {return new RaceMaelstromGeiser();}}, + {"scripts\\ai\\RACING\\OBJECTS\\FV_RACE_SMASH_EGG_IMAGINE_SERVER.lua", []() {return new FvRaceSmashEggImagineServer();}}, + {"scripts\\02_server\\Map\\FV\\Racing\\FV_RACING_COLUMNS.lua", []() {return new FvRacingColumns();}}, + {"scripts\\ai\\RACING\\OBJECTS\\RACE_SMASH_SERVER.lua", []() {return new RaceSmashServer();}}, + + //NT + {"scripts\\02_server\\Map\\NT\\L_NT_SENTINELWALKWAY_SERVER.lua", []() {return new NtSentinelWalkwayServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_PARADOXTELE_SERVER.lua", []() {return new NtParadoxTeleServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_DARKITECT_REVEAL_SERVER.lua", []() {return new NtDarkitectRevealServer();}}, + {"scripts\\02_server\\Map\\General\\L_BANK_INTERACT_SERVER.lua", []() {return new BankInteractServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_VENTURESPEEDPAD_SERVER.lua", []() {return new NtVentureSpeedPadServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_VENTURE_CANNON_SERVER.lua", []() {return new NtVentureCannonServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_COMBAT_CHALLENGE_SERVER.lua", []() {return new NtCombatChallengeServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_COMBAT_CHALLENGE_DUMMY.lua", []() {return new NtCombatChallengeDummy();}}, + {"scripts\\02_server\\Map\\NT\\\\L_NT_COMBAT_EXPLODING_TARGET.lua", []() {return new NtCombatChallengeExplodingDummy();}}, + {"scripts\\02_server\\Map\\General\\L_BASE_INTERACT_DROP_LOOT_SERVER.lua", []() {return new BaseInteractDropLootServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_ASSEMBLYTUBE_SERVER.lua", []() {return new NtAssemblyTubeServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_PARADOX_PANEL_SERVER.lua", []() {return new NtParadoxPanelServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_IMAG_BEAM_BUFFER.lua", []() {return new NtImagBeamBuffer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_BEAM_IMAGINATION_COLLECTORS.lua", []() {return new NtBeamImaginationCollectors();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_DIRT_CLOUD_SERVER.lua", []() {return new NtDirtCloudServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_CONSOLE_TELEPORT_SERVER.lua", []() {return new NtConsoleTeleportServer();}}, + {"scripts\\02_server\\Map\\NT\\L_SPAWN_STEGO_SERVER.lua", []() {return new SpawnStegoServer();}}, + {"scripts\\02_server\\Map\\NT\\L_SPAWN_SABERCAT_SERVER.lua", []() {return new SpawnSaberCatServer();}}, + {"scripts\\02_server\\Map\\NT\\L_SPAWN_SHRAKE_SERVER.lua", []() {return new SpawnShrakeServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_DUKE_SERVER.lua", []() {return new NtDukeServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_HAEL_SERVER.lua", []() {return new NtHaelServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_FACTION_SPY_SERVER.lua", []() {return new NtFactionSpyServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_OVERBUILD_SERVER.lua", []() {return new NtOverbuildServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_VANDA_SERVER.lua", []() {return new NtVandaServer();}}, + {"scripts\\02_server\\Map\\General\\L_FORCE_VOLUME_SERVER.lua", []() {return new ForceVolumeServer();}}, + {"scripts\\02_server\\Map\\General\\L_FRICTION_VOLUME_SERVER.lua", []() {return new FrictionVolumeServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_XRAY_SERVER.lua", []() {return new NtXRayServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_SLEEPING_GUARD.lua", []() {return new NtSleepingGuard();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_IMAGIMETER_VISIBILITY_SERVER.lua", []() {return new NTImagimeterVisibility();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_PIPE_VISIBILITY_SERVER.lua", []() {return new NTPipeVisibilityServer();}}, + {"scripts\\ai\\MINIGAME\\Objects\\MINIGAME_BLUE_MARK.lua", []() {return new MinigameBlueMark();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_NAOMI_BREADCRUMB_SERVER.lua", []() {return new NtNaomiBreadcrumbServer();}}, + {"scripts\\02_server\\Map\\NT\\L_NT_NAOMI_DIRT_SERVER.lua", []() {return new NTNaomiDirtServer();}}, + + //AM Crux + {"scripts\\02_server\\Map\\AM\\L_AM_CONSOLE_TELEPORT_SERVER.lua", []() {return new AmConsoleTeleportServer();}}, + {"scripts\\02_server\\Map\\AM\\L_RANDOM_SPAWNER_FIN.lua", []() {return new RandomSpawnerFin();}}, + {"scripts\\02_server\\Map\\AM\\L_RANDOM_SPAWNER_PIT.lua", []() {return new RandomSpawnerPit();}}, + {"scripts\\02_server\\Map\\AM\\L_RANDOM_SPAWNER_STR.lua", []() {return new RandomSpawnerStr();}}, + {"scripts\\02_server\\Map\\AM\\L_RANDOM_SPAWNER_ZIP.lua", []() {return new RandomSpawnerZip();}}, + {"scripts\\02_server\\Enemy\\AM\\L_AM_DARKLING_MECH.lua", []() {return new AmDarklingMech();}}, + {"scripts\\02_server\\Map\\AM\\L_BRIDGE.lua", []() {return new AmBridge();}}, + {"scripts\\02_server\\Map\\AM\\L_DRAW_BRIDGE.lua", []() {return new AmDrawBridge();}}, + {"scripts\\02_server\\Map\\AM\\L_SHIELD_GENERATOR.lua", []() {return new AmShieldGenerator();}}, + {"scripts\\02_server\\Map\\AM\\L_SHIELD_GENERATOR_QUICKBUILD.lua", []() {return new AmShieldGeneratorQuickbuild();}}, + {"scripts\\02_server\\Map\\AM\\L_DROPSHIP_COMPUTER.lua", []() {return new AmDropshipComputer();}}, + {"scripts\\02_server\\Map\\AM\\L_SCROLL_READER_SERVER.lua", []() {return new AmScrollReaderServer();}}, + {"scripts\\02_server\\Map\\AM\\L_TEMPLE_SKILL_VOLUME.lua", []() {return new AmTemplateSkillVolume();}}, + {"scripts\\02_server\\Enemy\\General\\L_ENEMY_NJ_BUFF.lua", []() {return new EnemyNjBuff();}}, + {"scripts\\02_server\\Enemy\\AM\\L_AM_SKELETON_ENGINEER.lua", []() {return new AmSkeletonEngineer();}}, + {"scripts\\02_server\\Map\\AM\\L_SKULLKIN_DRILL.lua", []() {return new AmSkullkinDrill();}}, + {"scripts\\02_server\\Map\\AM\\L_SKULLKIN_DRILL_STAND.lua", []() {return new AmSkullkinDrillStand();}}, + {"scripts\\02_server\\Map\\AM\\L_SKULLKIN_TOWER.lua", []() {return new AmSkullkinTower();}}, + {"scripts\\02_server\\Enemy\\AM\\L_AM_NAMED_DARKLING_DRAGON.lua", []() {return new AmDarklingDragon();}}, + {"scripts\\02_server\\Enemy\\AM\\L_AM_DARKLING_APE.lua", []() {return new BaseEnemyApe();}}, + {"scripts\\02_server\\Map\\AM\\L_BLUE_X.lua", []() {return new AmBlueX();}}, + {"scripts\\02_server\\Map\\AM\\L_TEAPOT_SERVER.lua", []() {return new AmTeapotServer();}}, + + //Ninjago + {"scripts\\02_server\\Map\\njhub\\L_GARMADON_CELEBRATION_SERVER.lua", []() {return new NjGarmadonCelebration();}}, + {"scripts\\02_server\\Map\\njhub\\L_WU_NPC.lua", []() {return new NjWuNPC();}}, + {"scripts\\02_server\\Map\\njhub\\L_SCROLL_CHEST_SERVER.lua", []() {return new NjScrollChestServer();}}, + {"scripts\\02_server\\Map\\njhub\\L_COLE_NPC.lua", []() {return new NjColeNPC();}}, + {"scripts\\02_server\\Map\\njhub\\L_JAY_MISSION_ITEMS.lua", []() {return new NjJayMissionItems();}}, + {"scripts\\02_server\\Map\\njhub\\L_NPC_MISSION_SPINJITZU_SERVER.lua", []() {return new NjNPCMissionSpinjitzuServer();}}, + {"scripts\\02_server\\Map\\njhub\\L_ENEMY_SKELETON_SPAWNER.lua", []() {return new EnemySkeletonSpawner();}}, + {"scripts\\02_server\\Map\\General\\L_NJ_RAIL_SWITCH.lua", []() {return new NjRailSwitch();}}, + {"scripts\\02_server\\Map\\General\\Ninjago\\L_RAIL_ACTIVATORS_SERVER.lua", []() {return new NjRailActivatorsServer();}}, + {"scripts\\02_server\\Map\\General\\Ninjago\\L_RAIL_POST_SERVER.lua", []() {return new NjRailPostServer();}}, + {"scripts\\02_server\\Map\\General\\Ninjago\\L_ICE_RAIL_ACTIVATOR_SERVER.lua", []() {return new NjIceRailActivator();}}, + {"scripts\\02_server\\Map\\njhub\\L_FALLING_TILE.lua", []() {return new FallingTile();}}, + {"scripts\\02_server\\Enemy\\General\\L_ENEMY_NJ_BUFF_STUN_IMMUNITY.lua", []() {return new EnemyNjBuff();}}, + {"scripts\\02_server\\Map\\njhub\\L_IMAGINATION_SHRINE_SERVER.lua", []() {return new ImaginationShrineServer();}}, + {"scripts\\02_server\\Map\\njhub\\L_LIEUTENANT.lua", []() {return new Lieutenant();}}, + {"scripts\\02_server\\Map\\njhub\\L_RAIN_OF_ARROWS.lua", []() {return new RainOfArrows();}}, + {"scripts\\02_server\\Map\\njhub\\L_CAVE_PRISON_CAGE.lua", []() {return new CavePrisonCage();}}, + {"scripts\\02_server\\Map\\njhub\\boss_instance\\L_MONASTERY_BOSS_INSTANCE_SERVER.lua", []() {return new NjMonastryBossInstance();}}, + {"scripts\\02_server\\Map\\njhub\\L_CATAPULT_BOUNCER_SERVER.lua", []() {return new CatapultBouncerServer();}}, + {"scripts\\02_server\\Map\\njhub\\L_CATAPULT_BASE_SERVER.lua", []() {return new CatapultBaseServer();}}, + {"scripts\\02_server\\Map\\General\\Ninjago\\L_NJHUB_LAVA_PLAYER_DEATH_TRIGGER.lua", []() {return new NjhubLavaPlayerDeathTrigger();}}, + {"scripts\\02_server\\Map\\njhub\\L_MON_CORE_NOOK_DOORS.lua", []() {return new MonCoreNookDoors();}}, + {"scripts\\02_server\\Map\\njhub\\L_MON_CORE_SMASHABLE_DOORS.lua", []() {return new MonCoreSmashableDoors();}}, + {"scripts\\02_server\\Map\\njhub\\L_MON_CORE_SMASHABLE_DOORS.lua", []() {return new MonCoreSmashableDoors();}}, + {"scripts\\02_server\\Map\\njhub\\L_FLAME_JET_SERVER.lua", []() {return new FlameJetServer();}}, + {"scripts\\02_server\\Map\\njhub\\L_BURNING_TILE.lua", []() {return new BurningTile();}}, + {"scripts\\02_server\\Map\\njhub\\L_SPAWN_EARTH_PET_SERVER.lua", []() {return new NjEarthDragonPetServer();}}, + {"scripts\\02_server\\Map\\njhub\\L_EARTH_PET_SERVER.lua", []() {return new NjEarthPetServer();}}, + {"scripts\\02_server\\Map\\njhub\\L_DRAGON_EMBLEM_CHEST_SERVER.lua", []() {return new NjDragonEmblemChestServer();}}, + {"scripts\\02_server\\Map\\njhub\\L_NYA_MISSION_ITEMS.lua", []() {return new NjNyaMissionitems();}}, + + //DLU + {"scripts\\02_server\\DLU\\DLUVanityTeleportingObject.lua", []() {return new DLUVanityTeleportingObject();}}, + + //Survival Minigame + {"scripts\\02_server\\Enemy\\Survival\\L_AG_SURVIVAL_STROMBIE.lua", []() {return new AgSurvivalStromling();}}, + {"scripts\\02_server\\Enemy\\Survival\\L_AG_SURVIVAL_DARKLING_MECH.lua", []() {return new AgSurvivalMech();}}, + {"scripts\\02_server\\Enemy\\Survival\\L_AG_SURVIVAL_DARK_SPIDERLING.lua", []() {return new AgSurvivalSpiderling();}}, + + //Scripted Equipment + {"scripts\\EquipmentScripts\\Sunflower.lua", []() {return new Sunflower();}}, + {"scripts/EquipmentScripts/AnvilOfArmor.lua", []() {return new AnvilOfArmor();}}, + {"scripts/EquipmentScripts/FountainOfImagination.lua", []() {return new FountainOfImagination();}}, + {"scripts/EquipmentScripts/CauldronOfLife.lua", []() {return new CauldronOfLife();}}, + {"scripts\\02_server\\Equipment\\L_BOOTYDIG_SERVER.lua", []() {return new BootyDigServer();}}, + {"scripts\\EquipmentScripts\\PersonalFortress.lua", []() {return new PersonalFortress();}}, + {"scripts\\02_server\\Map\\General\\L_PROPERTY_DEVICE.lua", []() {return new PropertyDevice();}}, + {"scripts\\02_server\\Map\\General\\L_IMAG_BACKPACK_HEALS_SERVER.lua", []() {return new ImaginationBackpackHealServer();}}, + {"scripts\\ai\\GENERAL\\L_LEGO_DIE_ROLL.lua", []() {return new LegoDieRoll();}}, + {"scripts\\EquipmentScripts\\BuccaneerValiantShip.lua", []() {return new BuccaneerValiantShip();}}, + {"scripts\\EquipmentScripts\\FireFirstSkillonStartup.lua", []() {return new FireFirstSkillonStartup();}}, + {"scripts\\equipmenttriggers\\gempack.lua", []() {return new GemPack();}}, + {"scripts\\equipmenttriggers\\shardarmor.lua", []() {return new ShardArmor();}}, + {"scripts\\equipmenttriggers\\coilbackpack.lua", []() {return new TeslaPack();}}, + {"scripts\\EquipmentScripts\\stunImmunity.lua", []() {return new StunImmunity();}}, + + //FB + {"scripts\\ai\\NS\\WH\\L_ROCKHYDRANT_BROKEN.lua", []() {return new RockHydrantBroken();}}, + {"scripts\\ai\\NS\\L_NS_WH_FANS.lua", []() {return new WhFans();}}, + + //WBL + {"scripts\\zone\\LUPs\\WBL_generic_zone.lua", []() {return new WblGenericZone();}}, + + //Alpha + {"scripts\\ai\\FV\\L_TRIGGER_GAS.lua", []() {return new TriggerGas();}}, + {"scripts\\ai\\FV\\L_ACT_NINJA_SENSEI.lua", []() {return new ActNinjaSensei();}}, + + //Pickups + {"scripts\\ai\\SPEC\\L_SPECIAL_1_BRONZE-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(1);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_1_SILVER-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(100);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_10_BRONZE-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(10);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_10_GOLD-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(100000);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_10_SILVER-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(1000);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_25_BRONZE-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(25);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_25_GOLD-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(250000);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_25_SILVER-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(2500);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_IMAGINE-POWERUP-SPAWNER.lua", []() {return new SpecialPowerupSpawner(13);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_IMAGINE-POWERUP-SPAWNER-2PT.lua", []() {return new SpecialPowerupSpawner(129);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_LIFE-POWERUP-SPAWNER.lua", []() {return new SpecialPowerupSpawner(5);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_ARMOR-POWERUP-SPAWNER.lua", []() {return new SpecialPowerupSpawner(747);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_SPEED_BUFF_SPAWNER.lua", []() {return new SpecialSpeedBuffSpawner();}}, + + //Wild + {"scripts\\ai\\WILD\\L_WILD_GF_RAT.lua", []() {return new WildAndScared();}}, + {"scripts\\ai\\WILD\\L_WILD_GF_SNAIL.lua", []() {return new WildAndScared();}}, + {"scripts\\ai\\WILD\\L_WILD_GF_GLOWBUG.lua", []() {return new WildGfGlowbug();}}, + {"scripts\\ai\\WILD\\L_WILD_AMBIENT_CRAB.lua", []() {return new WildAmbientCrab();}}, + {"scripts\\ai\\WILD\\L_WILD_PANTS.lua", []() {return new WildPants();}}, + {"scripts\\ai\\WILD\\L_WILD_NINJA_BRICKS.lua", []() {return new WildNinjaBricks();}}, + {"scripts\\ai\\WILD\\L_WILD_NINJA_STUDENT.lua", []() {return new WildNinjaStudent();}}, + {"scripts\\ai\\WILD\\L_WILD_NINJA_SENSEI.lua", []() {return new WildNinjaSensei();}}, + {"scripts\\ai\\WILD\\L_LUP_generic_interact.lua", []() {return new LupGenericInteract();}}, + {"scripts\\zone\\LUPs\\RobotCity Intro\\WBL_RCIntro_RobotCitizenBlue.lua", []() {return new WblRobotCitizen();}}, + {"scripts\\zone\\LUPs\\RobotCity Intro\\WBL_RCIntro_RobotCitizenGreen.lua", []() {return new WblRobotCitizen();}}, + {"scripts\\zone\\LUPs\\RobotCity Intro\\WBL_RCIntro_RobotCitizenOrange.lua", []() {return new WblRobotCitizen();}}, + {"scripts\\zone\\LUPs\\RobotCity Intro\\WBL_RCIntro_RobotCitizenRed.lua", []() {return new WblRobotCitizen();}}, + {"scripts\\zone\\LUPs\\RobotCity Intro\\WBL_RCIntro_RobotCitizenYellow.lua", []() {return new WblRobotCitizen();}}, + + }; }; CppScripts::Script* const CppScripts::GetScript(Entity* parent, const std::string& scriptName) { - auto itr = m_Scripts.find(scriptName); - if (itr != m_Scripts.end()) { + auto itr = g_Scripts.find(scriptName); + if (itr != g_Scripts.end()) { return itr->second; } - Script* script = InvalidToReturn; + const auto itrTernary = scriptLoader.find(scriptName); + Script* script = itrTernary != scriptLoader.cend() ? itrTernary->second() : &InvalidToReturn; - //VE / AG: - if (scriptName == "scripts\\ai\\AG\\L_AG_SHIP_PLAYER_DEATH_TRIGGER.lua") - script = new AgShipPlayerDeathTrigger(); - else if (scriptName == "scripts\\ai\\NP\\L_NPC_NP_SPACEMAN_BOB.lua") - script = new NpcNpSpacemanBob(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_SPACE_STUFF.lua") // Broken, will (sometimes) display all animations at once on initial login - script = new AgSpaceStuff(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_SHIP_PLAYER_SHOCK_SERVER.lua") - script = new AgShipPlayerShockServer(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_IMAG_SMASHABLE.lua") - script = new AgImagSmashable(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_STORY_BOX_INTERACT_SERVER.lua") - script = new StoryBoxInteractServer(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_BINOCULARS.lua") - script = new Binoculars(); - else if (scriptName == "scripts\\ai\\WILD\\L_ALL_CRATE_CHICKEN.lua") - script = new AllCrateChicken(); - else if (scriptName == "scripts\\ai\\NS\\WH\\L_ROCKHYDRANT_SMASHABLE.lua") - script = new RockHydrantSmashable(); // Broken? - else if (scriptName == "scripts\\02_server\\Map\\SS\\L_SS_MODULAR_BUILD_SERVER.lua") - script = new SsModularBuildServer(); - else if (scriptName == "scripts\\02_server\\Map\\Property\\AG_Small\\L_ZONE_AG_PROPERTY.lua") - script = new ZoneAgProperty(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_POI_MISSION.lua") - script = new InvalidScript(); // this is done in Entity.cpp, not needed for our implementation - else if (scriptName == "scripts\\02_server\\Map\\General\\L_TOUCH_MISSION_UPDATE_SERVER.lua") - script = new TouchMissionUpdateServer(); - else if (scriptName == "scripts\\ai\\AG\\L_ACT_SHARK_PLAYER_DEATH_TRIGGER.lua") - script = new ActSharkPlayerDeathTrigger(); - else if (scriptName == "scripts\\02_server\\Enemy\\General\\L_BASE_ENEMY_MECH.lua") - script = new BaseEnemyMech(); - else if (scriptName == "scripts\\zone\\AG\\L_ZONE_AG_SURVIVAL.lua") - script = new ZoneAgSurvival(); - else if (scriptName == "scripts\\02_server\\Objects\\L_BUFF_STATION_SERVER.lua") - script = new AgSurvivalBuffStation(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_BUS_DOOR.lua") - script = new AgBusDoor(); - else if (scriptName == "scripts\\02_server\\Equipment\\L_MAESTROM_EXTRACTICATOR_SERVER.lua") - script = new MaestromExtracticatorServer(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L_AG_CAGED_BRICKS_SERVER.lua") - script = new AgCagedBricksServer(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L_NPC_WISP_SERVER.lua") - script = new NpcWispServer(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L_NPC_EPSILON_SERVER.lua") - script = new NpcEpsilonServer(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_TURRET.lua" || scriptName == "scripts\\ai\\AG\\L_AG_TURRET_FOR_SHIP.lua") - script = new AgTurret(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L_AG_LASER_SENSOR_SERVER.lua") - script = new AgLaserSensorServer(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L_AG_MONUMENT_LASER_SERVER.lua") - script = new AgMonumentLaserServer(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_FANS.lua") - script = new AgFans(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L_AG_MONUMENT_BIRDS.lua") - script = new AgMonumentBirds(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L_REMOVE_RENTAL_GEAR.lua") - script = new RemoveRentalGear(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L_NPC_NJ_ASSISTANT_SERVER.lua") - script = new NpcNjAssistantServer(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_SALUTING_NPCS.lua") - script = new AgSalutingNpcs(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_JET_EFFECT_SERVER.lua") - script = new AgJetEffectServer(); - else if (scriptName == "scripts\\02_server\\Enemy\\AG\\L_BOSS_SPIDER_QUEEN_ENEMY_SERVER.lua") - script = new BossSpiderQueenEnemyServer(); - else if (scriptName == "scripts\\02_server\\Map\\Property\\AG_Small\\L_ENEMY_SPIDER_SPAWNER.lua") - script = new EnemySpiderSpawner(); - else if (scriptName == "scripts/02_server/Map/Property/AG_Small/L_ENEMY_SPIDER_SPAWNER.lua") - script = new EnemySpiderSpawner(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_QB_Elevator.lua") - script = new AgQbElevator(); - else if (scriptName == "scripts\\ai\\PROPERTY\\AG\\L_AG_PROP_GUARD.lua") - script = new AgPropGuard(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L_AG_BUGSPRAYER.lua") - script = new AgBugsprayer(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L_NPC_AG_COURSE_STARTER.lua") - script = new NpcAgCourseStarter(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L__AG_MONUMENT_RACE_GOAL.lua") - script = new AgMonumentRaceGoal(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L__AG_MONUMENT_RACE_CANCEL.lua") - script = new AgMonumentRaceCancel(); - else if (scriptName == "scripts\\02_server\\Map\\AG_Spider_Queen\\L_ZONE_AG_SPIDER_QUEEN.lua") - script = new ZoneAgSpiderQueen(); - else if (scriptName == "scripts\\02_server\\Map\\AG_Spider_Queen\\L_SPIDER_BOSS_TREASURE_CHEST_SERVER.lua") - script = new SpiderBossTreasureChestServer(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L_NPC_COWBOY_SERVER.lua") - script = new NpcCowboyServer(); - else if (scriptName == "scripts\\02_server\\Map\\Property\\AG_Med\\L_ZONE_AG_MED_PROPERTY.lua") - script = new ZoneAgMedProperty(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_STROMBIE_PROPERTY.lua") - script = new AgStromlingProperty(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_DARKLING_MECH.lua") - script = new BaseEnemyMech(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_DARK_SPIDERLING.lua") - script = new AgDarkSpiderling(); - else if (scriptName == "scripts\\ai\\PROPERTY\\L_PROP_GUARDS.lua") - script = new AgPropguards(); - else if (scriptName == "scripts\\ai\\PROPERTY\\L_PROPERTY_FX_DAMAGE.lua") - script = new PropertyFXDamage(); - else if (scriptName == "scripts\\02_server\\Map\\AG\\L_NPC_PIRATE_SERVER.lua") - script = new NpcPirateServer(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_PICNIC_BLANKET.lua") - script = new AgPicnicBlanket(); - else if (scriptName == "scripts\\02_server\\Map\\Property\\L_PROPERTY_BANK_INTERACT_SERVER.lua") - script = new PropertyBankInteract(); - else if (scriptName == "scripts\\02_server\\Enemy\\VE\\L_VE_MECH.lua") - script = new VeMech(); - else if (scriptName == "scripts\\02_server\\Map\\VE\\L_MISSION_CONSOLE_SERVER.lua") - script = new VeMissionConsole(); - else if (scriptName == "scripts\\02_server\\Map\\VE\\L_EPSILON_SERVER.lua") - script = new VeEpsilonServer(); - // Win32 thinks this if chain is too long, let's cut it up and serve it as a three course meal - //NS: - if (scriptName == "scripts\\ai\\NS\\L_NS_MODULAR_BUILD.lua") - script = new NsModularBuild(); - else if (scriptName == "scripts\\ai\\NS\\L_NS_GET_FACTION_MISSION_SERVER.lua") - script = new NsGetFactionMissionServer(); - else if (scriptName == "scripts\\ai\\NS\\L_NS_QB_IMAGINATION_STATUE.lua") - script = new NsQbImaginationStatue(); - else if (scriptName == "scripts\\02_server\\Map\\NS\\CONCERT_CHOICEBUILD_MANAGER_SERVER.lua") - script = new NsConcertChoiceBuildManager(); - else if (scriptName == "scripts\\ai\\NS\\L_NS_CONCERT_CHOICEBUILD.lua") - script = new NsConcertChoiceBuild(); - else if (scriptName == "scripts\\ai\\NS\\L_NS_CONCERT_QUICKBUILD.lua") - script = new NsConcertQuickBuild(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_STAGE_PLATFORMS.lua") - script = new AgStagePlatforms(); - else if (scriptName == "scripts\\ai\\NS\\L_NS_CONCERT_INSTRUMENT_QB.lua") - script = new NsConcertInstrument(); - else if (scriptName == "scripts\\ai\\NS\\L_NS_JONNY_FLAG_MISSION_SERVER.lua") - script = new NsJohnnyMissionServer(); - else if (scriptName == "scripts\\02_server\\Objects\\L_STINKY_FISH_TARGET.lua") - script = new StinkyFishTarget(); - else if (scriptName == "scripts\\zone\\PROPERTY\\NS\\L_ZONE_NS_PROPERTY.lua") - script = new ZoneNsProperty(); - else if (scriptName == "scripts\\02_server\\Map\\Property\\NS_Med\\L_ZONE_NS_MED_PROPERTY.lua") - script = new ZoneNsMedProperty(); - else if (scriptName == "scripts\\02_server\\Map\\NS\\L_NS_TOKEN_CONSOLE_SERVER.lua") - script = new NsTokenConsoleServer(); - else if (scriptName == "scripts\\02_server\\Map\\NS\\L_NS_LUP_TELEPORT.lua") - script = new NsLupTeleport(); - else if (scriptName == "scripts\\02_server\\Map\\NS\\Waves\\L_ZONE_NS_WAVES.lua") - script = new ZoneNsWaves(); - else if (scriptName == "scripts\\02_server\\Enemy\\Waves\\L_WAVES_BOSS_HAMMERLING_ENEMY_SERVER.lua") - script = new WaveBossHammerling(); - else if (scriptName == "scripts\\02_server\\Enemy\\Waves\\L_WAVES_BOSS_APE_ENEMY_SERVER.lua") - script = new WaveBossApe(); - else if (scriptName == "scripts\\02_server\\Enemy\\Waves\\L_WAVES_BOSS_DARK_SPIDERLING_ENEMY_SERVER.lua") - script = new WaveBossSpiderling(); - else if (scriptName == "scripts\\02_server\\Enemy\\Waves\\L_WAVES_BOSS_HORESEMEN_ENEMY_SERVER.lua") - script = new WaveBossHorsemen(); - else if (scriptName == "scripts\\02_server\\Minigame\\General\\L_MINIGAME_TREASURE_CHEST_SERVER.lua") - script = new MinigameTreasureChestServer(); - else if (scriptName == "scripts\\02_server\\Map\\NS\\L_NS_LEGO_CLUB_DOOR.lua") - script = new NsLegoClubDoor(); - else if (scriptName == "scripts/ai/NS/L_CL_RING.lua") - script = new ClRing(); - else if (scriptName == "scripts\\ai\\WILD\\L_WILD_AMBIENTS.lua") - script = new WildAmbients(); - else if (scriptName == "scripts\\ai\\NS\\NS_PP_01\\L_NS_PP_01_TELEPORT.lua") - script = new PropertyDeathPlane(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_QB_SPAWNER.lua") - script = new QbSpawner(); - else if (scriptName == "scripts\\ai\\AG\\L_AG_QB_Wall.lua") - script = new AgQbWall(); - - //GF: - else if (scriptName == "scripts\\02_server\\Map\\GF\\L_GF_TORCH.lua") - script = new GfTikiTorch(); - else if (scriptName == "scripts\\ai\\GF\\L_SPECIAL_FIREPIT.lua") - script = new GfCampfire(); - else if (scriptName == "scripts\\ai\\GF\\L_GF_ORGAN.lua") - script = new GfOrgan(); - else if (scriptName == "scripts\\ai\\GF\\L_GF_BANANA.lua") - script = new GfBanana(); - else if (scriptName == "scripts\\ai\\GF\\L_GF_BANANA_CLUSTER.lua") - script = new GfBananaCluster(); - else if (scriptName == "scripts/ai/GF/L_GF_JAILKEEP_MISSION.lua") - script = new GfJailkeepMission(); - else if (scriptName == "scripts\\ai\\GF\\L_TRIGGER_AMBUSH.lua") - script = new TriggerAmbush(); - else if (scriptName == "scripts\\02_server\\Map\\GF\\L_GF_CAPTAINS_CANNON.lua") - script = new GfCaptainsCannon(); - else if (scriptName == "scripts\\02_server\\Map\\GF\\L_MAST_TELEPORT.lua") - script = new MastTeleport(); - else if (scriptName == "scripts\\ai\\GF\\L_GF_JAIL_WALLS.lua") - script = new GfJailWalls(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_QB_ENEMY_STUNNER.lua") - script = new QbEnemyStunner(); - else if (scriptName == "scripts\\ai\\GF\\L_GF_PET_DIG_BUILD.lua") - script = new PetDigBuild(); // Technically also used once in AG - else if (scriptName == "scripts\\02_server\\Map\\GF\\L_SPAWN_LION_SERVER.lua") - script = new SpawnLionServer(); - else if (scriptName == "scripts\\02_server\\Enemy\\General\\L_BASE_ENEMY_APE.lua") - script = new BaseEnemyApe(); - else if (scriptName == "scripts\\02_server\\Enemy\\General\\L_GF_APE_SMASHING_QB.lua") - script = new GfApeSmashingQB(); - else if (scriptName == "scripts\\zone\\PROPERTY\\GF\\L_ZONE_GF_PROPERTY.lua") - script = new ZoneGfProperty(); - else if (scriptName == "scripts\\ai\\GF\\L_GF_ARCHWAY.lua") - script = new GfArchway(); - else if (scriptName == "scripts\\ai\\GF\\L_GF_MAELSTROM_GEYSER.lua") - script = new GfMaelstromGeyser(); - else if (scriptName == "scripts\\ai\\GF\\L_PIRATE_REP.lua") - script = new PirateRep(); - else if (scriptName == "scripts\\ai\\GF\\L_GF_PARROT_CRASH.lua") - script = new GfParrotCrash(); - - // SG - else if (scriptName == "scripts\\ai\\MINIGAME\\SG_GF\\SERVER\\SG_CANNON.lua") - script = new SGCannon(); - else if (scriptName == "scripts\\ai\\MINIGAME\\SG_GF\\L_ZONE_SG_SERVER.lua") - script = new ZoneSGServer(); - - //PR: - else if (scriptName == "scripts\\client\\ai\\PR\\L_PR_WHISTLE.lua") - script = new PrWhistle(); - if (scriptName == "scripts\\02_server\\Map\\PR\\L_PR_SEAGULL_FLY.lua") - script = new PrSeagullFly(); - else if (scriptName == "scripts\\ai\\PETS\\L_HYDRANT_SMASHABLE.lua") - script = new HydrantSmashable(); - else if (scriptName == "scripts\\02_server\\map\\PR\\L_HYDRANT_BROKEN.lua") - script = new HydrantBroken(); - else if (scriptName == "scripts\\02_server\\Map\\General\\PET_DIG_SERVER.lua" || scriptName == "scripts\\02_server\\Map\\AM\\L_SKELETON_DRAGON_PET_DIG_SERVER.lua") - script = new PetDigServer(); - else if (scriptName == "scripts\\client\\ai\\PR\\L_CRAB_SERVER.lua") - script = new CrabServer(); - else if (scriptName == "scripts\\02_server\\Pets\\L_PET_FROM_DIG_SERVER.lua") - script = new PetFromDigServer(); - else if (scriptName == "scripts\\02_server\\Pets\\L_PET_FROM_OBJECT_SERVER.lua") - script = new PetFromObjectServer(); - else if (scriptName == "scripts\\02_server\\Pets\\L_DAMAGING_PET.lua") - script = new DamagingPets(); - else if (scriptName == "scripts\\02_server\\Map\\PR\\L_SPAWN_GRYPHON_SERVER.lua") - script = new SpawnGryphonServer(); - - //FV Scripts: - else if (scriptName == "scripts\\02_server\\Map\\FV\\L_ACT_CANDLE.lua") - script = new FvCandle(); - else if (scriptName == "scripts\\02_server\\Map\\FV\\L_ENEMY_RONIN_SPAWNER.lua") - script = new EnemyRoninSpawner(); - else if (scriptName == "scripts\\02_server\\Enemy\\FV\\L_FV_MAELSTROM_CAVALRY.lua") - script = new FvMaelstromCavalry(); - else if (scriptName == "scripts\\ai\\FV\\L_ACT_NINJA_TURRET_1.lua") - script = new ActNinjaTurret(); - else if (scriptName == "scripts\\02_server\\Map\\FV\\L_FV_HORSEMEN_TRIGGER.lua") - script = new FvHorsemenTrigger(); - else if (scriptName == "scripts\\ai\\FV\\L_FV_FLYING_CREVICE_DRAGON.lua") - script = new FvFlyingCreviceDragon(); - else if (scriptName == "scripts\\02_server\\Enemy\\FV\\L_FV_MAELSTROM_DRAGON.lua") - script = new FvMaelstromDragon(); - else if (scriptName == "scripts\\ai\\FV\\L_FV_DRAGON_SMASHING_GOLEM_QB.lua") - script = new FvDragonSmashingGolemQb(); - else if (scriptName == "scripts\\02_server\\Enemy\\General\\L_TREASURE_CHEST_DRAGON_SERVER.lua") - script = new TreasureChestDragonServer(); - else if (scriptName == "scripts\\ai\\GENERAL\\L_INSTANCE_EXIT_TRANSFER_PLAYER_TO_LAST_NON_INSTANCE.lua") - script = new InstanceExitTransferPlayerToLastNonInstance(); - else if (scriptName == "scripts\\ai\\FV\\L_NPC_FREE_GF_NINJAS.lua") - script = new FvFreeGfNinjas(); - else if (scriptName == "scripts\\ai\\FV\\L_FV_PANDA_SPAWNER_SERVER.lua") - script = new FvPandaSpawnerServer(); - else if (scriptName == "scripts\\ai\\FV\\L_FV_PANDA_SERVER.lua") - script = new FvPandaServer(); - else if (scriptName == "scripts\\zone\\PROPERTY\\FV\\L_ZONE_FV_PROPERTY.lua") - script = new ZoneFvProperty(); - else if (scriptName == "scripts\\ai\\FV\\L_FV_BRICK_PUZZLE_SERVER.lua") - script = new FvBrickPuzzleServer(); - else if (scriptName == "scripts\\ai\\FV\\L_FV_CONSOLE_LEFT_QUICKBUILD.lua") - script = new FvConsoleLeftQuickbuild(); - else if (scriptName == "scripts\\ai\\FV\\L_FV_CONSOLE_RIGHT_QUICKBUILD.lua") - script = new FvConsoleRightQuickbuild(); - else if (scriptName == "scripts\\ai\\FV\\L_FV_FACILITY_BRICK.lua") - script = new FvFacilityBrick(); - else if (scriptName == "scripts\\ai\\FV\\L_FV_FACILITY_PIPES.lua") - script = new FvFacilityPipes(); - else if (scriptName == "scripts\\02_server\\Map\\FV\\L_IMG_BRICK_CONSOLE_QB.lua") - script = new ImgBrickConsoleQB(); - else if (scriptName == "scripts\\ai\\FV\\L_ACT_PARADOX_PIPE_FIX.lua") - script = new ActParadoxPipeFix(); - else if (scriptName == "scripts\\ai\\FV\\L_FV_NINJA_GUARDS.lua") - script = new FvNinjaGuard(); - else if (scriptName == "scripts\\ai\\FV\\L_ACT_PASS_THROUGH_WALL.lua") - script = new FvPassThroughWall(); - else if (scriptName == "scripts\\ai\\FV\\L_ACT_BOUNCE_OVER_WALL.lua") - script = new FvBounceOverWall(); - else if (scriptName == "scripts\\02_server\\Map\\FV\\L_NPC_FONG.lua") - script = new FvFong(); - else if (scriptName == "scripts\\ai\\FV\\L_FV_MAELSTROM_GEYSER.lua") - script = new FvMaelstromGeyser(); - else if (scriptName == "scripts\\02_server\\Map\\FV\\Racing\\RACE_SHIP_LAP_COLUMNS_SERVER.lua") - script = new RaceShipLapColumnsServer(); - - // yes we know the lap numbers dont match the file name or anim. thats what they desgined it as. - else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\FV_RACE_DRAGON_LAP1_SERVER.lua") - script = new FvRaceDragon("lap_01", 2); - else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\FV_RACE_DRAGON_LAP2_SERVER.lua") - script = new FvRaceDragon("lap_02", 0); - else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\FV_RACE_DRAGON_LAP3_SERVER.lua") - script = new FvRaceDragon("lap_03", 1); - else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\FV_RACE_PILLAR_ABC_SERVER.lua") - script = new FvRacePillarABCServer(); - else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\FV_RACE_PILLAR_D_SERVER.lua") - script = new FvRacePillarDServer(); - else if (scriptName == "scripts\\02_server\\Map\\FV\\Racing\\RACE_FIREBALLS.lua") - script = new RaceFireballs(); - - - //Misc: - if (scriptName == "scripts\\02_server\\Map\\General\\L_EXPLODING_ASSET.lua") - script = new ExplodingAsset(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_WISHING_WELL_SERVER.lua") - script = new WishingWellServer(); - else if (scriptName == "scripts\\ai\\ACT\\L_ACT_PLAYER_DEATH_TRIGGER.lua") - script = new ActPlayerDeathTrigger(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_GROWING_FLOWER_SERVER.lua") - script = new GrowingFlower(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_TOKEN_CONSOLE_SERVER.lua") - script = new TokenConsoleServer(); - else if (scriptName == "scripts\\ai\\ACT\\FootRace\\L_ACT_BASE_FOOT_RACE.lua") - script = new BaseFootRaceManager(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_PROP_PLATFORM.lua") - script = new PropertyPlatform(); - else if (scriptName == "scripts\\02_server\\Map\\VE\\L_VE_BRICKSAMPLE_SERVER.lua") - script = new VeBricksampleServer(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_MAIL_BOX_SERVER.lua") - script = new MailBoxServer(); - else if (scriptName == "scripts\\ai\\ACT\\L_ACT_MINE.lua") - script = new ActMine(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_WANDERING_VENDOR.lua") - script = new WanderingVendor(); - - //Racing: - else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\RACE_IMAGINE_CRATE_SERVER.lua") - script = new RaceImagineCrateServer(); - else if (scriptName == "scripts\\ai\\ACT\\L_ACT_VEHICLE_DEATH_TRIGGER.lua") - script = new ActVehicleDeathTrigger(); - else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\RACE_IMAGINE_POWERUP.lua") - script = new RaceImaginePowerup(); - else if (scriptName == "scripts\\02_server\\Map\\FV\\Racing\\RACE_MAELSTROM_GEISER.lua") - script = new RaceMaelstromGeiser(); - else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\FV_RACE_SMASH_EGG_IMAGINE_SERVER.lua") - script = new FvRaceSmashEggImagineServer(); - else if (scriptName == "scripts\\02_server\\Map\\FV\\Racing\\FV_RACING_COLUMNS.lua") - script = new FvRacingColumns(); - else if (scriptName == "scripts\\ai\\RACING\\OBJECTS\\RACE_SMASH_SERVER.lua") - script = new RaceSmashServer(); - - //NT: - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_SENTINELWALKWAY_SERVER.lua") - script = new NtSentinelWalkwayServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_PARADOXTELE_SERVER.lua") - script = new NtParadoxTeleServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_DARKITECT_REVEAL_SERVER.lua") - script = new NtDarkitectRevealServer(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_BANK_INTERACT_SERVER.lua") - script = new BankInteractServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_VENTURESPEEDPAD_SERVER.lua") - script = new NtVentureSpeedPadServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_VENTURE_CANNON_SERVER.lua") - script = new NtVentureCannonServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_COMBAT_CHALLENGE_SERVER.lua") - script = new NtCombatChallengeServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_COMBAT_CHALLENGE_DUMMY.lua") - script = new NtCombatChallengeDummy(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\\\L_NT_COMBAT_EXPLODING_TARGET.lua") - script = new NtCombatChallengeExplodingDummy(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_BASE_INTERACT_DROP_LOOT_SERVER.lua") - script = new BaseInteractDropLootServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_ASSEMBLYTUBE_SERVER.lua") - script = new NtAssemblyTubeServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_PARADOX_PANEL_SERVER.lua") - script = new NtParadoxPanelServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_IMAG_BEAM_BUFFER.lua") - script = new NtImagBeamBuffer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_BEAM_IMAGINATION_COLLECTORS.lua") - script = new NtBeamImaginationCollectors(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_DIRT_CLOUD_SERVER.lua") - script = new NtDirtCloudServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_CONSOLE_TELEPORT_SERVER.lua") - script = new NtConsoleTeleportServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_SPAWN_STEGO_SERVER.lua") - script = new SpawnStegoServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_SPAWN_SABERCAT_SERVER.lua") - script = new SpawnSaberCatServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_SPAWN_SHRAKE_SERVER.lua") - script = new SpawnShrakeServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_DUKE_SERVER.lua") - script = new NtDukeServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_HAEL_SERVER.lua") - script = new NtHaelServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_FACTION_SPY_SERVER.lua") - script = new NtFactionSpyServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_OVERBUILD_SERVER.lua") - script = new NtOverbuildServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_VANDA_SERVER.lua") - script = new NtVandaServer(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_FORCE_VOLUME_SERVER.lua") - script = new ForceVolumeServer(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_FRICTION_VOLUME_SERVER.lua") - script = new FrictionVolumeServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_XRAY_SERVER.lua") - script = new NtXRayServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_SLEEPING_GUARD.lua") - script = new NtSleepingGuard(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_IMAGIMETER_VISIBILITY_SERVER.lua") - script = new NTImagimeterVisibility(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_PIPE_VISIBILITY_SERVER.lua") - script = new NTPipeVisibilityServer(); - else if (scriptName == "scripts\\ai\\MINIGAME\\Objects\\MINIGAME_BLUE_MARK.lua") - script = new MinigameBlueMark(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_NAOMI_BREADCRUMB_SERVER.lua") - script = new NtNaomiBreadcrumbServer(); - else if (scriptName == "scripts\\02_server\\Map\\NT\\L_NT_NAOMI_DIRT_SERVER.lua") - script = new NTNaomiDirtServer(); - - //AM: - if (scriptName == "scripts\\02_server\\Map\\AM\\L_AM_CONSOLE_TELEPORT_SERVER.lua") - script = new AmConsoleTeleportServer(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_RANDOM_SPAWNER_FIN.lua") - script = new RandomSpawnerFin(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_RANDOM_SPAWNER_PIT.lua") - script = new RandomSpawnerPit(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_RANDOM_SPAWNER_STR.lua") - script = new RandomSpawnerStr(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_RANDOM_SPAWNER_ZIP.lua") - script = new RandomSpawnerZip(); - else if (scriptName == "scripts\\02_server\\Enemy\\AM\\L_AM_DARKLING_MECH.lua") - script = new AmDarklingMech(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_BRIDGE.lua") - script = new AmBridge(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_DRAW_BRIDGE.lua") - script = new AmDrawBridge(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_SHIELD_GENERATOR.lua") - script = new AmShieldGenerator(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_SHIELD_GENERATOR_QUICKBUILD.lua") - script = new AmShieldGeneratorQuickbuild(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_DROPSHIP_COMPUTER.lua") - script = new AmDropshipComputer(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_SCROLL_READER_SERVER.lua") - script = new AmScrollReaderServer(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_TEMPLE_SKILL_VOLUME.lua") - script = new AmTemplateSkillVolume(); - else if (scriptName == "scripts\\02_server\\Enemy\\General\\L_ENEMY_NJ_BUFF.lua") - script = new EnemyNjBuff(); - else if (scriptName == "scripts\\02_server\\Enemy\\AM\\L_AM_SKELETON_ENGINEER.lua") - script = new AmSkeletonEngineer(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_SKULLKIN_DRILL.lua") - script = new AmSkullkinDrill(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_SKULLKIN_DRILL_STAND.lua") - script = new AmSkullkinDrillStand(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_SKULLKIN_TOWER.lua") - script = new AmSkullkinTower(); - else if (scriptName == "scripts\\02_server\\Enemy\\AM\\L_AM_NAMED_DARKLING_DRAGON.lua") - script = new AmDarklingDragon(); - else if (scriptName == "scripts\\02_server\\Enemy\\AM\\L_AM_DARKLING_DRAGON.lua") - script = new AmDarklingDragon(); - else if (scriptName == "scripts\\02_server\\Enemy\\AM\\L_AM_DARKLING_APE.lua") - script = new BaseEnemyApe(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_BLUE_X.lua") - script = new AmBlueX(); - else if (scriptName == "scripts\\02_server\\Map\\AM\\L_TEAPOT_SERVER.lua") - script = new AmTeapotServer(); - - // Ninjago - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_GARMADON_CELEBRATION_SERVER.lua") - script = new NjGarmadonCelebration(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_WU_NPC.lua") - script = new NjWuNPC(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_SCROLL_CHEST_SERVER.lua") - script = new NjScrollChestServer(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_COLE_NPC.lua") - script = new NjColeNPC(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_JAY_MISSION_ITEMS.lua") - script = new NjJayMissionItems(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_NPC_MISSION_SPINJITZU_SERVER.lua") - script = new NjNPCMissionSpinjitzuServer(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_ENEMY_SKELETON_SPAWNER.lua") - script = new EnemySkeletonSpawner(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_NJ_RAIL_SWITCH.lua") - script = new NjRailSwitch(); - else if (scriptName == "scripts\\02_server\\Map\\General\\Ninjago\\L_RAIL_ACTIVATORS_SERVER.lua") - script = new NjRailActivatorsServer(); - else if (scriptName == "scripts\\02_server\\Map\\General\\Ninjago\\L_RAIL_POST_SERVER.lua") - script = new NjRailPostServer(); - else if (scriptName == "scripts\\02_server\\Map\\General\\Ninjago\\L_ICE_RAIL_ACTIVATOR_SERVER.lua") - script = new NjIceRailActivator(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_FALLING_TILE.lua") - script = new FallingTile(); - else if (scriptName == "scripts\\02_server\\Enemy\\General\\L_ENEMY_NJ_BUFF_STUN_IMMUNITY.lua") - script = new EnemyNjBuff(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_IMAGINATION_SHRINE_SERVER.lua") - script = new ImaginationShrineServer(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_LIEUTENANT.lua") - script = new Lieutenant(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_RAIN_OF_ARROWS.lua") - script = new RainOfArrows(); - if (scriptName == "scripts\\02_server\\Map\\njhub\\L_CAVE_PRISON_CAGE.lua") - script = new CavePrisonCage(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\boss_instance\\L_MONASTERY_BOSS_INSTANCE_SERVER.lua") - script = new NjMonastryBossInstance(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_CATAPULT_BOUNCER_SERVER.lua") - script = new CatapultBouncerServer(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_CATAPULT_BASE_SERVER.lua") - script = new CatapultBaseServer(); - else if (scriptName == "scripts\\02_server\\Map\\General\\Ninjago\\L_NJHUB_LAVA_PLAYER_DEATH_TRIGGER.lua") - script = new NjhubLavaPlayerDeathTrigger(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_MON_CORE_NOOK_DOORS.lua") - script = new MonCoreNookDoors(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_MON_CORE_SMASHABLE_DOORS.lua") - script = new MonCoreSmashableDoors(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_FLAME_JET_SERVER.lua") - script = new FlameJetServer(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_BURNING_TILE.lua") - script = new BurningTile(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_SPAWN_EARTH_PET_SERVER.lua") - script = new NjEarthDragonPetServer(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_EARTH_PET_SERVER.lua") - script = new NjEarthPetServer(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_DRAGON_EMBLEM_CHEST_SERVER.lua") - script = new NjDragonEmblemChestServer(); - else if (scriptName == "scripts\\02_server\\Map\\njhub\\L_NYA_MISSION_ITEMS.lua") - script = new NjNyaMissionitems(); - - //DLU: - else if (scriptName == "scripts\\02_server\\DLU\\DLUVanityTeleportingObject.lua") - script = new DLUVanityTeleportingObject(); - - // Survival minigame - else if (scriptName == "scripts\\02_server\\Enemy\\Survival\\L_AG_SURVIVAL_STROMBIE.lua") - script = new AgSurvivalStromling(); - else if (scriptName == "scripts\\02_server\\Enemy\\Survival\\L_AG_SURVIVAL_DARKLING_MECH.lua") - script = new AgSurvivalMech(); - else if (scriptName == "scripts\\02_server\\Enemy\\Survival\\L_AG_SURVIVAL_DARK_SPIDERLING.lua") - script = new AgSurvivalSpiderling(); - - // Scripted equipment - else if (scriptName == "scripts\\EquipmentScripts\\Sunflower.lua") - script = new Sunflower(); - else if (scriptName == "scripts/EquipmentScripts/AnvilOfArmor.lua") - script = new AnvilOfArmor(); - else if (scriptName == "scripts/EquipmentScripts/FountainOfImagination.lua") - script = new FountainOfImagination(); - else if (scriptName == "scripts/EquipmentScripts/CauldronOfLife.lua") - script = new CauldronOfLife(); - else if (scriptName == "scripts\\02_server\\Equipment\\L_BOOTYDIG_SERVER.lua") - script = new BootyDigServer(); - else if (scriptName == "scripts\\EquipmentScripts\\PersonalFortress.lua") - script = new PersonalFortress(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_PROPERTY_DEVICE.lua") - script = new PropertyDevice(); - else if (scriptName == "scripts\\02_server\\Map\\General\\L_IMAG_BACKPACK_HEALS_SERVER.lua") - script = new ImaginationBackpackHealServer(); - else if (scriptName == "scripts\\ai\\GENERAL\\L_LEGO_DIE_ROLL.lua") - script = new LegoDieRoll(); - else if (scriptName == "scripts\\EquipmentScripts\\BuccaneerValiantShip.lua") - script = new BuccaneerValiantShip(); - else if (scriptName == "scripts\\EquipmentScripts\\FireFirstSkillonStartup.lua") - script = new FireFirstSkillonStartup(); - else if (scriptName == "scripts\\equipmenttriggers\\gempack.lua") - script = new GemPack(); - else if (scriptName == "scripts\\equipmenttriggers\\shardarmor.lua") - script = new ShardArmor(); - else if (scriptName == "scripts\\equipmenttriggers\\coilbackpack.lua") - script = new TeslaPack(); - else if (scriptName == "scripts\\EquipmentScripts\\stunImmunity.lua") - script = new StunImmunity(); - - // FB - else if (scriptName == "scripts\\ai\\NS\\WH\\L_ROCKHYDRANT_BROKEN.lua") - script = new RockHydrantBroken(); - else if (scriptName == "scripts\\ai\\NS\\L_NS_WH_FANS.lua") - script = new WhFans(); - - // WBL - else if (scriptName == "scripts\\zone\\LUPs\\WBL_generic_zone.lua") - script = new WblGenericZone(); - - // Alpha - if (scriptName == "scripts\\ai\\FV\\L_TRIGGER_GAS.lua") - script = new TriggerGas(); - else if (scriptName == "scripts\\ai\\FV\\L_ACT_NINJA_SENSEI.lua") - script = new ActNinjaSensei(); - - // pickups - if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_1_BRONZE-COIN-SPAWNER.lua") - script = new SpecialCoinSpawner(1); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_1_GOLD-COIN-SPAWNER.lua") - script = new SpecialCoinSpawner(10000); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_1_SILVER-COIN-SPAWNER.lua") - script = new SpecialCoinSpawner(100); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_10_BRONZE-COIN-SPAWNER.lua") - script = new SpecialCoinSpawner(10); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_10_GOLD-COIN-SPAWNER.lua") - script = new SpecialCoinSpawner(100000); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_10_SILVER-COIN-SPAWNER.lua") - script = new SpecialCoinSpawner(1000); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_25_BRONZE-COIN-SPAWNER.lua") - script = new SpecialCoinSpawner(25); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_25_GOLD-COIN-SPAWNER.lua") - script = new SpecialCoinSpawner(250000); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_25_SILVER-COIN-SPAWNER.lua") - script = new SpecialCoinSpawner(2500); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_IMAGINE-POWERUP-SPAWNER.lua") - script = new SpecialPowerupSpawner(13); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_IMAGINE-POWERUP-SPAWNER-2PT.lua") - script = new SpecialPowerupSpawner(129); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_LIFE-POWERUP-SPAWNER.lua") - script = new SpecialPowerupSpawner(5); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_ARMOR-POWERUP-SPAWNER.lua") - script = new SpecialPowerupSpawner(747); - else if (scriptName == "scripts\\ai\\SPEC\\L_SPECIAL_SPEED_BUFF_SPAWNER.lua") - script = new SpecialSpeedBuffSpawner(); - - // Wild - if (scriptName == "scripts\\ai\\WILD\\L_WILD_GF_RAT.lua" || scriptName == "scripts\\ai\\WILD\\L_WILD_GF_SNAIL.lua") - script = new WildAndScared(); - else if (scriptName == "scripts\\ai\\WILD\\L_WILD_GF_GLOWBUG.lua") - script = new WildGfGlowbug(); - else if (scriptName == "scripts\\ai\\WILD\\L_WILD_AMBIENT_CRAB.lua") - script = new WildAmbientCrab(); - else if (scriptName == "scripts\\ai\\WILD\\L_WILD_PANTS.lua") - script = new WildPants(); - else if (scriptName == "scripts\\ai\\WILD\\L_WILD_NINJA_BRICKS.lua") - script = new WildNinjaBricks(); - else if (scriptName == "scripts\\ai\\WILD\\L_WILD_NINJA_STUDENT.lua") - script = new WildNinjaStudent(); - else if (scriptName == "scripts\\ai\\WILD\\L_WILD_NINJA_SENSEI.lua") - script = new WildNinjaSensei(); - else if (scriptName == "scripts\\ai\\WILD\\L_LUP_generic_interact.lua") - script = new LupGenericInteract(); - else if (scriptName.rfind("scripts\\zone\\LUPs\\RobotCity Intro\\WBL_RCIntro_RobotCitizen", 0) == 0) - script = new WblRobotCitizen(); - - // handle invalid script reporting if the path is greater than zero and it's not an ignored script - // information not really needed for sys admins but is for developers - else if (script == InvalidToReturn) { + if (script == &InvalidToReturn) { if ((scriptName.length() > 0) && !((scriptName == "scripts\\02_server\\Enemy\\General\\L_SUSPEND_LUA_AI.lua") || (scriptName == "scripts\\02_server\\Enemy\\General\\L_BASE_ENEMY_SPIDERLING.lua") || - (scriptName =="scripts\\ai\\FV\\L_ACT_NINJA_STUDENT.lua") || + (scriptName == "scripts\\ai\\FV\\L_ACT_NINJA_STUDENT.lua") || (scriptName == "scripts\\ai\\WILD\\L_WILD_GF_FROG.lua") || (scriptName == "scripts\\empty.lua") )) LOG_DEBUG("LOT %i attempted to load CppScript for '%s', but returned InvalidScript.", parent->GetLOT(), scriptName.c_str()); } - m_Scripts[scriptName] = script; + g_Scripts[scriptName] = script; return script; } CppScripts::Script* const CppScripts::GetInvalidScript() { - return InvalidToReturn; + return &InvalidToReturn; } From d9d262d3f17abb57946f623a30209cb8fd463fda Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 16 May 2024 06:05:57 -0700 Subject: [PATCH 34/78] prevent building in a folder which contains spaces (#1583) --- CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index aa517182..5f8dbaf5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,11 @@ cmake_minimum_required(VERSION 3.25) project(Darkflame) + +# check if the path to the source directory contains a space +if("${CMAKE_SOURCE_DIR}" MATCHES " ") + message(FATAL_ERROR "The server cannot build in the path (" ${CMAKE_SOURCE_DIR} ") because it contains a space. Please move the server to a path without spaces.") +endif() + include(CTest) set(CMAKE_CXX_STANDARD 20) From c8e0bb0db0640fe17c5a188abe305e44f8e3c125 Mon Sep 17 00:00:00 2001 From: TAHuntling <38479763+TAHuntling@users.noreply.github.com> Date: Fri, 17 May 2024 00:02:30 -0500 Subject: [PATCH 35/78] feat: Command Sorting (#1580) * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp * Update dGame/dUtilities/SlashCommandHandler.cpp Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp * Update dGame/dUtilities/SlashCommandHandler.cpp Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp * Update dGame/dUtilities/SlashCommandHandler.cpp Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> * Update dGame/dUtilities/SlashCommandHandler.cpp Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> * Update dGame/dUtilities/SlashCommandHandler.cpp Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> * Update dGame/dUtilities/SlashCommandHandler.cpp Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> * Update dGame/dUtilities/SlashCommandHandler.cpp Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> * Update dGame/dUtilities/SlashCommandHandler.cpp Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> * Update SlashCommandHandler.cpp * Update dGame/dUtilities/SlashCommandHandler.cpp Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp --------- Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> --- dGame/dUtilities/SlashCommandHandler.cpp | 46 ++++++++++-------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index bf7e9eb4..3ba1ab38 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -19,7 +19,7 @@ #include "dServer.h" namespace { - std::vector<Command> CommandInfos; + std::map<std::string, Command> CommandInfos; std::map<std::string, Command> RegisteredCommands; } @@ -38,9 +38,8 @@ void SlashCommandHandler::RegisterCommand(Command command) { continue; } } - - CommandInfos.push_back(command); -}; + CommandInfos[command.aliases[0]] = command; +} void SlashCommandHandler::HandleChatCommand(const std::u16string& chat, Entity* entity, const SystemAddress& sysAddr) { auto input = GeneralUtils::UTF16ToWTF8(chat); @@ -74,38 +73,31 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& chat, Entity* } } -// This commands in here so we can access the CommandInfos to display info void GMZeroCommands::Help(Entity* entity, const SystemAddress& sysAddr, const std::string args) { std::ostringstream feedback; if (args.empty()) { feedback << "----- Commands -----"; - for (const auto& command : CommandInfos) { + for (const auto& [alias, command] : CommandInfos) { // TODO: Limit displaying commands based on GM level they require if (command.requiredLevel > entity->GetGMLevel()) continue; - LOG("Help command: %s", command.aliases[0].c_str()); - feedback << "\n/" << command.aliases[0] << ": " << command.help; + LOG("Help command: %s", alias.c_str()); + feedback << "\n/" << alias << ": " << command.help; } } else { - bool foundCommand = false; - for (const auto& command : CommandInfos) { - if (std::ranges::find(command.aliases, args) == command.aliases.end()) continue; - - if (entity->GetGMLevel() < command.requiredLevel) break; - foundCommand = true; - feedback << "----- " << command.aliases.at(0) << " -----\n"; - // info can be a localizable string - feedback << command.info; - if (command.aliases.size() == 1) break; - - feedback << "\nAliases: "; - for (size_t i = 0; i < command.aliases.size(); i++) { - if (i > 0) feedback << ", "; - feedback << command.aliases[i]; + auto it = CommandInfos.find(args); + if (it != CommandInfos.end() && entity->GetGMLevel() >= it->second.requiredLevel) { + feedback << "----- " << args << " -----\n"; + feedback << it->second.info; + if (it->second.aliases.size() > 1) { + feedback << "\nAliases: "; + for (size_t i = 0; i < it->second.aliases.size(); i++) { + if (i > 0) feedback << ", "; + feedback << it->second.aliases[i]; + } } + } else if (entity->GetGMLevel() > eGameMasterLevel::CIVILIAN) { + feedback << "Command " << std::quoted(args) << " does not exist!"; } - - // Let GameMasters know if the command doesn't exist - if (!foundCommand && entity->GetGMLevel() > eGameMasterLevel::CIVILIAN) feedback << "Command " << std::quoted(args) << " does not exist!"; } const auto feedbackStr = feedback.str(); if (!feedbackStr.empty()) GameMessages::SendSlashCommandFeedbackText(entity, GeneralUtils::ASCIIToUTF16(feedbackStr)); @@ -900,7 +892,7 @@ void SlashCommandHandler::Startup() { Command HelpCommand{ .help = "Display command info", - .info = "If a command is given, display detailed info on that command. Otherwise display a list of commands with short desctiptions.", + .info = "If a command is given, display detailed info on that command. Otherwise display a list of commands with short descriptions.", .aliases = { "help", "h"}, .handle = GMZeroCommands::Help, .requiredLevel = eGameMasterLevel::CIVILIAN From f2bf9a2a28fe76603a204482eee945416cac3cab Mon Sep 17 00:00:00 2001 From: David Markowitz <EmosewaMC@gmail.com> Date: Sat, 18 May 2024 02:05:55 -0700 Subject: [PATCH 36/78] Saving to database working --- dCommon/Logger.cpp | 4 +-- dDatabase/GameDatabase/GameDatabase.h | 4 ++- dDatabase/GameDatabase/ITables/IBehaviors.h | 20 ++++++++++++++ .../GameDatabase/ITables/IPropertyContents.h | 3 ++- dDatabase/GameDatabase/MySQL/MySQLDatabase.h | 4 ++- .../GameDatabase/MySQL/Tables/Behaviors.cpp | 14 ++++++++++ .../GameDatabase/MySQL/Tables/CMakeLists.txt | 1 + .../MySQL/Tables/PropertyContents.cpp | 18 +++++++------ dGame/Character.cpp | 4 +-- .../ControllablePhysicsComponent.cpp | 4 +-- dGame/dComponents/ModelComponent.cpp | 20 ++++++++++++++ dGame/dComponents/ModelComponent.h | 2 ++ .../PropertyManagementComponent.cpp | 27 ++++++++++++++++++- .../ControlBehaviorMessages/Action.cpp | 14 ++++++++++ .../ControlBehaviorMessages/Action.h | 5 ++++ .../StripUiPosition.cpp | 6 +++++ .../ControlBehaviorMessages/StripUiPosition.h | 5 ++++ dGame/dPropertyBehaviors/PropertyBehavior.cpp | 15 +++++++++++ dGame/dPropertyBehaviors/PropertyBehavior.h | 5 ++++ dGame/dPropertyBehaviors/State.cpp | 12 ++++++++- dGame/dPropertyBehaviors/State.h | 5 ++++ dGame/dPropertyBehaviors/Strip.cpp | 12 ++++++++- dGame/dPropertyBehaviors/Strip.h | 5 ++++ migrations/dlu/15_behavior_owner.sql | 3 +++ 24 files changed, 192 insertions(+), 20 deletions(-) create mode 100644 dDatabase/GameDatabase/ITables/IBehaviors.h create mode 100644 dDatabase/GameDatabase/MySQL/Tables/Behaviors.cpp create mode 100644 migrations/dlu/15_behavior_owner.sql diff --git a/dCommon/Logger.cpp b/dCommon/Logger.cpp index 1888f1eb..571b9661 100644 --- a/dCommon/Logger.cpp +++ b/dCommon/Logger.cpp @@ -53,8 +53,8 @@ void Logger::vLog(const char* format, va_list args) { struct tm* time = localtime(&t); char timeStr[70]; strftime(timeStr, sizeof(timeStr), "[%d-%m-%y %H:%M:%S ", time); - char message[2048]; - vsnprintf(message, 2048, format, args); + char message[131072]; + vsnprintf(message, 131072, format, args); for (const auto& writer : m_Writers) { writer->Log(timeStr, message); } diff --git a/dDatabase/GameDatabase/GameDatabase.h b/dDatabase/GameDatabase/GameDatabase.h index bcd8550b..f52c8c4e 100644 --- a/dDatabase/GameDatabase/GameDatabase.h +++ b/dDatabase/GameDatabase/GameDatabase.h @@ -23,6 +23,7 @@ #include "IActivityLog.h" #include "IIgnoreList.h" #include "IAccountsRewardCodes.h" +#include "IBehaviors.h" namespace sql { class Statement; @@ -40,7 +41,8 @@ class GameDatabase : public IMail, public ICommandLog, public IPlayerCheatDetections, public IBugReports, public IPropertyContents, public IProperty, public IPetNames, public ICharXml, public IMigrationHistory, public IUgc, public IFriends, public ICharInfo, - public IAccounts, public IActivityLog, public IAccountsRewardCodes, public IIgnoreList { + public IAccounts, public IActivityLog, public IAccountsRewardCodes, public IIgnoreList, + public IBehaviors { public: virtual ~GameDatabase() = default; // TODO: These should be made private. diff --git a/dDatabase/GameDatabase/ITables/IBehaviors.h b/dDatabase/GameDatabase/ITables/IBehaviors.h new file mode 100644 index 00000000..02b5178b --- /dev/null +++ b/dDatabase/GameDatabase/ITables/IBehaviors.h @@ -0,0 +1,20 @@ +#ifndef __IBEHAVIORS__H__ +#define __IBEHAVIORS__H__ + +#include <cstdint> + +#include "dCommonVars.h" + +class IBehaviors { +public: + struct Info { + LWOOBJID behaviorId{}; + uint32_t characterId{}; + std::string behaviorInfo; + }; + + virtual void AddBehavior(const Info& info) = 0; + virtual void RemoveBehavior(const uint32_t behaviorId) = 0; +}; + +#endif //!__IBEHAVIORS__H__ diff --git a/dDatabase/GameDatabase/ITables/IPropertyContents.h b/dDatabase/GameDatabase/ITables/IPropertyContents.h index c862ca94..25271302 100644 --- a/dDatabase/GameDatabase/ITables/IPropertyContents.h +++ b/dDatabase/GameDatabase/ITables/IPropertyContents.h @@ -16,6 +16,7 @@ public: LWOOBJID id{}; LOT lot{}; uint32_t ugcId{}; + std::array<LWOOBJID, 5> behaviors{}; }; // Inserts a new UGC model into the database. @@ -32,7 +33,7 @@ public: virtual void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) = 0; // Update the model position and rotation for the given property id. - virtual void UpdateModelPositionRotation(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation) = 0; + virtual void UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<LWOOBJID, std::string>, 5>& behaviors) = 0; // Remove the model for the given property id. virtual void RemoveModel(const LWOOBJID& modelId) = 0; diff --git a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h index 836ab56c..29df611c 100644 --- a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h +++ b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h @@ -74,7 +74,7 @@ public: std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override; void RemoveUnreferencedUgcModels() override; void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override; - void UpdateModelPositionRotation(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation) override; + void UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<LWOOBJID, std::string>, 5>& behaviors) override; void RemoveModel(const LWOOBJID& modelId) override; void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override; void InsertNewBugReport(const IBugReports::Info& info) override; @@ -108,6 +108,8 @@ public: std::vector<IIgnoreList::Info> GetIgnoreList(const uint32_t playerId) override; void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override; std::vector<uint32_t> GetRewardCodesByAccountID(const uint32_t account_id) override; + void AddBehavior(const IBehaviors::Info& info) override; + void RemoveBehavior(const uint32_t characterId) override; private: // Generic query functions that can be used for any query. diff --git a/dDatabase/GameDatabase/MySQL/Tables/Behaviors.cpp b/dDatabase/GameDatabase/MySQL/Tables/Behaviors.cpp new file mode 100644 index 00000000..91bcbd81 --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/Tables/Behaviors.cpp @@ -0,0 +1,14 @@ +#include "IBehaviors.h" + +#include "MySQLDatabase.h" + +void MySQLDatabase::AddBehavior(const IBehaviors::Info& info) { + ExecuteInsert( + "INSERT INTO behaviors (behavior_info, character_id, behavior_id) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE behavior_info = ?", + info.behaviorInfo, info.characterId, info.behaviorId, info.behaviorInfo + ); +} + +void MySQLDatabase::RemoveBehavior(const uint32_t behaviorId) { + ExecuteDelete("DELETE FROM behaviors WHERE behavior_id = ?", behaviorId); +} diff --git a/dDatabase/GameDatabase/MySQL/Tables/CMakeLists.txt b/dDatabase/GameDatabase/MySQL/Tables/CMakeLists.txt index 9f0e7baa..47cd220e 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/CMakeLists.txt +++ b/dDatabase/GameDatabase/MySQL/Tables/CMakeLists.txt @@ -2,6 +2,7 @@ set(DDATABASES_DATABASES_MYSQL_TABLES_SOURCES "Accounts.cpp" "AccountsRewardCodes.cpp" "ActivityLog.cpp" + "Behaviors.cpp" "BugReports.cpp" "CharInfo.cpp" "CharXml.cpp" diff --git a/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp b/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp index dba82d56..2f80b9df 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp @@ -32,21 +32,23 @@ void MySQLDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const IPr model.id, propertyId, model.ugcId == 0 ? std::nullopt : std::optional(model.ugcId), static_cast<uint32_t>(model.lot), model.position.x, model.position.y, model.position.z, model.rotation.x, model.rotation.y, model.rotation.z, model.rotation.w, name, "", // Model description. TODO implement this. - 0, // behavior 1. TODO implement this. - 0, // behavior 2. TODO implement this. - 0, // behavior 3. TODO implement this. - 0, // behavior 4. TODO implement this. - 0 // behavior 5. TODO implement this. + model.behaviors[0], // behavior 1 + model.behaviors[1], // behavior 2 + model.behaviors[2], // behavior 3 + model.behaviors[3], // behavior 4 + model.behaviors[4] // behavior 5 ); } catch (sql::SQLException& e) { LOG("Error inserting new property model: %s", e.what()); } } -void MySQLDatabase::UpdateModelPositionRotation(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation) { +void MySQLDatabase::UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<LWOOBJID, std::string>, 5>& behaviors) { ExecuteUpdate( - "UPDATE properties_contents SET x = ?, y = ?, z = ?, rx = ?, ry = ?, rz = ?, rw = ? WHERE id = ?;", - position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, rotation.w, propertyId); + "UPDATE properties_contents SET x = ?, y = ?, z = ?, rx = ?, ry = ?, rz = ?, rw = ?, " + "behavior_1 = ?, behavior_2 = ?, behavior_3 = ?, behavior_4 = ?, behavior_5 = ? WHERE id = ?;", + position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, rotation.w, propertyId, + behaviors[0].first, behaviors[1].first, behaviors[2].first, behaviors[3].first, behaviors[4].first); } void MySQLDatabase::RemoveModel(const LWOOBJID& modelId) { diff --git a/dGame/Character.cpp b/dGame/Character.cpp index 59a67462..7e0f73ec 100644 --- a/dGame/Character.cpp +++ b/dGame/Character.cpp @@ -239,7 +239,7 @@ void Character::SaveXMLToDatabase() { auto zoneInfo = Game::zoneManager->GetZone()->GetZoneID(); // lzid garbage, binary concat of zoneID, zoneInstance and zoneClone - if (zoneInfo.GetMapID() != 0 && zoneInfo.GetCloneID() == 0 && !Game::zoneManager->GetDisableSaveLocation()) { + // if (zoneInfo.GetMapID() != 0 && zoneInfo.GetCloneID() == 0 && !Game::zoneManager->GetDisableSaveLocation()) { uint64_t lzidConcat = zoneInfo.GetCloneID(); lzidConcat = (lzidConcat << 16) | uint16_t(zoneInfo.GetInstanceID()); lzidConcat = (lzidConcat << 16) | uint16_t(zoneInfo.GetMapID()); @@ -251,7 +251,7 @@ void Character::SaveXMLToDatabase() { // Set the target scene, custom attribute character->SetAttribute("tscene", m_TargetScene.c_str()); - } + // } auto emotes = character->FirstChildElement("ue"); if (!emotes) emotes = m_Doc.NewElement("ue"); diff --git a/dGame/dComponents/ControllablePhysicsComponent.cpp b/dGame/dComponents/ControllablePhysicsComponent.cpp index 18e4b19d..c969a7dd 100644 --- a/dGame/dComponents/ControllablePhysicsComponent.cpp +++ b/dGame/dComponents/ControllablePhysicsComponent.cpp @@ -187,7 +187,7 @@ void ControllablePhysicsComponent::UpdateXml(tinyxml2::XMLDocument& doc) { auto zoneInfo = Game::zoneManager->GetZone()->GetZoneID(); - if (zoneInfo.GetMapID() != 0 && zoneInfo.GetCloneID() == 0 && !Game::zoneManager->GetDisableSaveLocation()) { + // if (zoneInfo.GetMapID() != 0 && zoneInfo.GetCloneID() == 0 && !Game::zoneManager->GetDisableSaveLocation()) { character->SetAttribute("lzx", m_Position.x); character->SetAttribute("lzy", m_Position.y); character->SetAttribute("lzz", m_Position.z); @@ -195,7 +195,7 @@ void ControllablePhysicsComponent::UpdateXml(tinyxml2::XMLDocument& doc) { character->SetAttribute("lzry", m_Rotation.y); character->SetAttribute("lzrz", m_Rotation.z); character->SetAttribute("lzrw", m_Rotation.w); - } + // } } void ControllablePhysicsComponent::SetPosition(const NiPoint3& pos) { diff --git a/dGame/dComponents/ModelComponent.cpp b/dGame/dComponents/ModelComponent.cpp index 75f2a019..91e31e39 100644 --- a/dGame/dComponents/ModelComponent.cpp +++ b/dGame/dComponents/ModelComponent.cpp @@ -6,6 +6,7 @@ #include "BehaviorStates.h" #include "ControlBehaviorMsgs.h" +#include "tinyxml2.h" ModelComponent::ModelComponent(Entity* parent) : Component(parent) { m_OriginalPosition = m_Parent->GetDefaultPosition(); @@ -72,3 +73,22 @@ void ModelComponent::MoveToInventory(MoveToInventoryMessage& msg) { m_Behaviors.erase(m_Behaviors.begin() + msg.GetBehaviorIndex()); // TODO move to the inventory } + +std::array<std::pair<LWOOBJID, std::string>, 5> ModelComponent::GetBehaviorsForSave() const { + std::array<std::pair<LWOOBJID, std::string>, 5> toReturn; + for (auto i = 0; i < m_Behaviors.size(); i++) { + const auto& behavior = m_Behaviors.at(i); + if (behavior.GetBehaviorId() == -1) continue; + auto& [id, behaviorData] = toReturn[i]; + id = behavior.GetBehaviorId(); + tinyxml2::XMLDocument doc; + auto* root = doc.NewElement("Behavior"); + behavior.Serialize(*root); + doc.InsertFirstChild(root); + + tinyxml2::XMLPrinter printer(0, false, 0); + doc.Print(&printer); + behaviorData = printer.CStr(); + } + return toReturn; +} diff --git a/dGame/dComponents/ModelComponent.h b/dGame/dComponents/ModelComponent.h index dc6810eb..5b76708f 100644 --- a/dGame/dComponents/ModelComponent.h +++ b/dGame/dComponents/ModelComponent.h @@ -109,6 +109,8 @@ public: void VerifyBehaviors(); + std::array<std::pair<LWOOBJID, std::string>, 5> GetBehaviorsForSave() const; + private: /** * The behaviors of the model diff --git a/dGame/dComponents/PropertyManagementComponent.cpp b/dGame/dComponents/PropertyManagementComponent.cpp index ad96097b..6c393df1 100644 --- a/dGame/dComponents/PropertyManagementComponent.cpp +++ b/dGame/dComponents/PropertyManagementComponent.cpp @@ -21,9 +21,11 @@ #include "eObjectBits.h" #include "CharacterComponent.h" #include "PlayerManager.h" +#include "ModelComponent.h" #include <vector> #include "CppScripts.h" +#include <ranges> PropertyManagementComponent* PropertyManagementComponent::instance = nullptr; @@ -610,6 +612,12 @@ void PropertyManagementComponent::Save() { return; } + const auto* const owner = GetOwner(); + if (!owner) return; + + const auto* const character = owner->GetCharacter(); + if (!character) return; + auto present = Database::Get()->GetPropertyModels(propertyId); std::vector<LWOOBJID> modelIds; @@ -624,6 +632,20 @@ void PropertyManagementComponent::Save() { if (entity == nullptr) { continue; } + auto* modelComponent = entity->GetComponent<ModelComponent>(); + if (!modelComponent) continue; + const auto modelBehaviors = modelComponent->GetBehaviorsForSave(); + + // save the behaviors of the model + for (const auto& [behaviorId, behaviorStr] : modelBehaviors) { + if (behaviorStr.empty() || behaviorId == LWOOBJID_EMPTY) continue; + IBehaviors::Info info { + .behaviorId = behaviorId, + .characterId = character->GetID(), + .behaviorInfo = behaviorStr + }; + Database::Get()->AddBehavior(info); + } const auto position = entity->GetPosition(); const auto rotation = entity->GetRotation(); @@ -635,10 +657,13 @@ void PropertyManagementComponent::Save() { model.position = position; model.rotation = rotation; model.ugcId = 0; + for (auto i = 0; i < model.behaviors.size(); i++) { + model.behaviors[i] = modelBehaviors[i].first; + } Database::Get()->InsertNewPropertyModel(propertyId, model, "Objects_" + std::to_string(model.lot) + "_name"); } else { - Database::Get()->UpdateModelPositionRotation(id, position, rotation); + Database::Get()->UpdateModel(id, position, rotation, modelBehaviors); } } diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.cpp b/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.cpp index 3e62a2d7..4c917b48 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.cpp +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.cpp @@ -1,6 +1,8 @@ #include "Action.h" #include "Amf3.h" +#include "tinyxml2.h" + Action::Action(const AMFArrayValue& arguments) { for (const auto& [paramName, paramValue] : arguments.GetAssociative()) { if (paramName == "Type") { @@ -32,3 +34,15 @@ void Action::SendBehaviorBlocksToClient(AMFArrayValue& args) const { actionArgs->Insert(m_ValueParameterName, m_ValueParameterDouble); } } + +void Action::Serialize(tinyxml2::XMLElement& action) const { + action.SetAttribute("Type", m_Type.c_str()); + + if (m_ValueParameterName.empty()) return; + + if (m_ValueParameterName == "Message") { + action.SetAttribute(m_ValueParameterName.c_str(), m_ValueParameterString.c_str()); + } else { + action.SetAttribute(m_ValueParameterName.c_str(), m_ValueParameterDouble); + } +} diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.h index 988e616c..2f618316 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.h @@ -3,6 +3,10 @@ #include <string> +namespace tinyxml2 { + class XMLElement; +}; + class AMFArrayValue; /** @@ -20,6 +24,7 @@ public: void SendBehaviorBlocksToClient(AMFArrayValue& args) const; + void Serialize(tinyxml2::XMLElement& action) const; private: double m_ValueParameterDouble{ 0.0 }; std::string m_Type{ "" }; diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.cpp b/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.cpp index 56dc43ff..02f64879 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.cpp +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.cpp @@ -1,6 +1,7 @@ #include "StripUiPosition.h" #include "Amf3.h" +#include "tinyxml2.h" StripUiPosition::StripUiPosition(const AMFArrayValue& arguments, const std::string& uiKeyName) { const auto* const uiArray = arguments.GetArray(uiKeyName); @@ -21,3 +22,8 @@ void StripUiPosition::SendBehaviorBlocksToClient(AMFArrayValue& args) const { uiArgs->Insert("x", m_XPosition); uiArgs->Insert("y", m_YPosition); } + +void StripUiPosition::Serialize(tinyxml2::XMLElement& position) const { + position.SetAttribute("x", m_XPosition); + position.SetAttribute("y", m_YPosition); +} diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.h index f202210d..7bc2087e 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.h @@ -3,6 +3,10 @@ class AMFArrayValue; +namespace tinyxml2 { + class XMLElement; +} + /** * @brief The position of the first Action in a Strip * @@ -15,6 +19,7 @@ public: [[nodiscard]] double GetX() const noexcept { return m_XPosition; } [[nodiscard]] double GetY() const noexcept { return m_YPosition; } + void Serialize(tinyxml2::XMLElement& position) const; private: double m_XPosition{ 0.0 }; double m_YPosition{ 0.0 }; diff --git a/dGame/dPropertyBehaviors/PropertyBehavior.cpp b/dGame/dPropertyBehaviors/PropertyBehavior.cpp index 423751c4..278012ea 100644 --- a/dGame/dPropertyBehaviors/PropertyBehavior.cpp +++ b/dGame/dPropertyBehaviors/PropertyBehavior.cpp @@ -3,6 +3,7 @@ #include "Amf3.h" #include "BehaviorStates.h" #include "ControlBehaviorMsgs.h" +#include "tinyxml2.h" PropertyBehavior::PropertyBehavior() { m_LastEditedState = BehaviorState::HOME_STATE; @@ -124,3 +125,17 @@ void PropertyBehavior::SendBehaviorBlocksToClient(AMFArrayValue& args) const { // TODO Serialize the execution state of the behavior } + +void PropertyBehavior::Serialize(tinyxml2::XMLElement& behavior) const { + behavior.SetAttribute("id", m_BehaviorId); + behavior.SetAttribute("name", m_Name.c_str()); + behavior.SetAttribute("isLocked", isLocked); + behavior.SetAttribute("isLoot", isLoot); + + for (const auto& [stateId, state] : m_States) { + if (state.IsEmpty()) continue; + auto* const stateElement = behavior.InsertNewChildElement("State"); + stateElement->SetAttribute("id", static_cast<uint32_t>(stateId)); + state.Serialize(*stateElement); + } +} diff --git a/dGame/dPropertyBehaviors/PropertyBehavior.h b/dGame/dPropertyBehaviors/PropertyBehavior.h index c9cb4b98..50585f98 100644 --- a/dGame/dPropertyBehaviors/PropertyBehavior.h +++ b/dGame/dPropertyBehaviors/PropertyBehavior.h @@ -3,6 +3,10 @@ #include "State.h" +namespace tinyxml2 { + class XMLElement; +} + enum class BehaviorState : uint32_t; class AMFArrayValue; @@ -25,6 +29,7 @@ public: [[nodiscard]] int32_t GetBehaviorId() const noexcept { return m_BehaviorId; } void SetBehaviorId(int32_t id) noexcept { m_BehaviorId = id; } + void Serialize(tinyxml2::XMLElement& behavior) const; private: // The states this behavior has. diff --git a/dGame/dPropertyBehaviors/State.cpp b/dGame/dPropertyBehaviors/State.cpp index 0c8a11d9..9b321bad 100644 --- a/dGame/dPropertyBehaviors/State.cpp +++ b/dGame/dPropertyBehaviors/State.cpp @@ -2,6 +2,7 @@ #include "Amf3.h" #include "ControlBehaviorMsgs.h" +#include "tinyxml2.h" template <> void State::HandleMsg(AddStripMessage& msg) { @@ -134,4 +135,13 @@ void State::SendBehaviorBlocksToClient(AMFArrayValue& args) const { strip.SendBehaviorBlocksToClient(*stripArgs); } -}; +} + +void State::Serialize(tinyxml2::XMLElement& state) const { + for (const auto& strip : m_Strips) { + if (strip.IsEmpty()) continue; + + auto* const stripElement = state.InsertNewChildElement("Strip"); + strip.Serialize(*stripElement); + } +} diff --git a/dGame/dPropertyBehaviors/State.h b/dGame/dPropertyBehaviors/State.h index f0425763..e866b196 100644 --- a/dGame/dPropertyBehaviors/State.h +++ b/dGame/dPropertyBehaviors/State.h @@ -3,6 +3,10 @@ #include "Strip.h" +namespace tinyxml2 { + class XMLElement; +} + class AMFArrayValue; class State { @@ -13,6 +17,7 @@ public: void SendBehaviorBlocksToClient(AMFArrayValue& args) const; bool IsEmpty() const; + void Serialize(tinyxml2::XMLElement& state) const; private: std::vector<Strip> m_Strips; }; diff --git a/dGame/dPropertyBehaviors/Strip.cpp b/dGame/dPropertyBehaviors/Strip.cpp index 0f459e46..8271a240 100644 --- a/dGame/dPropertyBehaviors/Strip.cpp +++ b/dGame/dPropertyBehaviors/Strip.cpp @@ -2,6 +2,7 @@ #include "Amf3.h" #include "ControlBehaviorMsgs.h" +#include "tinyxml2.h" template <> void Strip::HandleMsg(AddStripMessage& msg) { @@ -83,4 +84,13 @@ void Strip::SendBehaviorBlocksToClient(AMFArrayValue& args) const { for (const auto& action : m_Actions) { action.SendBehaviorBlocksToClient(*actions); } -}; +} + +void Strip::Serialize(tinyxml2::XMLElement& strip) const { + auto* const positionElement = strip.InsertNewChildElement("Position"); + m_Position.Serialize(*positionElement); + for (const auto& action : m_Actions) { + auto* const actionElement = strip.InsertNewChildElement("Action"); + action.Serialize(*actionElement); + } +} diff --git a/dGame/dPropertyBehaviors/Strip.h b/dGame/dPropertyBehaviors/Strip.h index 107fee11..d8c6b9cf 100644 --- a/dGame/dPropertyBehaviors/Strip.h +++ b/dGame/dPropertyBehaviors/Strip.h @@ -6,6 +6,10 @@ #include <vector> +namespace tinyxml2 { + class XMLElement; +} + class AMFArrayValue; class Strip { @@ -16,6 +20,7 @@ public: void SendBehaviorBlocksToClient(AMFArrayValue& args) const; bool IsEmpty() const noexcept { return m_Actions.empty(); } + void Serialize(tinyxml2::XMLElement& strip) const; private: std::vector<Action> m_Actions; StripUiPosition m_Position; diff --git a/migrations/dlu/15_behavior_owner.sql b/migrations/dlu/15_behavior_owner.sql new file mode 100644 index 00000000..53e76f82 --- /dev/null +++ b/migrations/dlu/15_behavior_owner.sql @@ -0,0 +1,3 @@ +ALTER TABLE behaviors ADD COLUMN character_id BIGINT NOT NULL DEFAULT 0; +ALTER TABLE behaviors ADD COLUMN behavior_id BIGINT NOT NULL PRIMARY KEY; +ALTER TABLE behaviors DROP COLUMN id; From fd1c6ab2ea1c20dda82c0aeb27d4d43f3d29dfec Mon Sep 17 00:00:00 2001 From: David Markowitz <EmosewaMC@gmail.com> Date: Sat, 18 May 2024 02:12:23 -0700 Subject: [PATCH 37/78] Saving actually works this time --- dDatabase/GameDatabase/ITables/IBehaviors.h | 1 + dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/dDatabase/GameDatabase/ITables/IBehaviors.h b/dDatabase/GameDatabase/ITables/IBehaviors.h index 02b5178b..7e42869d 100644 --- a/dDatabase/GameDatabase/ITables/IBehaviors.h +++ b/dDatabase/GameDatabase/ITables/IBehaviors.h @@ -13,6 +13,7 @@ public: std::string behaviorInfo; }; + // This Add also takes care of updating if it exists. virtual void AddBehavior(const Info& info) = 0; virtual void RemoveBehavior(const uint32_t behaviorId) = 0; }; diff --git a/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp b/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp index 2f80b9df..4e36e4f6 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp @@ -47,8 +47,8 @@ void MySQLDatabase::UpdateModel(const LWOOBJID& propertyId, const NiPoint3& posi ExecuteUpdate( "UPDATE properties_contents SET x = ?, y = ?, z = ?, rx = ?, ry = ?, rz = ?, rw = ?, " "behavior_1 = ?, behavior_2 = ?, behavior_3 = ?, behavior_4 = ?, behavior_5 = ? WHERE id = ?;", - position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, rotation.w, propertyId, - behaviors[0].first, behaviors[1].first, behaviors[2].first, behaviors[3].first, behaviors[4].first); + position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, rotation.w, + behaviors[0].first, behaviors[1].first, behaviors[2].first, behaviors[3].first, behaviors[4].first, propertyId); } void MySQLDatabase::RemoveModel(const LWOOBJID& modelId) { From 0c4108e730bf54d78fe4c466527177c5de225e0a Mon Sep 17 00:00:00 2001 From: David Markowitz <EmosewaMC@gmail.com> Date: Sat, 18 May 2024 03:36:29 -0700 Subject: [PATCH 38/78] Add loading from database yahoo --- dDatabase/GameDatabase/ITables/IBehaviors.h | 5 +-- .../GameDatabase/ITables/IPropertyContents.h | 4 +-- dDatabase/GameDatabase/MySQL/MySQLDatabase.h | 7 ++-- .../GameDatabase/MySQL/Tables/Behaviors.cpp | 7 +++- .../MySQL/Tables/PropertyContents.cpp | 13 +++++-- dGame/Entity.cpp | 4 +-- dGame/dComponents/ModelComponent.cpp | 33 +++++++++++++++-- dGame/dComponents/ModelComponent.h | 4 ++- .../PropertyManagementComponent.cpp | 13 ++++++- .../ControlBehaviorMessages/Action.cpp | 35 +++++++++++++++++-- .../ControlBehaviorMessages/Action.h | 1 + .../StripUiPosition.cpp | 5 +++ .../ControlBehaviorMessages/StripUiPosition.h | 1 + dGame/dPropertyBehaviors/PropertyBehavior.cpp | 14 ++++++++ dGame/dPropertyBehaviors/PropertyBehavior.h | 1 + dGame/dPropertyBehaviors/State.cpp | 7 ++++ dGame/dPropertyBehaviors/State.h | 1 + dGame/dPropertyBehaviors/Strip.cpp | 12 +++++++ dGame/dPropertyBehaviors/Strip.h | 1 + 19 files changed, 149 insertions(+), 19 deletions(-) diff --git a/dDatabase/GameDatabase/ITables/IBehaviors.h b/dDatabase/GameDatabase/ITables/IBehaviors.h index 7e42869d..6b0f17b1 100644 --- a/dDatabase/GameDatabase/ITables/IBehaviors.h +++ b/dDatabase/GameDatabase/ITables/IBehaviors.h @@ -8,14 +8,15 @@ class IBehaviors { public: struct Info { - LWOOBJID behaviorId{}; + int32_t behaviorId{}; uint32_t characterId{}; std::string behaviorInfo; }; // This Add also takes care of updating if it exists. virtual void AddBehavior(const Info& info) = 0; - virtual void RemoveBehavior(const uint32_t behaviorId) = 0; + virtual std::string GetBehavior(const int32_t behaviorId) = 0; + virtual void RemoveBehavior(const int32_t behaviorId) = 0; }; #endif //!__IBEHAVIORS__H__ diff --git a/dDatabase/GameDatabase/ITables/IPropertyContents.h b/dDatabase/GameDatabase/ITables/IPropertyContents.h index 25271302..dda8fc11 100644 --- a/dDatabase/GameDatabase/ITables/IPropertyContents.h +++ b/dDatabase/GameDatabase/ITables/IPropertyContents.h @@ -16,7 +16,7 @@ public: LWOOBJID id{}; LOT lot{}; uint32_t ugcId{}; - std::array<LWOOBJID, 5> behaviors{}; + std::array<int32_t, 5> behaviors{}; }; // Inserts a new UGC model into the database. @@ -33,7 +33,7 @@ public: virtual void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) = 0; // Update the model position and rotation for the given property id. - virtual void UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<LWOOBJID, std::string>, 5>& behaviors) = 0; + virtual void UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) = 0; // Remove the model for the given property id. virtual void RemoveModel(const LWOOBJID& modelId) = 0; diff --git a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h index 29df611c..689622d0 100644 --- a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h +++ b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h @@ -74,7 +74,7 @@ public: std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override; void RemoveUnreferencedUgcModels() override; void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override; - void UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<LWOOBJID, std::string>, 5>& behaviors) override; + void UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) override; void RemoveModel(const LWOOBJID& modelId) override; void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override; void InsertNewBugReport(const IBugReports::Info& info) override; @@ -108,8 +108,9 @@ public: std::vector<IIgnoreList::Info> GetIgnoreList(const uint32_t playerId) override; void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override; std::vector<uint32_t> GetRewardCodesByAccountID(const uint32_t account_id) override; - void AddBehavior(const IBehaviors::Info& info) override; - void RemoveBehavior(const uint32_t characterId) override; + void AddBehavior(const IBehaviors::Info& info) override; + std::string GetBehavior(const int32_t behaviorId) override; + void RemoveBehavior(const int32_t characterId) override; private: // Generic query functions that can be used for any query. diff --git a/dDatabase/GameDatabase/MySQL/Tables/Behaviors.cpp b/dDatabase/GameDatabase/MySQL/Tables/Behaviors.cpp index 91bcbd81..f4647865 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/Behaviors.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/Behaviors.cpp @@ -9,6 +9,11 @@ void MySQLDatabase::AddBehavior(const IBehaviors::Info& info) { ); } -void MySQLDatabase::RemoveBehavior(const uint32_t behaviorId) { +void MySQLDatabase::RemoveBehavior(const int32_t behaviorId) { ExecuteDelete("DELETE FROM behaviors WHERE behavior_id = ?", behaviorId); } + +std::string MySQLDatabase::GetBehavior(const int32_t behaviorId) { + auto result = ExecuteSelect("SELECT behavior_info FROM behaviors WHERE behavior_id = ?", behaviorId); + return result->next() ? result->getString("behavior_info").c_str() : ""; +} diff --git a/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp b/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp index 4e36e4f6..05998785 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp @@ -1,7 +1,10 @@ #include "MySQLDatabase.h" std::vector<IPropertyContents::Model> MySQLDatabase::GetPropertyModels(const LWOOBJID& propertyId) { - auto result = ExecuteSelect("SELECT id, lot, x, y, z, rx, ry, rz, rw, ugc_id FROM properties_contents WHERE property_id = ?;", propertyId); + auto result = ExecuteSelect( + "SELECT id, lot, x, y, z, rx, ry, rz, rw, ugc_id, " + "behavior_1, behavior_2, behavior_3, behavior_4, behavior_5 " + "FROM properties_contents WHERE property_id = ?;", propertyId); std::vector<IPropertyContents::Model> toReturn; toReturn.reserve(result->rowsCount()); @@ -17,6 +20,12 @@ std::vector<IPropertyContents::Model> MySQLDatabase::GetPropertyModels(const LWO model.rotation.y = result->getFloat("ry"); model.rotation.z = result->getFloat("rz"); model.ugcId = result->getUInt64("ugc_id"); + model.behaviors[0] = result->getInt("behavior_1"); + model.behaviors[1] = result->getInt("behavior_2"); + model.behaviors[2] = result->getInt("behavior_3"); + model.behaviors[3] = result->getInt("behavior_4"); + model.behaviors[4] = result->getInt("behavior_5"); + toReturn.push_back(std::move(model)); } return toReturn; @@ -43,7 +52,7 @@ void MySQLDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const IPr } } -void MySQLDatabase::UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<LWOOBJID, std::string>, 5>& behaviors) { +void MySQLDatabase::UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) { ExecuteUpdate( "UPDATE properties_contents SET x = ?, y = ?, z = ?, rx = ?, ry = ?, rz = ?, rw = ?, " "behavior_1 = ?, behavior_2 = ?, behavior_3 = ?, behavior_4 = ?, behavior_5 = ? WHERE id = ?;", diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 2210403c..6699c595 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -225,7 +225,7 @@ void Entity::Initialize() { AddComponent<SimplePhysicsComponent>(simplePhysicsComponentID); - AddComponent<ModelComponent>(); + AddComponent<ModelComponent>()->LoadBehaviors(); AddComponent<RenderComponent>(); @@ -649,7 +649,7 @@ void Entity::Initialize() { } if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MODEL, -1) != -1 && !GetComponent<PetComponent>()) { - AddComponent<ModelComponent>(); + AddComponent<ModelComponent>()->LoadBehaviors(); if (!HasComponent(eReplicaComponentType::DESTROYABLE)) { auto* destroyableComponent = AddComponent<DestroyableComponent>(); destroyableComponent->SetHealth(1); diff --git a/dGame/dComponents/ModelComponent.cpp b/dGame/dComponents/ModelComponent.cpp index 91e31e39..733b17ee 100644 --- a/dGame/dComponents/ModelComponent.cpp +++ b/dGame/dComponents/ModelComponent.cpp @@ -8,6 +8,8 @@ #include "ControlBehaviorMsgs.h" #include "tinyxml2.h" +#include "Database.h" + ModelComponent::ModelComponent(Entity* parent) : Component(parent) { m_OriginalPosition = m_Parent->GetDefaultPosition(); m_OriginalRotation = m_Parent->GetDefaultRotation(); @@ -15,6 +17,31 @@ ModelComponent::ModelComponent(Entity* parent) : Component(parent) { m_userModelID = m_Parent->GetVarAs<LWOOBJID>(u"userModelID"); } +void ModelComponent::LoadBehaviors() { + auto behaviors = GeneralUtils::SplitString(m_Parent->GetVar<std::string>(u"userModelBehaviors"), ','); + for (const auto& behavior : behaviors) { + if (behavior.empty()) continue; + const auto behaviorId = GeneralUtils::TryParse<int32_t>(behavior); + if (!behaviorId.has_value() || behaviorId.value() == 0) continue; + LOG("Loading behavior %d", behaviorId.value()); + auto& inserted = m_Behaviors.emplace_back(); + inserted.SetBehaviorId(*behaviorId); + + const auto behaviorStr = Database::Get()->GetBehavior(behaviorId.value()); + + tinyxml2::XMLDocument behaviorXml; + auto res = behaviorXml.Parse(behaviorStr.c_str(), behaviorStr.size()); + LOG("Behavior %i %d: %s", res, behaviorId.value(), behaviorStr.c_str()); + + const auto* const behaviorRoot = behaviorXml.FirstChildElement("Behavior"); + if (!behaviorRoot) { + LOG("Failed to load behavior %d due to missing behavior root", behaviorId.value()); + continue; + } + inserted.Deserialize(*behaviorRoot); + } +} + void ModelComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { // ItemComponent Serialization. Pets do not get this serialization. if (!m_Parent->HasComponent(eReplicaComponentType::PET)) { @@ -74,8 +101,8 @@ void ModelComponent::MoveToInventory(MoveToInventoryMessage& msg) { // TODO move to the inventory } -std::array<std::pair<LWOOBJID, std::string>, 5> ModelComponent::GetBehaviorsForSave() const { - std::array<std::pair<LWOOBJID, std::string>, 5> toReturn; +std::array<std::pair<int32_t, std::string>, 5> ModelComponent::GetBehaviorsForSave() const { + std::array<std::pair<int32_t, std::string>, 5> toReturn{}; for (auto i = 0; i < m_Behaviors.size(); i++) { const auto& behavior = m_Behaviors.at(i); if (behavior.GetBehaviorId() == -1) continue; @@ -86,7 +113,7 @@ std::array<std::pair<LWOOBJID, std::string>, 5> ModelComponent::GetBehaviorsForS behavior.Serialize(*root); doc.InsertFirstChild(root); - tinyxml2::XMLPrinter printer(0, false, 0); + tinyxml2::XMLPrinter printer(0, true, 0); doc.Print(&printer); behaviorData = printer.CStr(); } diff --git a/dGame/dComponents/ModelComponent.h b/dGame/dComponents/ModelComponent.h index 5b76708f..ba225426 100644 --- a/dGame/dComponents/ModelComponent.h +++ b/dGame/dComponents/ModelComponent.h @@ -28,6 +28,8 @@ public: ModelComponent(Entity* parent); + void LoadBehaviors(); + void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; /** @@ -109,7 +111,7 @@ public: void VerifyBehaviors(); - std::array<std::pair<LWOOBJID, std::string>, 5> GetBehaviorsForSave() const; + std::array<std::pair<int32_t, std::string>, 5> GetBehaviorsForSave() const; private: /** diff --git a/dGame/dComponents/PropertyManagementComponent.cpp b/dGame/dComponents/PropertyManagementComponent.cpp index 6c393df1..ae046a26 100644 --- a/dGame/dComponents/PropertyManagementComponent.cpp +++ b/dGame/dComponents/PropertyManagementComponent.cpp @@ -595,6 +595,17 @@ void PropertyManagementComponent::Load() { settings.push_back(new LDFData<int>(u"componentWhitelist", 1)); } + std::ostringstream userModelBehavior; + bool firstAdded = false; + for (const auto& behavior : databaseModel.behaviors) { + if (behavior == LWOOBJID_EMPTY) continue; + if (firstAdded) userModelBehavior << ","; + userModelBehavior << behavior; + firstAdded = true; + } + + settings.push_back(new LDFData<std::string>(u"userModelBehaviors", userModelBehavior.str())); + node->config = settings; const auto spawnerId = Game::zoneManager->MakeSpawner(info); @@ -638,7 +649,7 @@ void PropertyManagementComponent::Save() { // save the behaviors of the model for (const auto& [behaviorId, behaviorStr] : modelBehaviors) { - if (behaviorStr.empty() || behaviorId == LWOOBJID_EMPTY) continue; + if (behaviorStr.empty() || behaviorId == -1 || behaviorId == 0) continue; IBehaviors::Info info { .behaviorId = behaviorId, .characterId = character->GetID(), diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.cpp b/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.cpp index 4c917b48..6a21be9b 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.cpp +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.cpp @@ -40,9 +40,40 @@ void Action::Serialize(tinyxml2::XMLElement& action) const { if (m_ValueParameterName.empty()) return; + action.SetAttribute("ValueParameterName", m_ValueParameterName.c_str()); + if (m_ValueParameterName == "Message") { - action.SetAttribute(m_ValueParameterName.c_str(), m_ValueParameterString.c_str()); + action.SetAttribute("Value", m_ValueParameterString.c_str()); } else { - action.SetAttribute(m_ValueParameterName.c_str(), m_ValueParameterDouble); + action.SetAttribute("Value", m_ValueParameterDouble); + } +} + +void Action::Deserialize(const tinyxml2::XMLElement& action) { + const char* type = nullptr; + action.QueryAttribute("Type", &type); + if (!type) { + LOG("No type found for an action?"); + return; + } + + m_Type = type; + + const char* valueParameterName = nullptr; + action.QueryAttribute("ValueParameterName", &valueParameterName); + if (valueParameterName) { + m_ValueParameterName = valueParameterName; + + if (m_ValueParameterName == "Message") { + const char* value = nullptr; + action.QueryAttribute("Value", &value); + if (value) { + m_ValueParameterString = value; + } else { + LOG("No value found for an action message?"); + } + } else { + action.QueryDoubleAttribute("Value", &m_ValueParameterDouble); + } } } diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.h index 2f618316..8146e08d 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.h @@ -25,6 +25,7 @@ public: void SendBehaviorBlocksToClient(AMFArrayValue& args) const; void Serialize(tinyxml2::XMLElement& action) const; + void Deserialize(const tinyxml2::XMLElement& action); private: double m_ValueParameterDouble{ 0.0 }; std::string m_Type{ "" }; diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.cpp b/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.cpp index 02f64879..ae153a5f 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.cpp +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.cpp @@ -27,3 +27,8 @@ void StripUiPosition::Serialize(tinyxml2::XMLElement& position) const { position.SetAttribute("x", m_XPosition); position.SetAttribute("y", m_YPosition); } + +void StripUiPosition::Deserialize(const tinyxml2::XMLElement& position) { + position.QueryDoubleAttribute("x", &m_XPosition); + position.QueryDoubleAttribute("y", &m_YPosition); +} diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.h index 7bc2087e..47501ff7 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.h @@ -20,6 +20,7 @@ public: [[nodiscard]] double GetY() const noexcept { return m_YPosition; } void Serialize(tinyxml2::XMLElement& position) const; + void Deserialize(const tinyxml2::XMLElement& position); private: double m_XPosition{ 0.0 }; double m_YPosition{ 0.0 }; diff --git a/dGame/dPropertyBehaviors/PropertyBehavior.cpp b/dGame/dPropertyBehaviors/PropertyBehavior.cpp index 278012ea..5bdb5827 100644 --- a/dGame/dPropertyBehaviors/PropertyBehavior.cpp +++ b/dGame/dPropertyBehaviors/PropertyBehavior.cpp @@ -139,3 +139,17 @@ void PropertyBehavior::Serialize(tinyxml2::XMLElement& behavior) const { state.Serialize(*stateElement); } } + + +void PropertyBehavior::Deserialize(const tinyxml2::XMLElement& behavior) { + m_Name = behavior.Attribute("name"); + behavior.QueryBoolAttribute("isLocked", &isLocked); + behavior.QueryBoolAttribute("isLoot", &isLoot); + + for (const auto* stateElement = behavior.FirstChildElement("State"); stateElement; stateElement = stateElement->NextSiblingElement("State")) { + int32_t stateId = -1; + stateElement->QueryIntAttribute("id", &stateId); + if (stateId < 0 || stateId > 5) continue; + m_States[static_cast<BehaviorState>(stateId)].Deserialize(*stateElement); + } +} diff --git a/dGame/dPropertyBehaviors/PropertyBehavior.h b/dGame/dPropertyBehaviors/PropertyBehavior.h index 50585f98..01eb1968 100644 --- a/dGame/dPropertyBehaviors/PropertyBehavior.h +++ b/dGame/dPropertyBehaviors/PropertyBehavior.h @@ -30,6 +30,7 @@ public: void SetBehaviorId(int32_t id) noexcept { m_BehaviorId = id; } void Serialize(tinyxml2::XMLElement& behavior) const; + void Deserialize(const tinyxml2::XMLElement& behavior); private: // The states this behavior has. diff --git a/dGame/dPropertyBehaviors/State.cpp b/dGame/dPropertyBehaviors/State.cpp index 9b321bad..1fb072c1 100644 --- a/dGame/dPropertyBehaviors/State.cpp +++ b/dGame/dPropertyBehaviors/State.cpp @@ -145,3 +145,10 @@ void State::Serialize(tinyxml2::XMLElement& state) const { strip.Serialize(*stripElement); } } + +void State::Deserialize(const tinyxml2::XMLElement& state) { + for (const auto* stripElement = state.FirstChildElement("Strip"); stripElement; stripElement = stripElement->NextSiblingElement("Strip")) { + auto& strip = m_Strips.emplace_back(); + strip.Deserialize(*stripElement); + } +} diff --git a/dGame/dPropertyBehaviors/State.h b/dGame/dPropertyBehaviors/State.h index e866b196..3e8c827f 100644 --- a/dGame/dPropertyBehaviors/State.h +++ b/dGame/dPropertyBehaviors/State.h @@ -18,6 +18,7 @@ public: bool IsEmpty() const; void Serialize(tinyxml2::XMLElement& state) const; + void Deserialize(const tinyxml2::XMLElement& state); private: std::vector<Strip> m_Strips; }; diff --git a/dGame/dPropertyBehaviors/Strip.cpp b/dGame/dPropertyBehaviors/Strip.cpp index 8271a240..d6dc050b 100644 --- a/dGame/dPropertyBehaviors/Strip.cpp +++ b/dGame/dPropertyBehaviors/Strip.cpp @@ -94,3 +94,15 @@ void Strip::Serialize(tinyxml2::XMLElement& strip) const { action.Serialize(*actionElement); } } + +void Strip::Deserialize(const tinyxml2::XMLElement& strip) { + const auto* positionElement = strip.FirstChildElement("Position"); + if (positionElement) { + m_Position.Deserialize(*positionElement); + } + + for (const auto* actionElement = strip.FirstChildElement("Action"); actionElement; actionElement = actionElement->NextSiblingElement("Action")) { + auto& action = m_Actions.emplace_back(); + action.Deserialize(*actionElement); + } +} diff --git a/dGame/dPropertyBehaviors/Strip.h b/dGame/dPropertyBehaviors/Strip.h index d8c6b9cf..8fd7d0fe 100644 --- a/dGame/dPropertyBehaviors/Strip.h +++ b/dGame/dPropertyBehaviors/Strip.h @@ -21,6 +21,7 @@ public: bool IsEmpty() const noexcept { return m_Actions.empty(); } void Serialize(tinyxml2::XMLElement& strip) const; + void Deserialize(const tinyxml2::XMLElement& strip); private: std::vector<Action> m_Actions; StripUiPosition m_Position; From 387c37505c4a58cbea203fd43b3b844efc02d575 Mon Sep 17 00:00:00 2001 From: David Markowitz <EmosewaMC@gmail.com> Date: Sat, 18 May 2024 03:39:25 -0700 Subject: [PATCH 39/78] undo debug changes --- dCommon/Logger.cpp | 4 ++-- dGame/Character.cpp | 4 ++-- dGame/dComponents/ControllablePhysicsComponent.cpp | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dCommon/Logger.cpp b/dCommon/Logger.cpp index 571b9661..1888f1eb 100644 --- a/dCommon/Logger.cpp +++ b/dCommon/Logger.cpp @@ -53,8 +53,8 @@ void Logger::vLog(const char* format, va_list args) { struct tm* time = localtime(&t); char timeStr[70]; strftime(timeStr, sizeof(timeStr), "[%d-%m-%y %H:%M:%S ", time); - char message[131072]; - vsnprintf(message, 131072, format, args); + char message[2048]; + vsnprintf(message, 2048, format, args); for (const auto& writer : m_Writers) { writer->Log(timeStr, message); } diff --git a/dGame/Character.cpp b/dGame/Character.cpp index 7e0f73ec..59a67462 100644 --- a/dGame/Character.cpp +++ b/dGame/Character.cpp @@ -239,7 +239,7 @@ void Character::SaveXMLToDatabase() { auto zoneInfo = Game::zoneManager->GetZone()->GetZoneID(); // lzid garbage, binary concat of zoneID, zoneInstance and zoneClone - // if (zoneInfo.GetMapID() != 0 && zoneInfo.GetCloneID() == 0 && !Game::zoneManager->GetDisableSaveLocation()) { + if (zoneInfo.GetMapID() != 0 && zoneInfo.GetCloneID() == 0 && !Game::zoneManager->GetDisableSaveLocation()) { uint64_t lzidConcat = zoneInfo.GetCloneID(); lzidConcat = (lzidConcat << 16) | uint16_t(zoneInfo.GetInstanceID()); lzidConcat = (lzidConcat << 16) | uint16_t(zoneInfo.GetMapID()); @@ -251,7 +251,7 @@ void Character::SaveXMLToDatabase() { // Set the target scene, custom attribute character->SetAttribute("tscene", m_TargetScene.c_str()); - // } + } auto emotes = character->FirstChildElement("ue"); if (!emotes) emotes = m_Doc.NewElement("ue"); diff --git a/dGame/dComponents/ControllablePhysicsComponent.cpp b/dGame/dComponents/ControllablePhysicsComponent.cpp index c969a7dd..18e4b19d 100644 --- a/dGame/dComponents/ControllablePhysicsComponent.cpp +++ b/dGame/dComponents/ControllablePhysicsComponent.cpp @@ -187,7 +187,7 @@ void ControllablePhysicsComponent::UpdateXml(tinyxml2::XMLDocument& doc) { auto zoneInfo = Game::zoneManager->GetZone()->GetZoneID(); - // if (zoneInfo.GetMapID() != 0 && zoneInfo.GetCloneID() == 0 && !Game::zoneManager->GetDisableSaveLocation()) { + if (zoneInfo.GetMapID() != 0 && zoneInfo.GetCloneID() == 0 && !Game::zoneManager->GetDisableSaveLocation()) { character->SetAttribute("lzx", m_Position.x); character->SetAttribute("lzy", m_Position.y); character->SetAttribute("lzz", m_Position.z); @@ -195,7 +195,7 @@ void ControllablePhysicsComponent::UpdateXml(tinyxml2::XMLDocument& doc) { character->SetAttribute("lzry", m_Rotation.y); character->SetAttribute("lzrz", m_Rotation.z); character->SetAttribute("lzrw", m_Rotation.w); - // } + } } void ControllablePhysicsComponent::SetPosition(const NiPoint3& pos) { From b3548de7da93f6bdbc6af4105df7c99c465540f6 Mon Sep 17 00:00:00 2001 From: David Markowitz <EmosewaMC@gmail.com> Date: Sat, 18 May 2024 03:52:36 -0700 Subject: [PATCH 40/78] debug logs and spacing --- dGame/dComponents/ModelComponent.cpp | 7 +++++-- dGame/dComponents/PropertyManagementComponent.cpp | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/dGame/dComponents/ModelComponent.cpp b/dGame/dComponents/ModelComponent.cpp index 733b17ee..91680987 100644 --- a/dGame/dComponents/ModelComponent.cpp +++ b/dGame/dComponents/ModelComponent.cpp @@ -21,9 +21,11 @@ void ModelComponent::LoadBehaviors() { auto behaviors = GeneralUtils::SplitString(m_Parent->GetVar<std::string>(u"userModelBehaviors"), ','); for (const auto& behavior : behaviors) { if (behavior.empty()) continue; + const auto behaviorId = GeneralUtils::TryParse<int32_t>(behavior); if (!behaviorId.has_value() || behaviorId.value() == 0) continue; - LOG("Loading behavior %d", behaviorId.value()); + + LOG_DEBUG("Loading behavior %d", behaviorId.value()); auto& inserted = m_Behaviors.emplace_back(); inserted.SetBehaviorId(*behaviorId); @@ -31,7 +33,7 @@ void ModelComponent::LoadBehaviors() { tinyxml2::XMLDocument behaviorXml; auto res = behaviorXml.Parse(behaviorStr.c_str(), behaviorStr.size()); - LOG("Behavior %i %d: %s", res, behaviorId.value(), behaviorStr.c_str()); + LOG_DEBUG("Behavior %i %d: %s", res, behaviorId.value(), behaviorStr.c_str()); const auto* const behaviorRoot = behaviorXml.FirstChildElement("Behavior"); if (!behaviorRoot) { @@ -108,6 +110,7 @@ std::array<std::pair<int32_t, std::string>, 5> ModelComponent::GetBehaviorsForSa if (behavior.GetBehaviorId() == -1) continue; auto& [id, behaviorData] = toReturn[i]; id = behavior.GetBehaviorId(); + tinyxml2::XMLDocument doc; auto* root = doc.NewElement("Behavior"); behavior.Serialize(*root); diff --git a/dGame/dComponents/PropertyManagementComponent.cpp b/dGame/dComponents/PropertyManagementComponent.cpp index ae046a26..d7be22ac 100644 --- a/dGame/dComponents/PropertyManagementComponent.cpp +++ b/dGame/dComponents/PropertyManagementComponent.cpp @@ -597,8 +597,11 @@ void PropertyManagementComponent::Load() { std::ostringstream userModelBehavior; bool firstAdded = false; - for (const auto& behavior : databaseModel.behaviors) { - if (behavior == LWOOBJID_EMPTY) continue; + for (auto behavior : databaseModel.behaviors) { + if (behavior < 0) { + LOG("Invalid behavior ID: %d, removing behavior reference from model", behavior); + behavior = 0; + } if (firstAdded) userModelBehavior << ","; userModelBehavior << behavior; firstAdded = true; From a50b2566897f9138f50dad02f777258d9451856c Mon Sep 17 00:00:00 2001 From: David Markowitz <EmosewaMC@gmail.com> Date: Sat, 18 May 2024 03:54:09 -0700 Subject: [PATCH 41/78] Update IBehaviors.h --- dDatabase/GameDatabase/ITables/IBehaviors.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dDatabase/GameDatabase/ITables/IBehaviors.h b/dDatabase/GameDatabase/ITables/IBehaviors.h index 6b0f17b1..531167e2 100644 --- a/dDatabase/GameDatabase/ITables/IBehaviors.h +++ b/dDatabase/GameDatabase/ITables/IBehaviors.h @@ -1,5 +1,5 @@ -#ifndef __IBEHAVIORS__H__ -#define __IBEHAVIORS__H__ +#ifndef IBEHAVIORS_H +#define IBEHAVIORS_H #include <cstdint> @@ -19,4 +19,4 @@ public: virtual void RemoveBehavior(const int32_t behaviorId) = 0; }; -#endif //!__IBEHAVIORS__H__ +#endif //!IBEHAVIORS_H From 00f36f3f285efdf125af5819b3e62ea7e487a4a3 Mon Sep 17 00:00:00 2001 From: David Markowitz <EmosewaMC@gmail.com> Date: Sat, 18 May 2024 04:15:07 -0700 Subject: [PATCH 42/78] missing include for windows --- dDatabase/GameDatabase/ITables/IPropertyContents.h | 1 + 1 file changed, 1 insertion(+) diff --git a/dDatabase/GameDatabase/ITables/IPropertyContents.h b/dDatabase/GameDatabase/ITables/IPropertyContents.h index dda8fc11..0d8d8b5c 100644 --- a/dDatabase/GameDatabase/ITables/IPropertyContents.h +++ b/dDatabase/GameDatabase/ITables/IPropertyContents.h @@ -1,6 +1,7 @@ #ifndef __IPROPERTIESCONTENTS__H__ #define __IPROPERTIESCONTENTS__H__ +#include <array> #include <cstdint> #include <string_view> From db2d4f02b5a4592d9c0fa0cd610835b9551ae89b Mon Sep 17 00:00:00 2001 From: David Markowitz <EmosewaMC@gmail.com> Date: Sat, 18 May 2024 04:16:07 -0700 Subject: [PATCH 43/78] preemptive include for windows --- dGame/dComponents/ModelComponent.h | 1 + 1 file changed, 1 insertion(+) diff --git a/dGame/dComponents/ModelComponent.h b/dGame/dComponents/ModelComponent.h index ba225426..9e23eafb 100644 --- a/dGame/dComponents/ModelComponent.h +++ b/dGame/dComponents/ModelComponent.h @@ -1,5 +1,6 @@ #pragma once +#include <array> #include <map> #include "dCommonVars.h" From d8f079cb1bb8d3285ff72598860b60661afc1c82 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 20 May 2024 00:43:57 -0700 Subject: [PATCH 44/78] fix mpc resetting on each world load (#1588) --- dGame/dGameMessages/GameMessages.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 93b10cec..1f757d7e 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -412,7 +412,8 @@ void GameMessages::SendPlatformResync(Entity* entity, const SystemAddress& sysAd bitStream.Write(qUnexpectedRotation.w); } - SEND_PACKET_BROADCAST; + if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; + SEND_PACKET; } void GameMessages::SendRestoreToPostLoadStats(Entity* entity, const SystemAddress& sysAddr) { From d6cac65a8dbb9254950ae0b28b612baf4b0ff193 Mon Sep 17 00:00:00 2001 From: TAHuntling <38479763+TAHuntling@users.noreply.github.com> Date: Tue, 21 May 2024 20:01:44 -0500 Subject: [PATCH 45/78] fix: Falling Off Edge in Pet Puzzle (#1584) * FloatFix * game activity setting * Update dNavMesh.cpp --------- Co-authored-by: David Markowitz <EmosewaMC@gmail.com> --- dGame/dComponents/PetComponent.cpp | 41 +++++++++++++++++++++++------- dNavigation/dNavMesh.cpp | 25 ++++++++++++++++++ dNavigation/dNavMesh.h | 4 +-- 3 files changed, 59 insertions(+), 11 deletions(-) diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index debe0bd8..073c09e1 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -32,6 +32,8 @@ #include "eGameMasterLevel.h" #include "eMissionState.h" #include "dNavMesh.h" +#include "eGameActivity.h" +#include "eStateChangeType.h" std::unordered_map<LWOOBJID, LWOOBJID> PetComponent::currentActivities{}; std::unordered_map<LWOOBJID, LWOOBJID> PetComponent::activePets{}; @@ -210,24 +212,23 @@ void PetComponent::OnUse(Entity* originator) { if (dpWorld::IsLoaded()) { NiPoint3 attempt = petPosition + forward * interactionDistance; - float y = dpWorld::GetNavMesh()->GetHeightAtPoint(attempt); + NiPoint3 nearestPoint = dpWorld::GetNavMesh()->NearestPoint(attempt); - while (std::abs(y - petPosition.y) > 4 && interactionDistance > 10) { + while (std::abs(nearestPoint.y - petPosition.y) > 4 && interactionDistance > 10) { const NiPoint3 forward = m_Parent->GetRotation().GetForwardVector(); attempt = originatorPosition + forward * interactionDistance; - y = dpWorld::GetNavMesh()->GetHeightAtPoint(attempt); + nearestPoint = dpWorld::GetNavMesh()->NearestPoint(attempt); interactionDistance -= 0.5f; } - position = attempt; + position = nearestPoint; } else { position = petPosition + forward * interactionDistance; } - auto rotation = NiQuaternion::LookAt(position, petPosition); GameMessages::SendNotifyPetTamingMinigame( @@ -246,11 +247,11 @@ void PetComponent::OnUse(Entity* originator) { m_Parent->GetObjectID(), LWOOBJID_EMPTY, originator->GetObjectID(), - true, + false, ePetTamingNotifyType::BEGIN, - petPosition, - position, - rotation, + NiPoint3Constant::ZERO, + NiPoint3Constant::ZERO, + NiQuaternion(0.0f, 0.0f, 0.0f, 0.0f), UNASSIGNED_SYSTEM_ADDRESS ); @@ -258,11 +259,18 @@ void PetComponent::OnUse(Entity* originator) { m_Tamer = originator->GetObjectID(); SetStatus(5); + Game::entityManager->SerializeEntity(m_Parent); currentActivities.insert_or_assign(m_Tamer, m_Parent->GetObjectID()); // Notify the start of a pet taming minigame m_Parent->GetScript()->OnNotifyPetTamingMinigame(m_Parent, originator, ePetTamingNotifyType::BEGIN); + + auto* characterComponent = originator->GetComponent<CharacterComponent>(); + if (characterComponent != nullptr) { + characterComponent->SetCurrentActivity(eGameActivity::PET_TAMING); + Game::entityManager->SerializeEntity(originator); + } } void PetComponent::Update(float deltaTime) { @@ -627,6 +635,11 @@ void PetComponent::RequestSetPetName(std::u16string name) { UNASSIGNED_SYSTEM_ADDRESS ); + auto* characterComponent = tamer->GetComponent<CharacterComponent>(); + if (characterComponent != nullptr) { + characterComponent->SetCurrentActivity(eGameActivity::NONE); + Game::entityManager->SerializeEntity(tamer); + } GameMessages::SendTerminateInteraction(m_Tamer, eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID()); auto* modelEntity = Game::entityManager->GetEntity(m_ModelId); @@ -666,6 +679,11 @@ void PetComponent::ClientExitTamingMinigame(bool voluntaryExit) { UNASSIGNED_SYSTEM_ADDRESS ); + auto* characterComponent = tamer->GetComponent<CharacterComponent>(); + if (characterComponent != nullptr) { + characterComponent->SetCurrentActivity(eGameActivity::NONE); + Game::entityManager->SerializeEntity(tamer); + } GameMessages::SendNotifyTamingModelLoadedOnServer(m_Tamer, tamer->GetSystemAddress()); GameMessages::SendTerminateInteraction(m_Tamer, eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID()); @@ -712,6 +730,11 @@ void PetComponent::ClientFailTamingMinigame() { UNASSIGNED_SYSTEM_ADDRESS ); + auto* characterComponent = tamer->GetComponent<CharacterComponent>(); + if (characterComponent != nullptr) { + characterComponent->SetCurrentActivity(eGameActivity::NONE); + Game::entityManager->SerializeEntity(tamer); + } GameMessages::SendNotifyTamingModelLoadedOnServer(m_Tamer, tamer->GetSystemAddress()); GameMessages::SendTerminateInteraction(m_Tamer, eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID()); diff --git a/dNavigation/dNavMesh.cpp b/dNavigation/dNavMesh.cpp index f49dd31e..d9584b00 100644 --- a/dNavigation/dNavMesh.cpp +++ b/dNavigation/dNavMesh.cpp @@ -112,6 +112,31 @@ void dNavMesh::LoadNavmesh() { m_NavMesh = mesh; } +NiPoint3 dNavMesh::NearestPoint(const NiPoint3& location, const float halfExtent) const { + NiPoint3 toReturn = location; + if (m_NavMesh != nullptr) { + float pos[3]; + pos[0] = location.x; + pos[1] = location.y; + pos[2] = location.z; + + dtPolyRef nearestRef = 0; + float polyPickExt[3] = { halfExtent, halfExtent, halfExtent }; + float nearestPoint[3] = { 0.0f, 0.0f, 0.0f }; + dtQueryFilter filter{}; + + auto hasPoly = m_NavQuery->findNearestPoly(pos, polyPickExt, &filter, &nearestRef, nearestPoint); + if (hasPoly != DT_SUCCESS) { + toReturn = location; + } else { + toReturn.x = nearestPoint[0]; + toReturn.y = nearestPoint[1]; + toReturn.z = nearestPoint[2]; + } + } + return toReturn; +} + float dNavMesh::GetHeightAtPoint(const NiPoint3& location, const float halfExtentsHeight) const { if (m_NavMesh == nullptr) { return location.y; diff --git a/dNavigation/dNavMesh.h b/dNavigation/dNavMesh.h index 8a55c649..60e07e7c 100644 --- a/dNavigation/dNavMesh.h +++ b/dNavigation/dNavMesh.h @@ -21,7 +21,7 @@ public: /** * Get the height at a point - * + * * @param location The location to check for height at. This is the center of the search area. * @param halfExtentsHeight The half extents height of the search area. This is the distance from the center to the top and bottom of the search area. * The larger the value of halfExtentsHeight is, the larger the performance cost of the query. @@ -29,7 +29,7 @@ public: */ float GetHeightAtPoint(const NiPoint3& location, const float halfExtentsHeight = 32.0f) const; std::vector<NiPoint3> GetPath(const NiPoint3& startPos, const NiPoint3& endPos, float speed = 10.0f); - + NiPoint3 NearestPoint(const NiPoint3& location, const float halfExtent = 32.0f) const; bool IsNavmeshLoaded() { return m_NavMesh != nullptr; } private: From ed00551982a76a5af8d90e27a653fc5902e72cdb Mon Sep 17 00:00:00 2001 From: TAHuntling <38479763+TAHuntling@users.noreply.github.com> Date: Tue, 21 May 2024 20:02:07 -0500 Subject: [PATCH 46/78] feat: Help Command Pagination (#1581) * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp * Fixed Comments Now able to do /command help to see info for said command. Additionally this works for aliases. Fixed serialization missing from merge. * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp * Update SlashCommandHandler.cpp --- dGame/dUtilities/SlashCommandHandler.cpp | 89 +++++++++++++++++------- 1 file changed, 65 insertions(+), 24 deletions(-) diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 3ba1ab38..9eccc268 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -8,6 +8,7 @@ #include "SlashCommandHandler.h" #include <iomanip> +#include <ranges> #include "DEVGMCommands.h" #include "GMGreaterThanZeroCommands.h" @@ -60,11 +61,9 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& chat, Entity* if (commandHandle.requiredLevel > eGameMasterLevel::CIVILIAN) Database::Get()->InsertSlashCommandUsage(entity->GetObjectID(), input); commandHandle.handle(entity, sysAddr, args); } else if (entity->GetGMLevel() != eGameMasterLevel::CIVILIAN) { - // We don't need to tell civilians they aren't high enough level error = "You are not high enough GM level to use \"" + command + "\""; } } else if (entity->GetGMLevel() == eGameMasterLevel::CIVILIAN) { - // We don't need to tell civilians commands don't exist error = "Command " + command + " does not exist!"; } @@ -75,32 +74,74 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& chat, Entity* void GMZeroCommands::Help(Entity* entity, const SystemAddress& sysAddr, const std::string args) { std::ostringstream feedback; - if (args.empty()) { - feedback << "----- Commands -----"; - for (const auto& [alias, command] : CommandInfos) { - // TODO: Limit displaying commands based on GM level they require - if (command.requiredLevel > entity->GetGMLevel()) continue; - LOG("Help command: %s", alias.c_str()); - feedback << "\n/" << alias << ": " << command.help; + constexpr size_t pageSize = 10; + + std::string trimmedArgs = args; + trimmedArgs.erase(trimmedArgs.begin(), std::find_if_not(trimmedArgs.begin(), trimmedArgs.end(), [](unsigned char ch) { + return std::isspace(ch); + })); + trimmedArgs.erase(std::find_if_not(trimmedArgs.rbegin(), trimmedArgs.rend(), [](unsigned char ch) { + return std::isspace(ch); + }).base(), trimmedArgs.end()); + + std::optional<uint32_t> parsedPage = GeneralUtils::TryParse<uint32_t>(trimmedArgs); + if (trimmedArgs.empty() || parsedPage.has_value()) { + size_t page = parsedPage.value_or(1); + + std::map<std::string, Command> accessibleCommands; + for (const auto& [commandName, command] : CommandInfos) { + if (command.requiredLevel <= entity->GetGMLevel()) { + accessibleCommands.emplace(commandName, command); + } + } + + size_t totalPages = (accessibleCommands.size() + pageSize - 1) / pageSize; + + if (page < 1 || page > totalPages) { + feedback << "Invalid page number. Total pages: " << totalPages; + GameMessages::SendSlashCommandFeedbackText(entity, GeneralUtils::ASCIIToUTF16(feedback.str())); + return; + } + + auto it = accessibleCommands.begin(); + std::advance(it, (page - 1) * pageSize); + size_t endIdx = std::min(page * pageSize, accessibleCommands.size()); + + feedback << "----- Commands (Page " << page << " of " << totalPages << ") -----"; + for (size_t i = (page - 1) * pageSize; i < endIdx; ++i, ++it) { + feedback << "\n/" << it->first << ": " << it->second.help; + } + + const auto feedbackStr = feedback.str(); + if (!feedbackStr.empty()) { + GameMessages::SendSlashCommandFeedbackText(entity, GeneralUtils::ASCIIToUTF16(feedbackStr)); + } + return; + } + + auto it = std::ranges::find_if(CommandInfos, [&trimmedArgs](const auto& pair) { + return std::ranges::find(pair.second.aliases, trimmedArgs) != pair.second.aliases.end(); + }); + + if (it != CommandInfos.end() && entity->GetGMLevel() >= it->second.requiredLevel) { + const auto& command = it->second; + feedback << "----- " << it->first << " Info -----\n"; + feedback << command.info << "\n"; + if (command.aliases.size() > 1) { + feedback << "Aliases: "; + for (size_t i = 0; i < command.aliases.size(); ++i) { + if (i > 0) feedback << ", "; + feedback << command.aliases[i]; + } } } else { - auto it = CommandInfos.find(args); - if (it != CommandInfos.end() && entity->GetGMLevel() >= it->second.requiredLevel) { - feedback << "----- " << args << " -----\n"; - feedback << it->second.info; - if (it->second.aliases.size() > 1) { - feedback << "\nAliases: "; - for (size_t i = 0; i < it->second.aliases.size(); i++) { - if (i > 0) feedback << ", "; - feedback << it->second.aliases[i]; - } - } - } else if (entity->GetGMLevel() > eGameMasterLevel::CIVILIAN) { - feedback << "Command " << std::quoted(args) << " does not exist!"; - } + feedback << "Command not found."; } + const auto feedbackStr = feedback.str(); - if (!feedbackStr.empty()) GameMessages::SendSlashCommandFeedbackText(entity, GeneralUtils::ASCIIToUTF16(feedbackStr)); + if (!feedbackStr.empty()) { + GameMessages::SendSlashCommandFeedbackText(entity, GeneralUtils::ASCIIToUTF16(feedbackStr)); + } } void SlashCommandHandler::SendAnnouncement(const std::string& title, const std::string& message) { From dea10c6d56dbb568df32b967843941af65b60182 Mon Sep 17 00:00:00 2001 From: TAHuntling <38479763+TAHuntling@users.noreply.github.com> Date: Wed, 22 May 2024 08:32:24 -0500 Subject: [PATCH 47/78] Client commands implementation (#1592) * Adding Client Commands Adding list of client commands provided to me by EmosewaMC * Finished adding client commands --- dGame/dUtilities/SlashCommandHandler.cpp | 379 ++++++++++++++++++ .../SlashCommands/GMZeroCommands.cpp | 4 + .../dUtilities/SlashCommands/GMZeroCommands.h | 1 + 3 files changed, 384 insertions(+) diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 9eccc268..428ccbcb 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -1048,4 +1048,383 @@ void SlashCommandHandler::Startup() { }; RegisterCommand(InstanceInfoCommand); + //Commands that are handled by the client + + Command faqCommand{ + .help = "Show the LU FAQ Page", + .info = "Show the LU FAQ Page", + .aliases = {"faq","faqs"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(faqCommand); + + Command teamChatCommand{ + .help = "Send a message to your teammates.", + .info = "Send a message to your teammates.", + .aliases = {"team","t"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(teamChatCommand); + + Command showStoreCommand{ + .help = "Show the LEGO shop page.", + .info = "Show the LEGO shop page.", + .aliases = {"shop","store"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(showStoreCommand); + + Command minigamesCommand{ + .help = "Show the LEGO minigames page!", + .info = "Show the LEGO minigames page!", + .aliases = {"minigames"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(minigamesCommand); + + Command forumsCommand{ + .help = "Show the LU Forums!", + .info = "Show the LU Forums!", + .aliases = {"forums"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(forumsCommand); + + Command exitGameCommand{ + .help = "Exit to desktop", + .info = "Exit to desktop", + .aliases = {"exit","quit"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(exitGameCommand); + + Command thumbsUpCommand{ + .help = "Oh, yeah!", + .info = "Oh, yeah!", + .aliases = {"thumb","thumbs","thumbsup"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(thumbsUpCommand); + + Command victoryCommand{ + .help = "Victory!", + .info = "Victory!", + .aliases = {"victory!"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(victoryCommand); + + Command backflipCommand{ + .help = "Do a flip!", + .info = "Do a flip!", + .aliases = {"backflip"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(backflipCommand); + + Command clapCommand{ + .help = "A round of applause!", + .info = "A round of applause!", + .aliases = {"clap"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(clapCommand); + + Command logoutCharacterCommand{ + .help = "Returns you to the character select screen.", + .info = "Returns you to the character select screen.", + .aliases = {"camp","logoutcharacter"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(logoutCharacterCommand); + + Command sayCommand{ + .help = "Say something outloud so that everyone can hear you", + .info = "Say something outloud so that everyone can hear you", + .aliases = {"s","say"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(sayCommand); + + Command whisperCommand{ + .help = "Send a private message to another player.", + .info = "Send a private message to another player.", + .aliases = {"tell","w","whisper"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(whisperCommand); + + Command locationCommand{ + .help = "Output your current location on the map to the chat box.", + .info = "Output your current location on the map to the chat box.", + .aliases = {"loc","locate","location"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(locationCommand); + + Command logoutCommand{ + .help = "Returns you to the login screen.", + .info = "Returns you to the login screen.", + .aliases = {"logout","logoutaccount"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(logoutCommand); + + Command shrugCommand{ + .help = "I dunno...", + .info = "I dunno...", + .aliases = {"shrug"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(shrugCommand); + + Command leaveTeamCommand{ + .help = "Leave your current team.", + .info = "Leave your current team.", + .aliases = {"leave","leaveteam","teamleave","tleave"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(leaveTeamCommand); + + Command teamLootTypeCommand{ + .help = "[rr|ffa] Set the loot for your current team (round-robin/free for all).", + .info = "[rr|ffa] Set the loot for your current team (round-robin/free for all).", + .aliases = {"setloot","teamsetloot","tloot","tsetloot"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(teamLootTypeCommand); + + Command removeFriendCommand{ + .help = "[name] Removes a player from your friends list.", + .info = "[name] Removes a player from your friends list.", + .aliases = {"removefriend"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(removeFriendCommand); + + Command yesCommand{ + .help = "Aye aye, captain!", + .info = "Aye aye, captain!", + .aliases = {"yes"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(yesCommand); + + Command teamInviteCommand{ + .help = "[name] Invite a player to your team.", + .info = "[name] Invite a player to your team.", + .aliases = {"invite","inviteteam","teaminvite","tinvite"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(teamInviteCommand); + + Command danceCommand{ + .help = "Dance 'til you can't dance no more.", + .info = "Dance 'til you can't dance no more.", + .aliases = {"dance"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(danceCommand); + + Command sighCommand{ + .help = "Another day, another brick.", + .info = "Another day, another brick.", + .aliases = {"sigh"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(sighCommand); + + Command recommendedOptionsCommand{ + .help = "Sets the recommended performance options in the cfg file", + .info = "Sets the recommended performance options in the cfg file", + .aliases = {"recommendedperfoptions"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(recommendedOptionsCommand); + + Command setTeamLeaderCommand{ + .help = "[name] Set the leader for your current team.", + .info = "[name] Set the leader for your current team.", + .aliases = {"leader","setleader","teamsetleader","tleader","tsetleader"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(setTeamLeaderCommand); + + Command cringeCommand{ + .help = "I don't even want to talk about it...", + .info = "I don't even want to talk about it...", + .aliases = {"cringe"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(cringeCommand); + + Command talkCommand{ + .help = "Jibber Jabber", + .info = "Jibber Jabber", + .aliases = {"talk"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(talkCommand); + + Command cancelQueueCommand{ + .help = "Cancel Your position in the queue if you are in one.", + .info = "Cancel Your position in the queue if you are in one.", + .aliases = {"cancelqueue"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(cancelQueueCommand); + + Command lowPerformanceCommand{ + .help = "Sets the default low-spec performance options in the cfg file", + .info = "Sets the default low-spec performance options in the cfg file", + .aliases = {"perfoptionslow"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(lowPerformanceCommand); + + Command kickFromTeamCommand{ + .help = "[name] Kick a player from your current team.", + .info = "[name] Kick a player from your current team.", + .aliases = {"kick","kickplayer","teamkickplayer","tkick","tkickplayer"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(kickFromTeamCommand); + + Command thanksCommand{ + .help = "Express your gratitude for another.", + .info = "Express your gratitude for another.", + .aliases = {"thanks"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(thanksCommand); + + Command waveCommand{ + .help = "Wave to other players.", + .info = "Wave to other players.", + .aliases = {"wave"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(waveCommand); + + Command whyCommand{ + .help = "Why|!?!!", + .info = "Why|!?!!", + .aliases = {"why"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(whyCommand); + + Command midPerformanceCommand{ + .help = "Sets the default medium-spec performance options in the cfg file", + .info = "Sets the default medium-spec performance options in the cfg file", + .aliases = {"perfoptionsmid"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(midPerformanceCommand); + + Command highPerformanceCommand{ + .help = "Sets the default high-spec performance options in the cfg file", + .info = "Sets the default high-spec performance options in the cfg file", + .aliases = {"perfoptionshigh"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(highPerformanceCommand); + + Command gaspCommand{ + .help = "Oh my goodness!", + .info = "Oh my goodness!", + .aliases = {"gasp"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(gaspCommand); + + Command ignoreCommand{ + .help = "[name] Add a player to your ignore list.", + .info = "[name] Add a player to your ignore list.", + .aliases = {"addignore"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(ignoreCommand); + + Command addFriendCommand{ + .help = "[name] Add a player to your friends list.", + .info = "[name] Add a player to your friends list.", + .aliases = {"addfriend"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(addFriendCommand); + + Command cryCommand{ + .help = "Show everyone your 'Aw' face.", + .info = "Show everyone your 'Aw' face.", + .aliases = {"cry"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(cryCommand); + + Command giggleCommand{ + .help = "A good little chuckle", + .info = "A good little chuckle", + .aliases = {"giggle"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(giggleCommand); + + Command saluteCommand{ + .help = "For those about to build...", + .info = "For those about to build...", + .aliases = {"salute"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(saluteCommand); + + Command removeIgnoreCommand{ + .help = "[name] Removes a player from your ignore list.", + .info = "[name] Removes a player from your ignore list.", + .aliases = {"removeIgnore"}, + .handle = GMZeroCommands::ClientHandled, + .requiredLevel = eGameMasterLevel::CIVILIAN + }; + RegisterCommand(removeIgnoreCommand); } diff --git a/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp b/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp index f183d5ea..6c9811c2 100644 --- a/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp +++ b/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp @@ -224,5 +224,9 @@ namespace GMZeroCommands { ChatPackets::SendSystemMessage(sysAddr, u"Map: " + (GeneralUtils::to_u16string(zoneId.GetMapID())) + u"\nClone: " + (GeneralUtils::to_u16string(zoneId.GetCloneID())) + u"\nInstance: " + (GeneralUtils::to_u16string(zoneId.GetInstanceID()))); } + + //For client side commands + void ClientHandled(Entity* entity, const SystemAddress& sysAddr, const std::string args) {} + }; diff --git a/dGame/dUtilities/SlashCommands/GMZeroCommands.h b/dGame/dUtilities/SlashCommands/GMZeroCommands.h index 3b2389b5..d3f6753d 100644 --- a/dGame/dUtilities/SlashCommands/GMZeroCommands.h +++ b/dGame/dUtilities/SlashCommands/GMZeroCommands.h @@ -15,6 +15,7 @@ namespace GMZeroCommands { void LeaveZone(Entity* entity, const SystemAddress& sysAddr, const std::string args); void Resurrect(Entity* entity, const SystemAddress& sysAddr, const std::string args); void InstanceInfo(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ClientHandled(Entity* entity, const SystemAddress& sysAddr, const std::string args); } #endif //!GMZEROCOMMANDS_H From dc430d975894e53f199282e75ff7c94d32151d09 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Wed, 22 May 2024 16:35:45 -0700 Subject: [PATCH 48/78] Add reputation as a repeatable mission reward (#1590) This reverts commit 7d1a28b492b263aba2008a5984dc0f5e7348a068. Add stubbing for abbreviations Reward reputation always if possible --- dGame/dMission/Mission.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/dGame/dMission/Mission.cpp b/dGame/dMission/Mission.cpp index c2ed2a42..2a841e39 100644 --- a/dGame/dMission/Mission.cpp +++ b/dGame/dMission/Mission.cpp @@ -454,6 +454,16 @@ void Mission::YieldRewards() { } } + // Even with no repeatable column, reputation is repeatable + if (info.reward_reputation > 0) { + missionComponent->Progress(eMissionTaskType::EARN_REPUTATION, 0, LWOOBJID_EMPTY, "", info.reward_reputation); + auto* const character = entity->GetComponent<CharacterComponent>(); + if (character) { + character->SetReputation(character->GetReputation() + info.reward_reputation); + GameMessages::SendUpdateReputation(entity->GetObjectID(), character->GetReputation(), entity->GetSystemAddress()); + } + } + if (m_Completions > 0) { std::vector<std::pair<LOT, uint32_t>> items; @@ -532,15 +542,6 @@ void Mission::YieldRewards() { modelInventory->SetSize(modelInventory->GetSize() + info.reward_bankinventory); } - if (info.reward_reputation > 0) { - missionComponent->Progress(eMissionTaskType::EARN_REPUTATION, 0, 0L, "", info.reward_reputation); - auto character = entity->GetComponent<CharacterComponent>(); - if (character) { - character->SetReputation(character->GetReputation() + info.reward_reputation); - GameMessages::SendUpdateReputation(entity->GetObjectID(), character->GetReputation(), entity->GetSystemAddress()); - } - } - if (info.reward_maxhealth > 0) { destroyableComponent->SetMaxHealth(destroyableComponent->GetMaxHealth() + static_cast<float>(info.reward_maxhealth), true); } From f0960d48b21d4d558ad378d6bcf9c7c0d6f8ba3d Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Wed, 22 May 2024 17:06:52 -0700 Subject: [PATCH 49/78] Add more modular saving of config data for items (#1591) * stubbing for saving item extra data * add declaration to header * modularize loading for all possible extra data * move logic to Item * remove extra map --- dCommon/LDFFormat.h | 74 ++++++++++++------------ dGame/dComponents/InventoryComponent.cpp | 41 ++++--------- dGame/dInventory/Item.cpp | 55 +++++++++++++++++- dGame/dInventory/Item.h | 14 +++++ 4 files changed, 115 insertions(+), 69 deletions(-) diff --git a/dCommon/LDFFormat.h b/dCommon/LDFFormat.h index 2cd9156c..054ddb42 100644 --- a/dCommon/LDFFormat.h +++ b/dCommon/LDFFormat.h @@ -31,22 +31,22 @@ public: virtual ~LDFBaseData() {} - virtual void WriteToPacket(RakNet::BitStream& packet) = 0; + virtual void WriteToPacket(RakNet::BitStream& packet) const = 0; - virtual const std::u16string& GetKey() = 0; + virtual const std::u16string& GetKey() const = 0; - virtual eLDFType GetValueType() = 0; + virtual eLDFType GetValueType() const = 0; /** Gets a string from the key/value pair * @param includeKey Whether or not to include the key in the data * @param includeTypeId Whether or not to include the type id in the data * @return The string representation of the data */ - virtual std::string GetString(bool includeKey = true, bool includeTypeId = true) = 0; + virtual std::string GetString(bool includeKey = true, bool includeTypeId = true) const = 0; - virtual std::string GetValueAsString() = 0; + virtual std::string GetValueAsString() const = 0; - virtual LDFBaseData* Copy() = 0; + virtual LDFBaseData* Copy() const = 0; /** * Given an input string, return the data as a LDF key. @@ -62,7 +62,7 @@ private: T value; //! Writes the key to the packet - void WriteKey(RakNet::BitStream& packet) { + void WriteKey(RakNet::BitStream& packet) const { packet.Write<uint8_t>(this->key.length() * sizeof(uint16_t)); for (uint32_t i = 0; i < this->key.length(); ++i) { packet.Write<uint16_t>(this->key[i]); @@ -70,7 +70,7 @@ private: } //! Writes the value to the packet - void WriteValue(RakNet::BitStream& packet) { + void WriteValue(RakNet::BitStream& packet) const { packet.Write<uint8_t>(this->GetValueType()); packet.Write(this->value); } @@ -90,7 +90,7 @@ public: /*! \return The value */ - const T& GetValue(void) { return this->value; } + const T& GetValue(void) const { return this->value; } //! Sets the value /*! @@ -102,13 +102,13 @@ public: /*! \return The value string */ - std::string GetValueString(void) { return ""; } + std::string GetValueString(void) const { return ""; } //! Writes the data to a packet /*! \param packet The packet */ - void WriteToPacket(RakNet::BitStream& packet) override { + void WriteToPacket(RakNet::BitStream& packet) const override { this->WriteKey(packet); this->WriteValue(packet); } @@ -117,13 +117,13 @@ public: /*! \return The key */ - const std::u16string& GetKey(void) override { return this->key; } + const std::u16string& GetKey(void) const override { return this->key; } //! Gets the LDF Type /*! \return The LDF value type */ - eLDFType GetValueType(void) override { return LDF_TYPE_UNKNOWN; } + eLDFType GetValueType(void) const override { return LDF_TYPE_UNKNOWN; } //! Gets the string data /*! @@ -131,7 +131,7 @@ public: \param includeTypeId Whether or not to include the type id in the data \return The string representation of the data */ - std::string GetString(const bool includeKey = true, const bool includeTypeId = true) override { + std::string GetString(const bool includeKey = true, const bool includeTypeId = true) const override { if (GetValueType() == -1) { return GeneralUtils::UTF16ToWTF8(this->key) + "=-1:<server variable>"; } @@ -154,11 +154,11 @@ public: return stream.str(); } - std::string GetValueAsString() override { + std::string GetValueAsString() const override { return this->GetValueString(); } - LDFBaseData* Copy() override { + LDFBaseData* Copy() const override { return new LDFData<T>(key, value); } @@ -166,19 +166,19 @@ public: }; // LDF Types -template<> inline eLDFType LDFData<std::u16string>::GetValueType(void) { return LDF_TYPE_UTF_16; }; -template<> inline eLDFType LDFData<int32_t>::GetValueType(void) { return LDF_TYPE_S32; }; -template<> inline eLDFType LDFData<float>::GetValueType(void) { return LDF_TYPE_FLOAT; }; -template<> inline eLDFType LDFData<double>::GetValueType(void) { return LDF_TYPE_DOUBLE; }; -template<> inline eLDFType LDFData<uint32_t>::GetValueType(void) { return LDF_TYPE_U32; }; -template<> inline eLDFType LDFData<bool>::GetValueType(void) { return LDF_TYPE_BOOLEAN; }; -template<> inline eLDFType LDFData<uint64_t>::GetValueType(void) { return LDF_TYPE_U64; }; -template<> inline eLDFType LDFData<LWOOBJID>::GetValueType(void) { return LDF_TYPE_OBJID; }; -template<> inline eLDFType LDFData<std::string>::GetValueType(void) { return LDF_TYPE_UTF_8; }; +template<> inline eLDFType LDFData<std::u16string>::GetValueType(void) const { return LDF_TYPE_UTF_16; }; +template<> inline eLDFType LDFData<int32_t>::GetValueType(void) const { return LDF_TYPE_S32; }; +template<> inline eLDFType LDFData<float>::GetValueType(void) const { return LDF_TYPE_FLOAT; }; +template<> inline eLDFType LDFData<double>::GetValueType(void) const { return LDF_TYPE_DOUBLE; }; +template<> inline eLDFType LDFData<uint32_t>::GetValueType(void) const { return LDF_TYPE_U32; }; +template<> inline eLDFType LDFData<bool>::GetValueType(void) const { return LDF_TYPE_BOOLEAN; }; +template<> inline eLDFType LDFData<uint64_t>::GetValueType(void) const { return LDF_TYPE_U64; }; +template<> inline eLDFType LDFData<LWOOBJID>::GetValueType(void) const { return LDF_TYPE_OBJID; }; +template<> inline eLDFType LDFData<std::string>::GetValueType(void) const { return LDF_TYPE_UTF_8; }; // The specialized version for std::u16string (UTF-16) template<> -inline void LDFData<std::u16string>::WriteValue(RakNet::BitStream& packet) { +inline void LDFData<std::u16string>::WriteValue(RakNet::BitStream& packet) const { packet.Write<uint8_t>(this->GetValueType()); packet.Write<uint32_t>(this->value.length()); @@ -189,7 +189,7 @@ inline void LDFData<std::u16string>::WriteValue(RakNet::BitStream& packet) { // The specialized version for bool template<> -inline void LDFData<bool>::WriteValue(RakNet::BitStream& packet) { +inline void LDFData<bool>::WriteValue(RakNet::BitStream& packet) const { packet.Write<uint8_t>(this->GetValueType()); packet.Write<uint8_t>(this->value); @@ -197,7 +197,7 @@ inline void LDFData<bool>::WriteValue(RakNet::BitStream& packet) { // The specialized version for std::string (UTF-8) template<> -inline void LDFData<std::string>::WriteValue(RakNet::BitStream& packet) { +inline void LDFData<std::string>::WriteValue(RakNet::BitStream& packet) const { packet.Write<uint8_t>(this->GetValueType()); packet.Write<uint32_t>(this->value.length()); @@ -206,18 +206,18 @@ inline void LDFData<std::string>::WriteValue(RakNet::BitStream& packet) { } } -template<> inline std::string LDFData<std::u16string>::GetValueString() { +template<> inline std::string LDFData<std::u16string>::GetValueString() const { return GeneralUtils::UTF16ToWTF8(this->value, this->value.size()); } -template<> inline std::string LDFData<int32_t>::GetValueString() { return std::to_string(this->value); } -template<> inline std::string LDFData<float>::GetValueString() { return std::to_string(this->value); } -template<> inline std::string LDFData<double>::GetValueString() { return std::to_string(this->value); } -template<> inline std::string LDFData<uint32_t>::GetValueString() { return std::to_string(this->value); } -template<> inline std::string LDFData<bool>::GetValueString() { return std::to_string(this->value); } -template<> inline std::string LDFData<uint64_t>::GetValueString() { return std::to_string(this->value); } -template<> inline std::string LDFData<LWOOBJID>::GetValueString() { return std::to_string(this->value); } +template<> inline std::string LDFData<int32_t>::GetValueString() const { return std::to_string(this->value); } +template<> inline std::string LDFData<float>::GetValueString() const { return std::to_string(this->value); } +template<> inline std::string LDFData<double>::GetValueString() const { return std::to_string(this->value); } +template<> inline std::string LDFData<uint32_t>::GetValueString() const { return std::to_string(this->value); } +template<> inline std::string LDFData<bool>::GetValueString() const { return std::to_string(this->value); } +template<> inline std::string LDFData<uint64_t>::GetValueString() const { return std::to_string(this->value); } +template<> inline std::string LDFData<LWOOBJID>::GetValueString() const { return std::to_string(this->value); } -template<> inline std::string LDFData<std::string>::GetValueString() { return this->value; } +template<> inline std::string LDFData<std::string>::GetValueString() const { return this->value; } #endif //!__LDFFORMAT__H__ diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 60dd071c..acb27796 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -558,19 +558,9 @@ void InventoryComponent::LoadXml(const tinyxml2::XMLDocument& document) { itemElement->QueryAttribute("parent", &parent); // End custom xml - std::vector<LDFBaseData*> config; + auto* item = new Item(id, lot, inventory, slot, count, bound, {}, parent, subKey); - auto* extraInfo = itemElement->FirstChildElement("x"); - - if (extraInfo) { - std::string modInfo = extraInfo->Attribute("ma"); - - LDFBaseData* moduleAssembly = new LDFData<std::u16string>(u"assemblyPartLOTs", GeneralUtils::ASCIIToUTF16(modInfo.substr(2, modInfo.size() - 1))); - - config.push_back(moduleAssembly); - } - - const auto* item = new Item(id, lot, inventory, slot, count, bound, config, parent, subKey); + item->LoadConfigXml(*itemElement); if (equipped) { const auto info = Inventory::FindItemComponent(lot); @@ -676,17 +666,7 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument& document) { itemElement->SetAttribute("parent", item->GetParent()); // End custom xml - for (auto* data : item->GetConfig()) { - if (data->GetKey() != u"assemblyPartLOTs") { - continue; - } - - auto* extraInfo = document.NewElement("x"); - - extraInfo->SetAttribute("ma", data->GetString(false).c_str()); - - itemElement->LinkEndChild(extraInfo); - } + item->SaveConfigXml(*itemElement); bagElement->LinkEndChild(itemElement); } @@ -1600,18 +1580,18 @@ void InventoryComponent::UpdatePetXml(tinyxml2::XMLDocument& document) { } -bool InventoryComponent::SetSkill(int32_t slot, uint32_t skillId){ +bool InventoryComponent::SetSkill(int32_t slot, uint32_t skillId) { BehaviorSlot behaviorSlot = BehaviorSlot::Invalid; - if (slot == 1 ) behaviorSlot = BehaviorSlot::Primary; - else if (slot == 2 ) behaviorSlot = BehaviorSlot::Offhand; - else if (slot == 3 ) behaviorSlot = BehaviorSlot::Neck; - else if (slot == 4 ) behaviorSlot = BehaviorSlot::Head; - else if (slot == 5 ) behaviorSlot = BehaviorSlot::Consumable; + if (slot == 1) behaviorSlot = BehaviorSlot::Primary; + else if (slot == 2) behaviorSlot = BehaviorSlot::Offhand; + else if (slot == 3) behaviorSlot = BehaviorSlot::Neck; + else if (slot == 4) behaviorSlot = BehaviorSlot::Head; + else if (slot == 5) behaviorSlot = BehaviorSlot::Consumable; else return false; return SetSkill(behaviorSlot, skillId); } -bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId){ +bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId) { if (skillId == 0) return false; const auto index = m_Skills.find(slot); if (index != m_Skills.end()) { @@ -1623,4 +1603,3 @@ bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId){ m_Skills.insert_or_assign(slot, skillId); return true; } - diff --git a/dGame/dInventory/Item.cpp b/dGame/dInventory/Item.cpp index b6193692..32603761 100644 --- a/dGame/dInventory/Item.cpp +++ b/dGame/dInventory/Item.cpp @@ -27,6 +27,23 @@ #include "CDComponentsRegistryTable.h" #include "CDPackageComponentTable.h" +namespace { + const std::map<std::string, std::string> ExtraSettingAbbreviations = { + { "assemblyPartLOTs", "ma" }, + { "blueprintID", "b" }, + { "userModelID", "ui" }, + { "userModelName", "un" }, + { "userModelDesc", "ud" }, + { "userModelHasBhvr", "ub" }, + { "userModelBehaviors", "ubh" }, + { "userModelBehaviorSourceID", "ubs" }, + { "userModelPhysicsType", "up" }, + { "userModelMod", "um" }, + { "userModelOpt", "uo" }, + { "reforgedLOT", "rl" }, + }; +} + Item::Item(const LWOOBJID id, const LOT lot, Inventory* inventory, const uint32_t slot, const uint32_t count, const bool bound, const std::vector<LDFBaseData*>& config, const LWOOBJID parent, LWOOBJID subKey, eLootSourceType lootSourceType) { if (!Inventory::IsValidItem(lot)) { return; @@ -122,6 +139,10 @@ uint32_t Item::GetSlot() const { return slot; } +std::vector<LDFBaseData*> Item::GetConfig() const { + return config; +} + std::vector<LDFBaseData*>& Item::GetConfig() { return config; } @@ -251,7 +272,7 @@ bool Item::Consume() { auto skills = skillsTable->Query([this](const CDObjectSkills entry) { return entry.objectTemplate == static_cast<uint32_t>(lot); - }); + }); auto success = false; @@ -515,3 +536,35 @@ Item::~Item() { config.clear(); } + +void Item::SaveConfigXml(tinyxml2::XMLElement& i) const { + tinyxml2::XMLElement* x = nullptr; + + for (const auto* config : this->config) { + const auto& key = GeneralUtils::UTF16ToWTF8(config->GetKey()); + const auto saveKey = ExtraSettingAbbreviations.find(key); + if (saveKey == ExtraSettingAbbreviations.end()) { + continue; + } + + if (!x) { + x = i.InsertNewChildElement("x"); + } + + const auto dataToSave = config->GetString(false); + x->SetAttribute(saveKey->second.c_str(), dataToSave.c_str()); + } +} + +void Item::LoadConfigXml(const tinyxml2::XMLElement& i) { + const auto* x = i.FirstChildElement("x"); + if (!x) return; + + for (const auto& pair : ExtraSettingAbbreviations) { + const auto* data = x->Attribute(pair.second.c_str()); + if (!data) continue; + + const auto value = pair.first + "=" + data; + config.push_back(LDFBaseData::DataFromString(value)); + } +} diff --git a/dGame/dInventory/Item.h b/dGame/dInventory/Item.h index 04d05d7c..72ff264c 100644 --- a/dGame/dInventory/Item.h +++ b/dGame/dInventory/Item.h @@ -9,6 +9,10 @@ #include "eInventoryType.h" #include "eLootSourceType.h" +namespace tinyxml2 { + class XMLElement; +}; + /** * An item that can be stored in an inventory and optionally consumed or equipped * TODO: ideally this should be a component @@ -116,6 +120,12 @@ public: */ std::vector<LDFBaseData*>& GetConfig(); + /** + * Returns current config info for this item, e.g. for rockets + * @return current config info for this item + */ + std::vector<LDFBaseData*> GetConfig() const; + /** * Returns the database info for this item * @return the database info for this item @@ -214,6 +224,10 @@ public: */ void RemoveFromInventory(); + void SaveConfigXml(tinyxml2::XMLElement& i) const; + + void LoadConfigXml(const tinyxml2::XMLElement& i); + private: /** * The object ID of this item From 8ae1a8ff7cb2b472eb42997b59976fc77b644b85 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Fri, 24 May 2024 07:15:30 -0700 Subject: [PATCH 50/78] fix stale reference (#1594) --- dGame/dComponents/VendorComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dGame/dComponents/VendorComponent.cpp b/dGame/dComponents/VendorComponent.cpp index abe11ea5..b9286d25 100644 --- a/dGame/dComponents/VendorComponent.cpp +++ b/dGame/dComponents/VendorComponent.cpp @@ -76,8 +76,8 @@ void VendorComponent::RefreshInventory(bool isCreation) { if (vendorItems.empty()) break; auto randomItemIndex = GeneralUtils::GenerateRandomNumber<int32_t>(0, vendorItems.size() - 1); const auto& randomItem = vendorItems.at(randomItemIndex); - vendorItems.erase(vendorItems.begin() + randomItemIndex); if (SetupItem(randomItem.itemid)) m_Inventory.push_back(SoldItem(randomItem.itemid, randomItem.sortPriority)); + vendorItems.erase(vendorItems.begin() + randomItemIndex); } } } From 8ca05241f260e8b8d92b42e497aa5dfe970450fc Mon Sep 17 00:00:00 2001 From: Aaron Kimbre <aronwk.aaron@gmail.com> Date: Fri, 24 May 2024 21:35:14 -0500 Subject: [PATCH 51/78] fix: prevent moving items between inventories under cetain circumsances --- ...eReponseMoveItemBetweenInventoryTypeCode.h | 21 ++++++++++ dGame/dGameMessages/GameMessages.cpp | 42 ++++++++++++++++++- dGame/dGameMessages/GameMessages.h | 2 + 3 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 dCommon/dEnums/eReponseMoveItemBetweenInventoryTypeCode.h diff --git a/dCommon/dEnums/eReponseMoveItemBetweenInventoryTypeCode.h b/dCommon/dEnums/eReponseMoveItemBetweenInventoryTypeCode.h new file mode 100644 index 00000000..b309f36d --- /dev/null +++ b/dCommon/dEnums/eReponseMoveItemBetweenInventoryTypeCode.h @@ -0,0 +1,21 @@ +#ifndef __EREPONSEMOVEITEMBETWEENINVENTORYTYPECODE__H__ +#define __EREPONSEMOVEITEMBETWEENINVENTORYTYPECODE__H__ + +#include <cstdint> + +enum class eReponseMoveItemBetweenInventoryTypeCode : uint32_t { + SUCCESS, + FAIL_GENERIC, + FAIL_INV_FULL, + FAIL_ITEM_NOT_FOUND, + FAIL_CANT_MOVE_TO_THAT_INV_TYPE, + FAIL_NOT_NEAR_BANK, + FAIL_CANT_SWAP_ITEMS, + FAIL_SOURCE_TYPE, + FAIL_WRONG_DEST_TYPE, + FAIL_SWAP_DEST_TYPE, + FAIL_CANT_MOVE_THINKING_HAT, + FAIL_DISMOUNT_BEFORE_MOVING +}; + +#endif //!__EREPONSEMOVEITEMBETWEENINVENTORYTYPECODE__H__ diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 1f757d7e..846a75ed 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -99,6 +99,7 @@ #include "ActivityManager.h" #include "PlayerManager.h" #include "eVendorTransactionResult.h" +#include "eReponseMoveItemBetweenInventoryTypeCode.h" #include "CDComponentsRegistryTable.h" #include "CDObjectsTable.h" @@ -4564,16 +4565,31 @@ void GameMessages::HandleRequestMoveItemBetweenInventoryTypes(RakNet::BitStream& if (inStream.ReadBit()) inStream.Read(itemLOT); if (invTypeDst == invTypeSrc) { + SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::FAIL_GENERIC); return; } auto* inventoryComponent = entity->GetComponent<InventoryComponent>(); - if (inventoryComponent != nullptr) { + if (inventoryComponent) { if (itemID != LWOOBJID_EMPTY) { auto* item = inventoryComponent->FindItemById(itemID); - if (!item) return; + if (!item) { + SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::FAIL_ITEM_NOT_FOUND); + return; + } + + if (item->GetLot() == 6086) { // Thinking hat + SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::FAIL_CANT_MOVE_THINKING_HAT); + return; + } + + auto* destInv = inventoryComponent->GetInventory(invTypeDst); + if (destInv && destInv->GetEmptySlots() == 0) { + SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::FAIL_INV_FULL); + return; + } // Despawn the pet if we are moving that pet to the vault. auto* petComponent = PetComponent::GetActivePet(entity->GetObjectID()); @@ -4582,10 +4598,32 @@ void GameMessages::HandleRequestMoveItemBetweenInventoryTypes(RakNet::BitStream& } inventoryComponent->MoveItemToInventory(item, invTypeDst, iStackCount, showFlyingLoot, false, false, destSlot); + SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::SUCCESS); } + } else { + SendResponseMoveItemBetweenInventoryTypes(entity->GetObjectID(), sysAddr, invTypeDst, invTypeSrc, eReponseMoveItemBetweenInventoryTypeCode::FAIL_GENERIC); } } +void GameMessages::SendResponseMoveItemBetweenInventoryTypes(LWOOBJID objectId, const SystemAddress& sysAddr, eInventoryType inventoryTypeDestination, eInventoryType inventoryTypeSource, eReponseMoveItemBetweenInventoryTypeCode response) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(objectId); + bitStream.Write(eGameMessageType::RESPONSE_MOVE_ITEM_BETWEEN_INVENTORY_TYPES); + + bitStream.Write(inventoryTypeDestination != eInventoryType::ITEMS); + if (inventoryTypeDestination != eInventoryType::ITEMS) bitStream.Write(inventoryTypeDestination); + + bitStream.Write(inventoryTypeSource != eInventoryType::ITEMS); + if (inventoryTypeSource != eInventoryType::ITEMS) bitStream.Write(inventoryTypeSource); + + bitStream.Write(response != eReponseMoveItemBetweenInventoryTypeCode::FAIL_GENERIC); + if (response != eReponseMoveItemBetweenInventoryTypeCode::FAIL_GENERIC) bitStream.Write(response); + + SEND_PACKET; +} + void GameMessages::SendShowActivityCountdown(LWOOBJID objectId, bool bPlayAdditionalSound, bool bPlayCountdownSound, std::u16string sndName, int32_t stateToPlaySoundOn, const SystemAddress& sysAddr) { CBITSTREAM; diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index b842710e..02c3e514 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -39,6 +39,7 @@ enum class eQuickBuildFailReason : uint32_t; enum class eQuickBuildState : uint32_t; enum class BehaviorSlot : int32_t; enum class eVendorTransactionResult : uint32_t; +enum class eReponseMoveItemBetweenInventoryTypeCode : int32_t; namespace GameMessages { class PropertyDataMessage; @@ -589,6 +590,7 @@ namespace GameMessages { //NT: void HandleRequestMoveItemBetweenInventoryTypes(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); + void SendResponseMoveItemBetweenInventoryTypes(LWOOBJID objectId, const SystemAddress& sysAddr, eInventoryType inventoryTypeDestination, eInventoryType inventoryTypeSource, eReponseMoveItemBetweenInventoryTypeCode response); void SendShowActivityCountdown(LWOOBJID objectId, bool bPlayAdditionalSound, bool bPlayCountdownSound, std::u16string sndName, int32_t stateToPlaySoundOn, const SystemAddress& sysAddr); From 86f335d64b7896865d04e5c4da759db6eeeb04a1 Mon Sep 17 00:00:00 2001 From: Aaron Kimbre <aronwk.aaron@gmail.com> Date: Fri, 24 May 2024 21:43:54 -0500 Subject: [PATCH 52/78] fix type --- dCommon/dEnums/eReponseMoveItemBetweenInventoryTypeCode.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dCommon/dEnums/eReponseMoveItemBetweenInventoryTypeCode.h b/dCommon/dEnums/eReponseMoveItemBetweenInventoryTypeCode.h index b309f36d..b99687d0 100644 --- a/dCommon/dEnums/eReponseMoveItemBetweenInventoryTypeCode.h +++ b/dCommon/dEnums/eReponseMoveItemBetweenInventoryTypeCode.h @@ -3,7 +3,7 @@ #include <cstdint> -enum class eReponseMoveItemBetweenInventoryTypeCode : uint32_t { +enum class eReponseMoveItemBetweenInventoryTypeCode : int32_t { SUCCESS, FAIL_GENERIC, FAIL_INV_FULL, From debc2a96e288c82f81aa8639e6ca724eb7772ae4 Mon Sep 17 00:00:00 2001 From: TAHuntling <38479763+TAHuntling@users.noreply.github.com> Date: Sat, 25 May 2024 03:43:32 -0500 Subject: [PATCH 53/78] Update CppScripts exclusion list (#1597) --- dScripts/CppScripts.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dScripts/CppScripts.cpp b/dScripts/CppScripts.cpp index 784edbdb..73452c47 100644 --- a/dScripts/CppScripts.cpp +++ b/dScripts/CppScripts.cpp @@ -700,7 +700,8 @@ CppScripts::Script* const CppScripts::GetScript(Entity* parent, const std::strin (scriptName == "scripts\\02_server\\Enemy\\General\\L_BASE_ENEMY_SPIDERLING.lua") || (scriptName == "scripts\\ai\\FV\\L_ACT_NINJA_STUDENT.lua") || (scriptName == "scripts\\ai\\WILD\\L_WILD_GF_FROG.lua") || - (scriptName == "scripts\\empty.lua") + (scriptName == "scripts\\empty.lua") || + (scriptName == "scripts\\ai\\AG\\L_AG_SENTINEL_GUARD.lua") )) LOG_DEBUG("LOT %i attempted to load CppScript for '%s', but returned InvalidScript.", parent->GetLOT(), scriptName.c_str()); } From 0348db72a593dbdb166e37e8672201aa9aee5a22 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sat, 25 May 2024 10:24:02 -0700 Subject: [PATCH 54/78] fiux mission (#1596) --- .../02_server/Enemy/AG/BossSpiderQueenEnemyServer.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.cpp b/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.cpp index 40b248f5..b64bb7a8 100644 --- a/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.cpp +++ b/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.cpp @@ -15,6 +15,7 @@ #include "SkillComponent.h" #include "eReplicaComponentType.h" #include "RenderComponent.h" +#include "PlayerManager.h" #include <vector> @@ -53,11 +54,13 @@ void BossSpiderQueenEnemyServer::OnStartup(Entity* self) { void BossSpiderQueenEnemyServer::OnDie(Entity* self, Entity* killer) { if (Game::zoneManager->GetZoneID().GetMapID() == instanceZoneID && killer) { - auto* missionComponent = killer->GetComponent<MissionComponent>(); - if (missionComponent == nullptr) - return; + for (const auto& player : PlayerManager::GetAllPlayers()) { + auto* missionComponent = player->GetComponent<MissionComponent>(); + if (missionComponent == nullptr) + return; - missionComponent->CompleteMission(instanceMissionID); + missionComponent->CompleteMission(instanceMissionID); + } } // There is suppose to be a 0.1 second delay here but that may be admitted? From e59525d2ae95acab72de1115c4dd76f6d00a970d Mon Sep 17 00:00:00 2001 From: David Markowitz <EmosewaMC@gmail.com> Date: Sat, 25 May 2024 19:32:18 -0700 Subject: [PATCH 55/78] Update CppScripts.cpp --- dScripts/CppScripts.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dScripts/CppScripts.cpp b/dScripts/CppScripts.cpp index 73452c47..58ef948e 100644 --- a/dScripts/CppScripts.cpp +++ b/dScripts/CppScripts.cpp @@ -580,6 +580,7 @@ namespace { {"scripts\\02_server\\Map\\AM\\L_SKULLKIN_DRILL_STAND.lua", []() {return new AmSkullkinDrillStand();}}, {"scripts\\02_server\\Map\\AM\\L_SKULLKIN_TOWER.lua", []() {return new AmSkullkinTower();}}, {"scripts\\02_server\\Enemy\\AM\\L_AM_NAMED_DARKLING_DRAGON.lua", []() {return new AmDarklingDragon();}}, + {"scripts\\02_server\\Enemy\\AM\\L_AM_DARKLING_DRAGON.lua", []() {return new AmDarklingDragon();}}, {"scripts\\02_server\\Enemy\\AM\\L_AM_DARKLING_APE.lua", []() {return new BaseEnemyApe();}}, {"scripts\\02_server\\Map\\AM\\L_BLUE_X.lua", []() {return new AmBlueX();}}, {"scripts\\02_server\\Map\\AM\\L_TEAPOT_SERVER.lua", []() {return new AmTeapotServer();}}, @@ -654,6 +655,7 @@ namespace { //Pickups {"scripts\\ai\\SPEC\\L_SPECIAL_1_BRONZE-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(1);}}, + {"scripts\\ai\\SPEC\\L_SPECIAL_1_GOLD-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(10000);}}, {"scripts\\ai\\SPEC\\L_SPECIAL_1_SILVER-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(100);}}, {"scripts\\ai\\SPEC\\L_SPECIAL_10_BRONZE-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(10);}}, {"scripts\\ai\\SPEC\\L_SPECIAL_10_GOLD-COIN-SPAWNER.lua", []() {return new SpecialCoinSpawner(100000);}}, From efa658bc31cd1597d4f50ded3dc688f223cb7f56 Mon Sep 17 00:00:00 2001 From: David Markowitz <EmosewaMC@gmail.com> Date: Sat, 25 May 2024 19:59:15 -0700 Subject: [PATCH 56/78] fix players using non-car skills in a race --- dGame/dComponents/RacingControlComponent.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dGame/dComponents/RacingControlComponent.cpp b/dGame/dComponents/RacingControlComponent.cpp index 21d39249..b896d3f2 100644 --- a/dGame/dComponents/RacingControlComponent.cpp +++ b/dGame/dComponents/RacingControlComponent.cpp @@ -25,6 +25,7 @@ #include "LeaderboardManager.h" #include "dZoneManager.h" #include "CDActivitiesTable.h" +#include "eStateChangeType.h" #include <ctime> #ifndef M_PI @@ -77,6 +78,9 @@ void RacingControlComponent::OnPlayerLoaded(Entity* player) { m_LoadedPlayers++; + // not live accurate to stun the player but prevents them from using skills during the race that are not meant to be used. + GameMessages::SendSetStunned(player->GetObjectID(), eStateChangeType::PUSH, player->GetSystemAddress(), LWOOBJID_EMPTY, true, true, true, true, true, true, true, true, true); + LOG("Loading player %i", m_LoadedPlayers); m_LobbyPlayers.push_back(player->GetObjectID()); From e966d3a644f7a2b34efbc3855cdee6c99aeaef01 Mon Sep 17 00:00:00 2001 From: TAHuntling <38479763+TAHuntling@users.noreply.github.com> Date: Mon, 27 May 2024 01:24:48 -0500 Subject: [PATCH 57/78] Chore: split VE script up (#1598) * Testing Scripts Testing splitting AgSpaceStuff into AgSpaceStuff and AgShipShake * fixed inclusions * Removed DoShake * cleaning up * consistent if statements * Update dScripts/ai/AG/AgShipShake.h Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> --------- Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> --- dScripts/CppScripts.cpp | 2 + dScripts/ai/AG/AgShipShake.cpp | 81 +++++++++++++++++++++++++++++++++ dScripts/ai/AG/AgShipShake.h | 16 +++++++ dScripts/ai/AG/AgSpaceStuff.cpp | 80 -------------------------------- dScripts/ai/AG/AgSpaceStuff.h | 9 ---- dScripts/ai/AG/CMakeLists.txt | 1 + 6 files changed, 100 insertions(+), 89 deletions(-) create mode 100644 dScripts/ai/AG/AgShipShake.cpp create mode 100644 dScripts/ai/AG/AgShipShake.h diff --git a/dScripts/CppScripts.cpp b/dScripts/CppScripts.cpp index 58ef948e..ed0de2ba 100644 --- a/dScripts/CppScripts.cpp +++ b/dScripts/CppScripts.cpp @@ -14,6 +14,7 @@ #include "AgShipPlayerDeathTrigger.h" #include "AgShipPlayerShockServer.h" #include "AgSpaceStuff.h" +#include "AgShipShake.h" #include "AgImagSmashable.h" #include "NpcNpSpacemanBob.h" #include "StoryBoxInteractServer.h" @@ -341,6 +342,7 @@ namespace { { "scripts\\ai\\AG\\L_AG_SHIP_PLAYER_DEATH_TRIGGER.lua", []() { return new AgShipPlayerDeathTrigger(); } }, {"scripts\\ai\\NP\\L_NPC_NP_SPACEMAN_BOB.lua", []() { return new NpcNpSpacemanBob(); } }, {"scripts\\ai\\AG\\L_AG_SPACE_STUFF.lua", []() { return new AgSpaceStuff();} }, + {"scripts\\ai\\AG\\L_AG_SHIP_SHAKE.lua", []() { return new AgShipShake();}}, {"scripts\\ai\\AG\\L_AG_SHIP_PLAYER_SHOCK_SERVER.lua", []() { return new AgShipPlayerShockServer();} }, {"scripts\\ai\\AG\\L_AG_IMAG_SMASHABLE.lua", []() { return new AgImagSmashable();} }, {"scripts\\02_server\\Map\\General\\L_STORY_BOX_INTERACT_SERVER.lua", []() { return new StoryBoxInteractServer();} }, diff --git a/dScripts/ai/AG/AgShipShake.cpp b/dScripts/ai/AG/AgShipShake.cpp new file mode 100644 index 00000000..7dd2323a --- /dev/null +++ b/dScripts/ai/AG/AgShipShake.cpp @@ -0,0 +1,81 @@ +#include "AgShipShake.h" +#include "EntityInfo.h" +#include "GeneralUtils.h" +#include "GameMessages.h" +#include "EntityManager.h" +#include "RenderComponent.h" +#include "Entity.h" + +void AgShipShake::OnStartup(Entity* self) { + EntityInfo info{}; + + info.pos = { -418, 585, -30 }; + info.lot = 33; + info.spawnerID = self->GetObjectID(); + + auto* ref = Game::entityManager->CreateEntity(info); + + Game::entityManager->ConstructEntity(ref); + + self->SetVar(u"ShakeObject", ref->GetObjectID()); + + self->AddTimer("ShipShakeIdle", 2.0f); + self->SetVar(u"RandomTime", 10); +} + +void AgShipShake::OnTimerDone(Entity* self, std::string timerName) { + auto* shipFxObject = GetEntityInGroup(ShipFX); + auto* shipFxObject2 = GetEntityInGroup(ShipFX2); + auto* debrisObject = GetEntityInGroup(DebrisFX); + if (timerName == "ShipShakeIdle") { + auto* ref = Game::entityManager->GetEntity(self->GetVar<LWOOBJID>(u"ShakeObject")); + + const auto randomTime = self->GetVar<int>(u"RandomTime"); + auto time = GeneralUtils::GenerateRandomNumber<int>(0, randomTime + 1); + + if (time < randomTime / 2) { + time += randomTime / 2; + } + + self->AddTimer("ShipShakeIdle", static_cast<float>(time)); + + if (ref) + GameMessages::SendPlayEmbeddedEffectOnAllClientsNearObject(ref, FXName, ref->GetObjectID(), 500.0f); + + + if (debrisObject) + GameMessages::SendPlayFXEffect(debrisObject, -1, u"DebrisFall", "Debris", LWOOBJID_EMPTY, 1.0f, 1.0f, true); + + const auto randomFx = GeneralUtils::GenerateRandomNumber<int>(0, 3); + + if (shipFxObject) { + std::string effectType = "shipboom" + std::to_string(randomFx); + GameMessages::SendPlayFXEffect(shipFxObject, 559, GeneralUtils::ASCIIToUTF16(effectType), "FX", LWOOBJID_EMPTY, 1.0f, 1.0f, true); + } + + self->AddTimer("ShipShakeExplode", 5.0f); + + if (shipFxObject2) + RenderComponent::PlayAnimation(shipFxObject2, u"explosion"); + } else if (timerName == "ShipShakeExplode") { + if (shipFxObject) + RenderComponent::PlayAnimation(shipFxObject, u"idle"); + if (shipFxObject2) + RenderComponent::PlayAnimation(shipFxObject2, u"idle"); + } +} + +Entity* AgShipShake::GetEntityInGroup(const std::string& group) { + auto entities = Game::entityManager->GetEntitiesInGroup(group); + Entity* en = nullptr; + + for (auto entity : entities) { + if (entity) { + en = entity; + break; + } + } + + return en; +} + diff --git a/dScripts/ai/AG/AgShipShake.h b/dScripts/ai/AG/AgShipShake.h new file mode 100644 index 00000000..4cc26f96 --- /dev/null +++ b/dScripts/ai/AG/AgShipShake.h @@ -0,0 +1,16 @@ +#pragma once +#include "CppScripts.h" + +class AgShipShake : public CppScripts::Script { +public: + void OnStartup(Entity* self) override; + void OnTimerDone(Entity* self, std::string timerName) override; + + std::string DebrisFX = "DebrisFX"; + std::string ShipFX = "ShipFX"; + std::string ShipFX2 = "ShipFX2"; + std::u16string FXName = u"camshake-bridge"; + +private: + Entity* GetEntityInGroup(const std::string& group); +}; diff --git a/dScripts/ai/AG/AgSpaceStuff.cpp b/dScripts/ai/AG/AgSpaceStuff.cpp index 130d4354..dedbd08c 100644 --- a/dScripts/ai/AG/AgSpaceStuff.cpp +++ b/dScripts/ai/AG/AgSpaceStuff.cpp @@ -8,21 +8,6 @@ void AgSpaceStuff::OnStartup(Entity* self) { self->AddTimer("FloaterScale", 5.0f); - - EntityInfo info{}; - - info.pos = { -418, 585, -30 }; - info.lot = 33; - info.spawnerID = self->GetObjectID(); - - auto* ref = Game::entityManager->CreateEntity(info); - - Game::entityManager->ConstructEntity(ref); - - self->SetVar(u"ShakeObject", ref->GetObjectID()); - - self->AddTimer("ShipShakeIdle", 2.0f); - self->SetVar(u"RandomTime", 10); } void AgSpaceStuff::OnTimerDone(Entity* self, std::string timerName) { @@ -37,70 +22,5 @@ void AgSpaceStuff::OnTimerDone(Entity* self, std::string timerName) { RenderComponent::PlayAnimation(self, u"path_0" + (GeneralUtils::to_u16string(pathType))); self->AddTimer("FloaterScale", randTime); - } else if (timerName == "ShipShakeExplode") { - DoShake(self, true); - } else if (timerName == "ShipShakeIdle") { - DoShake(self, false); } } - -void AgSpaceStuff::DoShake(Entity* self, bool explodeIdle) { - - if (!explodeIdle) { - auto* ref = Game::entityManager->GetEntity(self->GetVar<LWOOBJID>(u"ShakeObject")); - - const auto randomTime = self->GetVar<int>(u"RandomTime"); - auto time = GeneralUtils::GenerateRandomNumber<int>(0, randomTime + 1); - - if (time < randomTime / 2) { - time += randomTime / 2; - } - - self->AddTimer("ShipShakeIdle", static_cast<float>(time)); - - if (ref) - GameMessages::SendPlayEmbeddedEffectOnAllClientsNearObject(ref, FXName, ref->GetObjectID(), 500.0f); - - auto* debrisObject = GetEntityInGroup(DebrisFX); - - if (debrisObject) - GameMessages::SendPlayFXEffect(debrisObject, -1, u"DebrisFall", "Debris", LWOOBJID_EMPTY, 1.0f, 1.0f, true); - - const auto randomFx = GeneralUtils::GenerateRandomNumber<int>(0, 3); - - auto* shipFxObject = GetEntityInGroup(ShipFX); - if (shipFxObject) { - std::string effectType = "shipboom" + std::to_string(randomFx); - GameMessages::SendPlayFXEffect(shipFxObject, 559, GeneralUtils::ASCIIToUTF16(effectType), "FX", LWOOBJID_EMPTY, 1.0f, 1.0f, true); - } - - self->AddTimer("ShipShakeExplode", 5.0f); - - auto* shipFxObject2 = GetEntityInGroup(ShipFX2); - if (shipFxObject2) - RenderComponent::PlayAnimation(shipFxObject2, u"explosion"); - } else { - auto* shipFxObject = GetEntityInGroup(ShipFX); - auto* shipFxObject2 = GetEntityInGroup(ShipFX2); - - if (shipFxObject) - RenderComponent::PlayAnimation(shipFxObject, u"idle"); - - if (shipFxObject2) - RenderComponent::PlayAnimation(shipFxObject2, u"idle"); - } -} - -Entity* AgSpaceStuff::GetEntityInGroup(const std::string& group) { - auto entities = Game::entityManager->GetEntitiesInGroup(group); - Entity* en = nullptr; - - for (auto entity : entities) { - if (entity) { - en = entity; - break; - } - } - - return en; -} diff --git a/dScripts/ai/AG/AgSpaceStuff.h b/dScripts/ai/AG/AgSpaceStuff.h index 8d816691..568f2baf 100644 --- a/dScripts/ai/AG/AgSpaceStuff.h +++ b/dScripts/ai/AG/AgSpaceStuff.h @@ -5,14 +5,5 @@ class AgSpaceStuff : public CppScripts::Script { public: void OnStartup(Entity* self); void OnTimerDone(Entity* self, std::string timerName); - void DoShake(Entity* self, bool explodeIdle); - - std::string DebrisFX = "DebrisFX"; - std::string ShipFX = "ShipFX"; - std::string ShipFX2 = "ShipFX2"; - std::u16string FXName = u"camshake-bridge"; - -private: - Entity* GetEntityInGroup(const std::string& group); }; diff --git a/dScripts/ai/AG/CMakeLists.txt b/dScripts/ai/AG/CMakeLists.txt index e74aac78..101f86fd 100644 --- a/dScripts/ai/AG/CMakeLists.txt +++ b/dScripts/ai/AG/CMakeLists.txt @@ -1,6 +1,7 @@ set(DSCRIPTS_SOURCES_AI_AG "AgShipPlayerDeathTrigger.cpp" "AgSpaceStuff.cpp" + "AgShipShake.cpp" "AgShipPlayerShockServer.cpp" "AgImagSmashable.cpp" "ActSharkPlayerDeathTrigger.cpp" From cce57553661c9287607a2965960fcfaaf4a62318 Mon Sep 17 00:00:00 2001 From: Remco Hofman <remcohofman98@gmail.com> Date: Tue, 28 May 2024 02:46:09 +0200 Subject: [PATCH 58/78] Fix Dockerfile vanity COPY (#1604) Corrected an unintended mistake in the COPY commands for adding the vanity files to the Docker container, causing only the last file contents to be added to the file '/app/vanity/*' --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index efb82b42..9086cf17 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,7 +31,7 @@ COPY --from=build /app/build/*Server /app/ # Necessary suplimentary files COPY --from=build /app/build/*.ini /app/configs/ -COPY --from=build /app/build/vanity/*.* /app/vanity/* +COPY --from=build /app/build/vanity/*.* /app/vanity/ COPY --from=build /app/build/navmeshes /app/navmeshes COPY --from=build /app/build/migrations /app/migrations COPY --from=build /app/build/*.dcf /app/ @@ -39,7 +39,7 @@ COPY --from=build /app/build/*.dcf /app/ # backup of config and vanity files to copy to the host incase # of a mount clobbering the copy from above COPY --from=build /app/build/*.ini /app/default-configs/ -COPY --from=build /app/build/vanity/*.* /app/default-vanity/* +COPY --from=build /app/build/vanity/*.* /app/default-vanity/ # needed as the container runs with the root user # and therefore sudo doesn't exist From 01086d05c8dd9c08fb9220262b887fae2f7e525e Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 30 May 2024 21:53:03 -0700 Subject: [PATCH 59/78] fix: use after free and uninitialized memory (#1603) * fix use after free and uninitialized memory * add if check for packet lengths * move purge down further Its used in the if check too... --- dChatServer/ChatServer.cpp | 1 + dCommon/Diagnostics.cpp | 2 +- dGame/Character.cpp | 2 ++ dGame/Character.h | 18 +++++++++--------- dGame/User.cpp | 1 + dGame/dComponents/BaseCombatAIComponent.cpp | 3 ++- dGame/dComponents/InventoryComponent.cpp | 10 +++++----- dMasterServer/MasterServer.cpp | 1 + dWorldServer/WorldServer.cpp | 15 +++++++++------ 9 files changed, 31 insertions(+), 22 deletions(-) diff --git a/dChatServer/ChatServer.cpp b/dChatServer/ChatServer.cpp index 81b6ddef..d6e99230 100644 --- a/dChatServer/ChatServer.cpp +++ b/dChatServer/ChatServer.cpp @@ -179,6 +179,7 @@ int main(int argc, char** argv) { } void HandlePacket(Packet* packet) { + if (packet->length < 1) return; if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) { LOG("A server has disconnected, erasing their connected players from the list."); } else if (packet->data[0] == ID_NEW_INCOMING_CONNECTION) { diff --git a/dCommon/Diagnostics.cpp b/dCommon/Diagnostics.cpp index 2ed27fef..9f129194 100644 --- a/dCommon/Diagnostics.cpp +++ b/dCommon/Diagnostics.cpp @@ -201,7 +201,7 @@ void OnTerminate() { } void MakeBacktrace() { - struct sigaction sigact; + struct sigaction sigact{}; sigact.sa_sigaction = CritErrHdlr; sigact.sa_flags = SA_RESTART | SA_SIGINFO; diff --git a/dGame/Character.cpp b/dGame/Character.cpp index 59a67462..57a951d9 100644 --- a/dGame/Character.cpp +++ b/dGame/Character.cpp @@ -27,6 +27,8 @@ Character::Character(uint32_t id, User* parentUser) { m_ID = id; m_ParentUser = parentUser; m_OurEntity = nullptr; + m_GMLevel = eGameMasterLevel::CIVILIAN; + m_PermissionMap = static_cast<ePermissionMap>(0); } Character::~Character() { diff --git a/dGame/Character.h b/dGame/Character.h index 77f286f0..7a83325b 100644 --- a/dGame/Character.h +++ b/dGame/Character.h @@ -464,22 +464,22 @@ private: /** * The ID of this character. First 32 bits of the ObjectID. */ - uint32_t m_ID; + uint32_t m_ID{}; /** * The 64-bit unique ID used in the game. */ - LWOOBJID m_ObjectID; + LWOOBJID m_ObjectID{ LWOOBJID_EMPTY }; /** * The user that owns this character. */ - User* m_ParentUser; + User* m_ParentUser{}; /** * If the character is in game, this is the entity that it represents, else nullptr. */ - Entity* m_OurEntity; + Entity* m_OurEntity{}; /** * 0-9, the Game Master level of this character. @@ -506,17 +506,17 @@ private: /** * Whether the custom name of this character is rejected */ - bool m_NameRejected; + bool m_NameRejected{}; /** * The current amount of coins of this character */ - int64_t m_Coins; + int64_t m_Coins{}; /** * Whether the character is building */ - bool m_BuildMode; + bool m_BuildMode{}; /** * The items equipped by the character on world load @@ -583,7 +583,7 @@ private: /** * The ID of the properties of this character */ - uint32_t m_PropertyCloneID; + uint32_t m_PropertyCloneID{}; /** * The XML data for this character, stored as string @@ -613,7 +613,7 @@ private: /** * The last time this character logged in */ - uint64_t m_LastLogin; + uint64_t m_LastLogin{}; /** * The gameplay flags this character has (not just true values) diff --git a/dGame/User.cpp b/dGame/User.cpp index 0b2c3c3f..806d4611 100644 --- a/dGame/User.cpp +++ b/dGame/User.cpp @@ -12,6 +12,7 @@ User::User(const SystemAddress& sysAddr, const std::string& username, const std: m_AccountID = 0; m_Username = ""; m_SessionKey = ""; + m_MuteExpire = 0; m_MaxGMLevel = eGameMasterLevel::CIVILIAN; //The max GM level this account can assign to it's characters m_LastCharID = 0; diff --git a/dGame/dComponents/BaseCombatAIComponent.cpp b/dGame/dComponents/BaseCombatAIComponent.cpp index 60dceeef..bfb0bbfa 100644 --- a/dGame/dComponents/BaseCombatAIComponent.cpp +++ b/dGame/dComponents/BaseCombatAIComponent.cpp @@ -29,7 +29,8 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id): Component(parent) { m_Target = LWOOBJID_EMPTY; - SetAiState(AiState::spawn); + m_DirtyStateOrTarget = true; + m_State = AiState::spawn; m_Timer = 1.0f; m_StartPosition = parent->GetPosition(); m_MovementAI = nullptr; diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index acb27796..172877b0 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -875,8 +875,6 @@ void InventoryComponent::UnEquipItem(Item* item) { RemoveSlot(item->GetInfo().equipLocation); - PurgeProxies(item); - UnequipScripts(item); Game::entityManager->SerializeEntity(m_Parent); @@ -886,6 +884,8 @@ void InventoryComponent::UnEquipItem(Item* item) { PropertyManagementComponent::Instance()->GetParent()->OnZonePropertyModelRemovedWhileEquipped(m_Parent); Game::zoneManager->GetZoneControlObject()->OnZonePropertyModelRemovedWhileEquipped(m_Parent); } + + PurgeProxies(item); } @@ -1505,10 +1505,10 @@ void InventoryComponent::PurgeProxies(Item* item) { const auto root = item->GetParent(); if (root != LWOOBJID_EMPTY) { - item = FindItemById(root); + Item* itemRoot = FindItemById(root); - if (item != nullptr) { - UnEquipItem(item); + if (itemRoot != nullptr) { + UnEquipItem(itemRoot); } return; diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 3d5f4aff..09d1bba9 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -382,6 +382,7 @@ int main(int argc, char** argv) { } void HandlePacket(Packet* packet) { + if (packet->length < 1) return; if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION) { LOG("A server has disconnected"); diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 9e55d8b7..d3dc78cc 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -529,6 +529,7 @@ int main(int argc, char** argv) { } void HandlePacketChat(Packet* packet) { + if (packet->length < 1) return; if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) { LOG("Lost our connection to chat, zone(%i), instance(%i)", Game::server->GetZoneID(), Game::server->GetInstanceID()); @@ -542,7 +543,7 @@ void HandlePacketChat(Packet* packet) { chatConnected = true; } - if (packet->data[0] == ID_USER_PACKET_ENUM) { + if (packet->data[0] == ID_USER_PACKET_ENUM && packet->length >= 4) { if (static_cast<eConnectionType>(packet->data[1]) == eConnectionType::CHAT) { switch (static_cast<eChatMessageType>(packet->data[3])) { case eChatMessageType::WORLD_ROUTE_PACKET: { @@ -557,8 +558,9 @@ void HandlePacketChat(Packet* packet) { //Write our stream outwards: CBITSTREAM; - for (BitSize_t i = 0; i < inStream.GetNumberOfBytesUsed(); i++) { - bitStream.Write(packet->data[i + 16]); //16 bytes == header + playerID to skip + unsigned char data; + while (inStream.Read(data)) { + bitStream.Write(data); } SEND_PACKET; //send routed packet to player @@ -659,7 +661,7 @@ void HandlePacketChat(Packet* packet) { } void HandleMasterPacket(Packet* packet) { - + if (packet->length < 2) return; if (static_cast<eConnectionType>(packet->data[1]) != eConnectionType::MASTER || packet->length < 4) return; switch (static_cast<eMasterMessageType>(packet->data[3])) { case eMasterMessageType::REQUEST_PERSISTENT_ID_RESPONSE: { @@ -785,6 +787,7 @@ void HandleMasterPacket(Packet* packet) { } void HandlePacket(Packet* packet) { + if (packet->length < 1) return; if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) { auto user = UserManager::Instance()->GetUser(packet->systemAddress); if (!user) return; @@ -1207,8 +1210,8 @@ void HandlePacket(Packet* packet) { //Now write the rest of the data: auto data = inStream.GetData(); - for (uint32_t i = 0; i < size; ++i) { - bitStream.Write(data[i + 23]); + for (uint32_t i = 23; i - 23 < size && i < packet->length; ++i) { + bitStream.Write(data[i]); } Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE_ORDERED, 0, Game::chatSysAddr, false); From 342da927f5cd23eb69c8d682b0847b5742cae838 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 30 May 2024 21:53:13 -0700 Subject: [PATCH 60/78] fix dimantling items from not the model inventory (#1605) --- dGame/dGameMessages/GameMessages.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 1f757d7e..a97407d8 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -102,6 +102,7 @@ #include "CDComponentsRegistryTable.h" #include "CDObjectsTable.h" +#include "eItemType.h" void GameMessages::SendFireEventClientSide(const LWOOBJID& objectID, const SystemAddress& sysAddr, std::u16string args, const LWOOBJID& object, int64_t param1, int param2, const LWOOBJID& sender) { CBITSTREAM; @@ -5352,7 +5353,8 @@ void GameMessages::HandleRemoveItemFromInventory(RakNet::BitStream& inStream, En iStackCount = std::min<uint32_t>(item->GetCount(), iStackCount); if (bConfirmed) { - if (eInvType == eInventoryType::MODELS) { + const auto itemType = static_cast<eItemType>(item->GetInfo().itemType); + if (itemType == eItemType::MODEL || itemType == eItemType::LOOT_MODEL) { item->DisassembleModel(iStackCount); } auto lot = item->GetLot(); From a54600b41ed871f80bb8791f7ae321e7f9012661 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Fri, 31 May 2024 11:46:18 -0700 Subject: [PATCH 61/78] busting out the multimap ig (#1602) --- dGame/dBehaviors/BehaviorContext.cpp | 7 ++++--- dGame/dBehaviors/BehaviorContext.h | 2 +- dGame/dComponents/SkillComponent.cpp | 23 +++++++++++++++-------- dGame/dComponents/SkillComponent.h | 2 +- 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/dGame/dBehaviors/BehaviorContext.cpp b/dGame/dBehaviors/BehaviorContext.cpp index a3721d8f..abba74be 100644 --- a/dGame/dBehaviors/BehaviorContext.cpp +++ b/dGame/dBehaviors/BehaviorContext.cpp @@ -105,7 +105,7 @@ void BehaviorContext::ExecuteUpdates() { this->scheduledUpdates.clear(); } -void BehaviorContext::SyncBehavior(const uint32_t syncId, RakNet::BitStream& bitStream) { +bool BehaviorContext::SyncBehavior(const uint32_t syncId, RakNet::BitStream& bitStream) { BehaviorSyncEntry entry; auto found = false; @@ -128,7 +128,7 @@ void BehaviorContext::SyncBehavior(const uint32_t syncId, RakNet::BitStream& bit if (!found) { LOG("Failed to find behavior sync entry with sync id (%i)!", syncId); - return; + return false; } auto* behavior = entry.behavior; @@ -137,10 +137,11 @@ void BehaviorContext::SyncBehavior(const uint32_t syncId, RakNet::BitStream& bit if (behavior == nullptr) { LOG("Invalid behavior for sync id (%i)!", syncId); - return; + return false; } behavior->Sync(this, bitStream, branch); + return true; } diff --git a/dGame/dBehaviors/BehaviorContext.h b/dGame/dBehaviors/BehaviorContext.h index 3e6c0b1d..4922f736 100644 --- a/dGame/dBehaviors/BehaviorContext.h +++ b/dGame/dBehaviors/BehaviorContext.h @@ -93,7 +93,7 @@ struct BehaviorContext void ExecuteUpdates(); - void SyncBehavior(uint32_t syncId, RakNet::BitStream& bitStream); + bool SyncBehavior(uint32_t syncId, RakNet::BitStream& bitStream); void Update(float deltaTime); diff --git a/dGame/dComponents/SkillComponent.cpp b/dGame/dComponents/SkillComponent.cpp index d926ff2c..58b13dc5 100644 --- a/dGame/dComponents/SkillComponent.cpp +++ b/dGame/dComponents/SkillComponent.cpp @@ -38,7 +38,7 @@ bool SkillComponent::CastPlayerSkill(const uint32_t behaviorId, const uint32_t s context->skillID = skillID; - this->m_managedBehaviors.insert_or_assign(skillUid, context); + this->m_managedBehaviors.insert({ skillUid, context }); auto* behavior = Behavior::CreateBehavior(behaviorId); @@ -52,17 +52,24 @@ bool SkillComponent::CastPlayerSkill(const uint32_t behaviorId, const uint32_t s } void SkillComponent::SyncPlayerSkill(const uint32_t skillUid, const uint32_t syncId, RakNet::BitStream& bitStream) { - const auto index = this->m_managedBehaviors.find(skillUid); + const auto index = this->m_managedBehaviors.equal_range(skillUid); - if (index == this->m_managedBehaviors.end()) { + if (index.first == this->m_managedBehaviors.end()) { LOG("Failed to find skill with uid (%i)!", skillUid, syncId); return; } - auto* context = index->second; + bool foundSyncId = false; + for (auto it = index.first; it != index.second && !foundSyncId; ++it) { + const auto& context = it->second; - context->SyncBehavior(syncId, bitStream); + foundSyncId = context->SyncBehavior(syncId, bitStream); + } + + if (!foundSyncId) { + LOG("Failed to find sync id (%i) for skill with uid (%i)!", syncId, skillUid); + } } @@ -138,7 +145,7 @@ void SkillComponent::Update(const float deltaTime) { for (const auto& pair : this->m_managedBehaviors) pair.second->UpdatePlayerSyncs(deltaTime); } - std::map<uint32_t, BehaviorContext*> keep{}; + std::multimap<uint32_t, BehaviorContext*> keep{}; for (const auto& pair : this->m_managedBehaviors) { auto* context = pair.second; @@ -176,7 +183,7 @@ void SkillComponent::Update(const float deltaTime) { } } - keep.insert_or_assign(pair.first, context); + keep.insert({ pair.first, context }); } this->m_managedBehaviors = keep; @@ -285,7 +292,7 @@ SkillExecutionResult SkillComponent::CalculateBehavior( return { false, 0 }; } - this->m_managedBehaviors.insert_or_assign(context->skillUId, context); + this->m_managedBehaviors.insert({ context->skillUId, context }); if (!clientInitalized) { // Echo start skill diff --git a/dGame/dComponents/SkillComponent.h b/dGame/dComponents/SkillComponent.h index 24d92148..f74b4411 100644 --- a/dGame/dComponents/SkillComponent.h +++ b/dGame/dComponents/SkillComponent.h @@ -188,7 +188,7 @@ private: /** * All of the active skills mapped by their unique ID. */ - std::map<uint32_t, BehaviorContext*> m_managedBehaviors; + std::multimap<uint32_t, BehaviorContext*> m_managedBehaviors; /** * All active projectiles. From b56d077892f73d1ff4d1626a20e0717a541ca2a5 Mon Sep 17 00:00:00 2001 From: Aaron Kimbre <aronwk.aaron@gmail.com> Date: Mon, 3 Jun 2024 21:50:12 -0500 Subject: [PATCH 62/78] feat: spectate command --- dGame/dGameMessages/GameMessages.cpp | 14 ++++++++++++++ dGame/dGameMessages/GameMessages.h | 6 ++++++ dGame/dUtilities/SlashCommandHandler.cpp | 9 +++++++++ .../SlashCommands/GMGreaterThanZeroCommands.cpp | 8 ++++++++ .../SlashCommands/GMGreaterThanZeroCommands.h | 1 + 5 files changed, 38 insertions(+) diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index a97407d8..c6a4cf3d 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -6212,3 +6212,17 @@ void GameMessages::SendSlashCommandFeedbackText(Entity* entity, std::u16string t auto sysAddr = entity->GetSystemAddress(); SEND_PACKET; } + +void GameMessages::SendForceCameraTargetCycle(Entity* entity, bool bForceCycling, eCameraTargetCyclingMode cyclingMode, LWOOBJID optionalTargetID) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(entity->GetObjectID()); + bitStream.Write(eGameMessageType::FORCE_CAMERA_TARGET_CYCLE); + bitStream.Write(bForceCycling); + bitStream.Write(cyclingMode); + bitStream.Write(optionalTargetID); + + auto sysAddr = entity->GetSystemAddress(); + SEND_PACKET; +} diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index b842710e..21bdfb41 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -40,6 +40,11 @@ enum class eQuickBuildState : uint32_t; enum class BehaviorSlot : int32_t; enum class eVendorTransactionResult : uint32_t; +enum class eCameraTargetCyclingMode : int32_t { + ALLOW_CYCLE_TEAMMATES, + DISALLOW_CYCLING +}; + namespace GameMessages { class PropertyDataMessage; void SendFireEventClientSide(const LWOOBJID& objectID, const SystemAddress& sysAddr, std::u16string args, const LWOOBJID& object, int64_t param1, int param2, const LWOOBJID& sender); @@ -666,6 +671,7 @@ namespace GameMessages { void HandleCancelDonationOnPlayer(RakNet::BitStream& inStream, Entity* entity); void SendSlashCommandFeedbackText(Entity* entity, std::u16string text); + void SendForceCameraTargetCycle(Entity* entity, bool bForceCycling, eCameraTargetCyclingMode cyclingMode, LWOOBJID optionalTargetID); }; #endif // GAMEMESSAGES_H diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 428ccbcb..20f2634a 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -929,6 +929,15 @@ void SlashCommandHandler::Startup() { }; RegisterCommand(FindPlayerCommand); + Command SpectateCommand{ + .help = "Spectate a player", + .info = "Specify a player name to spectate. They must be in the same world as you", + .aliases = { "spectate", "follow" }, + .handle = GMGreaterThanZeroCommands::Spectate, + .requiredLevel = eGameMasterLevel::JUNIOR_MODERATOR + }; + RegisterCommand(SpectateCommand); + // Register GM Zero Commands Command HelpCommand{ diff --git a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp index ea33aa03..e2802d62 100644 --- a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp +++ b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp @@ -322,4 +322,12 @@ namespace GMGreaterThanZeroCommands { request.Serialize(bitStream); Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); } + + void Spectate(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (args.length() <= 0) GameMessages::SendSlashCommandFeedbackText(entity, u"No player Given"); + auto player = PlayerManager::GetPlayer(args); + if (!player) GameMessages::SendSlashCommandFeedbackText(entity, u"Player not found"); + GameMessages::SendSlashCommandFeedbackText(entity, u"Spectating Player"); + GameMessages::SendForceCameraTargetCycle(entity, false, eCameraTargetCyclingMode::DISALLOW_CYCLING, player->GetObjectID()); + } } diff --git a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h index 7cb3d8d7..c278fc0a 100644 --- a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h +++ b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.h @@ -15,6 +15,7 @@ namespace GMGreaterThanZeroCommands { void Title(Entity* entity, const SystemAddress& sysAddr, const std::string args); void ShowAll(Entity* entity, const SystemAddress& sysAddr, const std::string args); void FindPlayer(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void Spectate(Entity* entity, const SystemAddress& sysAddr, const std::string args); } #endif //!GMGREATERTHANZEROCOMMANDS_H From 2ef45bd7ee8bf466c52a90e286370f5221bc59e0 Mon Sep 17 00:00:00 2001 From: Aaron Kimbre <aronwk.aaron@gmail.com> Date: Mon, 3 Jun 2024 22:28:37 -0500 Subject: [PATCH 63/78] use empty --- dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp index e2802d62..e71c0058 100644 --- a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp +++ b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp @@ -324,7 +324,7 @@ namespace GMGreaterThanZeroCommands { } void Spectate(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - if (args.length() <= 0) GameMessages::SendSlashCommandFeedbackText(entity, u"No player Given"); + if (args.empty()) GameMessages::SendSlashCommandFeedbackText(entity, u"No player Given"); auto player = PlayerManager::GetPlayer(args); if (!player) GameMessages::SendSlashCommandFeedbackText(entity, u"Player not found"); GameMessages::SendSlashCommandFeedbackText(entity, u"Spectating Player"); From 1a14c29c39ff8e00f999cfe9ee5fd1ea73d4bcdf Mon Sep 17 00:00:00 2001 From: Aaron Kimbre <aronwk.aaron@gmail.com> Date: Mon, 3 Jun 2024 22:29:21 -0500 Subject: [PATCH 64/78] add returns, lol --- .../SlashCommands/GMGreaterThanZeroCommands.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp index e71c0058..c1cb6b4b 100644 --- a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp +++ b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp @@ -324,9 +324,16 @@ namespace GMGreaterThanZeroCommands { } void Spectate(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - if (args.empty()) GameMessages::SendSlashCommandFeedbackText(entity, u"No player Given"); + if (args.empty()) { + GameMessages::SendSlashCommandFeedbackText(entity, u"No player Given"); + return; + } + auto player = PlayerManager::GetPlayer(args); - if (!player) GameMessages::SendSlashCommandFeedbackText(entity, u"Player not found"); + if (!player) { + GameMessages::SendSlashCommandFeedbackText(entity, u"Player not found"); + return; + } GameMessages::SendSlashCommandFeedbackText(entity, u"Spectating Player"); GameMessages::SendForceCameraTargetCycle(entity, false, eCameraTargetCyclingMode::DISALLOW_CYCLING, player->GetObjectID()); } From 9d5d2a68eedc9172455370a3af8367fd4b949691 Mon Sep 17 00:00:00 2001 From: Aaron Kimbre <aronwk.aaron@gmail.com> Date: Mon, 3 Jun 2024 22:30:57 -0500 Subject: [PATCH 65/78] fix gm serialization --- dGame/dGameMessages/GameMessages.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index c6a4cf3d..a80bc1aa 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -6220,7 +6220,8 @@ void GameMessages::SendForceCameraTargetCycle(Entity* entity, bool bForceCycling bitStream.Write(entity->GetObjectID()); bitStream.Write(eGameMessageType::FORCE_CAMERA_TARGET_CYCLE); bitStream.Write(bForceCycling); - bitStream.Write(cyclingMode); + bitStream.Write(cyclingMode != eCameraTargetCyclingMode::ALLOW_CYCLE_TEAMMATES); + if (cyclingMode != eCameraTargetCyclingMode::ALLOW_CYCLE_TEAMMATES) bitStream.Write(cyclingMode); bitStream.Write(optionalTargetID); auto sysAddr = entity->GetSystemAddress(); From 3f22bf5cc04e914273481ea51a0f82a6d79d9aa1 Mon Sep 17 00:00:00 2001 From: Aaron Kimbre <aronwk.aaron@gmail.com> Date: Mon, 3 Jun 2024 22:44:54 -0500 Subject: [PATCH 66/78] Add an easy way to stop spectating --- dGame/dUtilities/SlashCommandHandler.cpp | 2 +- dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 20f2634a..477044b1 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -931,7 +931,7 @@ void SlashCommandHandler::Startup() { Command SpectateCommand{ .help = "Spectate a player", - .info = "Specify a player name to spectate. They must be in the same world as you", + .info = "Specify a player name to spectate. They must be in the same world as you. Leave blank to stop spectating", .aliases = { "spectate", "follow" }, .handle = GMGreaterThanZeroCommands::Spectate, .requiredLevel = eGameMasterLevel::JUNIOR_MODERATOR diff --git a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp index c1cb6b4b..19a40983 100644 --- a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp +++ b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp @@ -325,6 +325,7 @@ namespace GMGreaterThanZeroCommands { void Spectate(Entity* entity, const SystemAddress& sysAddr, const std::string args) { if (args.empty()) { + GameMessages::SendForceCameraTargetCycle(entity, false, eCameraTargetCyclingMode::DISALLOW_CYCLING, entity->GetObjectID()); GameMessages::SendSlashCommandFeedbackText(entity, u"No player Given"); return; } From ff38503597cf2c50e75ae60cd4fa4a7f7787a88d Mon Sep 17 00:00:00 2001 From: Aaron Kimbre <aronwk.aaron@gmail.com> Date: Mon, 3 Jun 2024 22:51:46 -0500 Subject: [PATCH 67/78] no feedback if empty --- dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp index 19a40983..b9eaf7bf 100644 --- a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp +++ b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp @@ -326,7 +326,6 @@ namespace GMGreaterThanZeroCommands { void Spectate(Entity* entity, const SystemAddress& sysAddr, const std::string args) { if (args.empty()) { GameMessages::SendForceCameraTargetCycle(entity, false, eCameraTargetCyclingMode::DISALLOW_CYCLING, entity->GetObjectID()); - GameMessages::SendSlashCommandFeedbackText(entity, u"No player Given"); return; } From 1454fcd00376a022f1568b23c633878f600cba1d Mon Sep 17 00:00:00 2001 From: Wincent Holm <wincent.holm@gmail.com> Date: Thu, 6 Jun 2024 11:00:44 +0200 Subject: [PATCH 68/78] Fix g++ 14 (#1610) * Fix g++ 14 * Update thirdparty/CMakeLists.txt Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> --------- Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> --- dCommon/dConfig.cpp | 1 + dNet/BitStreamUtils.h | 1 + thirdparty/CMakeLists.txt | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/dCommon/dConfig.cpp b/dCommon/dConfig.cpp index 56a43848..bed274b0 100644 --- a/dCommon/dConfig.cpp +++ b/dCommon/dConfig.cpp @@ -1,6 +1,7 @@ #include "dConfig.h" #include <sstream> +#include <algorithm> #include "BinaryPathFinder.h" #include "GeneralUtils.h" diff --git a/dNet/BitStreamUtils.h b/dNet/BitStreamUtils.h index 1322ec95..33fde564 100644 --- a/dNet/BitStreamUtils.h +++ b/dNet/BitStreamUtils.h @@ -5,6 +5,7 @@ #include "MessageIdentifiers.h" #include "BitStream.h" #include <string> +#include <algorithm> enum class eConnectionType : uint16_t; diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 41135a80..f6056476 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -23,6 +23,11 @@ if(NOT WIN32) target_include_directories(bcrypt PRIVATE "libbcrypt/include/bcrypt") endif() +# Need to define this on Clang and GNU for 'strdup' support +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + target_compile_definitions(bcrypt PRIVATE "_POSIX_C_SOURCE=200809L") +endif() + target_include_directories(bcrypt INTERFACE "libbcrypt/include") target_include_directories(bcrypt PRIVATE "libbcrypt/src") From fee0238e790febe2d7afcbbddbe124854af8f27d Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 9 Jun 2024 15:31:57 -0700 Subject: [PATCH 69/78] fix: master not using table data, remove 2 noisy logs (#1613) Tested with logs that queries to get soft and hard cap actually succeed now Logs about slash command handler command registration and vanity NPC creation in mis matched worlds are now removed. --- dGame/dUtilities/SlashCommandHandler.cpp | 1 - dGame/dUtilities/VanityUtilities.cpp | 1 - dMasterServer/MasterServer.cpp | 12 ++++++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 477044b1..ca4e03d4 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -31,7 +31,6 @@ void SlashCommandHandler::RegisterCommand(Command command) { } for (const auto& alias : command.aliases) { - LOG_DEBUG("Registering command %s", alias.c_str()); auto [_, success] = RegisteredCommands.try_emplace(alias, command); // Don't allow duplicate commands if (!success) { diff --git a/dGame/dUtilities/VanityUtilities.cpp b/dGame/dUtilities/VanityUtilities.cpp index cb893da3..6043fe63 100644 --- a/dGame/dUtilities/VanityUtilities.cpp +++ b/dGame/dUtilities/VanityUtilities.cpp @@ -285,7 +285,6 @@ void ParseXml(const std::string& file) { } if (zoneID.value() != currentZoneID) { - LOG_DEBUG("Skipping (%s) %i location because it is in %i and not the current zone (%i)", name, lot, zoneID.value(), currentZoneID); continue; } diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 09d1bba9..0f36ee37 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -40,6 +40,7 @@ #include "BitStreamUtils.h" #include "Start.h" #include "Server.h" +#include "CDZoneTableTable.h" namespace Game { Logger* logger = nullptr; @@ -277,6 +278,17 @@ int main(int argc, char** argv) { PersistentIDManager::Initialize(); Game::im = new InstanceManager(Game::logger, Game::server->GetIP()); + //Get CDClient initial information + try { + CDClientManager::LoadValuesFromDatabase(); + } catch (CppSQLite3Exception& e) { + LOG("Failed to initialize CDServer SQLite Database"); + LOG("May be caused by corrupted file: %s", (Game::assetManager->GetResPath() / "CDServer.sqlite").string().c_str()); + LOG("Error: %s", e.errorMessage()); + LOG("Error Code: %i", e.errorCode()); + return EXIT_FAILURE; + } + //Depending on the config, start up servers: if (Game::config->GetValue("prestart_servers") != "0") { StartChatServer(); From 6ad6e930c73982464c99859da7e344e5fe21f7ca Mon Sep 17 00:00:00 2001 From: David Markowitz <EmosewaMC@gmail.com> Date: Tue, 11 Jun 2024 02:29:25 -0700 Subject: [PATCH 70/78] move mission progression done now as soon as you cross the finish line on the last lap instead of after you click "rewards" --- dGame/dComponents/RacingControlComponent.cpp | 34 +++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/dGame/dComponents/RacingControlComponent.cpp b/dGame/dComponents/RacingControlComponent.cpp index b896d3f2..c701080d 100644 --- a/dGame/dComponents/RacingControlComponent.cpp +++ b/dGame/dComponents/RacingControlComponent.cpp @@ -398,25 +398,6 @@ void RacingControlComponent::HandleMessageBoxResponse(Entity* player, int32_t bu GameMessages::SendNotifyRacingClient( m_Parent->GetObjectID(), 2, 0, LWOOBJID_EMPTY, u"", player->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); - - auto* missionComponent = player->GetComponent<MissionComponent>(); - - if (missionComponent == nullptr) return; - - missionComponent->Progress(eMissionTaskType::RACING, 0, static_cast<LWOOBJID>(eRacingTaskParam::COMPETED_IN_RACE)); // Progress task for competing in a race - missionComponent->Progress(eMissionTaskType::RACING, data->smashedTimes, static_cast<LWOOBJID>(eRacingTaskParam::SAFE_DRIVER)); // Finish a race without being smashed. - - // If solo racing is enabled OR if there are 3 players in the race, progress placement tasks. - if (m_SoloRacing || m_LoadedPlayers > 2) { - missionComponent->Progress(eMissionTaskType::RACING, data->finished, static_cast<LWOOBJID>(eRacingTaskParam::FINISH_WITH_PLACEMENT)); // Finish in 1st place on a race - if (data->finished == 1) { - missionComponent->Progress(eMissionTaskType::RACING, Game::zoneManager->GetZone()->GetWorldID(), static_cast<LWOOBJID>(eRacingTaskParam::FIRST_PLACE_MULTIPLE_TRACKS)); // Finish in 1st place on multiple tracks. - missionComponent->Progress(eMissionTaskType::RACING, Game::zoneManager->GetZone()->GetWorldID(), static_cast<LWOOBJID>(eRacingTaskParam::WIN_RACE_IN_WORLD)); // Finished first place in specific world. - } - if (data->finished == m_LoadedPlayers) { - missionComponent->Progress(eMissionTaskType::RACING, Game::zoneManager->GetZone()->GetWorldID(), static_cast<LWOOBJID>(eRacingTaskParam::LAST_PLACE_FINISH)); // Finished first place in specific world. - } - } } else if ((id == "ACT_RACE_EXIT_THE_RACE?" || id == "Exit") && button == m_ActivityExitConfirm) { auto* vehicle = Game::entityManager->GetEntity(data->vehicleID); @@ -870,6 +851,21 @@ void RacingControlComponent::Update(float deltaTime) { // Entire race time missionComponent->Progress(eMissionTaskType::RACING, (raceTime) * 1000, static_cast<LWOOBJID>(eRacingTaskParam::TOTAL_TRACK_TIME)); + missionComponent->Progress(eMissionTaskType::RACING, 0, static_cast<LWOOBJID>(eRacingTaskParam::COMPETED_IN_RACE)); // Progress task for competing in a race + missionComponent->Progress(eMissionTaskType::RACING, player.smashedTimes, static_cast<LWOOBJID>(eRacingTaskParam::SAFE_DRIVER)); // Finish a race without being smashed. + + // If solo racing is enabled OR if there are 3 players in the race, progress placement tasks. + if (m_SoloRacing || m_RacingPlayers.size() > 2) { + missionComponent->Progress(eMissionTaskType::RACING, player.finished, static_cast<LWOOBJID>(eRacingTaskParam::FINISH_WITH_PLACEMENT)); // Finish in 1st place on a race + if (player.finished == 1) { + missionComponent->Progress(eMissionTaskType::RACING, Game::zoneManager->GetZone()->GetWorldID(), static_cast<LWOOBJID>(eRacingTaskParam::FIRST_PLACE_MULTIPLE_TRACKS)); // Finish in 1st place on multiple tracks. + missionComponent->Progress(eMissionTaskType::RACING, Game::zoneManager->GetZone()->GetWorldID(), static_cast<LWOOBJID>(eRacingTaskParam::WIN_RACE_IN_WORLD)); // Finished first place in specific world. + } + if (player.finished == m_RacingPlayers.size()) { + missionComponent->Progress(eMissionTaskType::RACING, Game::zoneManager->GetZone()->GetWorldID(), static_cast<LWOOBJID>(eRacingTaskParam::LAST_PLACE_FINISH)); // Finished first place in specific world. + } + } + auto* characterComponent = playerEntity->GetComponent<CharacterComponent>(); if (characterComponent != nullptr) { characterComponent->TrackRaceCompleted(m_Finished == 1); From f82a82f2549138a2798948a8b27521da7bdaa2f2 Mon Sep 17 00:00:00 2001 From: David Markowitz <EmosewaMC@gmail.com> Date: Mon, 17 Jun 2024 19:41:27 -0700 Subject: [PATCH 71/78] Create 16_big_behaviors.sql --- migrations/dlu/16_big_behaviors.sql | 1 + 1 file changed, 1 insertion(+) create mode 100644 migrations/dlu/16_big_behaviors.sql diff --git a/migrations/dlu/16_big_behaviors.sql b/migrations/dlu/16_big_behaviors.sql new file mode 100644 index 00000000..12edc0bc --- /dev/null +++ b/migrations/dlu/16_big_behaviors.sql @@ -0,0 +1 @@ +ALTER TABLE behaviors MODIFY behavior_info LONGTEXT DEFAULT NULL; From 262847048282536a80ad53d85f066e8ef3430451 Mon Sep 17 00:00:00 2001 From: David Markowitz <EmosewaMC@gmail.com> Date: Tue, 18 Jun 2024 14:24:03 -0700 Subject: [PATCH 72/78] set shield to false, add sync for done --- dGame/dBehaviors/BehaviorContext.cpp | 20 +++++++++++++++++++ dGame/dBehaviors/DamageAbsorptionBehavior.cpp | 10 +++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/dGame/dBehaviors/BehaviorContext.cpp b/dGame/dBehaviors/BehaviorContext.cpp index abba74be..95c4c84c 100644 --- a/dGame/dBehaviors/BehaviorContext.cpp +++ b/dGame/dBehaviors/BehaviorContext.cpp @@ -199,6 +199,26 @@ void BehaviorContext::UpdatePlayerSyncs(float deltaTime) { i++; continue; } + + if (this->skillUId != 0 && !clientInitalized) { + EchoSyncSkill echo; + echo.bDone = true; + echo.uiSkillHandle = this->skillUId; + echo.uiBehaviorHandle = entry.handle; + + RakNet::BitStream bitStream{}; + entry.behavior->SyncCalculation(this, bitStream, entry.branchContext); + + echo.sBitStream.assign(reinterpret_cast<char*>(bitStream.GetData()), bitStream.GetNumberOfBytesUsed()); + + RakNet::BitStream message; + BitStreamUtils::WriteHeader(message, eConnectionType::CLIENT, eClientMessageType::GAME_MSG); + message.Write(this->originator); + echo.Serialize(message); + + Game::server->Send(message, UNASSIGNED_SYSTEM_ADDRESS, true); + } + this->syncEntries.erase(this->syncEntries.begin() + i); } } diff --git a/dGame/dBehaviors/DamageAbsorptionBehavior.cpp b/dGame/dBehaviors/DamageAbsorptionBehavior.cpp index f657c8fd..e5711f4d 100644 --- a/dGame/dBehaviors/DamageAbsorptionBehavior.cpp +++ b/dGame/dBehaviors/DamageAbsorptionBehavior.cpp @@ -27,6 +27,8 @@ void DamageAbsorptionBehavior::Handle(BehaviorContext* context, RakNet::BitStrea destroyable->SetIsShielded(true); context->RegisterTimerBehavior(this, branch, target->GetObjectID()); + + Game::entityManager->SerializeEntity(target); } void DamageAbsorptionBehavior::Calculate(BehaviorContext* context, RakNet::BitStream& bitStream, BehaviorBranchContext branch) { @@ -52,7 +54,13 @@ void DamageAbsorptionBehavior::Timer(BehaviorContext* context, BehaviorBranchCon const auto toRemove = std::min(present, this->m_absorbAmount); - destroyable->SetDamageToAbsorb(present - toRemove); + const auto remaining = present - toRemove; + + destroyable->SetDamageToAbsorb(remaining); + + destroyable->SetIsShielded(remaining > 0); + + Game::entityManager->SerializeEntity(target); } void DamageAbsorptionBehavior::Load() { From b648b43c4d68eaf53a021e14742a48171498f639 Mon Sep 17 00:00:00 2001 From: David Markowitz <EmosewaMC@gmail.com> Date: Tue, 25 Jun 2024 20:52:46 -0700 Subject: [PATCH 73/78] ?? --- dGame/dUtilities/Preconditions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dGame/dUtilities/Preconditions.cpp b/dGame/dUtilities/Preconditions.cpp index 25e8b773..f58431af 100644 --- a/dGame/dUtilities/Preconditions.cpp +++ b/dGame/dUtilities/Preconditions.cpp @@ -26,7 +26,7 @@ Precondition::Precondition(const uint32_t condition) { if (result.eof()) { this->type = PreconditionType::ItemEquipped; this->count = 1; - this->values = { 0 }; + this->values = { 0u }; LOG("Failed to find precondition of id (%i)!", condition); From f2d72e7ed506406e26b9fc89fdee57490dde39cf Mon Sep 17 00:00:00 2001 From: David Markowitz <EmosewaMC@gmail.com> Date: Tue, 25 Jun 2024 21:02:40 -0700 Subject: [PATCH 74/78] Update Preconditions.cpp --- dGame/dUtilities/Preconditions.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dGame/dUtilities/Preconditions.cpp b/dGame/dUtilities/Preconditions.cpp index f58431af..cd920863 100644 --- a/dGame/dUtilities/Preconditions.cpp +++ b/dGame/dUtilities/Preconditions.cpp @@ -26,7 +26,8 @@ Precondition::Precondition(const uint32_t condition) { if (result.eof()) { this->type = PreconditionType::ItemEquipped; this->count = 1; - this->values = { 0u }; + this->values.clear(); + this->values.push_back(0); LOG("Failed to find precondition of id (%i)!", condition); From 59c4b35479e9b8f1d7693f7e75d21535ff638fc5 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Tue, 2 Jul 2024 01:55:42 -0700 Subject: [PATCH 75/78] Update Preconditions.cpp --- dGame/dUtilities/Preconditions.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dGame/dUtilities/Preconditions.cpp b/dGame/dUtilities/Preconditions.cpp index 25e8b773..cd920863 100644 --- a/dGame/dUtilities/Preconditions.cpp +++ b/dGame/dUtilities/Preconditions.cpp @@ -26,7 +26,8 @@ Precondition::Precondition(const uint32_t condition) { if (result.eof()) { this->type = PreconditionType::ItemEquipped; this->count = 1; - this->values = { 0 }; + this->values.clear(); + this->values.push_back(0); LOG("Failed to find precondition of id (%i)!", condition); From c3f6ef5a1daa095d920f37418bff161eb5e72291 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Wed, 3 Jul 2024 15:37:19 -0700 Subject: [PATCH 76/78] feat: add admin account creation options from cli (GM level) (#1620) * add admin account creation options from cli * use actual gm levels felt under delivered in previous iteration. * Update dMasterServer/MasterServer.cpp Co-authored-by: Daniel Seiler <me@xiphoseer.de> --------- Co-authored-by: Daniel Seiler <me@xiphoseer.de> --- dDatabase/GameDatabase/ITables/IAccounts.h | 3 ++ dDatabase/GameDatabase/MySQL/MySQLDatabase.h | 1 + .../GameDatabase/MySQL/Tables/Accounts.cpp | 4 ++ dMasterServer/MasterServer.cpp | 39 +++++++++++++++++-- 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/dDatabase/GameDatabase/ITables/IAccounts.h b/dDatabase/GameDatabase/ITables/IAccounts.h index 3f27dda6..a0377f4b 100644 --- a/dDatabase/GameDatabase/ITables/IAccounts.h +++ b/dDatabase/GameDatabase/ITables/IAccounts.h @@ -33,6 +33,9 @@ public: // Add a new account to the database. virtual void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) = 0; + + // Update the GameMaster level of an account. + virtual void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) = 0; }; #endif //!__IACCOUNTS__H__ diff --git a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h index 689622d0..a3019bea 100644 --- a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h +++ b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h @@ -111,6 +111,7 @@ public: void AddBehavior(const IBehaviors::Info& info) override; std::string GetBehavior(const int32_t behaviorId) override; void RemoveBehavior(const int32_t characterId) override; + void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override; private: // Generic query functions that can be used for any query. diff --git a/dDatabase/GameDatabase/MySQL/Tables/Accounts.cpp b/dDatabase/GameDatabase/MySQL/Tables/Accounts.cpp index 801f444d..9e9812f3 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/Accounts.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/Accounts.cpp @@ -35,3 +35,7 @@ void MySQLDatabase::UpdateAccountPassword(const uint32_t accountId, const std::s void MySQLDatabase::InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) { ExecuteInsert("INSERT INTO accounts (name, password, gm_level) VALUES (?, ?, ?);", username, bcryptpassword, static_cast<int32_t>(eGameMasterLevel::OPERATOR)); } + +void MySQLDatabase::UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) { + ExecuteUpdate("UPDATE accounts SET gm_level = ? WHERE id = ?;", static_cast<int32_t>(gmLevel), accountId); +} diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 0f36ee37..05955a85 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -41,6 +41,7 @@ #include "Start.h" #include "Server.h" #include "CDZoneTableTable.h" +#include "eGameMasterLevel.h" namespace Game { Logger* logger = nullptr; @@ -187,15 +188,20 @@ int main(int argc, char** argv) { std::cout << "Enter a username: "; std::cin >> username; + const auto checkIsAdmin = []() { + std::string admin; + std::cout << "What level of privilege should this account have? Please enter a number between 0 (Player) and 9 (Admin) inclusive. No entry will default to 0." << std::endl; + std::cin >> admin; + return admin; + }; + auto accountId = Database::Get()->GetAccountInfo(username); - if (accountId) { + if (accountId && accountId->id != 0) { LOG("Account with name \"%s\" already exists", username.c_str()); std::cout << "Do you want to change the password of that account? [y/n]?"; std::string prompt = ""; std::cin >> prompt; if (prompt == "y" || prompt == "yes") { - if (accountId->id == 0) return EXIT_FAILURE; - //Read the password from the console without echoing it. #ifdef __linux__ //This function is obsolete, but it only meant to be used by the @@ -220,6 +226,20 @@ int main(int argc, char** argv) { } else { LOG("Account \"%s\" was not updated.", username.c_str()); } + + std::cout << "Update admin privileges? [y/n]? "; + std::string admin; + std::cin >> admin; + bool updateAdmin = admin == "y" || admin == "yes"; + if (updateAdmin) { + auto gmLevel = GeneralUtils::TryParse<int32_t>(checkIsAdmin()).value_or(0); + if (gmLevel > 9 || gmLevel < 0) { + LOG("Invalid admin level. Defaulting to 0"); + gmLevel = 0; + } + Database::Get()->UpdateAccountGmLevel(accountId->id, static_cast<eGameMasterLevel>(gmLevel)); + } + return EXIT_SUCCESS; } @@ -250,6 +270,17 @@ int main(int argc, char** argv) { } LOG("Account created successfully!"); + + accountId = Database::Get()->GetAccountInfo(username); + if (accountId) { + auto gmLevel = GeneralUtils::TryParse<int32_t>(checkIsAdmin()).value_or(0); + if (gmLevel > 9 || gmLevel < 0) { + LOG("Invalid admin level. Defaulting to 0"); + gmLevel = 0; + } + Database::Get()->UpdateAccountGmLevel(accountId->id, static_cast<eGameMasterLevel>(gmLevel)); + } + return EXIT_SUCCESS; } @@ -558,7 +589,7 @@ void HandlePacket(Packet* packet) { inStream.Read(sessionKey); LUString username; inStream.Read(username); - + for (auto it : activeSessions) { if (it.second == username.string) { activeSessions.erase(it.first); From aaf446fe6ea7c21a28da25010b1f74af1efcabe5 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 1 Aug 2024 23:38:21 -0700 Subject: [PATCH 77/78] feat: Add Inventory Brick and Model groups (#1587) * Added feature grouping logic * Add saving of brick buckets * Add edge case check for max group count * Use vector for storing groups * Update InventoryComponent.cpp * Update InventoryComponent.h * Update InventoryComponent.h * fix string log format * Update GameMessages.cpp --- dCommon/dEnums/eInventoryType.h | 9 ++ dGame/dComponents/InventoryComponent.cpp | 124 +++++++++++++++++++++ dGame/dComponents/InventoryComponent.h | 43 ++++++- dGame/dGameMessages/GameMessageHandler.cpp | 7 ++ dGame/dGameMessages/GameMessages.cpp | 63 +++++++++++ dGame/dGameMessages/GameMessages.h | 3 + 6 files changed, 248 insertions(+), 1 deletion(-) diff --git a/dCommon/dEnums/eInventoryType.h b/dCommon/dEnums/eInventoryType.h index 12573aa4..1c6688b2 100644 --- a/dCommon/dEnums/eInventoryType.h +++ b/dCommon/dEnums/eInventoryType.h @@ -4,6 +4,9 @@ #define __EINVENTORYTYPE__H__ #include <cstdint> + +#include "magic_enum.hpp" + static const uint8_t NUMBER_OF_INVENTORIES = 17; /** * Represents the different types of inventories an entity may have @@ -56,4 +59,10 @@ public: }; }; +template <> +struct magic_enum::customize::enum_range<eInventoryType> { + static constexpr int min = 0; + static constexpr int max = 16; +}; + #endif //!__EINVENTORYTYPE__H__ diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 172877b0..d6883e17 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -37,6 +37,9 @@ #include "CDScriptComponentTable.h" #include "CDObjectSkillsTable.h" #include "CDSkillBehaviorTable.h" +#include "StringifiedEnum.h" + +#include <ranges> InventoryComponent::InventoryComponent(Entity* parent) : Component(parent) { this->m_Dirty = true; @@ -492,6 +495,11 @@ void InventoryComponent::LoadXml(const tinyxml2::XMLDocument& document) { return; } + auto* const groups = inventoryElement->FirstChildElement("grps"); + if (groups) { + LoadGroupXml(*groups); + } + m_Consumable = inventoryElement->IntAttribute("csl", LOT_NULL); auto* bag = bags->FirstChildElement(); @@ -630,6 +638,15 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument& document) { bags->LinkEndChild(bag); } + auto* groups = inventoryElement->FirstChildElement("grps"); + if (groups) { + groups->DeleteChildren(); + } else { + groups = inventoryElement->InsertNewChildElement("grps"); + } + + UpdateGroupXml(*groups); + auto* items = inventoryElement->FirstChildElement("items"); if (items == nullptr) { @@ -1603,3 +1620,110 @@ bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId) { m_Skills.insert_or_assign(slot, skillId); return true; } + +void InventoryComponent::UpdateGroup(const GroupUpdate& groupUpdate) { + if (groupUpdate.groupId.empty()) return; + + if (groupUpdate.inventory != eInventoryType::BRICKS && groupUpdate.inventory != eInventoryType::MODELS) { + LOG("Invalid inventory type for grouping %s", StringifiedEnum::ToString(groupUpdate.inventory).data()); + return; + } + + auto& groups = m_Groups[groupUpdate.inventory]; + auto groupItr = std::ranges::find_if(groups, [&groupUpdate](const Group& group) { + return group.groupId == groupUpdate.groupId; + }); + + if (groupUpdate.command != GroupUpdateCommand::ADD && groupItr == groups.end()) { + LOG("Group %s not found in inventory %s. Cannot process command.", groupUpdate.groupId.c_str(), StringifiedEnum::ToString(groupUpdate.inventory).data()); + return; + } + + if (groupUpdate.command == GroupUpdateCommand::ADD && groups.size() >= MaximumGroupCount) { + LOG("Cannot add group to inventory %s. Maximum group count reached.", StringifiedEnum::ToString(groupUpdate.inventory).data()); + return; + } + + switch (groupUpdate.command) { + case GroupUpdateCommand::ADD: { + auto& group = groups.emplace_back(); + group.groupId = groupUpdate.groupId; + group.groupName = groupUpdate.groupName; + break; + } + case GroupUpdateCommand::ADD_LOT: { + groupItr->lots.insert(groupUpdate.lot); + break; + } + case GroupUpdateCommand::REMOVE: { + groups.erase(groupItr); + break; + } + case GroupUpdateCommand::REMOVE_LOT: { + groupItr->lots.erase(groupUpdate.lot); + break; + } + case GroupUpdateCommand::MODIFY: { + groupItr->groupName = groupUpdate.groupName; + break; + } + default: { + LOG("Invalid group update command %i", groupUpdate.command); + break; + } + } +} + +void InventoryComponent::UpdateGroupXml(tinyxml2::XMLElement& groups) const { + for (const auto& [inventory, groupsData] : m_Groups) { + for (const auto& group : groupsData) { + auto* const groupElement = groups.InsertNewChildElement("grp"); + + groupElement->SetAttribute("id", group.groupId.c_str()); + groupElement->SetAttribute("n", group.groupName.c_str()); + groupElement->SetAttribute("t", static_cast<uint32_t>(inventory)); + groupElement->SetAttribute("u", 0); + std::ostringstream lots; + bool first = true; + for (const auto lot : group.lots) { + if (!first) lots << ' '; + first = false; + + lots << lot; + } + groupElement->SetAttribute("l", lots.str().c_str()); + } + } +} + +void InventoryComponent::LoadGroupXml(const tinyxml2::XMLElement& groups) { + auto* groupElement = groups.FirstChildElement("grp"); + + while (groupElement) { + const char* groupId = nullptr; + const char* groupName = nullptr; + const char* lots = nullptr; + uint32_t inventory = eInventoryType::INVALID; + + groupElement->QueryStringAttribute("id", &groupId); + groupElement->QueryStringAttribute("n", &groupName); + groupElement->QueryStringAttribute("l", &lots); + groupElement->QueryAttribute("t", &inventory); + + if (!groupId || !groupName || !lots) { + LOG("Failed to load group from xml id %i name %i lots %i", + groupId == nullptr, groupName == nullptr, lots == nullptr); + } else { + auto& group = m_Groups[static_cast<eInventoryType>(inventory)].emplace_back(); + group.groupId = groupId; + group.groupName = groupName; + + for (const auto& lotStr : GeneralUtils::SplitString(lots, ' ')) { + auto lot = GeneralUtils::TryParse<LOT>(lotStr); + if (lot) group.lots.insert(*lot); + } + } + + groupElement = groupElement->NextSiblingElement("grp"); + } +} diff --git a/dGame/dComponents/InventoryComponent.h b/dGame/dComponents/InventoryComponent.h index a1eb14d1..28158ab5 100644 --- a/dGame/dComponents/InventoryComponent.h +++ b/dGame/dComponents/InventoryComponent.h @@ -37,6 +37,35 @@ enum class eItemType : int32_t; */ class InventoryComponent final : public Component { public: + struct Group { + // Generated ID for the group. The ID is sent by the client and has the format user_group + Math.random() * UINT_MAX. + std::string groupId; + // Custom name assigned by the user. + std::string groupName; + // All the lots the user has in the group. + std::set<LOT> lots; + }; + + enum class GroupUpdateCommand { + ADD, + ADD_LOT, + MODIFY, + REMOVE, + REMOVE_LOT, + }; + + // Based on the command, certain fields will be used or not used. + // for example, ADD_LOT wont use groupName, MODIFY wont use lots, etc. + struct GroupUpdate { + std::string groupId; + std::string groupName; + LOT lot; + eInventoryType inventory; + GroupUpdateCommand command; + }; + + static constexpr uint32_t MaximumGroupCount = 50; + static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::INVENTORY; InventoryComponent(Entity* parent); @@ -367,14 +396,23 @@ public: */ void UnequipScripts(Item* unequippedItem); - std::map<BehaviorSlot, uint32_t> GetSkills(){ return m_Skills; }; + std::map<BehaviorSlot, uint32_t> GetSkills() { return m_Skills; }; bool SetSkill(int slot, uint32_t skillId); bool SetSkill(BehaviorSlot slot, uint32_t skillId); + void UpdateGroup(const GroupUpdate& groupUpdate); + void RemoveGroup(const std::string& groupId); + ~InventoryComponent() override; private: + /** + * The key is the inventory the group belongs to, the value maps' key is the id for the group. + * This is only used for bricks and model inventories. + */ + std::map<eInventoryType, std::vector<Group>> m_Groups{ { eInventoryType::BRICKS, {} }, { eInventoryType::MODELS, {} } }; + /** * All the inventory this entity possesses */ @@ -477,6 +515,9 @@ private: * @param document the xml doc to load from */ void UpdatePetXml(tinyxml2::XMLDocument& document); + + void LoadGroupXml(const tinyxml2::XMLElement& groups); + void UpdateGroupXml(tinyxml2::XMLElement& groups) const; }; #endif diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index d2432e36..76fabce2 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -685,6 +685,13 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System case eGameMessageType::REQUEST_VENDOR_STATUS_UPDATE: GameMessages::SendVendorStatusUpdate(entity, sysAddr, true); break; + case eGameMessageType::UPDATE_INVENTORY_GROUP: + GameMessages::HandleUpdateInventoryGroup(inStream, entity, sysAddr); + break; + case eGameMessageType::UPDATE_INVENTORY_GROUP_CONTENTS: + GameMessages::HandleUpdateInventoryGroupContents(inStream, entity, sysAddr); + break; + default: LOG_DEBUG("Received Unknown GM with ID: %4i, %s", messageID, StringifiedEnum::ToString(messageID).data()); break; diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 4bd667b4..2fba411b 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -6251,6 +6251,69 @@ void GameMessages::SendSlashCommandFeedbackText(Entity* entity, std::u16string t SEND_PACKET; } +void GameMessages::HandleUpdateInventoryGroup(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { + std::string action; + std::u16string groupName; + InventoryComponent::GroupUpdate groupUpdate; + bool locked{}; // All groups are locked by default + + uint32_t size{}; + if (!inStream.Read(size)) return; + action.resize(size); + if (!inStream.Read(action.data(), size)) return; + + if (!inStream.Read(size)) return; + groupUpdate.groupId.resize(size); + if (!inStream.Read(groupUpdate.groupId.data(), size)) return; + + if (!inStream.Read(size)) return; + groupName.resize(size); + if (!inStream.Read(reinterpret_cast<char*>(groupName.data()), size * 2)) return; + + if (!inStream.Read(groupUpdate.inventory)) return; + if (!inStream.Read(locked)) return; + + groupUpdate.groupName = GeneralUtils::UTF16ToWTF8(groupName); + + if (action == "ADD") groupUpdate.command = InventoryComponent::GroupUpdateCommand::ADD; + else if (action == "MODIFY") groupUpdate.command = InventoryComponent::GroupUpdateCommand::MODIFY; + else if (action == "REMOVE") groupUpdate.command = InventoryComponent::GroupUpdateCommand::REMOVE; + else { + LOG("Invalid action %s", action.c_str()); + return; + } + + auto* inventoryComponent = entity->GetComponent<InventoryComponent>(); + if (inventoryComponent) inventoryComponent->UpdateGroup(groupUpdate); +} + +void GameMessages::HandleUpdateInventoryGroupContents(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { + std::string action; + InventoryComponent::GroupUpdate groupUpdate; + + uint32_t size{}; + if (!inStream.Read(size)) return; + action.resize(size); + if (!inStream.Read(action.data(), size)) return; + + if (action == "ADD") groupUpdate.command = InventoryComponent::GroupUpdateCommand::ADD_LOT; + else if (action == "REMOVE") groupUpdate.command = InventoryComponent::GroupUpdateCommand::REMOVE_LOT; + else { + LOG("Invalid action %s", action.c_str()); + return; + } + + if (!inStream.Read(size)) return; + groupUpdate.groupId.resize(size); + if (!inStream.Read(groupUpdate.groupId.data(), size)) return; + + if (!inStream.Read(groupUpdate.inventory)) return; + if (!inStream.Read(groupUpdate.lot)) return; + + auto* inventoryComponent = entity->GetComponent<InventoryComponent>(); + if (inventoryComponent) inventoryComponent->UpdateGroup(groupUpdate); +} + void GameMessages::SendForceCameraTargetCycle(Entity* entity, bool bForceCycling, eCameraTargetCyclingMode cyclingMode, LWOOBJID optionalTargetID) { CBITSTREAM; CMSGHEADER; diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index e3c16471..090fcd4b 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -673,6 +673,9 @@ namespace GameMessages { void HandleCancelDonationOnPlayer(RakNet::BitStream& inStream, Entity* entity); void SendSlashCommandFeedbackText(Entity* entity, std::u16string text); + + void HandleUpdateInventoryGroup(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); + void HandleUpdateInventoryGroupContents(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); void SendForceCameraTargetCycle(Entity* entity, bool bForceCycling, eCameraTargetCyclingMode cyclingMode, LWOOBJID optionalTargetID); }; From 94b9731a2bed8ee49094f5b7942eb5af745b7c3c Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 11 Aug 2024 10:26:25 -0700 Subject: [PATCH 78/78] Use the correct bit field for checking whether or not to decrement progress (#1631) Tested that the cooking mission with johnny umami now no longer allows you to lose progress by deleting items. --- dGame/dMission/MissionTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dGame/dMission/MissionTask.cpp b/dGame/dMission/MissionTask.cpp index 2fe9bc9f..aa2b9bca 100644 --- a/dGame/dMission/MissionTask.cpp +++ b/dGame/dMission/MissionTask.cpp @@ -186,7 +186,7 @@ void MissionTask::Progress(int32_t value, LWOOBJID associate, const std::string& if (count < 0) { if (mission->IsMission() && type == eMissionTaskType::GATHER && InAllTargets(value)) { - if (parameters.size() > 0 && (parameters[0] & 1) != 0) { + if (parameters.size() > 0 && (parameters[0] & 4) != 0) { return; }