From 1d2de705fbadd1c11552554e537731e26b3573d0 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 8 Jun 2026 20:44:29 -0700 Subject: [PATCH 01/26] fix: old man npc mission (#1982) * fix: old man npc mission tested that the repeatable daily now has to actually be done and also can actually be done. * Update OldManNPC.cpp --- dGame/dUtilities/Preconditions.cpp | 1 + dScripts/02_server/Map/njhub/CMakeLists.txt | 1 + dScripts/02_server/Map/njhub/OldManNPC.cpp | 31 +++++++++++++++++++++ dScripts/02_server/Map/njhub/OldManNPC.h | 10 +++++++ dScripts/CppScripts.cpp | 2 ++ 5 files changed, 45 insertions(+) create mode 100644 dScripts/02_server/Map/njhub/OldManNPC.cpp create mode 100644 dScripts/02_server/Map/njhub/OldManNPC.h diff --git a/dGame/dUtilities/Preconditions.cpp b/dGame/dUtilities/Preconditions.cpp index f0cacad0..da18c81c 100644 --- a/dGame/dUtilities/Preconditions.cpp +++ b/dGame/dUtilities/Preconditions.cpp @@ -230,6 +230,7 @@ PreconditionExpression::PreconditionExpression(const std::string& conditions) { case '&': case ';': case '(': + case ':': b << conditions.substr(i + 1); done = true; break; diff --git a/dScripts/02_server/Map/njhub/CMakeLists.txt b/dScripts/02_server/Map/njhub/CMakeLists.txt index 94d99867..367d4647 100644 --- a/dScripts/02_server/Map/njhub/CMakeLists.txt +++ b/dScripts/02_server/Map/njhub/CMakeLists.txt @@ -18,6 +18,7 @@ set(DSCRIPTS_SOURCES_02_SERVER_MAP_NJHUB "NjJayMissionItems.cpp" "NjNPCMissionSpinjitzuServer.cpp" "NjNyaMissionitems.cpp" + "OldManNPC.cpp" "NjScrollChestServer.cpp" "NjWuNPC.cpp" "RainOfArrows.cpp") diff --git a/dScripts/02_server/Map/njhub/OldManNPC.cpp b/dScripts/02_server/Map/njhub/OldManNPC.cpp new file mode 100644 index 00000000..4d1f1056 --- /dev/null +++ b/dScripts/02_server/Map/njhub/OldManNPC.cpp @@ -0,0 +1,31 @@ +#include "OldManNPC.h" + +#include "eMissionState.h" +#include "Character.h" +#include "MissionComponent.h" + +void ResetMissions(Entity& user) { + for (int32_t i = 1; i < 7; i++) { + int32_t flag = 2020 + i; + auto* const character = user.GetCharacter(); + if (character) character->SetPlayerFlag(flag, false); + } +} + +void OldManNPC::OnUse(Entity* self, Entity* user) { + LOG(""); + const auto* const missionComponent = user->GetComponent(); + if (!missionComponent) return; + + const auto* const mission = missionComponent->GetMission(2039); + if (!mission) { + ResetMissions(*user); // shouldnt be needed for dlu but it is because the mission is null + return; + } + + const auto missionState = mission->GetMissionState(); + LOG("mission state %i", missionState); + if (missionState == eMissionState::AVAILABLE || missionState == eMissionState::COMPLETE_AVAILABLE) { + ResetMissions(*user); + } +} diff --git a/dScripts/02_server/Map/njhub/OldManNPC.h b/dScripts/02_server/Map/njhub/OldManNPC.h new file mode 100644 index 00000000..a7924f98 --- /dev/null +++ b/dScripts/02_server/Map/njhub/OldManNPC.h @@ -0,0 +1,10 @@ +#ifndef OLDMANNPC_H +#define OLDMANNPC_H + +#include "CppScripts.h" + +class OldManNPC : public CppScripts::Script { + void OnUse(Entity* self, Entity* user) override; +}; + +#endif //!OLDMANNPC_H diff --git a/dScripts/CppScripts.cpp b/dScripts/CppScripts.cpp index 4c0ba749..6300716f 100644 --- a/dScripts/CppScripts.cpp +++ b/dScripts/CppScripts.cpp @@ -278,6 +278,7 @@ #include "NjEarthPetServer.h" #include "NjDragonEmblemChestServer.h" #include "NjNyaMissionitems.h" +#include "OldManNPC.h" // Scripted equipment #include "AnvilOfArmor.h" @@ -628,6 +629,7 @@ namespace { {"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();}}, + {"scripts\\02_server\\Map\\njhub\\L_OLD_MAN_NPC.lua", []() {return new OldManNPC();}}, //DLU {"scripts\\02_server\\DLU\\DLUVanityTeleportingObject.lua", []() {return new DLUVanityTeleportingObject();}}, From ca0da9d3bfc5fad6f39a4e8209fbbaf1e583a108 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Mon, 8 Jun 2026 22:45:24 -0500 Subject: [PATCH 02/26] feat: preconditions improvements (#1983) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: implement missing precondition types (20, 21, 23) and pet checks Add DoesNotHaveFlag (23), NotFreeTrial (20), and MissionActive (21) to PreconditionType and implement their checks. Also implement PetDeployed and IsPetTaming using PetComponent static helpers, matching client behavior — both are simple boolean checks with no LOT comparison. LegoClubMember is set to always pass as DLU has no membership concept. * fix: update TODO comments for team check and racing licence preconditions * type Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- dGame/dUtilities/Preconditions.cpp | 27 ++++++++++++++++++++------- dGame/dUtilities/Preconditions.h | 5 ++++- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/dGame/dUtilities/Preconditions.cpp b/dGame/dUtilities/Preconditions.cpp index da18c81c..26f1f7bd 100644 --- a/dGame/dUtilities/Preconditions.cpp +++ b/dGame/dUtilities/Preconditions.cpp @@ -13,6 +13,7 @@ #include "DestroyableComponent.h" #include "GameMessages.h" #include "eMissionState.h" +#include "PetComponent.h" std::map Preconditions::cache = {}; @@ -79,6 +80,9 @@ bool Precondition::Check(Entity* player, bool evaluateCosts) const { case PreconditionType::DoesNotHaveRacingLicence: case PreconditionType::LegoClubMember: case PreconditionType::NoInteraction: + case PreconditionType::NotFreeTrial: + case PreconditionType::MissionActive: + case PreconditionType::DoesNotHaveFlag: any = true; break; case PreconditionType::DoesNotHaveItem: @@ -154,7 +158,7 @@ bool Precondition::CheckValue(Entity* player, const uint32_t value, bool evaluat if (missionComponent == nullptr) return false; return missionComponent->GetMissionState(value) >= eMissionState::COMPLETE; case PreconditionType::PetDeployed: - return false; // TODO + return PetComponent::GetActivePet(player->GetObjectID()) != nullptr; case PreconditionType::HasFlag: return character->GetPlayerFlag(value); case PreconditionType::WithinShape: @@ -162,9 +166,9 @@ bool Precondition::CheckValue(Entity* player, const uint32_t value, bool evaluat case PreconditionType::InBuild: return character->GetBuildMode(); case PreconditionType::TeamCheck: - return false; // TODO + return false; // TODO: requires knowing the player's minigame team assignment (red/blue etc.); DLU does not track this per-player case PreconditionType::IsPetTaming: - return false; // TODO + return PetComponent::GetTamingPet(player->GetObjectID()) != nullptr; case PreconditionType::HasFaction: for (const auto faction : destroyableComponent->GetFactionIDs()) { if (faction == static_cast(value)) { @@ -182,15 +186,24 @@ bool Precondition::CheckValue(Entity* player, const uint32_t value, bool evaluat return true; case PreconditionType::HasRacingLicence: - return false; // TODO + return false; // TODO: requires a racing licence level on the player; DLU does not track this case PreconditionType::DoesNotHaveRacingLicence: - return false; // TODO + return false; // TODO: requires a racing licence level on the player; DLU does not track this case PreconditionType::LegoClubMember: - return false; // TODO + return true; // Live LU opened LEGO CLUB to All players at some point, so always return true case PreconditionType::NoInteraction: - return false; // TODO + return false; // TODO: requires tracking the player's currently active interaction object; DLU does not track this case PreconditionType::HasLevel: return levelComponent->GetLevel() >= value; + case PreconditionType::NotFreeTrial: + return true; // DLU does not support free trial accounts; all players pass this check + case PreconditionType::MissionActive: { + if (missionComponent == nullptr) return false; + const auto state = missionComponent->GetMissionState(value); + return state == eMissionState::ACTIVE || state == eMissionState::COMPLETE_ACTIVE; + } + case PreconditionType::DoesNotHaveFlag: + return !character->GetPlayerFlag(value); default: return true; // There are a couple more unknown preconditions. Always return true in this case. } diff --git a/dGame/dUtilities/Preconditions.h b/dGame/dUtilities/Preconditions.h index 2b6e1216..0a1ac70b 100644 --- a/dGame/dUtilities/Preconditions.h +++ b/dGame/dUtilities/Preconditions.h @@ -26,7 +26,10 @@ enum class PreconditionType DoesNotHaveRacingLicence, LegoClubMember, NoInteraction, - HasLevel = 22 + NotFreeTrial, + MissionActive, + HasLevel, + DoesNotHaveFlag = 23 }; From 045e097b139bf8746d219edf2d2fb6b0b383f3cd Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 8 Jun 2026 20:57:33 -0700 Subject: [PATCH 03/26] chore: cleanup zoneIM (#1985) --- dNet/ZoneInstanceManager.cpp | 43 ++++++++++-------------------------- dNet/ZoneInstanceManager.h | 17 +++++--------- 2 files changed, 18 insertions(+), 42 deletions(-) diff --git a/dNet/ZoneInstanceManager.cpp b/dNet/ZoneInstanceManager.cpp index 354d3634..3be4b9a8 100644 --- a/dNet/ZoneInstanceManager.cpp +++ b/dNet/ZoneInstanceManager.cpp @@ -1,26 +1,17 @@ -#define _VARIADIC_MAX 10 #include "ZoneInstanceManager.h" // Custom Classes #include "MasterPackets.h" -#include "dServer.h" - -// C++ -#include // Static Variables ZoneInstanceManager* ZoneInstanceManager::m_Address = nullptr; //! Requests a zone transfer -void ZoneInstanceManager::RequestZoneTransfer(dServer* server, uint32_t zoneID, uint32_t zoneClone, bool mythranShift, std::function callback) { +void ZoneInstanceManager::RequestZoneTransfer(dServer* server, uint32_t zoneID, uint32_t zoneClone, bool mythranShift, TransferCallback callback) { + const auto nextID = ++currentRequestID; + requests[nextID] = callback; - ZoneTransferRequest* request = new ZoneTransferRequest(); - request->requestID = ++currentRequestID; - request->callback = callback; - - this->requests.push_back(request); - - MasterPackets::SendZoneTransferRequest(server, request->requestID, mythranShift, zoneID, zoneClone); + MasterPackets::SendZoneTransferRequest(server, nextID, mythranShift, zoneID, zoneClone); } //! Handles a zone transfer response @@ -43,18 +34,11 @@ void ZoneInstanceManager::HandleRequestZoneTransferResponse(Packet* packet) { LUString serverIP(255); inStream.Read(serverIP); - for (uint32_t i = 0; i < this->requests.size(); ++i) { - if (this->requests[i]->requestID == requestID) { - - // Call the request callback - this->requests[i]->callback(mythranShift, zoneID, zoneInstance, zoneClone, serverIP.string, serverPort); - - delete this->requests[i]; - this->requests.erase(this->requests.begin() + i); - return; - } + const auto entry = requests.find(requestID); + if (entry != requests.end()) { + entry->second(mythranShift, zoneID, zoneInstance, zoneClone, serverIP.string, serverPort); + requests.erase(entry); } - } void ZoneInstanceManager::CreatePrivateZone(dServer* server, uint32_t zoneID, uint32_t zoneClone, const std::string& password) { @@ -65,12 +49,9 @@ void ZoneInstanceManager::RequestPrivateZone( dServer* server, bool mythranShift, const std::string& password, - std::function callback) { - ZoneTransferRequest* request = new ZoneTransferRequest(); - request->requestID = ++currentRequestID; - request->callback = callback; + TransferCallback callback) { + const auto nextID = ++currentRequestID; + requests[nextID] = callback; - this->requests.push_back(request); - - MasterPackets::SendZoneRequestPrivate(server, request->requestID, mythranShift, password); + MasterPackets::SendZoneRequestPrivate(server, nextID, mythranShift, password); } diff --git a/dNet/ZoneInstanceManager.h b/dNet/ZoneInstanceManager.h index 47080cde..b47f92e2 100644 --- a/dNet/ZoneInstanceManager.h +++ b/dNet/ZoneInstanceManager.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -14,25 +15,20 @@ class dServer; \brief A class for handling zone transfers and zone-related functions */ - //! The zone request -struct ZoneTransferRequest { - uint64_t requestID; - std::function callback; -}; - //! The zone manager class ZoneInstanceManager { private: static ZoneInstanceManager* m_Address; //!< The singleton instance - std::vector requests; //!< The zone transfer requests + using TransferCallback = std::function; + std::map requests; //!< The zone transfer requests uint64_t currentRequestID; //!< The current request ID public: //! The singleton method static ZoneInstanceManager* Instance() { - if (m_Address == 0) { + if (m_Address == nullptr) { m_Address = new ZoneInstanceManager; m_Address->currentRequestID = 0; } @@ -47,7 +43,7 @@ public: \param mythranShift Whether or not this is a mythran shift \param callback The callback function */ - void RequestZoneTransfer(dServer* server, uint32_t zoneID, uint32_t zoneClone, bool mythranShift, std::function callback); + void RequestZoneTransfer(dServer* server, uint32_t zoneID, uint32_t zoneClone, bool mythranShift, TransferCallback callback); //! Handles a zone transfer response /*! @@ -58,6 +54,5 @@ public: void CreatePrivateZone(dServer* server, uint32_t zoneID, uint32_t zoneClone, const std::string& password); - void RequestPrivateZone(dServer* server, bool mythranShift, const std::string& password, std::function callback); - + void RequestPrivateZone(dServer* server, bool mythranShift, const std::string& password, TransferCallback callback); }; From a307f0601a84e4361461d3a5baf15b784b093d2f Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 8 Jun 2026 21:22:04 -0700 Subject: [PATCH 04/26] fix: bugs in private instances causing master crashes (#1986) * fix: bugs in private instances causing master crashes tested that creating a private instance and shutting down the server no longer crashes master * Update InstanceManager.cpp --- dMasterServer/InstanceManager.cpp | 7 ++++++- dMasterServer/InstanceManager.h | 1 + dMasterServer/MasterServer.cpp | 10 ++++------ 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/dMasterServer/InstanceManager.cpp b/dMasterServer/InstanceManager.cpp index c2ab3333..6ddd32d5 100644 --- a/dMasterServer/InstanceManager.cpp +++ b/dMasterServer/InstanceManager.cpp @@ -332,6 +332,12 @@ int InstanceManager::GetHardCap(LWOMAPID mapID) { return zone ? zone->population_hard_cap : 12; } +void InstanceManager::PruneUnreadyInstances() { + for (int i = static_cast(m_Instances.size()) - 1; i >= 0; i--) { + if (!m_Instances[i]->GetIsReady()) m_Instances.erase(m_Instances.cbegin() + i); + } +} + void Instance::SetShutdownComplete(const bool value) { m_Shutdown = value; } @@ -359,4 +365,3 @@ bool Instance::IsFull(bool isFriendTransfer) const { return true; } - diff --git a/dMasterServer/InstanceManager.h b/dMasterServer/InstanceManager.h index a6ba6d9a..5481efbc 100644 --- a/dMasterServer/InstanceManager.h +++ b/dMasterServer/InstanceManager.h @@ -133,6 +133,7 @@ public: const InstancePtr& CreatePrivateInstance(LWOMAPID mapID, LWOCLONEID cloneID, const std::string& password); const InstancePtr& FindPrivateInstance(const std::string& password); void SetIsShuttingDown(bool value) { this->m_IsShuttingDown = value; }; + void PruneUnreadyInstances(); private: std::string mExternalIP; diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 0ae71f07..4fd89fbe 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -665,7 +665,7 @@ void HandlePacket(Packet* packet) { inStream.Read(theirInstanceID); const auto& instance = - Game::im->FindInstance(theirZoneID, theirInstanceID); + Game::im->FindInstanceWithPrivate(theirZoneID, theirInstanceID); if (instance) { instance->AddPlayer(Player()); } else { @@ -762,7 +762,7 @@ void HandlePacket(Packet* packet) { LOG("Got world ready %i %i", zoneID, instanceID); - const auto& instance = Game::im->FindInstance(zoneID, instanceID); + const auto& instance = Game::im->FindInstanceWithPrivate(zoneID, instanceID); if (instance == nullptr) { LOG("Failed to find zone to ready"); @@ -858,13 +858,11 @@ int ShutdownSequence(int32_t signal) { } // A server might not be finished spinning up yet, remove all of those here. + // prune the unready ones before looping over all of them + Game::im->PruneUnreadyInstances(); for (const auto& instance : Game::im->GetInstances()) { if (!instance) continue; - if (!instance->GetIsReady()) { - Game::im->RemoveInstance(instance); - } - instance->SetIsShuttingDown(true); } From 93076dc36d62a6a34fb310863c18ca2342c17468 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 8 Jun 2026 21:42:32 -0700 Subject: [PATCH 05/26] chore: simplify metrics (#1987) * chore: simplify metrics rename to hpp and remove unused includes * feedback --- dCommon/Metrics.cpp | 144 +++++------------- dCommon/Metrics.h | 48 ++++++ dCommon/Metrics.hpp | 61 -------- dGame/EntityManager.cpp | 1 - dGame/LeaderboardManager.cpp | 3 +- dGame/dComponents/BaseCombatAIComponent.cpp | 1 - .../SlashCommands/DEVGMCommands.cpp | 18 +-- dWorldServer/WorldServer.cpp | 3 +- 8 files changed, 100 insertions(+), 179 deletions(-) create mode 100644 dCommon/Metrics.h delete mode 100644 dCommon/Metrics.hpp diff --git a/dCommon/Metrics.cpp b/dCommon/Metrics.cpp index 5232cf78..f196bde6 100644 --- a/dCommon/Metrics.cpp +++ b/dCommon/Metrics.cpp @@ -1,105 +1,77 @@ -#include "Metrics.hpp" +#include "Metrics.h" + +#include "StringifiedEnum.h" #include -std::unordered_map Metrics::m_Metrics = {}; -std::vector Metrics::m_Variables = { - MetricVariable::GameLoop, - MetricVariable::PacketHandling, - MetricVariable::UpdateEntities, - MetricVariable::UpdateSpawners, - MetricVariable::Physics, - MetricVariable::UpdateReplica, - MetricVariable::Ghosting, - MetricVariable::CPUTime, - MetricVariable::Sleep, - MetricVariable::Frame, -}; +namespace { + std::unordered_map g_Metrics = {}; + std::vector g_Variables = { + MetricVariable::GameLoop, + MetricVariable::PacketHandling, + MetricVariable::UpdateEntities, + MetricVariable::UpdateSpawners, + MetricVariable::Physics, + MetricVariable::UpdateReplica, + MetricVariable::Ghosting, + MetricVariable::CPUTime, + MetricVariable::Sleep, + MetricVariable::Frame, + }; +} void Metrics::AddMeasurement(MetricVariable variable, int64_t value) { - const auto& iter = m_Metrics.find(variable); - - Metric* metric; - - if (iter == m_Metrics.end()) { - metric = new Metric(); - - m_Metrics[variable] = metric; - } else { - metric = iter->second; - } + auto& metric = g_Metrics[variable]; AddMeasurement(metric, value); } -void Metrics::AddMeasurement(Metric* metric, int64_t value) { - const auto index = metric->measurementIndex; +void Metrics::AddMeasurement(Metric& metric, int64_t value) { + const auto index = metric.measurementIndex; - metric->measurements[index] = value; + metric.measurements[index] = value; - if (metric->max == -1 || value > metric->max) { - metric->max = value; - } else if (metric->min == -1 || metric->min > value) { - metric->min = value; + if (metric.max == -1 || value > metric.max) { + metric.max = value; + } else if (metric.min == -1 || metric.min > value) { + metric.min = value; } - if (metric->measurementSize < MAX_MEASURMENT_POINTS) { - metric->measurementSize++; + if (metric.measurementSize < MAX_MEASURMENT_POINTS) { + metric.measurementSize++; } - metric->measurementIndex = (index + 1) % MAX_MEASURMENT_POINTS; + metric.measurementIndex = (index + 1) % MAX_MEASURMENT_POINTS; } -const Metric* Metrics::GetMetric(MetricVariable variable) { - const auto& iter = m_Metrics.find(variable); - - if (iter == m_Metrics.end()) { - return nullptr; - } - - Metric* metric = iter->second; +const Metric& Metrics::GetMetric(MetricVariable variable) { + auto& metric = g_Metrics[variable]; int64_t average = 0; - for (size_t i = 0; i < metric->measurementSize; i++) { - average += metric->measurements[i]; + for (size_t i = 0; i < metric.measurementSize; i++) { + average += metric.measurements[i]; } - average /= metric->measurementSize; + average /= metric.measurementSize; - metric->average = average; + metric.average = average; return metric; } void Metrics::StartMeasurement(MetricVariable variable) { - const auto& iter = m_Metrics.find(variable); + auto& metric = g_Metrics[variable]; - Metric* metric; - - if (iter == m_Metrics.end()) { - metric = new Metric(); - - m_Metrics[variable] = metric; - } else { - metric = iter->second; - } - - metric->activeMeasurement = std::chrono::high_resolution_clock::now(); + metric.activeMeasurement = std::chrono::high_resolution_clock::now(); } void Metrics::EndMeasurement(MetricVariable variable) { const auto end = std::chrono::high_resolution_clock::now(); - const auto& iter = m_Metrics.find(variable); + auto& metric = g_Metrics[variable]; - if (iter == m_Metrics.end()) { - return; - } - - Metric* metric = iter->second; - - const auto elapsed = end - metric->activeMeasurement; + const auto elapsed = end - metric.activeMeasurement; const auto nanoseconds = std::chrono::duration_cast(elapsed).count(); @@ -110,44 +82,12 @@ float Metrics::ToMiliseconds(int64_t nanoseconds) { return static_cast(nanoseconds) / 1e6; } -std::string Metrics::MetricVariableToString(MetricVariable variable) { - switch (variable) { - case MetricVariable::GameLoop: - return "GameLoop"; - case MetricVariable::PacketHandling: - return "PacketHandling"; - case MetricVariable::UpdateEntities: - return "UpdateEntities"; - case MetricVariable::UpdateSpawners: - return "UpdateSpawners"; - case MetricVariable::Physics: - return "Physics"; - case MetricVariable::UpdateReplica: - return "UpdateReplica"; - case MetricVariable::Sleep: - return "Sleep"; - case MetricVariable::CPUTime: - return "CPUTime"; - case MetricVariable::Frame: - return "Frame"; - case MetricVariable::Ghosting: - return "Ghosting"; - - default: - return "Invalid"; - } +const std::string_view Metrics::MetricVariableToString(MetricVariable variable) { + return StringifiedEnum::ToString(variable); } const std::vector& Metrics::GetAllMetrics() { - return m_Variables; -} - -void Metrics::Clear() { - for (const auto& pair : m_Metrics) { - delete pair.second; - } - - m_Metrics.clear(); + return g_Variables; } /* RSS Memory utilities diff --git a/dCommon/Metrics.h b/dCommon/Metrics.h new file mode 100644 index 00000000..ddf9bf09 --- /dev/null +++ b/dCommon/Metrics.h @@ -0,0 +1,48 @@ +#pragma once + +#include "dCommonVars.h" +#include +#include +#include +#include +#include + +#define MAX_MEASURMENT_POINTS 1024 + +enum class MetricVariable : int32_t { + GameLoop, + PacketHandling, + UpdateEntities, + UpdateSpawners, + Physics, + UpdateReplica, + Ghosting, + CPUTime, + Sleep, + Frame, +}; + +struct Metric { + int64_t measurements[MAX_MEASURMENT_POINTS] = {}; + size_t measurementIndex = 0; + size_t measurementSize = 0; + int64_t max = -1; + int64_t min = -1; + int64_t average = 0; + std::chrono::time_point activeMeasurement; +}; + +namespace Metrics { + void AddMeasurement(MetricVariable variable, int64_t value); + void AddMeasurement(Metric& metric, int64_t value); + const Metric& GetMetric(MetricVariable variable); + void StartMeasurement(MetricVariable variable); + void EndMeasurement(MetricVariable variable); + float ToMiliseconds(int64_t nanoseconds); + const std::string_view MetricVariableToString(MetricVariable variable); + const std::vector& GetAllMetrics(); + + size_t GetPeakRSS(); + size_t GetCurrentRSS(); + size_t GetProcessID(); +}; diff --git a/dCommon/Metrics.hpp b/dCommon/Metrics.hpp deleted file mode 100644 index c03c914f..00000000 --- a/dCommon/Metrics.hpp +++ /dev/null @@ -1,61 +0,0 @@ -#pragma once - -#include "dCommonVars.h" -#include -#include -#include -#include - -#define MAX_MEASURMENT_POINTS 1024 - -enum class MetricVariable : int32_t -{ - GameLoop, - PacketHandling, - UpdateEntities, - UpdateSpawners, - Physics, - UpdateReplica, - Ghosting, - CPUTime, - Sleep, - Frame, -}; - -struct Metric -{ - int64_t measurements[MAX_MEASURMENT_POINTS] = {}; - size_t measurementIndex = 0; - size_t measurementSize = 0; - int64_t max = -1; - int64_t min = -1; - int64_t average = 0; - std::chrono::time_point activeMeasurement; -}; - -class Metrics -{ -public: - ~Metrics(); - - static void AddMeasurement(MetricVariable variable, int64_t value); - static void AddMeasurement(Metric* metric, int64_t value); - static const Metric* GetMetric(MetricVariable variable); - static void StartMeasurement(MetricVariable variable); - static void EndMeasurement(MetricVariable variable); - static float ToMiliseconds(int64_t nanoseconds); - static std::string MetricVariableToString(MetricVariable variable); - static const std::vector& GetAllMetrics(); - - static size_t GetPeakRSS(); - static size_t GetCurrentRSS(); - static size_t GetProcessID(); - - static void Clear(); - -private: - Metrics(); - - static std::unordered_map m_Metrics; - static std::vector m_Variables; -}; diff --git a/dGame/EntityManager.cpp b/dGame/EntityManager.cpp index 92258afc..fee2b66a 100644 --- a/dGame/EntityManager.cpp +++ b/dGame/EntityManager.cpp @@ -10,7 +10,6 @@ #include "SkillComponent.h" #include "SwitchComponent.h" #include "UserManager.h" -#include "Metrics.hpp" #include "dZoneManager.h" #include "MissionComponent.h" #include "Game.h" diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index a6f13c65..b8e40154 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -18,7 +18,8 @@ #include "DluAssert.h" #include "CDActivitiesTable.h" -#include "Metrics.hpp" + +#include namespace LeaderboardManager { std::map leaderboardCache; diff --git a/dGame/dComponents/BaseCombatAIComponent.cpp b/dGame/dComponents/BaseCombatAIComponent.cpp index 5f6399da..b97211b8 100644 --- a/dGame/dComponents/BaseCombatAIComponent.cpp +++ b/dGame/dComponents/BaseCombatAIComponent.cpp @@ -23,7 +23,6 @@ #include "SkillComponent.h" #include "QuickBuildComponent.h" #include "DestroyableComponent.h" -#include "Metrics.hpp" #include "CDComponentsRegistryTable.h" #include "CDPhysicsComponentTable.h" #include "dNavMesh.h" diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp index f1b92e26..f5dbd29f 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp @@ -13,7 +13,7 @@ #include "dpShapeSphere.h" #include "dZoneManager.h" #include "EntityInfo.h" -#include "Metrics.hpp" +#include "Metrics.h" #include "PlayerManager.h" #include "SlashCommandHandler.h" #include "UserManager.h" @@ -1288,18 +1288,14 @@ namespace DEVGMCommands { response.Insert("serverInfo", true); auto* info = response.InsertArray("data"); for (const auto variable : Metrics::GetAllMetrics()) { - auto& metricData = info->PushDebug(StringifiedEnum::ToString(variable)); + auto& metricData = info->PushDebug(Metrics::MetricVariableToString(variable)); - auto* metric = Metrics::GetMetric(variable); + const auto& metric = Metrics::GetMetric(variable); - if (metric == nullptr) { - continue; - } - - metricData.PushDebug("Maximum") = std::to_string(Metrics::ToMiliseconds(metric->max)) + "ms"; - metricData.PushDebug("Minimum") = std::to_string(Metrics::ToMiliseconds(metric->min)) + "ms"; - metricData.PushDebug("Average") = std::to_string(Metrics::ToMiliseconds(metric->average)) + "ms"; - metricData.PushDebug("Measurements Count") = std::to_string(metric->measurementSize); + metricData.PushDebug("Maximum") = std::to_string(Metrics::ToMiliseconds(metric.max)) + "ms"; + metricData.PushDebug("Minimum") = std::to_string(Metrics::ToMiliseconds(metric.min)) + "ms"; + metricData.PushDebug("Average") = std::to_string(Metrics::ToMiliseconds(metric.average)) + "ms"; + metricData.PushDebug("Measurements Count") = std::to_string(metric.measurementSize); } auto& processInfo = info->PushDebug("Process Info"); processInfo.PushDebug("Peak RSS") = std::to_string(static_cast(Metrics::GetPeakRSS()) / 1.024e6) + "MB"; diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 48a11cf2..8de1d4e5 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -14,7 +14,7 @@ #include "dConfig.h" #include "dpWorld.h" #include "dZoneManager.h" -#include "Metrics.hpp" +#include "Metrics.h" #include "PerformanceManager.h" #include "Diagnostics.h" #include "BinaryPathFinder.h" @@ -1538,7 +1538,6 @@ void FinalizeShutdown() { LOG("Shutdown complete, zone (%i), instance (%i)", Game::server->GetZoneID(), g_InstanceID); //Delete our objects here: - Metrics::Clear(); dpWorld::Shutdown(); Database::Destroy("WorldServer"); if (Game::chatFilter) delete Game::chatFilter; From bb8f569354d181a254c520d9847c4ed14801490e Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Tue, 9 Jun 2026 14:05:21 -0700 Subject: [PATCH 06/26] chore: Remove pointer usage in trading (#1988) * chore: Remove pointer usage in trading tested that I could still do a trade * Update TradingManager.h * remove returned object --- dGame/TradingManager.cpp | 42 ++++++++++------------------ dGame/TradingManager.h | 23 +++++++-------- dGame/dGameMessages/GameMessages.cpp | 8 +++--- 3 files changed, 29 insertions(+), 44 deletions(-) diff --git a/dGame/TradingManager.cpp b/dGame/TradingManager.cpp index c7143354..43d1c98a 100644 --- a/dGame/TradingManager.cpp +++ b/dGame/TradingManager.cpp @@ -10,6 +10,11 @@ #include "CharacterComponent.h" #include "MissionComponent.h" #include "eMissionTaskType.h" +#include + +namespace { + std::unique_ptr g_EmptyTrade; +} TradingManager* TradingManager::m_Address = nullptr; @@ -233,55 +238,38 @@ void Trade::SendUpdateToOther(LWOOBJID participant) { GameMessages::SendServerTradeUpdate(other->GetObjectID(), coins, items, other->GetSystemAddress()); } -TradingManager::TradingManager() { -} - -TradingManager::~TradingManager() { - for (const auto& pair : trades) { - delete pair.second; - } - - trades.clear(); -} - -Trade* TradingManager::GetTrade(LWOOBJID tradeId) const { +const std::unique_ptr& TradingManager::GetTrade(LWOOBJID tradeId) const { const auto& pair = trades.find(tradeId); - if (pair == trades.end()) return nullptr; + if (pair == trades.end()) return g_EmptyTrade; return pair->second; } -Trade* TradingManager::GetPlayerTrade(LWOOBJID playerId) const { - for (const auto& pair : trades) { - if (pair.second->IsParticipant(playerId)) { - return pair.second; +const std::unique_ptr& TradingManager::GetPlayerTrade(LWOOBJID playerId) const { + for (const auto& trade : trades | std::views::values) { + if (trade->IsParticipant(playerId)) { + return trade; } } - return nullptr; + return g_EmptyTrade; } void TradingManager::CancelTrade(const LWOOBJID canceller, LWOOBJID tradeId, const bool sendCancelMessage) { - auto* trade = GetTrade(tradeId); + const auto& trade = GetTrade(tradeId); if (trade == nullptr) return; if (sendCancelMessage) trade->Cancel(canceller); - delete trade; - trades.erase(tradeId); } -Trade* TradingManager::NewTrade(LWOOBJID participantA, LWOOBJID participantB) { +void TradingManager::NewTrade(LWOOBJID participantA, LWOOBJID participantB) { const LWOOBJID tradeId = ObjectIDManager::GenerateObjectID(); - auto* trade = new Trade(tradeId, participantA, participantB); - - trades[tradeId] = trade; + trades.insert_or_assign(tradeId, std::make_unique(tradeId, participantA, participantB)); LOG("Created new trade between (%llu) <-> (%llu)", participantA, participantB); - - return trade; } diff --git a/dGame/TradingManager.h b/dGame/TradingManager.h index fa55aa9d..d9984a5b 100644 --- a/dGame/TradingManager.h +++ b/dGame/TradingManager.h @@ -2,15 +2,16 @@ #include "Entity.h" -struct TradeItem -{ +#include +#include + +struct TradeItem { LWOOBJID itemId; LOT itemLot; uint32_t itemCount; }; -class Trade -{ +class Trade { public: explicit Trade(LWOOBJID tradeId, LWOOBJID participantA, LWOOBJID participantB); ~Trade(); @@ -50,8 +51,7 @@ private: }; -class TradingManager -{ +class TradingManager { public: static TradingManager* Instance() { if (!m_Address) { @@ -61,16 +61,13 @@ public: return m_Address; } - explicit TradingManager(); - ~TradingManager(); - - Trade* GetTrade(LWOOBJID tradeId) const; - Trade* GetPlayerTrade(LWOOBJID playerId) const; + const std::unique_ptr& GetTrade(LWOOBJID tradeId) const; + const std::unique_ptr& GetPlayerTrade(LWOOBJID playerId) const; void CancelTrade(const LWOOBJID canceller, LWOOBJID tradeId, const bool sendCancelMessage = true); - Trade* NewTrade(LWOOBJID participantA, LWOOBJID participantB); + void NewTrade(LWOOBJID participantA, LWOOBJID participantB); private: static TradingManager* m_Address; //For singleton method - std::unordered_map trades; + std::unordered_map> trades; }; diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 3af6cb48..5edd47f2 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -3221,7 +3221,7 @@ void GameMessages::HandleClientTradeRequest(RakNet::BitStream& inStream, Entity* LOG("Trade request to (%llu)", i64Invitee); - auto* trade = TradingManager::Instance()->GetPlayerTrade(entity->GetObjectID()); + const auto& trade = TradingManager::Instance()->GetPlayerTrade(entity->GetObjectID()); if (trade != nullptr) { if (!trade->IsParticipant(i64Invitee)) { @@ -3244,7 +3244,7 @@ void GameMessages::HandleClientTradeRequest(RakNet::BitStream& inStream, Entity* } void GameMessages::HandleClientTradeCancel(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { - auto* trade = TradingManager::Instance()->GetPlayerTrade(entity->GetObjectID()); + const auto& trade = TradingManager::Instance()->GetPlayerTrade(entity->GetObjectID()); if (trade == nullptr) return; @@ -3258,7 +3258,7 @@ void GameMessages::HandleClientTradeAccept(RakNet::BitStream& inStream, Entity* LOG("Trade accepted from (%llu) -> (%d)", entity->GetObjectID(), bFirst); - auto* trade = TradingManager::Instance()->GetPlayerTrade(entity->GetObjectID()); + const auto& trade = TradingManager::Instance()->GetPlayerTrade(entity->GetObjectID()); if (trade == nullptr) return; @@ -3324,7 +3324,7 @@ void GameMessages::HandleClientTradeUpdate(RakNet::BitStream& inStream, Entity* LOG("Trade item from (%llu) -> (%llu)/(%llu), (%i), (%llu), (%i), (%i)", entity->GetObjectID(), itemId, itemId2, lot, unknown1, unknown2, unknown3); } - auto* trade = TradingManager::Instance()->GetPlayerTrade(entity->GetObjectID()); + const auto& trade = TradingManager::Instance()->GetPlayerTrade(entity->GetObjectID()); if (trade == nullptr) return; From 90607bdd5c1a27c0379bccf1d6e65dbc74fad000 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 11 Jun 2026 07:11:52 -0700 Subject: [PATCH 07/26] fix: setting smashable ignoring lnv settings (#1992) tested that the computer build on crux no longer stays around for +12 seconds --- dGame/Entity.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index e9e4b4e8..5dde43e5 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -427,7 +427,7 @@ void Entity::Initialize() { comp->SetMaxArmor(destCompData[0].armor); comp->SetDeathBehavior(destCompData[0].death_behavior); - comp->SetIsSmashable(destCompData[0].isSmashable); + comp->SetIsSmashable(comp->GetIsSmashable() || destCompData[0].isSmashable); comp->SetLootMatrixID(destCompData[0].LootMatrixIndex); comp->SetCurrencyIndex(destCompData[0].CurrencyIndex); From 707880b5fc4c6871af8652ad2f552e06822ed903 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 11 Jun 2026 07:12:06 -0700 Subject: [PATCH 08/26] fix: fv pipe quick build not spawning as it should (#1991) tested that the pipe now spawns a ROCK that you can build. This ROCK you build spawns the PIPE now. new bug: if you start building the ROCK and stop, the pipe will spawn instead of the previous rock. --- dGame/Entity.cpp | 3 +-- dGame/dComponents/QuickBuildComponent.cpp | 30 +++++++++++++++++++++++ dGame/dComponents/QuickBuildComponent.h | 2 ++ dZoneManager/Spawner.cpp | 3 ++- 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 5dde43e5..d88aa1d5 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -1613,8 +1613,7 @@ void Entity::Kill(Entity* murderer, const eKillType killType) { else Game::entityManager->DestroyEntity(this); } - const auto& grpNameQBShowBricks = GetVar(u"grpNameQBShowBricks"); - + const auto& grpNameQBShowBricks = GetVarAsString(u"grpNameQBShowBricks"); if (!grpNameQBShowBricks.empty()) { auto spawners = Game::zoneManager->GetSpawnersByName(grpNameQBShowBricks); diff --git a/dGame/dComponents/QuickBuildComponent.cpp b/dGame/dComponents/QuickBuildComponent.cpp index 0940d19d..52b2ddee 100644 --- a/dGame/dComponents/QuickBuildComponent.cpp +++ b/dGame/dComponents/QuickBuildComponent.cpp @@ -22,6 +22,8 @@ #include "RenderComponent.h" #include "CppScripts.h" +#include "StringifiedEnum.h" +#include "Amf3.h" QuickBuildComponent::QuickBuildComponent(Entity* const entity, const int32_t componentID) : Component{ entity, componentID } { std::u16string checkPreconditions = entity->GetVar(u"CheckPrecondition"); @@ -42,6 +44,7 @@ QuickBuildComponent::QuickBuildComponent(Entity* const entity, const int32_t com } SpawnActivator(); + RegisterMsg(&QuickBuildComponent::OnGetObjectReportInfo); } QuickBuildComponent::~QuickBuildComponent() { @@ -568,3 +571,30 @@ void QuickBuildComponent::AddQuickBuildCompleteCallback(const std::function& callback) { m_QuickBuildStateCallbacks.push_back(callback); } + +bool QuickBuildComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) { + auto& quickbuild = reportInfo.info->PushDebug("Quick Build"); + quickbuild.PushDebug("State") = StringifiedEnum::ToString(m_State).data(); + quickbuild.PushDebug("Timer") = m_Timer; + quickbuild.PushDebug("TimerIncomplete") = m_TimerIncomplete; + quickbuild.PushDebug("ActivatorPosition").PushDebug(m_ActivatorPosition); + quickbuild.PushDebug("ActivatorId") = std::to_string(m_ActivatorId); + quickbuild.PushDebug("ShowResetEffect") = m_ShowResetEffect; + quickbuild.PushDebug("Taken") = m_Taken; + quickbuild.PushDebug("ResetTime") = m_ResetTime; + quickbuild.PushDebug("CompleteTime") = m_CompleteTime; + quickbuild.PushDebug("TakeImagination") = m_TakeImagination; + quickbuild.PushDebug("Interruptible") = m_Interruptible; + quickbuild.PushDebug("SelfActivator") = m_SelfActivator; + auto& modules = quickbuild.PushDebug("CustomModules"); + for (const auto cmodule : m_CustomModules) modules.PushDebug("Module") = cmodule; + quickbuild.PushDebug("ActivityId") = m_ActivityId; + quickbuild.PushDebug("PostImaginationCost") = m_PostImaginationCost; + quickbuild.PushDebug("TimeBeforeSmash") = m_TimeBeforeSmash; + quickbuild.PushDebug("TimeBeforeDrain") = m_TimeBeforeDrain; + quickbuild.PushDebug("DrainedImagination") = m_DrainedImagination; + quickbuild.PushDebug("RepositionPlayer") = m_RepositionPlayer; + quickbuild.PushDebug("SoftTimer") = m_SoftTimer; + quickbuild.PushDebug("Builder") = std::to_string(m_Builder); + return true; +} diff --git a/dGame/dComponents/QuickBuildComponent.h b/dGame/dComponents/QuickBuildComponent.h index 8f5f1773..4a3b9ef8 100644 --- a/dGame/dComponents/QuickBuildComponent.h +++ b/dGame/dComponents/QuickBuildComponent.h @@ -261,6 +261,8 @@ public: m_StateDirty = true; } private: + + bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo); /** * Whether or not the quickbuild state has been changed since we last serialized it. */ diff --git a/dZoneManager/Spawner.cpp b/dZoneManager/Spawner.cpp index df966055..cccdfff0 100644 --- a/dZoneManager/Spawner.cpp +++ b/dZoneManager/Spawner.cpp @@ -55,7 +55,8 @@ Spawner::Spawner(const SpawnerInfo info) { m_SpawnSmashFoundGroup = true; m_SpawnOnSmashID = ssSpawner ? ssSpawner->m_Info.spawnerID : LWOOBJID_EMPTY; ssSpawner->AddSpawnedEntityDieCallback([=, this]() { - Spawn(); + // Intentionally left as a non debug log since i have no idea how much stuff this would affect + LOG("WOULD HAVE SPAWNED %i", m_EntityInfo.lot); }); } } From e5b8e5c6b78f8a3f27463092aeef8a56b4c4d752 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 11 Jun 2026 07:12:31 -0700 Subject: [PATCH 09/26] fix: buggy hitboxes during ag foot race (#1993) * physics fixes * check ptr --- .../dComponents/ProximityMonitorComponent.cpp | 28 +++++++++++ dGame/dComponents/ProximityMonitorComponent.h | 1 + dPhysics/dpGrid.cpp | 50 +++++++++---------- 3 files changed, 53 insertions(+), 26 deletions(-) diff --git a/dGame/dComponents/ProximityMonitorComponent.cpp b/dGame/dComponents/ProximityMonitorComponent.cpp index fd3f1b5d..14e440d5 100644 --- a/dGame/dComponents/ProximityMonitorComponent.cpp +++ b/dGame/dComponents/ProximityMonitorComponent.cpp @@ -4,6 +4,8 @@ #include "ControllablePhysicsComponent.h" #include "EntityManager.h" #include "SimplePhysicsComponent.h" +#include "Amf3.h" +#include "dpShapeSphere.h" const std::unordered_set ProximityMonitorComponent::m_EmptyObjectSet = {}; @@ -12,6 +14,7 @@ ProximityMonitorComponent::ProximityMonitorComponent(Entity* parent, const int32 SetProximityRadius(radiusSmall, "rocketSmall"); SetProximityRadius(radiusLarge, "rocketLarge"); } + RegisterMsg(&ProximityMonitorComponent::OnGetObjectReportInfo); } ProximityMonitorComponent::~ProximityMonitorComponent() { @@ -60,6 +63,31 @@ bool ProximityMonitorComponent::IsInProximity(const std::string& name, LWOOBJID return collisions.contains(objectID); } +bool ProximityMonitorComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) { + auto& proxInfo = reportInfo.info->PushDebug("Proximity Monitor"); + for (const auto& [name, entity] : m_ProximitiesData) { + if (!entity) continue; + auto& proxAmf = proxInfo.PushDebug(name); + const auto* const shape = entity->GetShape(); + if (shape && shape->GetShapeType() == dpShapeType::Sphere) { + const auto* const sphere = static_cast(shape); + proxAmf.PushDebug("Radius") = sphere->GetRadius(); + } + proxAmf.PushDebug("Sleeping") = entity->GetSleeping(); + proxAmf.PushDebug("Scale") = entity->GetScale(); + proxAmf.PushDebug("Gargantuan") = entity->GetIsGargantuan(); + proxAmf.PushDebug("Static") = entity->GetIsStatic(); + proxAmf.PushDebug("Position").PushDebug(entity->GetPosition()); + proxAmf.PushDebug("Rotation").PushDebug(entity->GetRotation()); + auto& collidingAmf = proxAmf.PushDebug("Colliding Objects"); + for (const auto& colliding : entity->GetCurrentlyCollidingObjects()) { + collidingAmf.PushDebug(std::to_string(colliding)); + } + } + + return true; +} + void ProximityMonitorComponent::Update(float deltaTime) { for (const auto& prox : m_ProximitiesData) { if (!prox.second) continue; diff --git a/dGame/dComponents/ProximityMonitorComponent.h b/dGame/dComponents/ProximityMonitorComponent.h index b83c0df0..b4aa1f1a 100644 --- a/dGame/dComponents/ProximityMonitorComponent.h +++ b/dGame/dComponents/ProximityMonitorComponent.h @@ -64,6 +64,7 @@ public: private: + bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo); /** * All the proximity sensors for this component, indexed by name */ diff --git a/dPhysics/dpGrid.cpp b/dPhysics/dpGrid.cpp index 7a0db1f8..c338efe3 100644 --- a/dPhysics/dpGrid.cpp +++ b/dPhysics/dpGrid.cpp @@ -2,6 +2,7 @@ #include "dpEntity.h" #include +#include dpGrid::dpGrid(int numCells, int cellSize) { NUM_CELLS = numCells; @@ -122,38 +123,35 @@ void dpGrid::HandleEntity(dpEntity* entity, dpEntity* other) { void dpGrid::HandleCell(int x, int z, float deltaTime) { auto& entities = m_Cells[x][z]; //vector of entities contained within this cell. - for (auto en : entities) { + for (auto* en : entities) { if (!en) continue; if (en->GetIsStatic() || en->GetSleeping()) continue; //Check against all entities that are in the same cell as us - for (auto other : entities) - HandleEntity(en, other); + for (auto other : entities) HandleEntity(en, other); - //To try neighbouring cells as well: (can be disabled if needed) - //we only check 4 of the 8 neighbouring cells, otherwise we'd get duplicates and cpu cycles wasted... - - if (x > 0 && z > 0) { - for (auto other : m_Cells[x - 1][z - 1]) - HandleEntity(en, other); + // All 8 neighbours in one pass. + // staticOnly=false — canonical 4: covers each dynamic-vs-dynamic pair exactly once, + // since the higher-index cell checks back to the lower-index cell. + // staticOnly=true — skipped 4: dynamic entities there are handled when those cells + // process their own en loop; static ones never drive a loop, so + // we handle them here explicitly to avoid missing exits. + struct NeighbourCheck { int dx, dz; bool staticOnly; }; + constexpr NeighbourCheck kNeighbours[8] = { + { -1, -1, false }, { -1, 0, false }, { 0, -1, false }, { -1, 1, false }, + { 1, -1, true }, { 1, 0, true }, { 0, 1, true }, { 1, 1, true }, + }; + for (auto [dx, dz, staticOnly] : kNeighbours) { + const int nx = x + dx; + const int nz = z + dz; + // Ensure the cell we're checking is within the valid range + if (nx < 0 || nx >= NUM_CELLS || nz < 0 || nz >= NUM_CELLS) continue; + for (auto* other : m_Cells[nx][nz]) { + if (!staticOnly || (other && other->GetIsStatic())) + HandleEntity(en, other); + } } - if (x > 0) { - for (auto other : m_Cells[x - 1][z]) - HandleEntity(en, other); - } - - if (z > 0) { - for (auto other : m_Cells[x][z - 1]) - HandleEntity(en, other); - } - - if (x > 0 && z < NUM_CELLS - 1) { - for (auto other : m_Cells[x - 1][z + 1]) - HandleEntity(en, other); - } - - for (auto& [id, entity] : m_GargantuanObjects) - HandleEntity(en, entity); + for (auto* entity : m_GargantuanObjects | std::views::values) HandleEntity(en, entity); } } From 1e9b18fa9d86ab2593cd1700b65503bf18656da9 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 11 Jun 2026 07:12:43 -0700 Subject: [PATCH 10/26] chore: cleanup usage of pointers in the activity component (#1989) * chore: cleanup usage of pointers in the activity component * feedback --- dGame/dComponents/ActivityComponent.cpp | 313 ++++++++---------- dGame/dComponents/ActivityComponent.h | 62 ++-- .../02_server/Map/AG/NpcAgCourseStarter.cpp | 20 +- dScripts/ActivityManager.cpp | 2 +- 4 files changed, 167 insertions(+), 230 deletions(-) diff --git a/dGame/dComponents/ActivityComponent.cpp b/dGame/dComponents/ActivityComponent.cpp index 2dfd5e95..7fa73f01 100644 --- a/dGame/dComponents/ActivityComponent.cpp +++ b/dGame/dComponents/ActivityComponent.cpp @@ -22,6 +22,7 @@ #include "eMatchUpdate.h" #include "ServiceType.h" #include "MessageType/Chat.h" +#include "ObjectIDManager.h" #include "CDCurrencyTableTable.h" #include "CDActivityRewardsTable.h" @@ -29,6 +30,11 @@ #include "LeaderboardManager.h" #include "CharacterComponent.h" #include "Amf3.h" +#include + +namespace { + const ActivityInstance g_EmptyInstance{ nullptr, CDActivities{} }; +} ActivityComponent::ActivityComponent(Entity* parent, int32_t componentID) : Component(parent, componentID) { RegisterMsg(&ActivityComponent::OnGetObjectReportInfo); @@ -71,9 +77,9 @@ void ActivityComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsIniti if (m_DirtyActivityInfo) { outBitStream.Write(m_ActivityPlayers.size()); if (!m_ActivityPlayers.empty()) { - for (const auto& activityPlayer : m_ActivityPlayers) { - outBitStream.Write(activityPlayer->playerID); - for (const auto& activityValue : activityPlayer->values) { + for (const auto& [playerID, values] : m_ActivityPlayers) { + outBitStream.Write(playerID); + for (const auto& activityValue : values) { outBitStream.Write(activityValue); } } @@ -111,78 +117,81 @@ void ActivityComponent::PlayerJoin(Entity* player) { if (HasLobby()) { PlayerJoinLobby(player); } else if (!IsPlayedBy(player)) { - auto* instance = NewInstance(); - instance->AddParticipant(player); + NewInstance().AddParticipant(player); } } void ActivityComponent::PlayerJoinLobby(Entity* player) { if (!m_Parent->HasComponent(eReplicaComponentType::QUICK_BUILD)) GameMessages::SendMatchResponse(player, player->GetSystemAddress(), 0); // tell the client they joined a lobby - LobbyPlayer* newLobbyPlayer = new LobbyPlayer(); - newLobbyPlayer->entityID = player->GetObjectID(); - Lobby* playerLobby = nullptr; + LobbyPlayer newLobbyPlayer{}; + newLobbyPlayer.entityID = player->GetObjectID(); + LWOOBJID playerLobbyID = LWOOBJID_EMPTY; auto* character = player->GetCharacter(); if (character != nullptr) character->SetLastNonInstanceZoneID(Game::zoneManager->GetZone()->GetWorldID()); - for (Lobby* lobby : m_Queue) { - if (lobby->players.size() < m_ActivityInfo.maxTeamSize || m_ActivityInfo.maxTeamSize == 1 && lobby->players.size() < m_ActivityInfo.maxTeams) { + for (auto& [lobbyID, lobby] : m_Queue) { + if (lobby.players.size() < m_ActivityInfo.maxTeamSize || m_ActivityInfo.maxTeamSize == 1 && lobby.players.size() < m_ActivityInfo.maxTeams) { // If an empty slot in an existing lobby is found - lobby->players.push_back(newLobbyPlayer); - playerLobby = lobby; + lobby.players.push_back(newLobbyPlayer); + playerLobbyID = lobbyID; // Update the joining player on players already in the lobby, and update players already in the lobby on the joining player - std::string matchUpdateJoined = "player=9:" + std::to_string(player->GetObjectID()) + "\nplayerName=0:" + player->GetCharacter()->GetName(); - for (LobbyPlayer* joinedPlayer : lobby->players) { - auto* entity = joinedPlayer->GetEntity(); + LDFData playerLDF("player", player->GetObjectID()); + LDFData playerName("playerName", player->GetCharacter()->GetName()); + std::string matchUpdateJoined = playerLDF.GetString() + "\n" + playerName.GetString(); + for (const auto& joinedPlayer : lobby.players) { + auto* const entity = joinedPlayer.GetEntity(); if (entity == nullptr) { continue; } - std::string matchUpdate = "player=9:" + std::to_string(entity->GetObjectID()) + "\nplayerName=0:" + entity->GetCharacter()->GetName(); + LDFData entityLDF("player", entity->GetObjectID()); + LDFData entityName("playerName", entity->GetCharacter()->GetName()); + std::string matchUpdate = entityLDF.GetString() + "\n" + entityName.GetString(); GameMessages::SendMatchUpdate(player, player->GetSystemAddress(), matchUpdate, eMatchUpdate::PLAYER_ADDED); - PlayerReady(entity, joinedPlayer->ready); + PlayerReady(entity, joinedPlayer.ready); GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchUpdateJoined, eMatchUpdate::PLAYER_ADDED); } + break; } } - if (!playerLobby) { + if (playerLobbyID == LWOOBJID_EMPTY) { // If all lobbies are full - playerLobby = new Lobby(); - playerLobby->players.push_back(newLobbyPlayer); - playerLobby->timer = m_ActivityInfo.waitTime / 1000; - m_Queue.push_back(playerLobby); + playerLobbyID = ObjectIDManager::GenerateObjectID(); + auto& newLobby = m_Queue[playerLobbyID]; + newLobby.players.push_back(newLobbyPlayer); + newLobby.timer = m_ActivityInfo.waitTime / 1000; } + const auto& lobby = m_Queue[playerLobbyID]; - if (m_ActivityInfo.maxTeamSize != 1 && playerLobby->players.size() >= m_ActivityInfo.minTeamSize || m_ActivityInfo.maxTeamSize == 1 && playerLobby->players.size() >= m_ActivityInfo.minTeams) { + if (m_ActivityInfo.maxTeamSize != 1 && lobby.players.size() >= m_ActivityInfo.minTeamSize || m_ActivityInfo.maxTeamSize == 1 && lobby.players.size() >= m_ActivityInfo.minTeams) { // Update the joining player on the match timer - std::string matchTimerUpdate = "time=3:" + std::to_string(playerLobby->timer); - GameMessages::SendMatchUpdate(player, player->GetSystemAddress(), matchTimerUpdate, eMatchUpdate::PHASE_WAIT_READY); + LDFData matchTimer("time", lobby.timer); + GameMessages::SendMatchUpdate(player, player->GetSystemAddress(), matchTimer.GetString(), eMatchUpdate::PHASE_WAIT_READY); } } void ActivityComponent::PlayerLeave(LWOOBJID playerID) { - // Removes the player from a lobby and notifies the others, not applicable for non-lobby instances - for (Lobby* lobby : m_Queue) { - for (int i = 0; i < lobby->players.size(); ++i) { - if (lobby->players[i]->entityID == playerID) { - std::string matchUpdateLeft = "player=9:" + std::to_string(playerID); - for (LobbyPlayer* lobbyPlayer : lobby->players) { - auto* entity = lobbyPlayer->GetEntity(); + for (auto& lobby : m_Queue | std::views::values) { + for (int i = 0; i < lobby.players.size(); i++) { + const auto& player = lobby.players[i]; + if (player.entityID == playerID) { + LDFData matchUpdateLeft("player", playerID); + for (const auto& lobbyPlayer : lobby.players) { + auto* const entity = lobbyPlayer.GetEntity(); if (entity == nullptr) continue; - GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchUpdateLeft, eMatchUpdate::PLAYER_REMOVED); + GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchUpdateLeft.GetString(), eMatchUpdate::PLAYER_REMOVED); } - delete lobby->players[i]; - lobby->players[i] = nullptr; - lobby->players.erase(lobby->players.begin() + i); + lobby.players.erase(lobby.players.begin() + i); return; } @@ -191,85 +200,79 @@ void ActivityComponent::PlayerLeave(LWOOBJID playerID) { } void ActivityComponent::Update(float deltaTime) { - std::vector lobbiesToRemove{}; + std::vector lobbiesToRemove{}; // Ticks all the lobbies, not applicable for non-instance activities - for (Lobby* lobby : m_Queue) { - for (LobbyPlayer* player : lobby->players) { - auto* entity = player->GetEntity(); + for (auto& [lobbyID, lobby] : m_Queue) { + for (const auto& player : lobby.players) { + const auto* const entity = player.GetEntity(); if (entity == nullptr) { - PlayerLeave(player->entityID); + PlayerLeave(player.entityID); return; } } - if (lobby->players.empty()) { - lobbiesToRemove.push_back(lobby); + if (lobby.players.empty()) { + lobbiesToRemove.push_back(lobbyID); continue; } // Update the match time for all players - if (m_ActivityInfo.maxTeamSize != 1 && lobby->players.size() >= m_ActivityInfo.minTeamSize - || m_ActivityInfo.maxTeamSize == 1 && lobby->players.size() >= m_ActivityInfo.minTeams) { - if (lobby->timer == m_ActivityInfo.waitTime / 1000) { - for (LobbyPlayer* joinedPlayer : lobby->players) { - auto* entity = joinedPlayer->GetEntity(); + if (m_ActivityInfo.maxTeamSize != 1 && lobby.players.size() >= m_ActivityInfo.minTeamSize + || m_ActivityInfo.maxTeamSize == 1 && lobby.players.size() >= m_ActivityInfo.minTeams) { + if (lobby.timer == m_ActivityInfo.waitTime / 1000) { + for (const auto& joinedPlayer : lobby.players) { + auto* const entity = joinedPlayer.GetEntity(); if (entity == nullptr) continue; - std::string matchTimerUpdate = "time=3:" + std::to_string(lobby->timer); - GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchTimerUpdate, eMatchUpdate::PHASE_WAIT_READY); + LDFData matchTimerUpdate("time", lobby.timer); + GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchTimerUpdate.GetString(), eMatchUpdate::PHASE_WAIT_READY); } } - lobby->timer -= deltaTime; + lobby.timer -= deltaTime; } bool lobbyReady = true; - for (LobbyPlayer* player : lobby->players) { - if (player->ready) continue; + for (const auto& player : lobby.players) { + if (player.ready) continue; lobbyReady = false; } // If everyone's ready, jump the timer - if (lobbyReady && lobby->timer > m_ActivityInfo.startDelay / 1000) { - lobby->timer = m_ActivityInfo.startDelay / 1000; + if (lobbyReady && lobby.timer > m_ActivityInfo.startDelay / 1000) { + lobby.timer = m_ActivityInfo.startDelay / 1000; // Update players in lobby on switch to start delay - std::string matchTimerUpdate = "time=3:" + std::to_string(lobby->timer); - for (LobbyPlayer* player : lobby->players) { - auto* entity = player->GetEntity(); + LDFData matchTimerUpdate("time", lobby.timer); + for (const auto& player : lobby.players) { + auto* const entity = player.GetEntity(); if (entity == nullptr) continue; - GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchTimerUpdate, eMatchUpdate::PHASE_WAIT_START); + GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchTimerUpdate.GetString(), eMatchUpdate::PHASE_WAIT_START); } } // The timer has elapsed, start the instance - if (lobby->timer <= 0.0f) { + if (lobby.timer <= 0.0f) { LOG("Setting up instance."); - ActivityInstance* instance = NewInstance(); - LoadPlayersIntoInstance(instance, lobby->players); - instance->StartZone(); - lobbiesToRemove.push_back(lobby); + auto& instance = NewInstance(); + LoadPlayersIntoInstance(instance, lobby.players); + instance.StartZone(); + lobbiesToRemove.push_back(lobbyID); } } - while (!lobbiesToRemove.empty()) { - RemoveLobby(lobbiesToRemove.front()); - lobbiesToRemove.erase(lobbiesToRemove.begin()); + for (const auto id : lobbiesToRemove) { + RemoveLobby(id); } } -void ActivityComponent::RemoveLobby(Lobby* lobby) { - for (int i = 0; i < m_Queue.size(); ++i) { - if (m_Queue[i] == lobby) { - m_Queue.erase(m_Queue.begin() + i); - return; - } - } +void ActivityComponent::RemoveLobby(const LWOOBJID lobbyID) { + if (m_Queue.contains(lobbyID)) m_Queue.erase(lobbyID); } bool ActivityComponent::HasLobby() const { @@ -278,9 +281,9 @@ bool ActivityComponent::HasLobby() const { } bool ActivityComponent::PlayerIsInQueue(Entity* player) { - for (Lobby* lobby : m_Queue) { - for (LobbyPlayer* lobbyPlayer : lobby->players) { - if (player->GetObjectID() == lobbyPlayer->entityID) return true; + for (const auto& lobby : m_Queue | std::views::values) { + for (const auto& lobbyPlayer : lobby.players) { + if (player->GetObjectID() == lobbyPlayer.entityID) return true; } } @@ -288,8 +291,8 @@ bool ActivityComponent::PlayerIsInQueue(Entity* player) { } bool ActivityComponent::IsPlayedBy(Entity* player) const { - for (const auto* instance : this->m_Instances) { - for (const auto* instancePlayer : instance->GetParticipants()) { + for (const auto& instance : m_Instances) { + for (const auto* instancePlayer : instance.GetParticipants()) { if (instancePlayer != nullptr && instancePlayer->GetObjectID() == player->GetObjectID()) return true; } @@ -299,8 +302,8 @@ bool ActivityComponent::IsPlayedBy(Entity* player) const { } bool ActivityComponent::IsPlayedBy(LWOOBJID playerID) const { - for (const auto* instance : this->m_Instances) { - for (const auto* instancePlayer : instance->GetParticipants()) { + for (const auto& instance : m_Instances) { + for (const auto* instancePlayer : instance.GetParticipants()) { if (instancePlayer != nullptr && instancePlayer->GetObjectID() == playerID) return true; } @@ -329,136 +332,95 @@ bool ActivityComponent::TakeCost(Entity* player) const { } void ActivityComponent::PlayerReady(Entity* player, bool bReady) { - for (Lobby* lobby : m_Queue) { - for (LobbyPlayer* lobbyPlayer : lobby->players) { - if (lobbyPlayer->entityID == player->GetObjectID()) { + for (auto& lobby : m_Queue | std::views::values) { + for (auto& lobbyPlayer : lobby.players) { + if (lobbyPlayer.entityID == player->GetObjectID()) { - lobbyPlayer->ready = bReady; + lobbyPlayer.ready = bReady; // Update players in lobby on player being ready - std::string matchReadyUpdate = "player=9:" + std::to_string(player->GetObjectID()); + LDFData matchReadyUpdate("player", player->GetObjectID()); eMatchUpdate readyStatus = eMatchUpdate::PLAYER_READY; if (!bReady) readyStatus = eMatchUpdate::PLAYER_NOT_READY; - for (LobbyPlayer* otherPlayer : lobby->players) { - auto* entity = otherPlayer->GetEntity(); + for (const auto& otherPlayer : lobby.players) { + auto* const entity = otherPlayer.GetEntity(); if (entity == nullptr) continue; - GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchReadyUpdate, readyStatus); + GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchReadyUpdate.GetString(), readyStatus); } } } } } -ActivityInstance* ActivityComponent::NewInstance() { - auto* instance = new ActivityInstance(m_Parent, m_ActivityInfo); - m_Instances.push_back(instance); - return instance; +ActivityInstance& ActivityComponent::NewInstance() { + m_Instances.push_back(ActivityInstance(m_Parent, m_ActivityInfo)); + return m_Instances.back(); } -void ActivityComponent::LoadPlayersIntoInstance(ActivityInstance* instance, const std::vector& lobby) const { - for (LobbyPlayer* player : lobby) { - auto* entity = player->GetEntity(); +void ActivityComponent::LoadPlayersIntoInstance(ActivityInstance& instance, const std::vector& lobby) const { + for (const auto& player : lobby) { + auto* const entity = player.GetEntity(); if (entity == nullptr || !CheckCost(entity)) { continue; } - instance->AddParticipant(entity); + instance.AddParticipant(entity); } } -const std::vector& ActivityComponent::GetInstances() const { - return m_Instances; -} - -ActivityInstance* ActivityComponent::GetInstance(const LWOOBJID playerID) { - for (const auto* instance : GetInstances()) { - for (const auto* participant : instance->GetParticipants()) { +const ActivityInstance& ActivityComponent::GetInstance(const LWOOBJID playerID) const { + for (const auto& instance : m_Instances) { + for (const auto* participant : instance.GetParticipants()) { if (participant->GetObjectID() == playerID) - return const_cast(instance); + return instance; } } - return nullptr; + return g_EmptyInstance; } -void ActivityComponent::ClearInstances() { - for (ActivityInstance* instance : m_Instances) { - delete instance; - } - m_Instances.clear(); -} - -ActivityPlayer* ActivityComponent::GetActivityPlayerData(LWOOBJID playerID) { - for (auto* activityData : m_ActivityPlayers) { - if (activityData->playerID == playerID) { - return activityData; - } - } - - return nullptr; +bool ActivityComponent::PlayerHasActivityData(LWOOBJID playerID) const { + return m_ActivityPlayers.contains(playerID); } void ActivityComponent::RemoveActivityPlayerData(LWOOBJID playerID) { - for (size_t i = 0; i < m_ActivityPlayers.size(); i++) { - if (m_ActivityPlayers[i]->playerID == playerID) { - delete m_ActivityPlayers[i]; - m_ActivityPlayers[i] = nullptr; - - m_ActivityPlayers.erase(m_ActivityPlayers.begin() + i); - m_DirtyActivityInfo = true; - Game::entityManager->SerializeEntity(m_Parent); - - return; - } - } -} - -ActivityPlayer* ActivityComponent::AddActivityPlayerData(LWOOBJID playerID) { - auto* data = GetActivityPlayerData(playerID); - if (data != nullptr) - return data; - - m_ActivityPlayers.push_back(new ActivityPlayer{ playerID, {} }); + m_ActivityPlayers.erase(playerID); m_DirtyActivityInfo = true; - Game::entityManager->SerializeEntity(m_Parent); - - return GetActivityPlayerData(playerID); } -float_t ActivityComponent::GetActivityValue(LWOOBJID playerID, uint32_t index) { - auto value = -1.0f; +float_t ActivityComponent::GetActivityValue(LWOOBJID playerID, uint32_t index) const { + float value = -1.0f; - auto* data = GetActivityPlayerData(playerID); - if (data != nullptr) { - value = data->values[std::min(index, static_cast(9))]; + const auto& data = m_ActivityPlayers.find(playerID); + if (data != m_ActivityPlayers.cend()) { + value = data->second[std::min(index, static_cast(9))]; } - + LOG_DEBUG("Player %llu has score %f at index %i", playerID, value, index); return value; } void ActivityComponent::SetActivityValue(LWOOBJID playerID, uint32_t index, float_t value) { - auto* data = AddActivityPlayerData(playerID); - if (data != nullptr) { - data->values[std::min(index, static_cast(9))] = value; - } + auto& data = m_ActivityPlayers[playerID]; + data[std::min(index, static_cast(9))] = value; + LOG_DEBUG("%llu index %i has score of %f", playerID, index, value); m_DirtyActivityInfo = true; Game::entityManager->SerializeEntity(m_Parent); } void ActivityComponent::PlayerRemove(LWOOBJID playerID) { - for (auto* instance : GetInstances()) { - auto participants = instance->GetParticipants(); + for (int i = 0; i < m_Instances.size(); i++) { + auto& instance = m_Instances[i]; + auto participants = instance.GetParticipants(); for (const auto* participant : participants) { if (participant != nullptr && participant->GetObjectID() == playerID) { - instance->RemoveParticipant(participant); + instance.RemoveParticipant(participant); RemoveActivityPlayerData(playerID); // If the instance is empty after the delete of the participant, delete the instance too - if (instance->GetParticipants().empty()) { - m_Instances.erase(std::find(m_Instances.begin(), m_Instances.end(), instance)); - delete instance; + if (instance.GetParticipants().empty()) { + m_Instances.erase(m_Instances.begin() + i); } return; } @@ -595,14 +557,13 @@ bool ActivityComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& auto& instances = activityInfo.PushDebug("Instances: " + std::to_string(m_Instances.size())); size_t i = 0; for (const auto& activityInstance : m_Instances) { - if (!activityInstance) continue; auto& instance = instances.PushDebug("Instance " + std::to_string(i++)); - instance.PushDebug("Score") = activityInstance->GetScore(); - instance.PushDebug("Next Zone Clone ID") = activityInstance->GetNextZoneCloneID(); + instance.PushDebug("Score") = activityInstance.GetScore(); + instance.PushDebug("Next Zone Clone ID") = activityInstance.GetNextZoneCloneID(); { auto& activityInfo = instance.PushDebug("Activity Info"); - const auto& instanceActInfo = activityInstance->GetActivityInfo(); + const auto& instanceActInfo = activityInstance.GetActivityInfo(); activityInfo.PushDebug("ActivityID") = instanceActInfo.ActivityID; activityInfo.PushDebug("locStatus") = instanceActInfo.locStatus; activityInfo.PushDebug("instanceMapID") = instanceActInfo.instanceMapID; @@ -625,7 +586,7 @@ bool ActivityComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& } auto& participants = instance.PushDebug("Participants"); - for (const auto* participant : activityInstance->GetParticipants()) { + for (const auto* participant : activityInstance.GetParticipants()) { if (!participant) continue; auto* character = participant->GetCharacter(); if (!character) continue; @@ -635,38 +596,36 @@ bool ActivityComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& auto& queue = activityInfo.PushDebug("Queue"); i = 0; - for (const auto& lobbyQueue : m_Queue) { + for (const auto& lobbyQueue : m_Queue | std::views::values) { auto& lobby = queue.PushDebug("Lobby " + std::to_string(i++)); - lobby.PushDebug("Timer") = lobbyQueue->timer; + lobby.PushDebug("Timer") = lobbyQueue.timer; auto& players = lobby.PushDebug("Players"); - for (const auto* player : lobbyQueue->players) { - if (!player) continue; - auto* playerEntity = player->GetEntity(); + for (const auto& player : lobbyQueue.players) { + const auto* const playerEntity = player.GetEntity(); if (!playerEntity) continue; auto* character = playerEntity->GetCharacter(); if (!character) continue; - players.PushDebug(std::to_string(playerEntity->GetObjectID()) + ": " + character->GetName()) = player->ready ? "Ready" : "Not Ready"; + players.PushDebug(std::to_string(playerEntity->GetObjectID()) + ": " + character->GetName()) = player.ready ? "Ready" : "Not Ready"; } } auto& activityPlayers = activityInfo.PushDebug("Activity Players"); - for (const auto* activityPlayer : m_ActivityPlayers) { - if (!activityPlayer) continue; - auto* const activityPlayerEntity = Game::entityManager->GetEntity(activityPlayer->playerID); + for (const auto& [playerID, playerScores] : m_ActivityPlayers) { + auto* const activityPlayerEntity = Game::entityManager->GetEntity(playerID); if (!activityPlayerEntity) continue; auto* character = activityPlayerEntity->GetCharacter(); if (!character) continue; - auto& playerData = activityPlayers.PushDebug(std::to_string(activityPlayer->playerID) + " " + character->GetName()); + auto& playerData = activityPlayers.PushDebug(std::to_string(playerID) + " " + character->GetName()); auto& scores = playerData.PushDebug("Scores"); for (size_t i = 0; i < 10; ++i) { - scores.PushDebug(std::to_string(i)) = activityPlayer->values[i]; + scores.PushDebug(std::to_string(i)) = playerScores[i]; } } - + activityInfo.PushDebug("ActivityID") = m_ActivityID; return true; } diff --git a/dGame/dComponents/ActivityComponent.h b/dGame/dComponents/ActivityComponent.h index 4f423d8e..856ca70f 100644 --- a/dGame/dComponents/ActivityComponent.h +++ b/dGame/dComponents/ActivityComponent.h @@ -8,14 +8,15 @@ #include "eReplicaComponentType.h" #include "CDActivitiesTable.h" +#include namespace GameMessages { class GameMsg; }; - /** - * Represents an instance of an activity, having participants and score - */ +/** + * Represents an instance of an activity, having participants and score + */ class ActivityInstance { public: ActivityInstance(Entity* parent, CDActivities activityInfo) { m_Parent = parent; m_ActivityInfo = activityInfo; }; @@ -104,7 +105,7 @@ struct LobbyPlayer { /** * The ID of the entity that is in the lobby */ - LWOOBJID entityID; + LWOOBJID entityID = LWOOBJID_EMPTY; /** * Whether or not the entity is ready @@ -126,12 +127,12 @@ struct Lobby { /** * The lobby of players */ - std::vector players; + std::vector players; /** * The timer that determines when the activity should start */ - float timer; + float timer{}; }; /** @@ -142,12 +143,12 @@ struct ActivityPlayer { /** * The entity that the score is tracked for */ - LWOOBJID playerID; + LWOOBJID playerID{}; /** * The list of score for this entity */ - float values[10]; + float values[10]{}; }; /** @@ -194,13 +195,13 @@ public: * @param instance the instance to load the players into * @param lobby the players to load into the instance */ - void LoadPlayersIntoInstance(ActivityInstance* instance, const std::vector& lobby) const; + void LoadPlayersIntoInstance(ActivityInstance& instance, const std::vector& lobby) const; /** * Removes a lobby from the activity manager * @param lobby the lobby to remove */ - void RemoveLobby(Lobby* lobby); + void RemoveLobby(const LWOOBJID lobbyID); /** * Marks a player as (un)ready in a lobby @@ -246,7 +247,7 @@ public: */ bool IsPlayedBy(LWOOBJID playerID) const; - /** + /** * Checks if the entity has enough cost to play this activity * @param player the entity to check * @return true if the entity has enough cost to play this activity, false otherwise @@ -271,20 +272,14 @@ public: * Creates a new instance for this activity * @return a new instance for this activity */ - ActivityInstance* NewInstance(); - - /** - * Returns all the currently active instances of this activity - * @return all the currently active instances of this activity - */ - const std::vector& GetInstances() const; + ActivityInstance& NewInstance(); /** * Returns the instance that some entity is currently playing in * @param playerID the entity to check for * @return if any, the instance that the entity is currently in */ - ActivityInstance* GetInstance(const LWOOBJID playerID); + const ActivityInstance& GetInstance(const LWOOBJID playerID) const; /** * @brief Reloads the config settings for this component @@ -292,23 +287,12 @@ public: */ void ReloadConfig(); - /** - * Removes all the instances - */ - void ClearInstances(); - - /** - * Returns all the score for the players that are currently playing this activity - * @return - */ - std::vector GetActivityPlayers() { return m_ActivityPlayers; }; - /** * Returns activity data for a specific entity (e.g. score and such). * @param playerID the entity to get data for * @return the activity data (score) for the passed player in this activity, if it exists */ - ActivityPlayer* GetActivityPlayerData(LWOOBJID playerID); + bool PlayerHasActivityData(LWOOBJID playerID) const; /** * Sets some score value for an entity @@ -324,7 +308,7 @@ public: * @param index the index to get score for * @return activity score for the passed parameters */ - float_t GetActivityValue(LWOOBJID playerID, uint32_t index); + float_t GetActivityValue(LWOOBJID playerID, uint32_t index) const; /** * Removes activity score tracking for some entity @@ -332,13 +316,6 @@ public: */ void RemoveActivityPlayerData(LWOOBJID playerID); - /** - * Adds activity score tracking for some entity - * @param playerID the entity to add the activity score for - * @return the created entry - */ - ActivityPlayer* AddActivityPlayerData(LWOOBJID playerID); - /** * Sets the mapID that this activity points to * @param mapID the map ID to set @@ -346,7 +323,6 @@ public: void SetInstanceMapID(uint32_t mapID) { m_ActivityInfo.instanceMapID = mapID; }; private: - bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& msg); /** * The database information for this activity @@ -356,17 +332,17 @@ private: /** * All the active instances of this activity */ - std::vector m_Instances; + std::vector m_Instances; /** * The current lobbies for this activity */ - std::vector m_Queue; + std::map m_Queue; /** * All the activity score for the players in this activity */ - std::vector m_ActivityPlayers; + std::map> m_ActivityPlayers; /** * The activity id diff --git a/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp b/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp index fc724fb9..7e8ca7e8 100644 --- a/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp +++ b/dScripts/02_server/Map/AG/NpcAgCourseStarter.cpp @@ -19,7 +19,7 @@ void NpcAgCourseStarter::OnUse(Entity* self, Entity* user) { const auto userId = user->GetObjectID(); const auto& userSysAddr = user->GetSystemAddress(); - if (scriptedActivityComponent->GetActivityPlayerData(userId) != nullptr) { + if (scriptedActivityComponent->PlayerHasActivityData(userId)) { GameMessages::SendNotifyClientObject(selfId, u"exit", 0, 0, LWOOBJID_EMPTY, "", userSysAddr); } else { GameMessages::SendNotifyClientObject(selfId, u"start", 0, 0, LWOOBJID_EMPTY, "", userSysAddr); @@ -45,18 +45,18 @@ void NpcAgCourseStarter::OnMessageBoxResponse(Entity* self, Entity* sender, int3 GameMessages::SendNotifyClientObject(selfId, u"start_timer", 0, 0, LWOOBJID_EMPTY, "", senderSysAddr); GameMessages::SendActivityStart(selfId, senderSysAddr); - auto* const data = scriptedActivityComponent->AddActivityPlayerData(senderId); - if (data->values[1] != 0) return; + const auto score = scriptedActivityComponent->GetActivityValue(senderId, 1); + if (score != 0 && score != -1.0f) return; const auto raceStartTime = Game::server->GetUptime() + std::chrono::seconds(4); // Offset for starting timer const auto fRaceStartTime = std::chrono::duration>(raceStartTime).count(); - data->values[1] = fRaceStartTime; + scriptedActivityComponent->SetActivityValue(senderId, 1, fRaceStartTime); Game::entityManager->SerializeEntity(self); } else if (identifier == u"FootRaceCancel") { GameMessages::SendNotifyClientObject(selfId, u"stop_timer", 0, 0, LWOOBJID_EMPTY, "", senderSysAddr); - if (scriptedActivityComponent->GetActivityPlayerData(senderId) != nullptr) { + if (scriptedActivityComponent->PlayerHasActivityData(senderId)) { GameMessages::SendNotifyClientObject(selfId, u"exit", 0, 0, LWOOBJID_EMPTY, "", senderSysAddr); } else { GameMessages::SendNotifyClientObject(selfId, u"start", 0, 0, LWOOBJID_EMPTY, "", senderSysAddr); @@ -74,8 +74,7 @@ void NpcAgCourseStarter::OnFireEventServerSide(Entity* self, Entity* sender, std const auto senderId = sender->GetObjectID(); const auto& senderSysAddr = sender->GetSystemAddress(); - auto* const data = scriptedActivityComponent->GetActivityPlayerData(senderId); - if (!data) return; + if (!scriptedActivityComponent->PlayerHasActivityData(senderId)) return; if (args == "course_cancel") { GameMessages::SendNotifyClientObject(selfId, u"cancel_timer", 0, 0, @@ -84,8 +83,11 @@ void NpcAgCourseStarter::OnFireEventServerSide(Entity* self, Entity* sender, std } else if (args == "course_finish") { const auto raceEndTime = Game::server->GetUptime(); const auto fRaceEndTime = std::chrono::duration>(raceEndTime).count(); - const auto raceTimeElapsed = fRaceEndTime - data->values[1]; - data->values[2] = raceTimeElapsed; + const float startTime = scriptedActivityComponent->GetActivityValue(senderId, 1); + if (startTime == 0 || startTime == -1.0f) return; + + const auto raceTimeElapsed = fRaceEndTime - startTime; + scriptedActivityComponent->SetActivityValue(senderId, 2, raceTimeElapsed); auto* const missionComponent = sender->GetComponent(); if (missionComponent != nullptr) { diff --git a/dScripts/ActivityManager.cpp b/dScripts/ActivityManager.cpp index 047eb76f..35459538 100644 --- a/dScripts/ActivityManager.cpp +++ b/dScripts/ActivityManager.cpp @@ -114,7 +114,7 @@ uint32_t ActivityManager::CalculateActivityRating(Entity* self, const LWOOBJID p if (sac == nullptr) return 0; - return sac->GetInstance(playerID)->GetParticipants().size(); + return sac->GetInstance(playerID).GetParticipants().size(); } uint32_t ActivityManager::GetActivityID(const Entity* self) { From 9f8d300340388c9048674111206f12f7e81048f2 Mon Sep 17 00:00:00 2001 From: Daniel Seiler Date: Fri, 12 Jun 2026 03:38:38 +0200 Subject: [PATCH 11/26] fix(docker): Unset MAXIMUM_OUTGOING_BANDWIDTH by default (#1548) --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index dbd16603..8e4cb760 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -43,6 +43,7 @@ services: - MYSQL_PASSWORD=${MARIADB_PASSWORD:?error} - EXTERNAL_IP=${EXTERNAL_IP:-localhost} - CLIENT_NET_VERSION=${CLIENT_NET_VERSION:-171022} + - MAXIMUM_OUTGOING_BANDWIDTH=${MAXIMUM_OUTGOING_BANDWIDTH:0} depends_on: - darkflamedb ports: From 90db1ac6995da866e2cacdf88163cdce8cd1ebb4 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sat, 13 Jun 2026 00:21:53 -0700 Subject: [PATCH 12/26] fix: incorrect network variable being set (#1994) --- dScripts/02_server/Map/AM/AmDrawBridge.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dScripts/02_server/Map/AM/AmDrawBridge.cpp b/dScripts/02_server/Map/AM/AmDrawBridge.cpp index 590024b5..c356145b 100644 --- a/dScripts/02_server/Map/AM/AmDrawBridge.cpp +++ b/dScripts/02_server/Map/AM/AmDrawBridge.cpp @@ -48,7 +48,7 @@ void AmDrawBridge::OnTimerDone(Entity* self, std::string timerName) { } self->SetNetworkVar(u"BridgeLeaving", true); - self->SetVar(u"BridgeDown", false); + self->SetNetworkVar(u"InUse", false); } else if (timerName == "SmashEffectBridge") { self->SetNetworkVar(u"SmashBridge", 5); } else if (timerName == "rotateBridgeDown") { From 0101933f5cae1ba5c09ee54301ceeb462afe0362 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 14 Jun 2026 20:54:52 -0700 Subject: [PATCH 13/26] chore: cleanup pointer management for LDF data (#1995) * change network settings from vector to LwoNameValue * move settings on Entity to managed memory * Migrate more members * chore: remove pointer leakage from raw ldf pointers * feedback * fix ci --- .github/workflows/build-and-test.yml | 6 +- CMakeLists.txt | 4 - CMakePresets.json | 2 +- dCommon/CMakeLists.txt | 9 +- dCommon/LDFFormat.cpp | 266 +++++++++--------- dCommon/LDFFormat.h | 94 ++++++- dCommon/NiPoint3.h | 4 +- dCommon/NiQuaternion.h | 4 +- dCommon/dEnums/dCommonVars.h | 12 - dGame/Entity.cpp | 43 +-- dGame/Entity.h | 138 ++++----- dGame/LeaderboardManager.cpp | 49 ++-- dGame/LeaderboardManager.h | 5 +- dGame/dComponents/CharacterComponent.cpp | 3 +- dGame/dComponents/DestroyableComponent.cpp | 6 +- dGame/dComponents/InventoryComponent.cpp | 31 +- dGame/dComponents/InventoryComponent.h | 4 +- dGame/dComponents/ModelComponent.cpp | 4 +- dGame/dComponents/PhantomPhysicsComponent.h | 1 - .../PropertyManagementComponent.cpp | 72 ++--- dGame/dComponents/RacingControlComponent.cpp | 18 +- dGame/dComponents/RacingControlComponent.h | 2 +- dGame/dComponents/ScriptComponent.cpp | 10 +- dGame/dComponents/SkillComponent.cpp | 3 +- dGame/dEntity/EntityInfo.h | 4 +- dGame/dGameMessages/GameMessages.cpp | 59 ++-- dGame/dGameMessages/GameMessages.h | 7 +- dGame/dInventory/EquippedItem.h | 2 +- dGame/dInventory/Item.cpp | 26 +- dGame/dInventory/Item.h | 10 +- dGame/dMission/MissionTask.cpp | 1 - .../SlashCommands/DEVGMCommands.cpp | 10 +- dGame/dUtilities/VanityUtilities.cpp | 16 +- dGame/dUtilities/VanityUtilities.h | 2 +- dNet/WorldPackets.cpp | 23 +- .../02_server/Enemy/AM/AmDarklingDragon.cpp | 15 +- .../02_server/Enemy/FV/FvMaelstromDragon.cpp | 15 +- .../02_server/Enemy/General/BaseEnemyApe.cpp | 11 +- .../02_server/Enemy/General/BaseEnemyMech.cpp | 5 +- .../Map/AG_Spider_Queen/ZoneAgSpiderQueen.cpp | 4 +- dScripts/02_server/Map/AM/AmSkullkinTower.cpp | 4 +- .../02_server/Map/General/PetDigServer.cpp | 10 +- dScripts/02_server/Map/General/QbSpawner.cpp | 14 +- .../Map/NS/NsConcertChoiceBuildManager.cpp | 10 +- .../Map/NT/NtCombatChallengeServer.cpp | 2 +- .../Map/Property/AG_Small/ZoneAgProperty.cpp | 6 +- .../boss_instance/NjMonastryBossInstance.cpp | 4 +- .../02_server/Objects/StinkyFishTarget.cpp | 4 +- dScripts/SpawnPetBaseServer.cpp | 10 +- dScripts/ai/FV/FvPandaSpawnerServer.cpp | 6 +- dScripts/ai/GF/GfBanana.cpp | 2 +- dScripts/ai/GF/PetDigBuild.cpp | 8 +- .../ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp | 23 +- dScripts/ai/NS/WH/RockHydrantSmashable.cpp | 4 +- dScripts/ai/PETS/HydrantSmashable.cpp | 4 +- dScripts/ai/RACING/TRACK_FV/FvRaceServer.cpp | 68 +++-- dScripts/ai/RACING/TRACK_GF/GfRaceServer.cpp | 68 +++-- dScripts/ai/RACING/TRACK_NS/NsRaceServer.cpp | 68 +++-- .../TRACK_NS_WINTER/NsWinterRaceServer.cpp | 68 +++-- dZoneManager/Level.cpp | 30 +- dZoneManager/Level.h | 2 +- dZoneManager/Spawner.h | 2 +- dZoneManager/Zone.cpp | 4 +- dZoneManager/Zone.h | 2 +- dZoneManager/dZMCommon.h | 3 +- thirdparty/CMakeLists.txt | 2 +- thirdparty/recastnavigation | 2 +- 67 files changed, 676 insertions(+), 754 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index c26f0dee..3963fdbb 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -34,12 +34,12 @@ jobs: if: ${{ matrix.os == 'windows-2025' }} uses: microsoft/setup-msbuild@30375c66a4eea26614e0d39710365f22f8b0af57 # v3 with: - vs-version: '[17,18)' + vs-version: '[18,19)' msbuild-architecture: x64 - - name: Get CMake 3.x + - name: Get CMake uses: lukka/get-cmake@591817e96fcad43505fb4eae36172462abb3a42e # v4.3.3 with: - cmakeVersion: "~3.25.0" + cmakeVersion: "latest" - name: cmake uses: lukka/run-cmake@5d55ea7949e25f69f0ecb516d8d572297e03a956 # v10.9 with: diff --git a/CMakeLists.txt b/CMakeLists.txt index 76930398..63786950 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,10 +16,6 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON) -if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0") - set(CMAKE_POLICY_VERSION_MINIMUM 3.5) -endif() - set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Export the compile commands for debugging set(CMAKE_POLICY_DEFAULT_CMP0063 NEW) # Set CMAKE visibility policy to NEW on project and subprojects set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) # Set C and C++ symbol visibility to hide inlined functions diff --git a/CMakePresets.json b/CMakePresets.json index 2819f320..bc6c8dad 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -49,7 +49,7 @@ "inherits": "default", "displayName": "[Multi] Windows (MSVC)", "description": "Set architecture to 64-bit (b/c RakNet)", - "generator": "Visual Studio 17 2022", + "generator": "Visual Studio 18 2026", "binaryDir": "${sourceDir}/build/msvc", "architecture": { "value": "x64" diff --git a/dCommon/CMakeLists.txt b/dCommon/CMakeLists.txt index ba67974e..0e7c7d52 100644 --- a/dCommon/CMakeLists.txt +++ b/dCommon/CMakeLists.txt @@ -49,11 +49,10 @@ if (UNIX) elseif (WIN32) include(FetchContent) - # TODO Keep an eye on the zlib repository for an update to disable testing. Don't forget to update CMakePresets FetchContent_Declare( zlib - URL https://github.com/madler/zlib/archive/refs/tags/v1.2.11.zip - URL_HASH MD5=9d6a627693163bbbf3f26403a3a0b0b1 + URL https://github.com/madler/zlib/archive/refs/tags/v1.3.2.zip + URL_HASH MD5=adbba6eef8960c3412818b2e241f46dc GIT_PROGRESS TRUE GIT_SHALLOW 1 ) @@ -62,12 +61,12 @@ elseif (WIN32) set(CMAKE_POLICY_DEFAULT_CMP0048 NEW) # Disable warning about the minimum version of cmake used for bcrypt being deprecated in the future set(CMAKE_WARN_DEPRECATED OFF CACHE BOOL "" FORCE) + # Disable zlib tests + set(ZLIB_BUILD_TESTING OFF CACHE BOOL "" FORCE) FetchContent_MakeAvailable(zlib) set(ZLIB_INCLUDE_DIRS ${zlib_SOURCE_DIR} ${zlib_BINARY_DIR}) - set_target_properties(zlib PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${ZLIB_INCLUDE_DIRS}") - add_library(ZLIB::ZLIB ALIAS zlib) else () message( FATAL_ERROR diff --git a/dCommon/LDFFormat.cpp b/dCommon/LDFFormat.cpp index da28ae6e..04ba643a 100644 --- a/dCommon/LDFFormat.cpp +++ b/dCommon/LDFFormat.cpp @@ -10,163 +10,151 @@ #include #include -using LDFKey = std::string_view; -using LDFTypeAndValue = std::string_view; - -using LDFType = std::string_view; -using LDFValue = std::string_view; - //! Returns a pointer to a LDFData value based on string format -LDFBaseData* LDFBaseData::DataFromString(const std::string_view& format) { +std::unique_ptr LDFBaseData::DataFromString(const std::string_view& format) { + std::unique_ptr toReturn; // A valid LDF must be at least 3 characters long (=0:) is the shortest valid LDF (empty UTF-16 key with no initial value) - if (format.empty() || format.length() <= 2) return nullptr; - auto equalsPosition = format.find('='); - // You can have an empty key, just make sure the type and value might exist - if (equalsPosition == std::string::npos || equalsPosition == (format.size() - 1)) return nullptr; + if (!format.empty() && format.length() > 2) { + auto equalsPosition = format.find('='); + // You can have an empty key, just make sure the type and value might exist + if (equalsPosition != std::string::npos && equalsPosition != (format.size() - 1)) { - std::pair keyValue; - keyValue.first = format.substr(0, equalsPosition); - keyValue.second = format.substr(equalsPosition + 1, format.size()); + const std::string_view keyValue = format.substr(0, equalsPosition); + const std::string_view typeAndValue = format.substr(equalsPosition + 1, format.size()); - std::u16string key = GeneralUtils::ASCIIToUTF16(keyValue.first); + const auto key = GeneralUtils::ASCIIToUTF16(keyValue); - auto colonPosition = keyValue.second.find(':'); + const auto colonPosition = typeAndValue.find(':'); - // If : is the first thing after an =, then this is an invalid LDF since - // we dont have a type to use. - if (colonPosition == std::string::npos || colonPosition == 0) return nullptr; + // If : is the first thing after an =, then this is an invalid LDF since + // we dont have a type to use. + if (colonPosition != std::string::npos && colonPosition != 0) { + const std::string_view ldfType = typeAndValue.substr(0, colonPosition); + const std::string_view ldfValue = typeAndValue.substr(colonPosition + 1, typeAndValue.size()); - std::pair ldfTypeAndValue; - ldfTypeAndValue.first = keyValue.second.substr(0, colonPosition); - ldfTypeAndValue.second = keyValue.second.substr(colonPosition + 1, keyValue.second.size()); + // Only allow empty values for string values. + if (!ldfValue.empty() || (ldfType == "0" /* UTF-16 */ || ldfType == "13" /* UTF-8 */)) { + const eLDFType type = GeneralUtils::TryParse(ldfType, LDF_TYPE_UNKNOWN); + switch (type) { + case LDF_TYPE_UTF_16: { + std::u16string data = GeneralUtils::UTF8ToUTF16(ldfValue); + toReturn.reset(new LDFData(key, data)); + break; + } - // Only allow empty values for string values. - if (ldfTypeAndValue.second.size() == 0 && !(ldfTypeAndValue.first == "0" || ldfTypeAndValue.first == "13")) return nullptr; + case LDF_TYPE_S32: { + const auto data = GeneralUtils::TryParse(ldfValue); + if (data) { + toReturn.reset(new LDFData(key, data.value())); + } else { + LOG("Warning: Attempted to process invalid int32 value (%s) from string (%s)", ldfValue.data(), format.data()); + } - eLDFType type; - char* storage; - try { - type = static_cast(strtol(ldfTypeAndValue.first.data(), &storage, 10)); - } catch (std::exception) { - LOG("Attempted to process invalid ldf type (%s) from string (%s)", ldfTypeAndValue.first.data(), format.data()); - return nullptr; - } + break; + } - LDFBaseData* returnValue = nullptr; - switch (type) { - case LDF_TYPE_UTF_16: { - std::u16string data = GeneralUtils::UTF8ToUTF16(ldfTypeAndValue.second); - returnValue = new LDFData(key, data); - break; - } + case LDF_TYPE_FLOAT: { + const auto data = GeneralUtils::TryParse(ldfValue); + if (data) { + toReturn.reset(new LDFData(key, data.value())); + } else { + LOG("Warning: Attempted to process invalid float value (%s) from string (%s)", ldfValue.data(), format.data()); + } + break; + } - case LDF_TYPE_S32: { - const auto data = GeneralUtils::TryParse(ldfTypeAndValue.second); - if (!data) { - LOG("Warning: Attempted to process invalid int32 value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data()); - return nullptr; - } - returnValue = new LDFData(key, data.value()); + case LDF_TYPE_DOUBLE: { + const auto data = GeneralUtils::TryParse(ldfValue); + if (data) { + toReturn.reset(new LDFData(key, data.value())); + } else { + LOG("Warning: Attempted to process invalid double value (%s) from string (%s)", ldfValue.data(), format.data()); + } + break; + } - break; - } + case LDF_TYPE_U32: + { + uint32_t data; + bool parsed = true; + // Have to do this really weird parsing to allow for copy ellision + if (ldfValue == "true") { + data = 1; + } else if (ldfValue == "false") { + data = 0; + } else { + const auto dataOptional = GeneralUtils::TryParse(ldfValue); + if (!dataOptional) { + LOG("Warning: Attempted to process invalid uint32 value (%s) from string (%s)", ldfValue.data(), format.data()); + parsed = false; + } else { + data = dataOptional.value(); + } + } - case LDF_TYPE_FLOAT: { - const auto data = GeneralUtils::TryParse(ldfTypeAndValue.second); - if (!data) { - LOG("Warning: Attempted to process invalid float value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data()); - return nullptr; - } - returnValue = new LDFData(key, data.value()); - break; - } + if (parsed) toReturn.reset(new LDFData(key, data)); + break; + } - case LDF_TYPE_DOUBLE: { - const auto data = GeneralUtils::TryParse(ldfTypeAndValue.second); - if (!data) { - LOG("Warning: Attempted to process invalid double value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data()); - return nullptr; - } - returnValue = new LDFData(key, data.value()); - break; - } + case LDF_TYPE_BOOLEAN: { + bool data; + bool parsed = true; + // Have to do this really weird parsing to allow for copy ellision + if (ldfValue == "true") { + data = true; + } else if (ldfValue == "false") { + data = false; + } else { + const auto dataOptional = GeneralUtils::TryParse(ldfValue); + if (!dataOptional) { + LOG("Warning: Attempted to process invalid bool value (%s) from string (%s)", ldfValue.data(), format.data()); + parsed = false; + } else { + data = dataOptional.value(); + } + } - case LDF_TYPE_U32: - { - uint32_t data; + if (parsed) toReturn.reset(new LDFData(key, data)); + break; + } - if (ldfTypeAndValue.second == "true") { - data = 1; - } else if (ldfTypeAndValue.second == "false") { - data = 0; - } else { - const auto dataOptional = GeneralUtils::TryParse(ldfTypeAndValue.second); - if (!dataOptional) { - LOG("Warning: Attempted to process invalid uint32 value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data()); - return nullptr; + case LDF_TYPE_U64: { + const auto data = GeneralUtils::TryParse(ldfValue); + if (data) { + toReturn.reset(new LDFData(key, data.value())); + } else { + LOG("Warning: Attempted to process invalid uint64 value (%s) from string (%s)", ldfValue.data(), format.data()); + } + break; + } + + case LDF_TYPE_OBJID: { + const auto data = GeneralUtils::TryParse(ldfValue); + if (data) { + toReturn.reset(new LDFData(key, data.value())); + } else { + LOG("Warning: Attempted to process invalid LWOOBJID value (%s) from string (%s)", ldfValue.data(), format.data()); + } + break; + } + + case LDF_TYPE_UTF_8: { + toReturn.reset(new LDFData(key, ldfValue.data())); + break; + } + + case LDF_TYPE_UNKNOWN: + [[fallthrough]]; + default: { + LOG("Warning: Attempted to process invalid unknown value (%s) from string (%s)", ldfValue.data(), format.data()); + break; + } + + } + } } - data = dataOptional.value(); } - - returnValue = new LDFData(key, data); - break; } - case LDF_TYPE_BOOLEAN: { - bool data; - - if (ldfTypeAndValue.second == "true") { - data = true; - } else if (ldfTypeAndValue.second == "false") { - data = false; - } else { - const auto dataOptional = GeneralUtils::TryParse(ldfTypeAndValue.second); - if (!dataOptional) { - LOG("Warning: Attempted to process invalid bool value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data()); - return nullptr; - } - data = dataOptional.value(); - } - - returnValue = new LDFData(key, data); - break; - } - - case LDF_TYPE_U64: { - const auto data = GeneralUtils::TryParse(ldfTypeAndValue.second); - if (!data) { - LOG("Warning: Attempted to process invalid uint64 value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data()); - return nullptr; - } - returnValue = new LDFData(key, data.value()); - break; - } - - case LDF_TYPE_OBJID: { - const auto data = GeneralUtils::TryParse(ldfTypeAndValue.second); - if (!data) { - LOG("Warning: Attempted to process invalid LWOOBJID value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data()); - return nullptr; - } - returnValue = new LDFData(key, data.value()); - break; - } - - case LDF_TYPE_UTF_8: { - std::string data = ldfTypeAndValue.second.data(); - returnValue = new LDFData(key, data); - break; - } - - case LDF_TYPE_UNKNOWN: { - LOG("Warning: Attempted to process invalid unknown value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data()); - break; - } - - default: { - LOG("Warning: Attempted to process invalid LDF type (%d) from string (%s)", type, format.data()); - break; - } - } - return returnValue; + return toReturn; } diff --git a/dCommon/LDFFormat.h b/dCommon/LDFFormat.h index 7c2e939b..81a6648c 100644 --- a/dCommon/LDFFormat.h +++ b/dCommon/LDFFormat.h @@ -1,11 +1,12 @@ -#ifndef __LDFFORMAT__H__ -#define __LDFFORMAT__H__ +#ifndef LDFFORMAT_H +#define LDFFORMAT_H // Custom Classes #include "dCommonVars.h" #include "GeneralUtils.h" // C++ +#include #include #include #include @@ -46,17 +47,17 @@ public: virtual std::string GetValueAsString() const = 0; - virtual LDFBaseData* Copy() const = 0; + virtual std::unique_ptr Copy() const = 0; /** * Given an input string, return the data as a LDF key. */ - static LDFBaseData* DataFromString(const std::string_view& format); + static std::unique_ptr DataFromString(const std::string_view& format); }; template -class LDFData: public LDFBaseData { +class LDFData : public LDFBaseData { private: std::u16string key; T value; @@ -164,8 +165,8 @@ public: return this->GetValueString(); } - LDFBaseData* Copy() const override { - return new LDFData(key, value); + std::unique_ptr Copy() const override { + return std::make_unique>(key, value); } inline static const T Default = {}; @@ -226,4 +227,81 @@ template<> inline std::string LDFData::GetValueString() const { return template<> inline std::string LDFData::GetValueString() const { return this->value; } -#endif //!__LDFFORMAT__H__ +struct LwoNameValue { + using LDFPtr = std::unique_ptr; + using ValueType = std::map; + + LwoNameValue& operator=(const LwoNameValue& other) { + this->values = other.Copy(); + return *this; + } + + template + void Insert(const std::u16string& key, const T& value) { + this->values.insert_or_assign(key, std::unique_ptr(std::make_unique>(key, value))); + } + + void Insert(const std::u16string& key, const char* value) { + this->Insert(key, value); + } + + void Insert(const std::u16string& key, const char16_t* value) { + this->Insert(key, value); + } + + template + void Insert(const std::string& key, const T& value) { + this->Insert(GeneralUtils::UTF8ToUTF16(key), value); + } + + void Insert(const std::string& key, const char* value) { + this->Insert(GeneralUtils::UTF8ToUTF16(key), value); + } + + void Insert(const std::string& key, const char16_t* value) { + this->Insert(GeneralUtils::UTF8ToUTF16(key), value); + } + + const LDFPtr& ParseInsert(const std::string& data) { + LDFPtr toInsert(LDFBaseData::DataFromString(data)); + return toInsert ? + this->values.insert_or_assign(toInsert->GetKey(), std::move(toInsert)).first->second : + this->values.insert_or_assign(u"FAILED_TO_PARSE_" + GeneralUtils::UTF8ToUTF16(data), std::make_unique>("", "")).first->second; + } + + const LDFPtr& ParseInsert(const std::u16string& data) { + return this->ParseInsert(GeneralUtils::UTF16ToWTF8(data)); + } + + ValueType::const_iterator begin() const { + return this->values.cbegin(); + } + + ValueType::const_iterator end() const { + return this->values.cend(); + } + + void Erase(const std::u16string& key) { + this->values.erase(key); + } + + void Erase(const std::string& key) { + this->Erase(GeneralUtils::ASCIIToUTF16(key)); + } + + LwoNameValue() = default; + + LwoNameValue(const LwoNameValue& other) { + this->values = other.Copy(); + } + + ValueType values; +private: + ValueType Copy() const { + ValueType copy; + for (const auto& [key, value] : this->values) copy.insert_or_assign(key, value->Copy()); + return copy; + } +}; + +#endif //!LDFFORMAT_H diff --git a/dCommon/NiPoint3.h b/dCommon/NiPoint3.h index b968a4de..d1b65c93 100644 --- a/dCommon/NiPoint3.h +++ b/dCommon/NiPoint3.h @@ -1,6 +1,8 @@ #ifndef __NIPOINT3_H__ #define __NIPOINT3_H__ - +#ifndef GLM_ENABLE_EXPERIMENTAL +# define GLM_ENABLE_EXPERIMENTAL +#endif /*! \file NiPoint3.hpp \brief Defines a point in space in XYZ coordinates diff --git a/dCommon/NiQuaternion.h b/dCommon/NiQuaternion.h index a4546e2c..c4ef0661 100644 --- a/dCommon/NiQuaternion.h +++ b/dCommon/NiQuaternion.h @@ -1,6 +1,8 @@ #ifndef NIQUATERNION_H #define NIQUATERNION_H - +#ifndef GLM_ENABLE_EXPERIMENTAL +# define GLM_ENABLE_EXPERIMENTAL +#endif // Custom Classes #include "NiPoint3.h" diff --git a/dCommon/dEnums/dCommonVars.h b/dCommon/dEnums/dCommonVars.h index fc1ccded..c3ed1b14 100644 --- a/dCommon/dEnums/dCommonVars.h +++ b/dCommon/dEnums/dCommonVars.h @@ -111,18 +111,6 @@ private: constexpr LWOSCENEID LWOSCENEID_INVALID = -1; -struct LWONameValue { - uint32_t length = 0; //!< The length of the name - std::u16string name; //!< The name - - LWONameValue() = default; - - LWONameValue(const std::u16string& name) { - this->name = name; - this->length = static_cast(name.length()); - } -}; - struct FriendData { public: bool isOnline = false; diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index d88aa1d5..25ffb1e4 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -949,13 +949,13 @@ void Entity::SetGMLevel(eGameMasterLevel value) { } } -void Entity::WriteLDFData(const std::vector& ldf, RakNet::BitStream& outBitStream) const { +void Entity::WriteLDFData(const LwoNameValue& ldf, RakNet::BitStream& outBitStream) const { RakNet::BitStream settingStream; - int32_t numberOfValidKeys = ldf.size(); + int32_t numberOfValidKeys = ldf.values.size(); // Writing keys value pairs the client does not expect to receive or interpret will result in undefined behavior, // so we need to filter out any keys that are not valid and fix the number of valid keys to be correct. - for (LDFBaseData* data : ldf) { + for (const auto& data : ldf.values | std::views::values) { if (data && data->GetValueType() != eLDFType::LDF_TYPE_UNKNOWN) { data->WriteToPacket(settingStream); } else { @@ -987,16 +987,16 @@ void Entity::WriteBaseReplicaData(RakNet::BitStream& outBitStream, eReplicaPacke const auto& syncLDF = GetVar>(u"syncLDF"); // Only sync for models. - if (!m_Settings.empty() && (GetComponent() && !GetComponent())) { + if (!m_Settings.values.empty() && (GetComponent() && !GetComponent())) { outBitStream.Write1(); // Has ldf data WriteLDFData(m_Settings, outBitStream); } else if (!syncLDF.empty()) { // Find all the ldf data we need to write - std::vector ldfData; - ldfData.reserve(m_Settings.size()); + LwoNameValue ldfData; for (const auto& data : syncLDF) { - ldfData.push_back(GetVarData(data)); + const auto* toInsert = GetVarData(data); + if (toInsert) ldfData.values.insert_or_assign(data, toInsert->Copy()); } outBitStream.Write1(); // Has ldf data @@ -2045,13 +2045,7 @@ void Entity::SetI64(const std::u16string& name, const int64_t value) { } bool Entity::HasVar(const std::u16string& name) const { - for (auto* data : m_Settings) { - if (data->GetKey() == name) { - return true; - } - } - - return false; + return m_Settings.values.contains(name); } uint16_t Entity::GetNetworkId() const { @@ -2083,24 +2077,13 @@ void Entity::SendNetworkVar(const std::string& data, const SystemAddress& sysAdd GameMessages::SendSetNetworkScriptVar(this, sysAddr, data); } -LDFBaseData* Entity::GetVarData(const std::u16string& name) const { - for (auto* data : m_Settings) { - if (data == nullptr) { - continue; - } - - if (data->GetKey() != name) { - continue; - } - - return data; - } - - return nullptr; +const LDFBaseData* const Entity::GetVarData(const std::u16string& name) const { + const auto itr = m_Settings.values.find(name); + return itr != m_Settings.values.cend() ? itr->second.get() : nullptr; } std::string Entity::GetVarAsString(const std::u16string& name) const { - auto* data = GetVarData(name); + const auto* const data = GetVarData(name); return data ? data->GetValueAsString() : ""; } @@ -2276,7 +2259,7 @@ bool Entity::MsgRequestServerObjectInfo(GameMessages::RequestServerObjectInfo& r } auto& configData = objectInfo.PushDebug("Config Data"); - for (const auto config : m_Settings) { + for (const auto& config : m_Settings.values | std::views::values) { configData.PushDebug(GeneralUtils::UTF16ToWTF8(config->GetKey())) = config->GetValueAsString(); } diff --git a/dGame/Entity.h b/dGame/Entity.h index c07bd3f4..a8781d42 100644 --- a/dGame/Entity.h +++ b/dGame/Entity.h @@ -97,9 +97,9 @@ public: LWOOBJID GetSpawnerID() const { return m_SpawnerID; } - const std::vector& GetSettings() const { return m_Settings; } + const LwoNameValue& GetSettings() const { return m_Settings; } - const std::vector& GetNetworkSettings() const { return m_NetworkSettings; } + const LwoNameValue& GetNetworkSettings() const { return m_NetworkSettings; } bool GetIsDead() const; @@ -312,6 +312,12 @@ public: template void SetNetworkVar(const std::u16string& name, std::vector value, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS); + template + void SetNetworkVar(const std::string& name, T value, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS); + + template + LwoNameValue::ValueType::iterator InsertNetworkVar(const std::u16string& name, T value); + template T GetNetworkVar(const std::u16string& name); @@ -324,11 +330,6 @@ public: template ComponentType* AddComponent(VaArgs... args); - /** - * Get the LDF data. - */ - LDFBaseData* GetVarData(const std::u16string& name) const; - /** * Get the LDF value and convert it to a string. */ @@ -360,7 +361,7 @@ public: // This is the actual function that will be registered, which casts the base GameMsg to the derived type const auto castWrapper = [boundFunction](GameMessages::GameMsg& msg) { return boundFunction(static_cast(msg)); - }; + }; DerivedGameMsg msg; RegisterMsg(msg.msgId, castWrapper); } @@ -371,13 +372,20 @@ public: static Observable OnPlayerPositionUpdate; private: - void WriteLDFData(const std::vector& ldf, RakNet::BitStream& outBitStream) const; + + /** + * Get the LDF data. + */ + const LDFBaseData* const GetVarData(const std::u16string& name) const; + template + LwoNameValue::ValueType::iterator InsertLnvData(LwoNameValue& lnv, const std::u16string& key, T value); + void WriteLDFData(const LwoNameValue& ldf, RakNet::BitStream& outBitStream) const; LWOOBJID m_ObjectID; LOT m_TemplateID; - std::vector m_Settings; - std::vector m_NetworkSettings; + LwoNameValue m_Settings; + LwoNameValue m_NetworkSettings; NiPoint3 m_DefaultPosition; NiQuaternion m_DefaultRotation = QuatUtils::IDENTITY; @@ -459,13 +467,13 @@ T* Entity::GetComponent() const { template const T& Entity::GetVar(const std::u16string& name) const { - auto* data = GetVarData(name); + const auto* const data = GetVarData(name); if (data == nullptr) { return LDFData::Default; } - auto* typed = dynamic_cast*>(data); + auto* typed = dynamic_cast* const>(data); if (typed == nullptr) { return LDFData::Default; @@ -483,52 +491,44 @@ T Entity::GetVarAs(const std::u16string& name) const { template void Entity::SetVar(const std::u16string& name, T value) { - auto* data = GetVarData(name); + InsertLnvData(m_Settings, name, value); +} - if (data == nullptr) { - auto* data = new LDFData(name, value); - - m_Settings.push_back(data); - - return; +template +LwoNameValue::ValueType::iterator Entity::InsertLnvData(LwoNameValue& lnv, const std::u16string& key, T value) { + auto itr = lnv.values.find(key); + if (itr != lnv.values.end()) { + auto* lnvCast = dynamic_cast*>(itr->second.get()); + if (!lnvCast) { + // Is of different type + itr->second = std::make_unique>(key, value); + } else { + // Is the same type and exists + lnvCast->SetValue(value); + } + } else { + // Doesn't exist + itr = lnv.values.insert_or_assign(key, std::make_unique>(key, value)).first; } - auto* typed = dynamic_cast*>(data); + return itr; +} - if (typed == nullptr) { - return; - } - - typed->SetValue(value); +template +LwoNameValue::ValueType::iterator Entity::InsertNetworkVar(const std::u16string& name, T value) { + return InsertLnvData(m_NetworkSettings, name, value); } template void Entity::SetNetworkVar(const std::u16string& name, T value, const SystemAddress& sysAddr) { - LDFData* newData = nullptr; + const auto itr = InsertNetworkVar(name, value); - for (auto* data : m_NetworkSettings) { - if (data->GetKey() != name) - continue; + SendNetworkVar(itr->second->GetString(), sysAddr); +} - newData = dynamic_cast*>(data); - if (newData != nullptr) { - newData->SetValue(value); - } else { // If we're changing types - m_NetworkSettings.erase( - std::remove(m_NetworkSettings.begin(), m_NetworkSettings.end(), data), m_NetworkSettings.end() - ); - delete data; - } - - break; - } - - if (newData == nullptr) { - newData = new LDFData(name, value); - } - - m_NetworkSettings.push_back(newData); - SendNetworkVar(newData->GetString(true), sysAddr); +template +void Entity::SetNetworkVar(const std::string& name, T value, const SystemAddress& sysAddr) { + SetNetworkVar(GeneralUtils::UTF8ToUTF16(name), value, sysAddr); } template @@ -537,28 +537,11 @@ void Entity::SetNetworkVar(const std::u16string& name, std::vector values, co auto index = 1; for (const auto& value : values) { - LDFData* newData = nullptr; const auto& indexedName = name + u"." + GeneralUtils::to_u16string(index); - - for (auto* data : m_NetworkSettings) { - if (data->GetKey() != indexedName) - continue; - - newData = dynamic_cast*>(data); - newData->SetValue(value); - break; - } - - if (newData == nullptr) { - newData = new LDFData(indexedName, value); - } - - m_NetworkSettings.push_back(newData); - - if (index == values.size()) { - updates << newData->GetString(true); - } else { - updates << newData->GetString(true) << "\n"; + const auto itr = InsertNetworkVar(indexedName, value); + updates << itr->second->GetString(); + if (index != values.size()) { + updates << "\n"; } index++; @@ -569,18 +552,15 @@ void Entity::SetNetworkVar(const std::u16string& name, std::vector values, co template T Entity::GetNetworkVar(const std::u16string& name) { - for (auto* data : m_NetworkSettings) { - if (data == nullptr || data->GetKey() != name) - continue; + T toReturn = LDFData::Default; - auto* typed = dynamic_cast*>(data); - if (typed == nullptr) - continue; - - return typed->GetValue(); + const auto itr = m_NetworkSettings.values.find(name); + if (itr != m_NetworkSettings.values.cend()) { + auto* cast = dynamic_cast*>(itr->second.get()); + if (cast) toReturn = cast->GetValue(); } - return LDFData::Default; + return toReturn; } /** diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index b8e40154..a7ca59c1 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -39,10 +39,10 @@ Leaderboard::~Leaderboard() { } void Leaderboard::Clear() { - for (auto& entry : entries) for (auto ldfData : entry) delete ldfData; + entries.clear(); } -inline void WriteLeaderboardRow(std::ostringstream& leaderboard, const uint32_t& index, LDFBaseData* data) { +inline void WriteLeaderboardRow(std::ostringstream& leaderboard, const uint32_t& index, const std::unique_ptr& data) { leaderboard << "\nResult[0].Row[" << index << "]." << data->GetString(); } @@ -59,8 +59,8 @@ void Leaderboard::Serialize(RakNet::BitStream& bitStream) const { int32_t rowNumber = 0; for (auto& entry : entries) { - for (auto* data : entry) { - WriteLeaderboardRow(leaderboard, rowNumber, data); + for (const auto& data : entry.values | std::views::values) { + if (data) WriteLeaderboardRow(leaderboard, rowNumber, data); } rowNumber++; } @@ -85,57 +85,56 @@ void QueryToLdf(Leaderboard& leaderboard, const std::vector for (const auto& leaderboardEntry : leaderboardEntries) { constexpr int32_t MAX_NUM_DATA_PER_ROW = 9; auto& entry = leaderboard.PushBackEntry(); - entry.reserve(MAX_NUM_DATA_PER_ROW); - entry.push_back(new LDFData(u"CharacterID", leaderboardEntry.charId)); - entry.push_back(new LDFData(u"LastPlayed", leaderboardEntry.lastPlayedTimestamp)); - entry.push_back(new LDFData(u"NumPlayed", leaderboardEntry.numTimesPlayed)); - entry.push_back(new LDFData(u"name", GeneralUtils::ASCIIToUTF16(leaderboardEntry.name))); - entry.push_back(new LDFData(u"RowNumber", leaderboardEntry.ranking)); + entry.Insert(u"CharacterID", leaderboardEntry.charId); + entry.Insert(u"LastPlayed", leaderboardEntry.lastPlayedTimestamp); + entry.Insert(u"NumPlayed", leaderboardEntry.numTimesPlayed); + entry.Insert(u"name", GeneralUtils::ASCIIToUTF16(leaderboardEntry.name)); + entry.Insert(u"RowNumber", leaderboardEntry.ranking); switch (leaderboard.GetLeaderboardType()) { case ShootingGallery: - entry.push_back(new LDFData(u"Score", leaderboardEntry.primaryScore)); + entry.Insert(u"Score", leaderboardEntry.primaryScore); // Score:1 - entry.push_back(new LDFData(u"Streak", leaderboardEntry.secondaryScore)); + entry.Insert(u"Streak", leaderboardEntry.secondaryScore); // Streak:1 - entry.push_back(new LDFData(u"HitPercentage", leaderboardEntry.tertiaryScore)); + entry.Insert(u"HitPercentage", leaderboardEntry.tertiaryScore); // HitPercentage:3 between 0 and 1 break; case Racing: - entry.push_back(new LDFData(u"BestTime", leaderboardEntry.primaryScore)); + entry.Insert(u"BestTime", leaderboardEntry.primaryScore); // BestLapTime:3 - entry.push_back(new LDFData(u"BestLapTime", leaderboardEntry.secondaryScore)); + entry.Insert(u"BestLapTime", leaderboardEntry.secondaryScore); // BestTime:3 - entry.push_back(new LDFData(u"License", 1)); + entry.Insert(u"License", 1); // License:1 - 1 if player has completed mission 637 and 0 otherwise - entry.push_back(new LDFData(u"NumWins", leaderboardEntry.numWins)); + entry.Insert(u"NumWins", leaderboardEntry.numWins); // NumWins:1 break; case UnusedLeaderboard4: - entry.push_back(new LDFData(u"Points", leaderboardEntry.primaryScore)); + entry.Insert(u"Points", leaderboardEntry.primaryScore); // Points:1 break; case MonumentRace: - entry.push_back(new LDFData(u"Time", leaderboardEntry.primaryScore)); + entry.Insert(u"Time", leaderboardEntry.primaryScore); // Time:1(?) break; case FootRace: - entry.push_back(new LDFData(u"Time", leaderboardEntry.primaryScore)); + entry.Insert(u"Time", leaderboardEntry.primaryScore); // Time:1 break; case Survival: - entry.push_back(new LDFData(u"Points", leaderboardEntry.primaryScore)); + entry.Insert(u"Points", leaderboardEntry.primaryScore); // Points:1 - entry.push_back(new LDFData(u"Time", leaderboardEntry.secondaryScore)); + entry.Insert(u"Time", leaderboardEntry.secondaryScore); // Time:1 break; case SurvivalNS: - entry.push_back(new LDFData(u"Wave", leaderboardEntry.primaryScore)); + entry.Insert(u"Wave", leaderboardEntry.primaryScore); // Wave:1 - entry.push_back(new LDFData(u"Time", leaderboardEntry.secondaryScore)); + entry.Insert(u"Time", leaderboardEntry.secondaryScore); // Time:1 break; case Donations: - entry.push_back(new LDFData(u"Score", leaderboardEntry.primaryScore)); + entry.Insert(u"Score", leaderboardEntry.primaryScore); // Score:1 break; case None: diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index 760d282e..c99634c1 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -70,8 +70,7 @@ public: private: - using LeaderboardEntry = std::vector; - using LeaderboardEntries = std::vector; + using LeaderboardEntries = std::vector; LeaderboardEntries entries; LWOOBJID relatedPlayer; @@ -81,7 +80,7 @@ private: bool weekly; uint32_t numResults; public: - LeaderboardEntry& PushBackEntry() { + LwoNameValue& PushBackEntry() { return entries.emplace_back(); } diff --git a/dGame/dComponents/CharacterComponent.cpp b/dGame/dComponents/CharacterComponent.cpp index a703d884..0c5230f8 100644 --- a/dGame/dComponents/CharacterComponent.cpp +++ b/dGame/dComponents/CharacterComponent.cpp @@ -24,6 +24,7 @@ #include "WorldPackets.h" #include "MessageType/Game.h" #include +#include CharacterComponent::CharacterComponent(Entity* parent, const int32_t componentID, Character* character, const SystemAddress& systemAddress) : Component(parent, componentID) { m_Character = character; @@ -491,7 +492,7 @@ Item* CharacterComponent::RocketEquip(Entity* player) { if (!rocket) return rocket; // build and define the rocket config - for (LDFBaseData* data : rocket->GetConfig()) { + for (const auto& data : rocket->GetConfig().values | std::views::values) { if (data->GetKey() == u"assemblyPartLOTs") { std::string newRocketStr = data->GetValueAsString() + ";"; GeneralUtils::ReplaceInString(newRocketStr, "+", ";"); diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index ebccc662..90591367 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -881,9 +881,9 @@ void DestroyableComponent::FixStats() { int32_t currentImagination = destroyableComponent->GetImagination(); // Unequip all items - auto equipped = inventoryComponent->GetEquippedItems(); + const auto equipped = inventoryComponent->GetEquippedItems(); - for (auto& equippedItem : equipped) { + for (const auto& equippedItem : equipped) { // Get the item with the item ID auto* item = inventoryComponent->FindItemById(equippedItem.second.id); @@ -924,7 +924,7 @@ void DestroyableComponent::FixStats() { buffComponent->ReApplyBuffs(); // Requip all items - for (auto& equippedItem : equipped) { + for (const auto& equippedItem : equipped) { // Get the item with the item ID auto* item = inventoryComponent->FindItemById(equippedItem.second.id); diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index a7e8d5ed..2f3436fe 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -173,7 +173,7 @@ void InventoryComponent::AddItem( const uint32_t count, eLootSourceType lootSourceType, eInventoryType inventoryType, - const std::vector& config, + const LwoNameValue& config, const LWOOBJID parent, const bool showFlyingLoot, bool isModMoveAndEquip, @@ -204,7 +204,7 @@ void InventoryComponent::AddItem( auto* inventory = GetInventory(inventoryType); - if (!config.empty() || bound) { + if (!config.values.empty() || bound) { const auto slot = preferredSlot != -1 && inventory->IsSlotEmpty(preferredSlot) ? preferredSlot : inventory->FindEmptySlot(); if (slot == -1) { @@ -356,7 +356,7 @@ void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType in const auto subkey = item->GetSubKey(); - if (subkey == LWOOBJID_EMPTY && item->GetConfig().empty() && (!item->GetBound() || (item->GetBound() && item->GetInfo().isBOP))) { + if (subkey == LWOOBJID_EMPTY && item->GetConfig().values.empty() && (!item->GetBound() || (item->GetBound() && item->GetInfo().isBOP))) { auto left = std::min(count, origin->GetLotCount(lot)); while (left > 0) { @@ -379,11 +379,7 @@ void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType in isModMoveAndEquip = false; } } else { - std::vector config; - - for (auto* const data : item->GetConfig()) { - config.push_back(data->Copy()); - } + const auto config = item->GetConfig(); const auto delta = std::min(item->GetCount(), count); @@ -744,18 +740,17 @@ void InventoryComponent::Serialize(RakNet::BitStream& outBitStream, const bool b outBitStream.Write0(); - bool flag = !item.config.empty(); + bool flag = !item.config.values.empty(); outBitStream.Write(flag); if (flag) { RakNet::BitStream ldfStream; - ldfStream.Write(item.config.size()); // Key count - for (LDFBaseData* data : item.config) { + ldfStream.Write(item.config.values.size()); // Key count + for (const auto& data : item.config.values | std::views::values) { if (data->GetKey() == u"assemblyPartLOTs") { std::string newRocketStr = data->GetValueAsString() + ";"; GeneralUtils::ReplaceInString(newRocketStr, "+", ";"); - LDFData* ldf_data = new LDFData(u"assemblyPartLOTs", GeneralUtils::ASCIIToUTF16(newRocketStr)); - ldf_data->WriteToPacket(ldfStream); - delete ldf_data; + LDFData ldf_data(u"assemblyPartLOTs", GeneralUtils::ASCIIToUTF16(newRocketStr)); + ldf_data.WriteToPacket(ldfStream); } else { data->WriteToPacket(ldfStream); } @@ -782,7 +777,7 @@ void InventoryComponent::Update(float deltaTime) { } } -void InventoryComponent::UpdateSlot(const std::string& location, EquippedItem item, bool keepCurrent) { +void InventoryComponent::UpdateSlot(const std::string& location, const EquippedItem& item, bool keepCurrent) { const auto index = m_Equipped.find(location); if (index != m_Equipped.end()) { @@ -1080,7 +1075,7 @@ void InventoryComponent::PushEquippedItems() { } void InventoryComponent::PopEquippedItems() { - auto current = m_Equipped; + const auto current = m_Equipped; for (const auto& pair : current) { auto* const item = FindItemById(pair.second.id); @@ -1876,7 +1871,7 @@ bool InventoryComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo slot.PushDebug("Bind on equip") = item->GetInfo().isBOE; slot.PushDebug("Is currently bound") = item->GetBound(); auto& extra = slot.PushDebug("Extra Info"); - for (const auto* const setting : item->GetConfig()) { + for (const auto& setting : item->GetConfig().values | std::views::values) { if (setting) extra.PushDebug(GeneralUtils::UTF16ToWTF8(setting->GetKey())) = setting->GetValueAsString(); } } @@ -1892,7 +1887,7 @@ bool InventoryComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo equipSlot.PushDebug("Slot") = info.slot; equipSlot.PushDebug("Count") = info.count; auto& extra = equipSlot.PushDebug("Extra Info"); - for (const auto* const setting : info.config) { + for (const auto& setting : info.config.values | std::views::values) { if (setting) extra.PushDebug(GeneralUtils::UTF16ToWTF8(setting->GetKey())) = setting->GetValueAsString(); } } diff --git a/dGame/dComponents/InventoryComponent.h b/dGame/dComponents/InventoryComponent.h index beaf0efb..74ad326d 100644 --- a/dGame/dComponents/InventoryComponent.h +++ b/dGame/dComponents/InventoryComponent.h @@ -134,7 +134,7 @@ public: uint32_t count, eLootSourceType lootSourceType = eLootSourceType::NONE, eInventoryType inventoryType = INVALID, - const std::vector& config = {}, + const LwoNameValue& config = {}, LWOOBJID parent = LWOOBJID_EMPTY, bool showFlyingLoot = true, bool isModMoveAndEquip = false, @@ -213,7 +213,7 @@ public: * @param item the item to place * @param keepCurrent stores the item in an additional temp slot if there's already an item equipped */ - void UpdateSlot(const std::string& location, EquippedItem item, bool keepCurrent = false); + void UpdateSlot(const std::string& location, const EquippedItem& item, bool keepCurrent = false); /** * Removes a slot from the inventory diff --git a/dGame/dComponents/ModelComponent.cpp b/dGame/dComponents/ModelComponent.cpp index 14093b15..48585237 100644 --- a/dGame/dComponents/ModelComponent.cpp +++ b/dGame/dComponents/ModelComponent.cpp @@ -221,8 +221,8 @@ void ModelComponent::RemoveBehavior(MoveToInventoryMessage& msg, const bool keep auto* const inventoryComponent = playerEntity->GetComponent(); if (inventoryComponent && !behavior.GetIsLoot()) { // config is owned by the item - std::vector config; - config.push_back(new LDFData(u"userModelName", behavior.GetName())); + LwoNameValue config; + config.Insert(u"userModelName", behavior.GetName()); inventoryComponent->AddItem(7965, 1, eLootSourceType::PROPERTY, eInventoryType::BEHAVIORS, config, LWOOBJID_EMPTY, true, false, msg.GetBehaviorId()); } } diff --git a/dGame/dComponents/PhantomPhysicsComponent.h b/dGame/dComponents/PhantomPhysicsComponent.h index 115783ac..9a9e1880 100644 --- a/dGame/dComponents/PhantomPhysicsComponent.h +++ b/dGame/dComponents/PhantomPhysicsComponent.h @@ -12,7 +12,6 @@ #include "eReplicaComponentType.h" #include "PhysicsComponent.h" -class LDFBaseData; class Entity; class dpEntity; enum class ePhysicsEffectType : uint32_t ; diff --git a/dGame/dComponents/PropertyManagementComponent.cpp b/dGame/dComponents/PropertyManagementComponent.cpp index 3c62dc52..54371fde 100644 --- a/dGame/dComponents/PropertyManagementComponent.cpp +++ b/dGame/dComponents/PropertyManagementComponent.cpp @@ -346,10 +346,7 @@ void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const N info.spawner = nullptr; info.spawnerID = spawnerID; info.spawnerNodeID = 0; - - for (auto* setting : item->GetConfig()) { - info.settings.push_back(setting->Copy()); - } + info.settings = item->GetConfig(); Entity* newEntity = Game::entityManager->CreateEntity(info); if (newEntity != nullptr) { @@ -393,11 +390,11 @@ void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const N auto* spawner = Game::zoneManager->GetSpawner(spawnerId); - 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)); + info.nodes[0]->config.Insert(u"modelBehaviors", 0); + info.nodes[0]->config.Insert(u"userModelID", info.spawnerID); + info.nodes[0]->config.Insert(u"modelType", 2); + info.nodes[0]->config.Insert(u"propertyObjectID", true); + info.nodes[0]->config.Insert(u"componentWhitelist", 1); auto* model = spawner->Spawn(); auto* modelComponent = model->GetComponent(); @@ -476,28 +473,19 @@ void PropertyManagementComponent::DeleteModel(const LWOOBJID id, const int delet if (model->GetLOT() == 14) { //add it to the inv - std::vector settings; - + LwoNameValue actualConfig; + //fill our settings with BBB gurbage - LDFBaseData* ldfBlueprintID = new LDFData(u"blueprintid", model->GetVar(u"blueprintid")); - LDFBaseData* userModelDesc = new LDFData(u"userModelDesc", u"A cool model you made!"); - LDFBaseData* userModelHasBhvr = new LDFData(u"userModelHasBhvr", false); - LDFBaseData* userModelID = new LDFData(u"userModelID", model->GetVar(u"userModelID")); - LDFBaseData* userModelMod = new LDFData(u"userModelMod", false); - LDFBaseData* userModelName = new LDFData(u"userModelName", u"My Cool Model"); - LDFBaseData* propertyObjectID = new LDFData(u"userModelOpt", true); - LDFBaseData* modelType = new LDFData(u"userModelPhysicsType", 2); + actualConfig.Insert(u"blueprintid", model->GetVar(u"blueprintid")); + actualConfig.Insert(u"userModelDesc", u"A cool model you made!"); + actualConfig.Insert(u"userModelHasBhvr", false); + actualConfig.Insert(u"userModelID", model->GetVar(u"userModelID")); + actualConfig.Insert(u"userModelMod", false); + actualConfig.Insert(u"userModelName", u"My Cool Model"); + actualConfig.Insert(u"userModelOpt", true); + actualConfig.Insert(u"userModelPhysicsType", 2); - settings.push_back(ldfBlueprintID); - settings.push_back(userModelDesc); - settings.push_back(userModelHasBhvr); - settings.push_back(userModelID); - settings.push_back(userModelMod); - settings.push_back(userModelName); - settings.push_back(propertyObjectID); - settings.push_back(modelType); - - inventoryComponent->AddItem(6662, 1, eLootSourceType::DELETION, eInventoryType::MODELS_IN_BBB, settings, LWOOBJID_EMPTY, false, false, spawnerId); + inventoryComponent->AddItem(6662, 1, eLootSourceType::DELETION, eInventoryType::MODELS_IN_BBB, actualConfig, LWOOBJID_EMPTY, false, false, spawnerId); auto* item = inventoryComponent->FindItemBySubKey(spawnerId); if (item == nullptr) { @@ -615,23 +603,23 @@ void PropertyManagementComponent::Load() { info.spawnerID = databaseModel.id; - std::vector settings; + LwoNameValue& settings = node->config; //BBB property models need to have extra stuff set for them: if (databaseModel.lot == 14) { LWOOBJID blueprintID = databaseModel.ugcId; - 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)); + settings.Insert(u"blueprintid", blueprintID); + settings.Insert(u"componentWhitelist", 1); + settings.Insert(u"modelType", 2); + settings.Insert(u"propertyObjectID", true); + settings.Insert(u"userModelID", databaseModel.id); } else { - 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)); + settings.Insert(u"modelType", 2); + settings.Insert(u"userModelID", databaseModel.id); + settings.Insert(u"modelBehaviors", 0); + settings.Insert(u"propertyObjectID", true); + settings.Insert(u"componentWhitelist", 1); } std::ostringstream userModelBehavior; @@ -646,9 +634,7 @@ void PropertyManagementComponent::Load() { firstAdded = true; } - settings.push_back(new LDFData(u"userModelBehaviors", userModelBehavior.str())); - - node->config = settings; + settings.Insert(u"userModelBehaviors", userModelBehavior.str()); const auto spawnerId = Game::zoneManager->MakeSpawner(info); diff --git a/dGame/dComponents/RacingControlComponent.cpp b/dGame/dComponents/RacingControlComponent.cpp index 06cd02be..9eb43b9e 100644 --- a/dGame/dComponents/RacingControlComponent.cpp +++ b/dGame/dComponents/RacingControlComponent.cpp @@ -26,6 +26,7 @@ #include "dZoneManager.h" #include "CDActivitiesTable.h" #include "eStateChangeType.h" +#include #include #ifndef M_PI @@ -57,6 +58,7 @@ RacingControlComponent::RacingControlComponent(Entity* parent, const int32_t com CDActivitiesTable* activitiesTable = CDClientManager::GetTable(); std::vector activities = activitiesTable->Query([=](CDActivities entry) {return (entry.instanceMapID == worldID); }); for (CDActivities activity : activities) m_ActivityID = activity.ActivityID; + RegisterMsg(&RacingControlComponent::MsgConfigureRacingControl); } RacingControlComponent::~RacingControlComponent() {} @@ -178,11 +180,10 @@ void RacingControlComponent::LoadPlayerVehicle(Entity* player, moduleAssemblyComponent->SetSubKey(item->GetSubKey()); moduleAssemblyComponent->SetUseOptionalParts(false); - for (auto* config : item->GetConfig()) { - if (config->GetKey() == u"assemblyPartLOTs") { - moduleAssemblyComponent->SetAssemblyPartsLOTs( - GeneralUtils::ASCIIToUTF16(config->GetValueAsString())); - } + const auto& lnv = item->GetConfig().values; + const auto itr = lnv.find(u"assemblyPartLOTs"); + if (itr != lnv.end()) { + moduleAssemblyComponent->SetAssemblyPartsLOTs(GeneralUtils::ASCIIToUTF16(itr->second->GetValueAsString())); } } @@ -871,10 +872,10 @@ void RacingControlComponent::Update(float deltaTime) { } } -void RacingControlComponent::MsgConfigureRacingControl(const GameMessages::ConfigureRacingControl& msg) { - for (const auto& dataUnique : msg.racingSettings) { +bool RacingControlComponent::MsgConfigureRacingControl(const GameMessages::ConfigureRacingControl& msg) { + for (const auto& dataUnique : msg.racingSettings | std::views::values) { if (!dataUnique) continue; - const auto* const data = dataUnique.get(); + const auto* const data = dataUnique.get(); if (data->GetKey() == u"Race_PathName" && data->GetValueType() == LDF_TYPE_UTF_16) { m_PathName = static_cast*>(data)->GetValue(); } else if (data->GetKey() == u"activityID" && data->GetValueType() == LDF_TYPE_S32) { @@ -886,4 +887,5 @@ void RacingControlComponent::MsgConfigureRacingControl(const GameMessages::Confi m_MinimumPlayersForGroupAchievements = static_cast*>(data)->GetValue(); } } + return true; } diff --git a/dGame/dComponents/RacingControlComponent.h b/dGame/dComponents/RacingControlComponent.h index b4300d94..bedb636b 100644 --- a/dGame/dComponents/RacingControlComponent.h +++ b/dGame/dComponents/RacingControlComponent.h @@ -152,7 +152,7 @@ public: */ RacingPlayerInfo* GetPlayerData(LWOOBJID playerID); - void MsgConfigureRacingControl(const GameMessages::ConfigureRacingControl& msg); + bool MsgConfigureRacingControl(const GameMessages::ConfigureRacingControl& msg); private: diff --git a/dGame/dComponents/ScriptComponent.cpp b/dGame/dComponents/ScriptComponent.cpp index 87b6e6e1..2c9be34b 100644 --- a/dGame/dComponents/ScriptComponent.cpp +++ b/dGame/dComponents/ScriptComponent.cpp @@ -8,6 +8,8 @@ #include "GameMessages.h" #include "Amf3.h" +#include + ScriptComponent::ScriptComponent(Entity* parent, const int32_t componentID, const std::string& scriptName, bool serialized, bool client) : Component(parent, componentID) { m_Serialized = serialized; m_Client = client; @@ -20,7 +22,7 @@ ScriptComponent::ScriptComponent(Entity* parent, const int32_t componentID, cons void ScriptComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { if (bIsInitialUpdate) { const auto& networkSettings = m_Parent->GetNetworkSettings(); - auto hasNetworkSettings = !networkSettings.empty(); + auto hasNetworkSettings = !networkSettings.values.empty(); outBitStream.Write(hasNetworkSettings); if (hasNetworkSettings) { @@ -28,9 +30,9 @@ void ScriptComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitial // First write the most inner LDF data RakNet::BitStream ldfData; ldfData.Write(0); - ldfData.Write(networkSettings.size()); + ldfData.Write(networkSettings.values.size()); - for (auto* networkSetting : networkSettings) { + for (const auto& networkSetting : networkSettings.values | std::views::values) { networkSetting->WriteToPacket(ldfData); } @@ -56,7 +58,7 @@ bool ScriptComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& r auto& scriptInfo = reportInfo.info->PushDebug("Script"); scriptInfo.PushDebug("Script Name") = m_ScriptName.empty() ? "None" : m_ScriptName; auto& networkSettings = scriptInfo.PushDebug("Network Settings"); - for (const auto* const setting : m_Parent->GetNetworkSettings()) { + for (const auto& setting : m_Parent->GetNetworkSettings().values | std::views::values) { networkSettings.PushDebug(GeneralUtils::UTF16ToWTF8(setting->GetKey())) = setting->GetValueAsString(); } diff --git a/dGame/dComponents/SkillComponent.cpp b/dGame/dComponents/SkillComponent.cpp index 5ac27bb6..1d4eb29d 100644 --- a/dGame/dComponents/SkillComponent.cpp +++ b/dGame/dComponents/SkillComponent.cpp @@ -125,8 +125,7 @@ void SkillComponent::SyncPlayerProjectile(const LWOOBJID projectileId, RakNet::B this->m_managedProjectiles.erase(this->m_managedProjectiles.begin() + index); GameMessages::ActivityNotify notify; - notify.notification.push_back( std::make_unique>(u"shot_done", sync_entry.skillId)); - + notify.notification.Insert(u"shot_done", sync_entry.skillId); m_Parent->OnActivityNotify(notify); } diff --git a/dGame/dEntity/EntityInfo.h b/dGame/dEntity/EntityInfo.h index e16c315d..68eaa95d 100644 --- a/dGame/dEntity/EntityInfo.h +++ b/dGame/dEntity/EntityInfo.h @@ -34,7 +34,7 @@ struct EntityInfo { LOT lot; NiPoint3 pos; NiQuaternion rot = QuatUtils::IDENTITY; - std::vector settings; - std::vector networkSettings; + LwoNameValue settings; + LwoNameValue networkSettings; float scale; }; diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 5edd47f2..6b57e177 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #include "RakString.h" //CDB includes: @@ -465,20 +466,20 @@ void GameMessages::SendAddItemToInventoryClientSync(Entity* entity, const System bitStream.Write(lootSourceType != eLootSourceType::NONE); // Loot source if (lootSourceType != eLootSourceType::NONE) bitStream.Write(lootSourceType); - LWONameValue extraInfo; + std::u16string extraInfo; - auto config = item->GetConfig(); + const auto& config = item->GetConfig(); - for (auto* data : config) { - extraInfo.name += GeneralUtils::ASCIIToUTF16(data->GetString()) + u","; + for (const auto& data : config.values | std::views::values) { + extraInfo += GeneralUtils::ASCIIToUTF16(data->GetString()) + u","; } - if (extraInfo.name.length() > 0) extraInfo.name.pop_back(); // remove the last comma + if (extraInfo.length() > 0) extraInfo.pop_back(); // remove the last comma - bitStream.Write(extraInfo.name.size()); - if (extraInfo.name.size() > 0) { - for (uint32_t i = 0; i < extraInfo.name.size(); ++i) { - bitStream.Write(extraInfo.name[i]); + bitStream.Write(extraInfo.size()); + if (extraInfo.size() > 0) { + for (uint32_t i = 0; i < extraInfo.size(); ++i) { + bitStream.Write(extraInfo[i]); } bitStream.Write(0x00); } @@ -743,13 +744,9 @@ void GameMessages::SendBroadcastTextToChatbox(Entity* entity, const SystemAddres bitStream.Write(entity->GetObjectID()); bitStream.Write(MessageType::Game::BROADCAST_TEXT_TO_CHATBOX); - LWONameValue attribs; - attribs.name = attrs; - attribs.length = attrs.size(); - - bitStream.Write(attribs.length); - for (uint32_t i = 0; i < attribs.length; ++i) { - bitStream.Write(attribs.name[i]); + bitStream.Write(attrs.size()); + for (uint32_t i = 0; i < attrs.size(); ++i) { + bitStream.Write(attrs[i]); } bitStream.Write(0x00); // Null Terminator @@ -2621,11 +2618,11 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent info.spawnerID = entity->GetObjectID(); info.spawnerNodeID = 0; - info.settings.push_back(new LDFData(u"blueprintid", blueprintIDs[i])); - 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", modelIDs[i])); + info.settings.Insert(u"blueprintid", blueprintIDs[i]); + info.settings.Insert(u"componentWhitelist", 1); + info.settings.Insert(u"modelType", 2); + info.settings.Insert(u"propertyObjectID", true); + info.settings.Insert(u"userModelID", modelIDs[i]); Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr); if (newEntity) { Game::entityManager->ConstructEntity(newEntity); @@ -5266,7 +5263,8 @@ void GameMessages::HandleRemoveItemFromInventory(RakNet::BitStream& inStream, En int eInvType = INVENTORY_MAX; bool eLootTypeSourceIsDefault = false; int eLootTypeSource = LOOTTYPE_NONE; - LWONameValue extraInfo; + int32_t extraInfoLength = 0; + std::u16string extraInfo; bool forceDeletion = true; bool iLootTypeSourceIsDefault = false; LWOOBJID iLootTypeSource = LWOOBJID_EMPTY; @@ -5292,12 +5290,12 @@ void GameMessages::HandleRemoveItemFromInventory(RakNet::BitStream& inStream, En if (eInvTypeIsDefault) inStream.Read(eInvType); inStream.Read(eLootTypeSourceIsDefault); if (eLootTypeSourceIsDefault) inStream.Read(eLootTypeSource); - inStream.Read(extraInfo.length); - if (extraInfo.length > 0) { - for (uint32_t i = 0; i < extraInfo.length; ++i) { + inStream.Read(extraInfoLength); + if (extraInfoLength > 0) { + for (uint32_t i = 0; i < extraInfoLength; ++i) { uint16_t character; inStream.Read(character); - extraInfo.name.push_back(character); + extraInfo.push_back(character); } uint16_t nullTerm; inStream.Read(nullTerm); @@ -5505,10 +5503,8 @@ void GameMessages::HandleModularBuildFinish(RakNet::BitStream& inStream, Entity* //inv->UnequipItem(inv->GetItemStackByLOT(6086, eInventoryType::ITEMS)); // take off the thinking cap //Game::entityManager->SerializeEntity(entity); - const auto moduleAssembly = new LDFData(u"assemblyPartLOTs", modules); - - std::vector config; - config.push_back(moduleAssembly); + LwoNameValue config; + config.Insert(u"assemblyPartLOTs", modules); LWOOBJID newID = ObjectIDManager::GetPersistentID(); @@ -5754,7 +5750,6 @@ void GameMessages::HandleUseNonEquipmentItem(RakNet::BitStream& inStream, Entity void GameMessages::HandleMatchRequest(RakNet::BitStream& inStream, Entity* entity) { LWOOBJID activator; - //std::map additionalPlayers; uint32_t playerChoicesLen; std::string playerChoices; int type; @@ -6309,7 +6304,7 @@ namespace GameMessages { bitStream.Write(id); std::string toWrite; - for (const auto* item : localizeParams) { + for (const auto& item : localizeParams | std::views::values) { toWrite += item->GetString() + "\n"; } if (!toWrite.empty()) toWrite.pop_back(); diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index b7558d4b..8937e028 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -13,6 +13,7 @@ #include "Brick.h" #include "MessageType/Game.h" #include "eGameMasterLevel.h" +#include "LDFFormat.h" class AMFBaseValue; class AMFArrayValue; @@ -711,7 +712,7 @@ namespace GameMessages { bool translate{}; int32_t time{}; std::u16string id{}; - std::vector localizeParams{}; + LwoNameValue localizeParams{}; std::u16string imageName{}; std::u16string text{}; void Serialize(RakNet::BitStream& bitStream) const override; @@ -734,7 +735,7 @@ namespace GameMessages { struct ConfigureRacingControl : public GameMsg { ConfigureRacingControl() : GameMsg(MessageType::Game::CONFIGURE_RACING_CONTROL) {} - std::vector> racingSettings{}; + LwoNameValue racingSettings{}; }; struct SetModelToBuild : public GameMsg { @@ -754,7 +755,7 @@ namespace GameMessages { struct ActivityNotify : public GameMsg { ActivityNotify() : GameMsg(MessageType::Game::ACTIVITY_NOTIFY) {} - std::vector> notification{}; + LwoNameValue notification{}; }; struct ShootingGalleryFire : public GameMsg { diff --git a/dGame/dInventory/EquippedItem.h b/dGame/dInventory/EquippedItem.h index 78da9e8d..ec10f95b 100644 --- a/dGame/dInventory/EquippedItem.h +++ b/dGame/dInventory/EquippedItem.h @@ -31,5 +31,5 @@ struct EquippedItem /** * The configuration of the item with any extra data */ - std::vector config = {}; + LwoNameValue config = {}; }; diff --git a/dGame/dInventory/Item.cpp b/dGame/dInventory/Item.cpp index 19b536a4..f18cd0b3 100644 --- a/dGame/dInventory/Item.cpp +++ b/dGame/dInventory/Item.cpp @@ -28,6 +28,7 @@ #include "CDObjectSkillsTable.h" #include "CDComponentsRegistryTable.h" #include "CDPackageComponentTable.h" +#include namespace { const std::map ExtraSettingAbbreviations = { @@ -46,7 +47,7 @@ namespace { }; } -Item::Item(const LWOOBJID id, const LOT lot, Inventory* inventory, const uint32_t slot, const uint32_t count, const bool bound, const std::vector& config, const LWOOBJID parent, LWOOBJID subKey, eLootSourceType lootSourceType) { +Item::Item(const LWOOBJID id, const LOT lot, Inventory* inventory, const uint32_t slot, const uint32_t count, const bool bound, const LwoNameValue& config, const LWOOBJID parent, LWOOBJID subKey, eLootSourceType lootSourceType) { if (!Inventory::IsValidItem(lot)) { return; } @@ -71,7 +72,7 @@ Item::Item( Inventory* inventory, const uint32_t slot, const uint32_t count, - const std::vector& config, + const LwoNameValue& config, const LWOOBJID parent, bool showFlyingLoot, bool isModMoveAndEquip, @@ -131,11 +132,11 @@ uint32_t Item::GetSlot() const { return slot; } -std::vector Item::GetConfig() const { +const LwoNameValue& Item::GetConfig() const { return config; } -std::vector& Item::GetConfig() { +LwoNameValue& Item::GetConfig() { return config; } @@ -379,7 +380,7 @@ void Item::UseNonEquip(Item* item) { } void Item::Disassemble(const eInventoryType inventoryType) { - for (auto* data : config) { + for (const auto& data : config.values | std::views::values) { if (data->GetKey() == u"assemblyPartLOTs") { auto modStr = data->GetValueAsString(); @@ -530,18 +531,12 @@ void Item::RemoveFromInventory() { Item::~Item() { delete preconditions; - - for (auto* value : config) { - delete value; - } - - config.clear(); } void Item::SaveConfigXml(tinyxml2::XMLElement& i) const { tinyxml2::XMLElement* x = nullptr; - for (const auto* config : this->config) { + for (const auto& config : config.values | std::views::values) { const auto& key = GeneralUtils::UTF16ToWTF8(config->GetKey()); const auto saveKey = ExtraSettingAbbreviations.find(key); if (saveKey == ExtraSettingAbbreviations.end()) { @@ -561,12 +556,11 @@ 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()); + for (const auto& [fullName, abbreviation] : ExtraSettingAbbreviations) { + const auto* data = x->Attribute(abbreviation.c_str()); if (!data) continue; - const auto value = pair.first + "=" + data; - config.push_back(LDFBaseData::DataFromString(value)); + config.ParseInsert(fullName + "=" + data); } } diff --git a/dGame/dInventory/Item.h b/dGame/dInventory/Item.h index 846a7aa7..baccf28f 100644 --- a/dGame/dInventory/Item.h +++ b/dGame/dInventory/Item.h @@ -40,7 +40,7 @@ public: uint32_t slot, uint32_t count, bool bound, - const std::vector& config, + const LwoNameValue& config, LWOOBJID parent, LWOOBJID subKey, eLootSourceType lootSourceType = eLootSourceType::NONE @@ -64,7 +64,7 @@ public: Inventory* inventory, uint32_t slot = 0, uint32_t count = 1, - const std::vector& config = {}, + const LwoNameValue& config = {}, LWOOBJID parent = LWOOBJID_EMPTY, bool showFlyingLoot = true, bool isModMoveAndEquip = false, @@ -118,13 +118,13 @@ public: * Returns current config info for this item, e.g. for rockets * @return current config info for this item */ - std::vector& GetConfig(); + LwoNameValue& GetConfig(); /** * Returns current config info for this item, e.g. for rockets * @return current config info for this item */ - std::vector GetConfig() const; + const LwoNameValue& GetConfig() const; /** * Returns the database info for this item @@ -269,7 +269,7 @@ private: /** * Config data for this item, e.g. for rocket parts and car parts */ - std::vector config; + LwoNameValue config; /** * The inventory this item belongs to diff --git a/dGame/dMission/MissionTask.cpp b/dGame/dMission/MissionTask.cpp index 872796a8..f23deb6e 100644 --- a/dGame/dMission/MissionTask.cpp +++ b/dGame/dMission/MissionTask.cpp @@ -218,7 +218,6 @@ void MissionTask::Progress(int32_t value, LWOOBJID associate, const std::string& uint32_t activityId; uint32_t lot; uint32_t collectionId; - std::vector settings; switch (type) { case eMissionTaskType::UNKNOWN: diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp index f5dbd29f..745798b8 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp @@ -802,7 +802,7 @@ namespace DEVGMCommands { info.spawner = nullptr; info.spawnerID = entity->GetObjectID(); info.spawnerNodeID = 0; - info.settings = { new LDFData(u"SpawnedFromSlashCommand", true) }; + info.settings.Insert(u"SpawnedFromSlashCommand", true); Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr); @@ -844,7 +844,7 @@ namespace DEVGMCommands { info.spawner = nullptr; info.spawnerID = entity->GetObjectID(); info.spawnerNodeID = 0; - info.settings = { new LDFData(u"SpawnedFromSlashCommand", true) }; + info.settings.Insert(u"SpawnedFromSlashCommand", true); auto playerPosition = entity->GetPosition(); while (numberToSpawn > 0) { @@ -1268,10 +1268,10 @@ namespace DEVGMCommands { auto* inventoryComponent = entity->GetComponent(); if (!inventoryComponent) return; - std::vector data{}; - data.push_back(new LDFData(u"reforgedLOT", reforgedItem.value())); + LwoNameValue config; + config.Insert(u"reforgedLOT", reforgedItem.value()); - inventoryComponent->AddItem(baseItem.value(), 1, eLootSourceType::MODERATION, eInventoryType::INVALID, data); + inventoryComponent->AddItem(baseItem.value(), 1, eLootSourceType::MODERATION, eInventoryType::INVALID, config); } void Crash(Entity* entity, const SystemAddress& sysAddr, const std::string args) { diff --git a/dGame/dUtilities/VanityUtilities.cpp b/dGame/dUtilities/VanityUtilities.cpp index 48d3c0da..950f26e2 100644 --- a/dGame/dUtilities/VanityUtilities.cpp +++ b/dGame/dUtilities/VanityUtilities.cpp @@ -45,10 +45,8 @@ void VanityUtilities::SpawnVanity() { info.pos = { 259.5f, 246.4f, -705.2f }; info.rot = { 0.0f, 0.0f, 1.0f, 0.0f }; info.spawnerID = Game::entityManager->GetZoneControlEntity()->GetObjectID(); - info.settings = { - new LDFData(u"hasCustomText", true), - new LDFData(u"customText", ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/TESTAMENT.md").string())) - }; + info.settings.Insert(u"hasCustomText", true); + info.settings.Insert(u"customText", ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/TESTAMENT.md").string())); auto* entity = Game::entityManager->CreateEntity(info); Game::entityManager->ConstructEntity(entity); @@ -231,7 +229,7 @@ void ParseXml(const std::string& file) { auto* configElement = object->FirstChildElement("config"); std::vector keys = {}; - std::vector config = {}; + LwoNameValue config; if (configElement) { for (auto* key = configElement->FirstChildElement("key"); key != nullptr; key = key->NextSiblingElement("key")) { @@ -239,16 +237,16 @@ void ParseXml(const std::string& file) { auto* data = key->GetText(); if (!data) continue; - LDFBaseData* configData = LDFBaseData::DataFromString(data); + const auto& configData = config.ParseInsert(data); if (configData->GetKey() == u"useLocationsAsRandomSpawnPoint" && configData->GetValueType() == eLDFType::LDF_TYPE_BOOLEAN) { - useLocationsAsRandomSpawnPoint = static_cast(configData); + useLocationsAsRandomSpawnPoint = static_cast*>(configData.get())->GetValue(); + config.Erase(u"useLocationsAsRandomSpawnPoint"); continue; } keys.push_back(configData->GetKey()); - config.push_back(configData); } } - if (!keys.empty()) config.push_back(new LDFData>(u"syncLDF", keys)); + if (!keys.empty()) config.Insert>(u"syncLDF", keys); VanityObject objectData{ .m_Name = name, diff --git a/dGame/dUtilities/VanityUtilities.h b/dGame/dUtilities/VanityUtilities.h index 2afd4a6b..4da74479 100644 --- a/dGame/dUtilities/VanityUtilities.h +++ b/dGame/dUtilities/VanityUtilities.h @@ -19,7 +19,7 @@ struct VanityObject { std::vector m_Equipment; std::vector m_Phrases; std::map> m_Locations; - std::vector m_Config; + LwoNameValue m_Config; }; diff --git a/dNet/WorldPackets.cpp b/dNet/WorldPackets.cpp index 91117056..e963b10c 100644 --- a/dNet/WorldPackets.cpp +++ b/dNet/WorldPackets.cpp @@ -11,6 +11,7 @@ #include "BitStreamUtils.h" #include +#include void HTTPMonitorInfo::Serialize(RakNet::BitStream& bitStream) const { bitStream.Write(port); @@ -88,18 +89,18 @@ void WorldPackets::SendCreateCharacter(const SystemAddress& sysAddr, int64_t rep RakNet::BitStream data; - std::vector> ldfData; - ldfData.push_back(std::move(make_unique>(u"objid", player))); - ldfData.push_back(std::move(make_unique>(u"template", 1))); - ldfData.push_back(std::move(make_unique>(u"xmlData", xmlData))); - ldfData.push_back(std::move(make_unique>(u"name", username))); - ldfData.push_back(std::move(make_unique>(u"gmlevel", static_cast(gm)))); - ldfData.push_back(std::move(make_unique>(u"chatmode", static_cast(gm)))); - ldfData.push_back(std::move(make_unique>(u"reputation", reputation))); - ldfData.push_back(std::move(make_unique>(u"propertycloneid", cloneID))); + LwoNameValue ldfData; + ldfData.Insert(u"objid", player); + ldfData.Insert(u"template", 1); + ldfData.Insert(u"xmlData", xmlData); + ldfData.Insert(u"name", username); + ldfData.Insert(u"gmlevel", static_cast(gm)); + ldfData.Insert(u"chatmode", static_cast(gm)); + ldfData.Insert(u"reputation", reputation); + ldfData.Insert(u"propertycloneid", cloneID); - data.Write(ldfData.size()); - for (const auto& toSerialize : ldfData) toSerialize->WriteToPacket(data); + data.Write(ldfData.values.size()); + for (const auto& toSerialize : ldfData | std::views::values) toSerialize->WriteToPacket(data); //Compress the data before sending: const uint32_t reservedSize = ZCompression::GetMaxCompressedLength(data.GetNumberOfBytesUsed()); diff --git a/dScripts/02_server/Enemy/AM/AmDarklingDragon.cpp b/dScripts/02_server/Enemy/AM/AmDarklingDragon.cpp index 61d25436..5fbbda20 100644 --- a/dScripts/02_server/Enemy/AM/AmDarklingDragon.cpp +++ b/dScripts/02_server/Enemy/AM/AmDarklingDragon.cpp @@ -97,17 +97,14 @@ void AmDarklingDragon::OnHitOrHealResult(Entity* self, Entity* attacker, int32_t info.pos = objectPosition; info.rot = rotation; info.spawnerID = self->GetObjectID(); - info.settings = { - new LDFData(u"rebuild_activators", + info.settings.Insert(u"rebuild_activators", std::to_string(objectPosition.x + forward.x) + "\x1f" + std::to_string(objectPosition.y) + "\x1f" + - std::to_string(objectPosition.z + forward.z) - ), - new LDFData(u"respawn", 100000), - new LDFData(u"rebuild_reset_time", 15), - new LDFData(u"no_timed_spawn", true), - new LDFData(u"Dragon", self->GetObjectID()) - }; + std::to_string(objectPosition.z + forward.z)); + info.settings.Insert(u"respawn", 100000); + info.settings.Insert(u"rebuild_reset_time", 15); + info.settings.Insert(u"no_timed_spawn", true); + info.settings.Insert(u"Dragon", self->GetObjectID()); auto* golemObject = Game::entityManager->CreateEntity(info); diff --git a/dScripts/02_server/Enemy/FV/FvMaelstromDragon.cpp b/dScripts/02_server/Enemy/FV/FvMaelstromDragon.cpp index b4b038b8..2ddc6309 100644 --- a/dScripts/02_server/Enemy/FV/FvMaelstromDragon.cpp +++ b/dScripts/02_server/Enemy/FV/FvMaelstromDragon.cpp @@ -113,17 +113,14 @@ void FvMaelstromDragon::OnHitOrHealResult(Entity* self, Entity* attacker, int32_ info.pos = objectPosition; info.rot = rotation; info.spawnerID = self->GetObjectID(); - info.settings = { - new LDFData(u"rebuild_activators", + info.settings.Insert(u"rebuild_activators", std::to_string(objectPosition.x + forward.x) + "\x1f" + std::to_string(objectPosition.y) + "\x1f" + - std::to_string(objectPosition.z + forward.z) - ), - new LDFData(u"respawn", 100000), - new LDFData(u"rebuild_reset_time", 15), - new LDFData(u"no_timed_spawn", true), - new LDFData(u"Dragon", self->GetObjectID()) - }; + std::to_string(objectPosition.z + forward.z)); + info.settings.Insert(u"respawn", 100000); + info.settings.Insert(u"rebuild_reset_time", 15); + info.settings.Insert(u"no_timed_spawn", true); + info.settings.Insert(u"Dragon", self->GetObjectID()); auto* golemObject = Game::entityManager->CreateEntity(info); diff --git a/dScripts/02_server/Enemy/General/BaseEnemyApe.cpp b/dScripts/02_server/Enemy/General/BaseEnemyApe.cpp index 9c71c284..26948c57 100644 --- a/dScripts/02_server/Enemy/General/BaseEnemyApe.cpp +++ b/dScripts/02_server/Enemy/General/BaseEnemyApe.cpp @@ -82,15 +82,12 @@ void BaseEnemyApe::OnTimerDone(Entity* self, std::string timerName) { entityInfo.spawnerID = self->GetObjectID(); entityInfo.lot = self->GetVar(u"QuickbuildAnchorLOT") != 0 ? self->GetVar(u"QuickbuildAnchorLOT") : 7549; - entityInfo.settings = { - new LDFData(u"rebuild_activators", + entityInfo.settings.Insert(u"rebuild_activators", std::to_string(objectPosition.GetX()) + "\x1f" + std::to_string(objectPosition.GetY()) + "\x1f" + - std::to_string(objectPosition.GetZ()) - ), - new LDFData(u"no_timed_spawn", true), - new LDFData(u"ape", self->GetObjectID()) - }; + std::to_string(objectPosition.GetZ())); + entityInfo.settings.Insert(u"no_timed_spawn", true); + entityInfo.settings.Insert(u"ape", self->GetObjectID()); auto* anchor = Game::entityManager->CreateEntity(entityInfo, nullptr, self); Game::entityManager->ConstructEntity(anchor); diff --git a/dScripts/02_server/Enemy/General/BaseEnemyMech.cpp b/dScripts/02_server/Enemy/General/BaseEnemyMech.cpp index 8e70d4c3..45f1e9ad 100644 --- a/dScripts/02_server/Enemy/General/BaseEnemyMech.cpp +++ b/dScripts/02_server/Enemy/General/BaseEnemyMech.cpp @@ -23,7 +23,7 @@ void BaseEnemyMech::OnDie(Entity* self, Entity* killer) { NiPoint3 newLoc = { controlPhys->GetPosition().x, dpWorld::GetNavMesh()->GetHeightAtPoint(controlPhys->GetPosition()), controlPhys->GetPosition().z }; EntityInfo info = EntityInfo(); - std::vector cfg; + LwoNameValue cfg; std::u16string activatorPosStr; activatorPosStr += (GeneralUtils::to_u16string(controlPhys->GetPosition().x)); activatorPosStr.push_back(0x1f); @@ -31,8 +31,7 @@ void BaseEnemyMech::OnDie(Entity* self, Entity* killer) { activatorPosStr.push_back(0x1f); activatorPosStr += (GeneralUtils::to_u16string(controlPhys->GetPosition().z)); - LDFBaseData* activatorPos = new LDFData(u"rebuild_activators", activatorPosStr); - cfg.push_back(activatorPos); + cfg.Insert(u"rebuild_activators", activatorPosStr); info.lot = qbTurretLOT; info.pos = newLoc; info.rot = controlPhys->GetRotation(); diff --git a/dScripts/02_server/Map/AG_Spider_Queen/ZoneAgSpiderQueen.cpp b/dScripts/02_server/Map/AG_Spider_Queen/ZoneAgSpiderQueen.cpp index 2091041b..2b367ed2 100644 --- a/dScripts/02_server/Map/AG_Spider_Queen/ZoneAgSpiderQueen.cpp +++ b/dScripts/02_server/Map/AG_Spider_Queen/ZoneAgSpiderQueen.cpp @@ -71,9 +71,7 @@ void ZoneAgSpiderQueen::OnTimerDone(Entity* self, std::string timerName) { info.pos = spawnTarget->GetPosition(); info.rot = spawnTarget->GetRotation(); info.lot = chestObject; - info.settings = { - new LDFData(u"parent_tag", self->GetObjectID()) - }; + info.settings.Insert(u"parent_tag", self->GetObjectID()); auto* chest = Game::entityManager->CreateEntity(info); Game::entityManager->ConstructEntity(chest); diff --git a/dScripts/02_server/Map/AM/AmSkullkinTower.cpp b/dScripts/02_server/Map/AM/AmSkullkinTower.cpp index 17ae2ea9..19c39f8b 100644 --- a/dScripts/02_server/Map/AM/AmSkullkinTower.cpp +++ b/dScripts/02_server/Map/AM/AmSkullkinTower.cpp @@ -37,12 +37,10 @@ void AmSkullkinTower::SpawnLegs(Entity* self, const std::string& loc) { return; } - std::vector config = { new LDFData(u"Leg", loc) }; - EntityInfo info{}; info.lot = legLOT; info.spawnerID = self->GetObjectID(); - info.settings = config; + info.settings.Insert("Leg", loc); info.rot = newRot; if (loc == "Right") { diff --git a/dScripts/02_server/Map/General/PetDigServer.cpp b/dScripts/02_server/Map/General/PetDigServer.cpp index 77a50e5a..e20194a4 100644 --- a/dScripts/02_server/Map/General/PetDigServer.cpp +++ b/dScripts/02_server/Map/General/PetDigServer.cpp @@ -204,12 +204,10 @@ void PetDigServer::SpawnPet(Entity* self, const Entity* owner, const DigInfo dig info.pos = self->GetPosition(); info.rot = self->GetRotation(); info.spawnerID = self->GetSpawnerID(); - info.settings = { - new LDFData(u"tamer", owner->GetObjectID()), - new LDFData(u"group", "pet" + std::to_string(owner->GetObjectID())), - new LDFData(u"spawnAnim", "spawn-pet"), - new LDFData(u"spawnTimer", 1.0) - }; + info.settings.Insert(u"tamer", owner->GetObjectID()); + info.settings.Insert(u"group", "pet" + std::to_string(owner->GetObjectID())); + info.settings.Insert(u"spawnAnim", "spawn-pet"); + info.settings.Insert(u"spawnTimer", 1.0); auto* spawnedPet = Game::entityManager->CreateEntity(info); Game::entityManager->ConstructEntity(spawnedPet); diff --git a/dScripts/02_server/Map/General/QbSpawner.cpp b/dScripts/02_server/Map/General/QbSpawner.cpp index 0562fdd8..9081ec57 100644 --- a/dScripts/02_server/Map/General/QbSpawner.cpp +++ b/dScripts/02_server/Map/General/QbSpawner.cpp @@ -66,14 +66,12 @@ void QbSpawner::OnTimerDone(Entity* self, std::string timerName) { info.rot = newRot; info.spawnerID = self->GetObjectID(); info.spawnerNodeID = 0; - info.settings = { - new LDFData(u"no_timed_spawn", true), - new LDFData(u"aggroRadius", 70), - new LDFData(u"softtetherRadius", 80), - new LDFData(u"tetherRadius", 90), - new LDFData(u"wanderRadius", 5), - new LDFData(u"mobTableLoc", i) - }; + info.settings.Insert(u"no_timed_spawn", true); + info.settings.Insert(u"aggroRadius", 70); + info.settings.Insert(u"softtetherRadius", 80); + info.settings.Insert(u"tetherRadius", 90); + info.settings.Insert(u"wanderRadius", 5); + info.settings.Insert(u"mobTableLoc", i); auto* child = Game::entityManager->CreateEntity(info, nullptr, self); Game::entityManager->ConstructEntity(child); diff --git a/dScripts/02_server/Map/NS/NsConcertChoiceBuildManager.cpp b/dScripts/02_server/Map/NS/NsConcertChoiceBuildManager.cpp index 5306a1dd..aab66449 100644 --- a/dScripts/02_server/Map/NS/NsConcertChoiceBuildManager.cpp +++ b/dScripts/02_server/Map/NS/NsConcertChoiceBuildManager.cpp @@ -34,12 +34,10 @@ void NsConcertChoiceBuildManager::SpawnCrate(Entity* self) { info.pos = self->GetPosition(); info.rot = self->GetRotation(); info.spawnerID = self->GetObjectID(); - info.settings = { - new LDFData(u"startsQBActivator", true), - new LDFData(u"grpNameQBShowBricks", crate.group + std::to_string(groupNumber)), - new LDFData(u"groupID", GeneralUtils::ASCIIToUTF16("Crate_" + group)), - new LDFData(u"crateTime", crate.time), - }; + info.settings.Insert(u"startsQBActivator", true); + info.settings.Insert(u"grpNameQBShowBricks", crate.group + std::to_string(groupNumber)); + info.settings.Insert(u"groupID", GeneralUtils::ASCIIToUTF16("Crate_" + group)); + info.settings.Insert(u"crateTime", crate.time); auto* spawnedCrate = Game::entityManager->CreateEntity(info); Game::entityManager->ConstructEntity(spawnedCrate); diff --git a/dScripts/02_server/Map/NT/NtCombatChallengeServer.cpp b/dScripts/02_server/Map/NT/NtCombatChallengeServer.cpp index 2b2f16f9..c036b023 100644 --- a/dScripts/02_server/Map/NT/NtCombatChallengeServer.cpp +++ b/dScripts/02_server/Map/NT/NtCombatChallengeServer.cpp @@ -89,7 +89,7 @@ void NtCombatChallengeServer::SpawnTargetDummy(Entity* self) { info.spawnerID = self->GetObjectID(); info.pos = self->GetPosition(); info.rot = self->GetRotation(); - info.settings = { new LDFData(u"custom_script_server", "scripts\\02_server\\Map\\NT\\L_NT_COMBAT_CHALLENGE_DUMMY.lua") }; + info.settings.Insert(u"custom_script_server", "scripts\\02_server\\Map\\NT\\L_NT_COMBAT_CHALLENGE_DUMMY.lua"); auto* dummy = Game::entityManager->CreateEntity(info, nullptr, self); diff --git a/dScripts/02_server/Map/Property/AG_Small/ZoneAgProperty.cpp b/dScripts/02_server/Map/Property/AG_Small/ZoneAgProperty.cpp index 39fae3aa..18054365 100644 --- a/dScripts/02_server/Map/Property/AG_Small/ZoneAgProperty.cpp +++ b/dScripts/02_server/Map/Property/AG_Small/ZoneAgProperty.cpp @@ -95,10 +95,8 @@ void ZoneAgProperty::LoadInstance(Entity* self) { for (auto* spawner : Game::zoneManager->GetSpawnersByName(self->GetVar(InstancerSpawner))) { for (auto* spawnerNode : spawner->m_Info.nodes) { - spawnerNode->config.push_back( - new LDFData(u"custom_script_server", - R"(scripts\ai\GENERAL\L_INSTANCE_EXIT_TRANSFER_PLAYER_TO_LAST_NON_INSTANCE.lua)")); - spawnerNode->config.push_back(new LDFData(u"transferText", u"SPIDER_QUEEN_EXIT_QUESTION")); + spawnerNode->config.Insert(u"custom_script_server", R"(scripts\ai\GENERAL\L_INSTANCE_EXIT_TRANSFER_PLAYER_TO_LAST_NON_INSTANCE.lua)"); + spawnerNode->config.Insert(u"transferText", u"SPIDER_QUEEN_EXIT_QUESTION"); } } diff --git a/dScripts/02_server/Map/njhub/boss_instance/NjMonastryBossInstance.cpp b/dScripts/02_server/Map/njhub/boss_instance/NjMonastryBossInstance.cpp index d7be9d32..be47b53b 100644 --- a/dScripts/02_server/Map/njhub/boss_instance/NjMonastryBossInstance.cpp +++ b/dScripts/02_server/Map/njhub/boss_instance/NjMonastryBossInstance.cpp @@ -515,9 +515,7 @@ void NjMonastryBossInstance::FightOver(Entity* self) { info.pos = treasureChest->GetPosition(); info.rot = treasureChest->GetRotation(); info.spawnerID = self->GetObjectID(); - info.settings = { - new LDFData(u"parent_tag", self->GetObjectID()) - }; + info.settings.Insert(u"parent_tag", self->GetObjectID()); // Finally spawn a treasure chest at the correct spawn point auto* chestObject = Game::entityManager->CreateEntity(info); diff --git a/dScripts/02_server/Objects/StinkyFishTarget.cpp b/dScripts/02_server/Objects/StinkyFishTarget.cpp index 9840235d..ae595b89 100644 --- a/dScripts/02_server/Objects/StinkyFishTarget.cpp +++ b/dScripts/02_server/Objects/StinkyFishTarget.cpp @@ -21,9 +21,7 @@ void StinkyFishTarget::OnSkillEventFired(Entity* self, Entity* caster, const std entityInfo.pos = self->GetPosition(); entityInfo.rot = self->GetRotation(); entityInfo.spawnerID = self->GetObjectID(); - entityInfo.settings = { - new LDFData(u"no_timed_spawn", true) - }; + entityInfo.settings.Insert(u"no_timed_spawn", true); auto* fish = Game::entityManager->CreateEntity(entityInfo); Game::entityManager->ConstructEntity(fish); diff --git a/dScripts/SpawnPetBaseServer.cpp b/dScripts/SpawnPetBaseServer.cpp index d2374162..04a24ae6 100644 --- a/dScripts/SpawnPetBaseServer.cpp +++ b/dScripts/SpawnPetBaseServer.cpp @@ -26,12 +26,10 @@ void SpawnPetBaseServer::OnUse(Entity* self, Entity* user) { info.rot = spawner->GetRotation(); info.lot = self->GetVar(u"petLOT"); info.spawnerID = self->GetObjectID(); - info.settings = { - new LDFData(u"tamer", user->GetObjectID()), - new LDFData(u"groupID", petType + (GeneralUtils::to_u16string(user->GetObjectID())) + u";" + petType + u"s"), - new LDFData(u"spawnAnim", self->GetVar(u"spawnAnim")), - new LDFData(u"spawnTimer", 1.0f) - }; + info.settings.Insert(u"tamer", user->GetObjectID()); + info.settings.Insert(u"groupID", petType + (GeneralUtils::to_u16string(user->GetObjectID())) + u";" + petType + u"s"); + info.settings.Insert(u"spawnAnim", self->GetVar(u"spawnAnim")); + info.settings.Insert(u"spawnTimer", 1.0f); auto* pet = Game::entityManager->CreateEntity(info); Game::entityManager->ConstructEntity(pet); diff --git a/dScripts/ai/FV/FvPandaSpawnerServer.cpp b/dScripts/ai/FV/FvPandaSpawnerServer.cpp index bc9f1c8a..e02bcb55 100644 --- a/dScripts/ai/FV/FvPandaSpawnerServer.cpp +++ b/dScripts/ai/FV/FvPandaSpawnerServer.cpp @@ -38,10 +38,8 @@ void FvPandaSpawnerServer::OnCollisionPhantom(Entity* self, Entity* target) { info.spawnerID = target->GetObjectID(); info.pos = self->GetPosition(); info.lot = 5643; - info.settings = { - new LDFData(u"tamer", target->GetObjectID()), - new LDFData(u"groupID", u"panda" + (GeneralUtils::to_u16string(target->GetObjectID())) + u";pandas") - }; + info.settings.Insert(u"tamer", target->GetObjectID()); + info.settings.Insert(u"groupID", u"panda" + (GeneralUtils::to_u16string(target->GetObjectID())) + u";pandas"); auto* panda = Game::entityManager->CreateEntity(info); Game::entityManager->ConstructEntity(panda); diff --git a/dScripts/ai/GF/GfBanana.cpp b/dScripts/ai/GF/GfBanana.cpp index 0b436396..51c9f230 100644 --- a/dScripts/ai/GF/GfBanana.cpp +++ b/dScripts/ai/GF/GfBanana.cpp @@ -66,7 +66,7 @@ void GfBanana::OnHit(Entity* self, Entity* attacker) { info.pos.z -= QuatUtils::Right(rotation).z * 5; info.rot = rotation; info.spawnerID = self->GetObjectID(); - info.settings = { new LDFData(u"motionType", 5) }; + info.settings.Insert(u"motionType", 5); auto* const newEn = Game::entityManager->CreateEntity(info, nullptr, self); Game::entityManager->ConstructEntity(newEn); } diff --git a/dScripts/ai/GF/PetDigBuild.cpp b/dScripts/ai/GF/PetDigBuild.cpp index 9caf2f8c..b720907e 100644 --- a/dScripts/ai/GF/PetDigBuild.cpp +++ b/dScripts/ai/GF/PetDigBuild.cpp @@ -13,14 +13,12 @@ void PetDigBuild::OnQuickBuildComplete(Entity* self, Entity* target) { info.pos = pos; info.rot = self->GetRotation(); info.spawnerID = self->GetSpawnerID(); - info.settings = { - new LDFData(u"builder", target->GetObjectID()), - new LDFData(u"X", self->GetObjectID()) - }; + info.settings.Insert(u"builder", target->GetObjectID()); + info.settings.Insert(u"X", self->GetObjectID()); if (!flagNumber.empty()) { info.lot = 7410; // Normal GF treasure - info.settings.push_back(new LDFData(u"groupID", u"Flag" + flagNumber)); + info.settings.Insert(u"groupID", u"Flag" + flagNumber); } else { auto* missionComponent = target->GetComponent(); if (missionComponent != nullptr && missionComponent->GetMissionState(746) == eMissionState::ACTIVE) { diff --git a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp index 3dd2773f..473cd7c5 100644 --- a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp +++ b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp @@ -261,15 +261,13 @@ void SGCannon::DoSpawnTimerFunc(Entity* self, const std::string& name) { info.spawnerID = self->GetObjectID(); info.pos = path->pathWaypoints[0].position; - info.settings = { - new LDFData(u"SpawnData", toSpawn), - new LDFData(u"custom_script_server", "scripts/ai/ACT/SG_TARGET.lua"), // this script is never loaded - new LDFData(u"custom_script_client", "scripts/client/ai/SG_TARGET_CLIENT.lua"), - new LDFData(u"attached_path", path->pathName), - new LDFData(u"attached_path_start", 0), - new LDFData(u"groupID", u"SGEnemy"), - new LDFData(u"wave", self->GetVar(ThisWaveVariable)), - }; + info.settings.Insert(u"SpawnData", toSpawn); + info.settings.Insert(u"custom_script_server", "scripts/ai/ACT/SG_TARGET.lua"); // this script is never loaded; + info.settings.Insert(u"custom_script_client", "scripts/client/ai/SG_TARGET_CLIENT.lua"); + info.settings.Insert(u"attached_path", path->pathName); + info.settings.Insert(u"attached_path_start", 0); + info.settings.Insert(u"groupID", u"SGEnemy"); + info.settings.Insert(u"wave", self->GetVar(ThisWaveVariable)); auto* enemy = Game::entityManager->CreateEntity(info, nullptr, self); @@ -621,10 +619,11 @@ void SGCannon::OnActivityNotify(Entity* self, GameMessages::ActivityNotify& noti if (!self->GetVar(GameStartedVariable)) return; const auto& params = notify.notification; - if (params.empty()) return; + const auto itr = params.values.find(u"shot_done"); + if (itr == params.values.end()) return; - const auto& param = params[0]; - if (param->GetValueType() != LDF_TYPE_S32 || param->GetKey() != u"shot_done") return; + const auto& param = itr->second; + if (param->GetValueType() != LDF_TYPE_S32) return; const auto superChargeShotDone = static_cast*>(param.get())->GetValue() == GetConstants().cannonSuperChargeSkill; diff --git a/dScripts/ai/NS/WH/RockHydrantSmashable.cpp b/dScripts/ai/NS/WH/RockHydrantSmashable.cpp index d388baac..6549a85b 100644 --- a/dScripts/ai/NS/WH/RockHydrantSmashable.cpp +++ b/dScripts/ai/NS/WH/RockHydrantSmashable.cpp @@ -6,13 +6,11 @@ void RockHydrantSmashable::OnDie(Entity* self, Entity* killer) { const auto hydrantName = self->GetVar(u"hydrant"); - LDFBaseData* data = new LDFData(u"hydrant", GeneralUtils::UTF16ToWTF8(hydrantName)); - EntityInfo info{}; info.lot = ROCK_HYDRANT_BROKEN; info.pos = self->GetPosition(); info.rot = self->GetRotation(); - info.settings = { data }; + info.settings.Insert(u"hydrant", GeneralUtils::UTF16ToWTF8(hydrantName)); info.spawnerID = self->GetSpawnerID(); auto* hydrant = Game::entityManager->CreateEntity(info); diff --git a/dScripts/ai/PETS/HydrantSmashable.cpp b/dScripts/ai/PETS/HydrantSmashable.cpp index fc83a5d3..9e770615 100644 --- a/dScripts/ai/PETS/HydrantSmashable.cpp +++ b/dScripts/ai/PETS/HydrantSmashable.cpp @@ -6,13 +6,11 @@ void HydrantSmashable::OnDie(Entity* self, Entity* killer) { const auto hydrantName = self->GetVar(u"hydrant"); - LDFBaseData* data = new LDFData(u"hydrant", GeneralUtils::UTF16ToWTF8(hydrantName)); - EntityInfo info{}; info.lot = HYDRANT_BROKEN; info.pos = self->GetPosition(); info.rot = self->GetRotation(); - info.settings = { data }; + info.settings.Insert(u"hydrant", GeneralUtils::UTF16ToWTF8(hydrantName)); info.spawnerID = self->GetSpawnerID(); auto* hydrant = Game::entityManager->CreateEntity(info); diff --git a/dScripts/ai/RACING/TRACK_FV/FvRaceServer.cpp b/dScripts/ai/RACING/TRACK_FV/FvRaceServer.cpp index 5dd43f48..9c53a990 100644 --- a/dScripts/ai/RACING/TRACK_FV/FvRaceServer.cpp +++ b/dScripts/ai/RACING/TRACK_FV/FvRaceServer.cpp @@ -10,46 +10,42 @@ void FvRaceServer::OnStartup(Entity* self) { GameMessages::ConfigureRacingControl config; auto& raceSet = config.racingSettings; - raceSet.push_back(make_unique>(u"GameType", u"Racing")); - raceSet.push_back(make_unique>(u"GameState", u"Starting")); - raceSet.push_back(make_unique>(u"Number_Of_PlayersPerTeam", 6)); - raceSet.push_back(make_unique>(u"Minimum_Players_to_Start", 2)); - raceSet.push_back(make_unique>(u"Minimum_Players_for_Group_Achievements", 2)); + raceSet.Insert(u"GameType", u"Racing"); + raceSet.Insert(u"GameState", u"Starting"); + raceSet.Insert(u"Number_Of_PlayersPerTeam", 6); + raceSet.Insert(u"Minimum_Players_to_Start", 2); + raceSet.Insert(u"Minimum_Players_for_Group_Achievements", 2); - raceSet.push_back(make_unique>(u"Car_Object", 7703)); - raceSet.push_back(make_unique>(u"Race_PathName", u"MainPath")); - raceSet.push_back(make_unique>(u"Current_Lap", 1)); - raceSet.push_back(make_unique>(u"Number_of_Laps", 3)); - raceSet.push_back(make_unique>(u"activityID", 54)); + raceSet.Insert(u"Car_Object", 7703); + raceSet.Insert(u"Race_PathName", u"MainPath"); + raceSet.Insert(u"Current_Lap", 1); + raceSet.Insert(u"Number_of_Laps", 3); + raceSet.Insert(u"activityID", 54); - raceSet.push_back(make_unique>(u"Place_1", 100)); - raceSet.push_back(make_unique>(u"Place_2", 90)); - raceSet.push_back(make_unique>(u"Place_3", 80)); - raceSet.push_back(make_unique>(u"Place_4", 70)); - raceSet.push_back(make_unique>(u"Place_5", 60)); - raceSet.push_back(make_unique>(u"Place_6", 50)); + raceSet.Insert(u"Place_1", 100); + raceSet.Insert(u"Place_2", 90); + raceSet.Insert(u"Place_3", 80); + raceSet.Insert(u"Place_4", 70); + raceSet.Insert(u"Place_5", 60); + raceSet.Insert(u"Place_6", 50); - raceSet.push_back(make_unique>(u"Num_of_Players_1", 15)); - raceSet.push_back(make_unique>(u"Num_of_Players_2", 25)); - raceSet.push_back(make_unique>(u"Num_of_Players_3", 50)); - raceSet.push_back(make_unique>(u"Num_of_Players_4", 85)); - raceSet.push_back(make_unique>(u"Num_of_Players_5", 90)); - raceSet.push_back(make_unique>(u"Num_of_Players_6", 100)); + raceSet.Insert(u"Num_of_Players_1", 15); + raceSet.Insert(u"Num_of_Players_2", 25); + raceSet.Insert(u"Num_of_Players_3", 50); + raceSet.Insert(u"Num_of_Players_4", 85); + raceSet.Insert(u"Num_of_Players_5", 90); + raceSet.Insert(u"Num_of_Players_6", 100); - raceSet.push_back(make_unique>(u"Number_of_Spawn_Groups", 1)); - raceSet.push_back(make_unique>(u"Red_Spawners", 4847)); - raceSet.push_back(make_unique>(u"Blue_Spawners", 4848)); - raceSet.push_back(make_unique>(u"Blue_Flag", 4850)); - raceSet.push_back(make_unique>(u"Red_Flag", 4851)); - raceSet.push_back(make_unique>(u"Red_Point", 4846)); - raceSet.push_back(make_unique>(u"Blue_Point", 4845)); - raceSet.push_back(make_unique>(u"Red_Mark", 4844)); - raceSet.push_back(make_unique>(u"Blue_Mark", 4843)); + raceSet.Insert(u"Number_of_Spawn_Groups", 1); + raceSet.Insert(u"Red_Spawners", 4847); + raceSet.Insert(u"Blue_Spawners", 4848); + raceSet.Insert(u"Blue_Flag", 4850); + raceSet.Insert(u"Red_Flag", 4851); + raceSet.Insert(u"Red_Point", 4846); + raceSet.Insert(u"Blue_Point", 4845); + raceSet.Insert(u"Red_Mark", 4844); + raceSet.Insert(u"Blue_Mark", 4843); - const std::vector racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL); - for (auto* const racingController : racingControllers) { - auto* racingComponent = racingController->GetComponent(); - if (racingComponent) racingComponent->MsgConfigureRacingControl(config); - } + config.Send(self->GetObjectID()); } diff --git a/dScripts/ai/RACING/TRACK_GF/GfRaceServer.cpp b/dScripts/ai/RACING/TRACK_GF/GfRaceServer.cpp index d465d57f..c7a4cf94 100644 --- a/dScripts/ai/RACING/TRACK_GF/GfRaceServer.cpp +++ b/dScripts/ai/RACING/TRACK_GF/GfRaceServer.cpp @@ -10,46 +10,42 @@ void GfRaceServer::OnStartup(Entity* self) { GameMessages::ConfigureRacingControl config; auto& raceSet = config.racingSettings; - raceSet.push_back(make_unique>(u"GameType", u"Racing")); - raceSet.push_back(make_unique>(u"GameState", u"Starting")); - raceSet.push_back(make_unique>(u"Number_Of_PlayersPerTeam", 6)); - raceSet.push_back(make_unique>(u"Minimum_Players_to_Start", 2)); - raceSet.push_back(make_unique>(u"Minimum_Players_for_Group_Achievements", 2)); + raceSet.Insert(u"GameType", u"Racing"); + raceSet.Insert(u"GameState", u"Starting"); + raceSet.Insert(u"Number_Of_PlayersPerTeam", 6); + raceSet.Insert(u"Minimum_Players_to_Start", 2); + raceSet.Insert(u"Minimum_Players_for_Group_Achievements", 2); - raceSet.push_back(make_unique>(u"Car_Object", 7703)); - raceSet.push_back(make_unique>(u"Race_PathName", u"MainPath")); - raceSet.push_back(make_unique>(u"Current_Lap", 1)); - raceSet.push_back(make_unique>(u"Number_of_Laps", 3)); - raceSet.push_back(make_unique>(u"activityID", 39)); + raceSet.Insert(u"Car_Object", 7703); + raceSet.Insert(u"Race_PathName", u"MainPath"); + raceSet.Insert(u"Current_Lap", 1); + raceSet.Insert(u"Number_of_Laps", 3); + raceSet.Insert(u"activityID", 39); - raceSet.push_back(make_unique>(u"Place_1", 100)); - raceSet.push_back(make_unique>(u"Place_2", 90)); - raceSet.push_back(make_unique>(u"Place_3", 80)); - raceSet.push_back(make_unique>(u"Place_4", 70)); - raceSet.push_back(make_unique>(u"Place_5", 60)); - raceSet.push_back(make_unique>(u"Place_6", 50)); + raceSet.Insert(u"Place_1", 100); + raceSet.Insert(u"Place_2", 90); + raceSet.Insert(u"Place_3", 80); + raceSet.Insert(u"Place_4", 70); + raceSet.Insert(u"Place_5", 60); + raceSet.Insert(u"Place_6", 50); - raceSet.push_back(make_unique>(u"Num_of_Players_1", 15)); - raceSet.push_back(make_unique>(u"Num_of_Players_2", 25)); - raceSet.push_back(make_unique>(u"Num_of_Players_3", 50)); - raceSet.push_back(make_unique>(u"Num_of_Players_4", 85)); - raceSet.push_back(make_unique>(u"Num_of_Players_5", 90)); - raceSet.push_back(make_unique>(u"Num_of_Players_6", 100)); + raceSet.Insert(u"Num_of_Players_1", 15); + raceSet.Insert(u"Num_of_Players_2", 25); + raceSet.Insert(u"Num_of_Players_3", 50); + raceSet.Insert(u"Num_of_Players_4", 85); + raceSet.Insert(u"Num_of_Players_5", 90); + raceSet.Insert(u"Num_of_Players_6", 100); - raceSet.push_back(make_unique>(u"Number_of_Spawn_Groups", 1)); - raceSet.push_back(make_unique>(u"Red_Spawners", 4847)); - raceSet.push_back(make_unique>(u"Blue_Spawners", 4848)); - raceSet.push_back(make_unique>(u"Blue_Flag", 4850)); - raceSet.push_back(make_unique>(u"Red_Flag", 4851)); - raceSet.push_back(make_unique>(u"Red_Point", 4846)); - raceSet.push_back(make_unique>(u"Blue_Point", 4845)); - raceSet.push_back(make_unique>(u"Red_Mark", 4844)); - raceSet.push_back(make_unique>(u"Blue_Mark", 4843)); + raceSet.Insert(u"Number_of_Spawn_Groups", 1); + raceSet.Insert(u"Red_Spawners", 4847); + raceSet.Insert(u"Blue_Spawners", 4848); + raceSet.Insert(u"Blue_Flag", 4850); + raceSet.Insert(u"Red_Flag", 4851); + raceSet.Insert(u"Red_Point", 4846); + raceSet.Insert(u"Blue_Point", 4845); + raceSet.Insert(u"Red_Mark", 4844); + raceSet.Insert(u"Blue_Mark", 4843); - const std::vector racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL); - for (auto* const racingController : racingControllers) { - auto* racingComponent = racingController->GetComponent(); - if (racingComponent) racingComponent->MsgConfigureRacingControl(config); - } + config.Send(self->GetObjectID()); } diff --git a/dScripts/ai/RACING/TRACK_NS/NsRaceServer.cpp b/dScripts/ai/RACING/TRACK_NS/NsRaceServer.cpp index 5ef6c6d1..3c9efa3a 100644 --- a/dScripts/ai/RACING/TRACK_NS/NsRaceServer.cpp +++ b/dScripts/ai/RACING/TRACK_NS/NsRaceServer.cpp @@ -10,45 +10,41 @@ void NsRaceServer::OnStartup(Entity* self) { GameMessages::ConfigureRacingControl config; auto& raceSet = config.racingSettings; - raceSet.push_back(make_unique>(u"GameType", u"Racing")); - raceSet.push_back(make_unique>(u"GameState", u"Starting")); - raceSet.push_back(make_unique>(u"Number_Of_PlayersPerTeam", 6)); - raceSet.push_back(make_unique>(u"Minimum_Players_to_Start", 2)); - raceSet.push_back(make_unique>(u"Minimum_Players_for_Group_Achievements", 2)); + raceSet.Insert(u"GameType", u"Racing"); + raceSet.Insert(u"GameState", u"Starting"); + raceSet.Insert(u"Number_Of_PlayersPerTeam", 6); + raceSet.Insert(u"Minimum_Players_to_Start", 2); + raceSet.Insert(u"Minimum_Players_for_Group_Achievements", 2); - raceSet.push_back(make_unique>(u"Car_Object", 7703)); - raceSet.push_back(make_unique>(u"Race_PathName", u"MainPath")); - raceSet.push_back(make_unique>(u"Current_Lap", 1)); - raceSet.push_back(make_unique>(u"Number_of_Laps", 3)); - raceSet.push_back(make_unique>(u"activityID", 42)); + raceSet.Insert(u"Car_Object", 7703); + raceSet.Insert(u"Race_PathName", u"MainPath"); + raceSet.Insert(u"Current_Lap", 1); + raceSet.Insert(u"Number_of_Laps", 3); + raceSet.Insert(u"activityID", 42); - raceSet.push_back(make_unique>(u"Place_1", 100)); - raceSet.push_back(make_unique>(u"Place_2", 90)); - raceSet.push_back(make_unique>(u"Place_3", 80)); - raceSet.push_back(make_unique>(u"Place_4", 70)); - raceSet.push_back(make_unique>(u"Place_5", 60)); - raceSet.push_back(make_unique>(u"Place_6", 50)); + raceSet.Insert(u"Place_1", 100); + raceSet.Insert(u"Place_2", 90); + raceSet.Insert(u"Place_3", 80); + raceSet.Insert(u"Place_4", 70); + raceSet.Insert(u"Place_5", 60); + raceSet.Insert(u"Place_6", 50); - raceSet.push_back(make_unique>(u"Num_of_Players_1", 15)); - raceSet.push_back(make_unique>(u"Num_of_Players_2", 25)); - raceSet.push_back(make_unique>(u"Num_of_Players_3", 50)); - raceSet.push_back(make_unique>(u"Num_of_Players_4", 85)); - raceSet.push_back(make_unique>(u"Num_of_Players_5", 90)); - raceSet.push_back(make_unique>(u"Num_of_Players_6", 100)); + raceSet.Insert(u"Num_of_Players_1", 15); + raceSet.Insert(u"Num_of_Players_2", 25); + raceSet.Insert(u"Num_of_Players_3", 50); + raceSet.Insert(u"Num_of_Players_4", 85); + raceSet.Insert(u"Num_of_Players_5", 90); + raceSet.Insert(u"Num_of_Players_6", 100); - raceSet.push_back(make_unique>(u"Number_of_Spawn_Groups", 1)); - raceSet.push_back(make_unique>(u"Red_Spawners", 4847)); - raceSet.push_back(make_unique>(u"Blue_Spawners", 4848)); - raceSet.push_back(make_unique>(u"Blue_Flag", 4850)); - raceSet.push_back(make_unique>(u"Red_Flag", 4851)); - raceSet.push_back(make_unique>(u"Red_Point", 4846)); - raceSet.push_back(make_unique>(u"Blue_Point", 4845)); - raceSet.push_back(make_unique>(u"Red_Mark", 4844)); - raceSet.push_back(make_unique>(u"Blue_Mark", 4843)); + raceSet.Insert(u"Number_of_Spawn_Groups", 1); + raceSet.Insert(u"Red_Spawners", 4847); + raceSet.Insert(u"Blue_Spawners", 4848); + raceSet.Insert(u"Blue_Flag", 4850); + raceSet.Insert(u"Red_Flag", 4851); + raceSet.Insert(u"Red_Point", 4846); + raceSet.Insert(u"Blue_Point", 4845); + raceSet.Insert(u"Red_Mark", 4844); + raceSet.Insert(u"Blue_Mark", 4843); - std::vector racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL); - for (auto* const racingController : racingControllers) { - auto* racingComponent = racingController->GetComponent(); - if (racingComponent) racingComponent->MsgConfigureRacingControl(config); - } + config.Send(self->GetObjectID()); } diff --git a/dScripts/ai/RACING/TRACK_NS_WINTER/NsWinterRaceServer.cpp b/dScripts/ai/RACING/TRACK_NS_WINTER/NsWinterRaceServer.cpp index 9a7712a1..8baaa4c6 100644 --- a/dScripts/ai/RACING/TRACK_NS_WINTER/NsWinterRaceServer.cpp +++ b/dScripts/ai/RACING/TRACK_NS_WINTER/NsWinterRaceServer.cpp @@ -9,45 +9,41 @@ void NsWinterRaceServer::OnStartup(Entity* self) { GameMessages::ConfigureRacingControl config; auto& raceSet = config.racingSettings; - raceSet.push_back(make_unique>("GameType", u"Racing")); - raceSet.push_back(make_unique>("GameState", u"Starting")); - raceSet.push_back(make_unique>("Number_Of_PlayersPerTeam", 6)); - raceSet.push_back(make_unique>("Minimum_Players_to_Start", 2)); - raceSet.push_back(make_unique>("Minimum_Players_for_Group_Achievments", 2)); + raceSet.Insert("GameType", u"Racing"); + raceSet.Insert("GameState", u"Starting"); + raceSet.Insert("Number_Of_PlayersPerTeam", 6); + raceSet.Insert("Minimum_Players_to_Start", 2); + raceSet.Insert("Minimum_Players_for_Group_Achievments", 2); - raceSet.push_back(make_unique>("Car_Object", 7703)); - raceSet.push_back(make_unique>("Race_PathName", u"MainPath")); - raceSet.push_back(make_unique>("Current_Lap", 1)); - raceSet.push_back(make_unique>("Number_of_Laps", 3)); - raceSet.push_back(make_unique>("activityID", 60)); + raceSet.Insert("Car_Object", 7703); + raceSet.Insert("Race_PathName", u"MainPath"); + raceSet.Insert("Current_Lap", 1); + raceSet.Insert("Number_of_Laps", 3); + raceSet.Insert("activityID", 60); - raceSet.push_back(make_unique>("Place_1", 100)); - raceSet.push_back(make_unique>("Place_2", 90)); - raceSet.push_back(make_unique>("Place_3", 80)); - raceSet.push_back(make_unique>("Place_4", 70)); - raceSet.push_back(make_unique>("Place_5", 60)); - raceSet.push_back(make_unique>("Place_6", 50)); + raceSet.Insert("Place_1", 100); + raceSet.Insert("Place_2", 90); + raceSet.Insert("Place_3", 80); + raceSet.Insert("Place_4", 70); + raceSet.Insert("Place_5", 60); + raceSet.Insert("Place_6", 50); - raceSet.push_back(make_unique>("Num_of_Players_1", 15)); - raceSet.push_back(make_unique>("Num_of_Players_2", 25)); - raceSet.push_back(make_unique>("Num_of_Players_3", 50)); - raceSet.push_back(make_unique>("Num_of_Players_4", 85)); - raceSet.push_back(make_unique>("Num_of_Players_5", 90)); - raceSet.push_back(make_unique>("Num_of_Players_6", 100)); + raceSet.Insert("Num_of_Players_1", 15); + raceSet.Insert("Num_of_Players_2", 25); + raceSet.Insert("Num_of_Players_3", 50); + raceSet.Insert("Num_of_Players_4", 85); + raceSet.Insert("Num_of_Players_5", 90); + raceSet.Insert("Num_of_Players_6", 100); - raceSet.push_back(make_unique>("Number_of_Spawn_Groups", 1)); - raceSet.push_back(make_unique>("Red_Spawners", 4847)); - raceSet.push_back(make_unique>("Blue_Spawners", 4848)); - raceSet.push_back(make_unique>("Blue_Flag", 4850)); - raceSet.push_back(make_unique>("Red_Flag", 4851)); - raceSet.push_back(make_unique>("Red_Point", 4846)); - raceSet.push_back(make_unique>("Blue_Point", 4845)); - raceSet.push_back(make_unique>("Red_Mark", 4844)); - raceSet.push_back(make_unique>("Blue_Mark", 4843)); + raceSet.Insert("Number_of_Spawn_Groups", 1); + raceSet.Insert("Red_Spawners", 4847); + raceSet.Insert("Blue_Spawners", 4848); + raceSet.Insert("Blue_Flag", 4850); + raceSet.Insert("Red_Flag", 4851); + raceSet.Insert("Red_Point", 4846); + raceSet.Insert("Blue_Point", 4845); + raceSet.Insert("Red_Mark", 4844); + raceSet.Insert("Blue_Mark", 4843); - const auto racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL); - for (auto* const racingController : racingControllers) { - auto* const racingComponent = racingController->GetComponent(); - if (racingComponent) racingComponent->MsgConfigureRacingControl(config); - } + config.Send(self->GetObjectID()); } diff --git a/dZoneManager/Level.cpp b/dZoneManager/Level.cpp index 40ee6d30..72fbdcdd 100644 --- a/dZoneManager/Level.cpp +++ b/dZoneManager/Level.cpp @@ -16,6 +16,7 @@ #include "AssetManager.h" #include "ClientVersion.h" #include "dConfig.h" +#include Level::Level(Zone* parentZone, const std::string& filepath) { m_ParentZone = parentZone; @@ -30,7 +31,7 @@ Level::Level(Zone* parentZone, const std::string& filepath) { ReadChunks(stream); } -void Level::MakeSpawner(SceneObject obj) { +void Level::MakeSpawner(const SceneObject& obj) { SpawnerInfo spawnInfo = SpawnerInfo(); SpawnerNode* node = new SpawnerNode(); spawnInfo.templateID = obj.lot; @@ -40,7 +41,7 @@ void Level::MakeSpawner(SceneObject obj) { node->rotation = obj.rotation; node->config = obj.settings; spawnInfo.nodes.push_back(node); - for (LDFBaseData* data : obj.settings) { + for (const auto& data : obj.settings.values | std::views::values) { if (!data) continue; if (data->GetKey() == u"spawntemplate") { spawnInfo.templateID = GeneralUtils::TryParse(data->GetValueAsString(), 0); @@ -70,7 +71,7 @@ void Level::MakeSpawner(SceneObject obj) { if (data->GetValueType() == eLDFType::LDF_TYPE_FLOAT) // Floats are in seconds { spawnInfo.respawnTime = GeneralUtils::TryParse(data->GetValueAsString(), 0.0f); - } else if (data->GetValueType() == eLDFType::LDF_TYPE_U32) // Ints are in ms? + } else if (data->GetValueType() == eLDFType::LDF_TYPE_U32) // Ints are in ms { spawnInfo.respawnTime = GeneralUtils::TryParse(data->GetValueAsString(), 0) / 1000; } @@ -86,13 +87,13 @@ void Level::MakeSpawner(SceneObject obj) { if (spawnInfo.groups.back().empty()) spawnInfo.groups.erase(spawnInfo.groups.end() - 1); } if (data->GetKey() == u"no_auto_spawn") { - spawnInfo.noAutoSpawn = static_cast*>(data)->GetValue(); + spawnInfo.noAutoSpawn = GeneralUtils::TryParse(data->GetValueAsString(), false); } if (data->GetKey() == u"no_timed_spawn") { - spawnInfo.noTimedSpawn = static_cast*>(data)->GetValue(); + spawnInfo.noTimedSpawn = GeneralUtils::TryParse(data->GetValueAsString(), false); } if (data->GetKey() == u"spawnActivator") { - spawnInfo.spawnActivator = static_cast*>(data)->GetValue(); + spawnInfo.spawnActivator = GeneralUtils::TryParse(data->GetValueAsString(), false); } } @@ -257,20 +258,14 @@ void Level::ReadSceneObjectDataChunk(std::istream& file, Header& header) { Game::zoneManager->GetZoneMut()->SetSpawnRot(obj.rotation); } - std::string sData = GeneralUtils::UTF16ToWTF8(ldfString); - std::stringstream ssData(sData); - std::string token; - char deliminator = '\n'; - - while (std::getline(ssData, token, deliminator)) { - LDFBaseData* ldfData = LDFBaseData::DataFromString(token); - obj.settings.push_back(ldfData); + for (const auto& token : GeneralUtils::SplitString(GeneralUtils::UTF16ToWTF8(ldfString), '\n')) { + obj.settings.ParseInsert(token); } // We should never have more than 1 zone control object bool skipLoadingObject = obj.lot == zoneControlObject->GetLOT(); - for (LDFBaseData* data : obj.settings) { + for (const auto& data : obj.settings | std::views::values) { if (!data) continue; if (data->GetKey() == u"gatingOnFeature") { gating.featureName = data->GetValueAsString(); @@ -296,11 +291,6 @@ void Level::ReadSceneObjectDataChunk(std::istream& file, Header& header) { } if (skipLoadingObject) { - for (auto* setting : obj.settings) { - delete setting; - setting = nullptr; - } - continue; } diff --git a/dZoneManager/Level.h b/dZoneManager/Level.h index 1f0b4f2e..22860fe7 100644 --- a/dZoneManager/Level.h +++ b/dZoneManager/Level.h @@ -40,7 +40,7 @@ public: public: Level(Zone* parentZone, const std::string& filepath); - static void MakeSpawner(SceneObject obj); + static void MakeSpawner(const SceneObject& obj); std::map m_ChunkHeaders; private: diff --git a/dZoneManager/Spawner.h b/dZoneManager/Spawner.h index 701ceb29..3f01161d 100644 --- a/dZoneManager/Spawner.h +++ b/dZoneManager/Spawner.h @@ -17,7 +17,7 @@ struct SpawnerNode { uint32_t nodeID = 0; uint32_t nodeMax = 1; std::vector entities; - std::vector config; + LwoNameValue config; }; struct SpawnerInfo { diff --git a/dZoneManager/Zone.cpp b/dZoneManager/Zone.cpp index 282f4c45..893d9598 100644 --- a/dZoneManager/Zone.cpp +++ b/dZoneManager/Zone.cpp @@ -111,7 +111,7 @@ void Zone::LoadZoneIntoMemory() { node->nodeID = 0; node->config = waypoint.config; - for (LDFBaseData* data : waypoint.config) { + for (const auto& data : waypoint.config | std::views::values) { if (!data) continue; if (data->GetKey() == u"spawner_node_id") { @@ -465,7 +465,7 @@ void Zone::LoadPath(std::istream& file) { command.data = value; } else LOG("Tried to load invalid waypoint command '%s'", parameter.c_str()); } else { - waypoint.config.emplace_back(LDFBaseData::DataFromString(parameter + "=" + value)); + waypoint.config.ParseInsert(parameter + "=" + value); } } diff --git a/dZoneManager/Zone.h b/dZoneManager/Zone.h index 20010c2c..4cb74efa 100644 --- a/dZoneManager/Zone.h +++ b/dZoneManager/Zone.h @@ -76,7 +76,7 @@ struct PathWaypoint { CameraPathWaypoint camera; RacingPathWaypoint racing; float speed{}; - std::vector config; + LwoNameValue config; std::vector commands; }; diff --git a/dZoneManager/dZMCommon.h b/dZoneManager/dZMCommon.h index 5acdc6b7..53f1d3c4 100644 --- a/dZoneManager/dZMCommon.h +++ b/dZoneManager/dZMCommon.h @@ -13,9 +13,8 @@ struct SceneObject { NiPoint3 position; NiQuaternion rotation = QuatUtils::IDENTITY; float scale = 1.0f; - //std::string settings; uint32_t value3; - std::vector settings; + LwoNameValue settings; }; #define LOT_MARKER_PLAYER_START 1931 diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index fe72f2da..61c13de2 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -70,7 +70,7 @@ endif() FetchContent_Declare( glm GIT_REPOSITORY https://github.com/g-truc/glm.git - GIT_TAG bf71a834948186f4097caa076cd2663c69a10e1e #refs/tags/1.0.1 + GIT_TAG 8d1fd52e5ab5590e2c81768ace50c72bae28f2ed #refs/tags/1.0.3 GIT_PROGRESS TRUE GIT_SHALLOW 1 ) diff --git a/thirdparty/recastnavigation b/thirdparty/recastnavigation index c5cbd530..9f4ce644 160000 --- a/thirdparty/recastnavigation +++ b/thirdparty/recastnavigation @@ -1 +1 @@ -Subproject commit c5cbd53024c8a9d8d097a4371215e3342d2fdc87 +Subproject commit 9f4ce64458dfae86e1239c525ddc219c4e9e06f1 From 7937951f7ff8f20a613aa7a5d16e850334106b5c Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 15 Jun 2026 00:20:43 -0700 Subject: [PATCH 14/26] fix: mech build not showing up (#1996) tested that the mech build, the fv rock build, and the frakjaw builds (to get to the instancer) all function as intented --- dGame/Entity.cpp | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 25ffb1e4..c4265470 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -1615,23 +1615,8 @@ void Entity::Kill(Entity* murderer, const eKillType killType) { const auto& grpNameQBShowBricks = GetVarAsString(u"grpNameQBShowBricks"); if (!grpNameQBShowBricks.empty()) { - auto spawners = Game::zoneManager->GetSpawnersByName(grpNameQBShowBricks); - - Spawner* spawner = nullptr; - - if (!spawners.empty()) { - spawner = spawners[0]; - } else { - spawners = Game::zoneManager->GetSpawnersInGroup(grpNameQBShowBricks); - - if (!spawners.empty()) { - spawner = spawners[0]; - } - } - - if (spawner != nullptr) { - spawner->Spawn(); - } + for (auto* const spawner : Game::zoneManager->GetSpawnersByName(grpNameQBShowBricks)) if (spawner) spawner->Spawn(); + for (auto* const spawner : Game::zoneManager->GetSpawnersInGroup(grpNameQBShowBricks)) if (spawner) spawner->Spawn(); } // Track a player being smashed From c0d055e66ea8d3d872f9f57396731071d407e63f Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 15 Jun 2026 09:44:24 -0700 Subject: [PATCH 15/26] fix dragon fire trails (#1997) tested that the fire breath attack works now (hack fix) --- dGame/dBehaviors/TacArcBehavior.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dGame/dBehaviors/TacArcBehavior.cpp b/dGame/dBehaviors/TacArcBehavior.cpp index bea246aa..076fc515 100644 --- a/dGame/dBehaviors/TacArcBehavior.cpp +++ b/dGame/dBehaviors/TacArcBehavior.cpp @@ -207,6 +207,10 @@ void TacArcBehavior::Load() { GetFloat("offset_y", 0.0f), GetFloat("offset_z", 0.0f) ); + // https://explorer.lu/skills/behaviors/6212/6203 HACK: i cant figure out why the dragon fire wall doesnt work with the offset, probably has to be fixed with the near/far height parameters + if (m_behaviorId == 6203) { + this->m_offset = NiPoint3Constant::ZERO; + } this->m_method = GetInt("method", 1); this->m_upperBound = GetFloat("upper_bound", 4.4f); this->m_lowerBound = GetFloat("lower_bound", 0.4f) - 5.0f; // Makes it so players and objects can still be targetted when slightly below the caster. FIXME: use bounding spheres at some point From 79bb48d3bcc7ca6185f704160680be678ba5bab5 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Tue, 16 Jun 2026 07:49:30 -0700 Subject: [PATCH 16/26] feat: implement a bunch of basic scripts that don't really do anything (#1999) * feat: implement a bunch of basic scripts that don't really do anything None of these do anything noticeable or break anything * fixes --- dGame/dGameMessages/GameMessages.h | 7 +++++++ dScripts/02_server/DLU/CMakeLists.txt | 1 + dScripts/02_server/DLU/RegisterWithZoneControl.cpp | 12 ++++++++++++ dScripts/02_server/DLU/RegisterWithZoneControl.h | 14 ++++++++++++++ dScripts/02_server/Map/njhub/OldManNPC.cpp | 2 -- dScripts/CppScripts.cpp | 14 ++++++++++++-- 6 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 dScripts/02_server/DLU/RegisterWithZoneControl.cpp create mode 100644 dScripts/02_server/DLU/RegisterWithZoneControl.h diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 8937e028..eef568d3 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -962,5 +962,12 @@ namespace GameMessages { LWOOBJID childID{}; }; + + struct ObjectLoaded : public GameMsg { + ObjectLoaded() : GameMsg(MessageType::Game::OBJECT_LOADED) {} + + LWOOBJID objectID{}; + LOT lot{}; + }; }; #endif // GAMEMESSAGES_H diff --git a/dScripts/02_server/DLU/CMakeLists.txt b/dScripts/02_server/DLU/CMakeLists.txt index fb257d3e..3185df56 100644 --- a/dScripts/02_server/DLU/CMakeLists.txt +++ b/dScripts/02_server/DLU/CMakeLists.txt @@ -1,3 +1,4 @@ set(DSCRIPTS_SOURCES_02_SERVER_DLU "DLUVanityTeleportingObject.cpp" + "RegisterWithZoneControl.cpp" PARENT_SCOPE) diff --git a/dScripts/02_server/DLU/RegisterWithZoneControl.cpp b/dScripts/02_server/DLU/RegisterWithZoneControl.cpp new file mode 100644 index 00000000..e588f240 --- /dev/null +++ b/dScripts/02_server/DLU/RegisterWithZoneControl.cpp @@ -0,0 +1,12 @@ +#include "RegisterWithZoneControl.h" + +#include "Entity.h" +#include "EntityManager.h" +#include "GameMessages.h" + +void RegisterWithZoneControl::OnStartup(Entity* self) { + GameMessages::ObjectLoaded objLoaded; + objLoaded.objectID = self->GetObjectID(); + objLoaded.lot = self->GetLOT(); + objLoaded.Send(Game::entityManager->GetZoneControlEntity()->GetObjectID()); +} diff --git a/dScripts/02_server/DLU/RegisterWithZoneControl.h b/dScripts/02_server/DLU/RegisterWithZoneControl.h new file mode 100644 index 00000000..dda89882 --- /dev/null +++ b/dScripts/02_server/DLU/RegisterWithZoneControl.h @@ -0,0 +1,14 @@ +// Darkflame Universe +// Copyright 2026 + +#ifndef REGISTERWITHZONECONTROL_H +#define REGISTERWITHZONECONTROL_H + +#include "CppScripts.h" + +class RegisterWithZoneControl : public CppScripts::Script { +public: + void OnStartup(Entity* self) override; +}; + +#endif //!REGISTERWITHZONECONTROL_H diff --git a/dScripts/02_server/Map/njhub/OldManNPC.cpp b/dScripts/02_server/Map/njhub/OldManNPC.cpp index 4d1f1056..8a649389 100644 --- a/dScripts/02_server/Map/njhub/OldManNPC.cpp +++ b/dScripts/02_server/Map/njhub/OldManNPC.cpp @@ -13,7 +13,6 @@ void ResetMissions(Entity& user) { } void OldManNPC::OnUse(Entity* self, Entity* user) { - LOG(""); const auto* const missionComponent = user->GetComponent(); if (!missionComponent) return; @@ -24,7 +23,6 @@ void OldManNPC::OnUse(Entity* self, Entity* user) { } const auto missionState = mission->GetMissionState(); - LOG("mission state %i", missionState); if (missionState == eMissionState::AVAILABLE || missionState == eMissionState::COMPLETE_AVAILABLE) { ResetMissions(*user); } diff --git a/dScripts/CppScripts.cpp b/dScripts/CppScripts.cpp index 6300716f..a1f37153 100644 --- a/dScripts/CppScripts.cpp +++ b/dScripts/CppScripts.cpp @@ -339,6 +339,8 @@ #include "ImaginationBackPack.h" #include "NsWinterRaceServer.h" +#include "RegisterWithZoneControl.h" + #include #include #include @@ -663,6 +665,7 @@ namespace { //WBL {"scripts\\zone\\LUPs\\WBL_generic_zone.lua", []() {return new WblGenericZone();}}, + {"scripts\\zone\\LUPs\\Moonbase Intro\\MOONBASE-INTRO_INTRO_CINEMATIC.lua", []() {return new WblGenericZone();}}, //Alpha {"scripts\\ai\\FV\\L_TRIGGER_GAS.lua", []() {return new TriggerGas();}}, @@ -708,7 +711,8 @@ namespace { {"scripts\\ai\\RACING\\OBJECTS\\VEHICLE_DEATH_TRIGGER_WATER_SERVER.lua", []() {return new VehicleDeathTriggerWaterServer();}}, {"scripts\\equipmenttriggers\\L_TRIAL_FACTION_ARMOR_SERVER.lua", []() {return new TrialFactionArmorServer();}}, {"scripts\\equipmenttriggers\\ImaginationBackPack.lua", []() {return new ImaginationBackPack();}}, - + {"scripts\\ai\\MINIGAME\\SG_GF\\SERVER\\SG_CANNON_INSTANCE_ACTOR.lua", [](){return new RegisterWithZoneControl();}}, + {"scripts\\ai\\MINIGAME\\SG_GF\\SERVER\\SG_CANNON_INSTANCE_EFFECT.lua", [](){return new RegisterWithZoneControl();}}, }; std::set g_ExcludedScripts = { @@ -732,6 +736,11 @@ namespace { "scripts\\zone\\LUPs\\RobotCity Intro\\WBL_RCIntro_InfectedCitizen.lua", "scripts\\ai\\MINIGAME\\SIEGE\\OBJECTS\\ATTACKER_BOUNCER_SERVER.lua", "scripts\\ai\\AG\\L_AG_ZONE_PLAYER.lua", + "scripts\\ai\\GENERAL\\L_NPC_GENERIC_MOVEMENT.lua", // Really old alpha script + "scripts\\zone\\LUPs\\DeepFreeze Intro\\WBL_Enemy_Beaver.lua", // Really old alpha script + "scripts\\ai\\GENERAL\\L_NPC_GENERIC_WANDER_SMALL.lua", // Really old alpha script + "scripts\\ai\\NP\\L_NPC_NP_OLD_MAN_SHERLAND.lua", // This NPC doesn't even exist in modern crux, the only place this is used... + "scripts\\02_server\\Map\\General\\L_SIMPLE_MOVER_SWITCH.lua", // This platform does not exist even when moved manually on a client }; }; @@ -745,7 +754,8 @@ CppScripts::Script* const CppScripts::GetScript(Entity* parent, const std::strin Script* script = itrTernary != scriptLoader.cend() ? itrTernary->second() : &InvalidToReturn; if (script == &InvalidToReturn && !scriptName.empty() && !g_ExcludedScripts.contains(scriptName)) { - LOG_DEBUG("LOT %i attempted to load CppScript for '%s', but returned InvalidScript.", parent->GetLOT(), scriptName.c_str()); + const auto [x, y, z] = parent->GetPosition(); + LOG_DEBUG("LOT %i at %f %f %f attempted to load CppScript for '%s', but returned InvalidScript.", parent->GetLOT(), x, y, z, scriptName.c_str()); } g_Scripts[scriptName] = script; From c898356eba5217933ba4e3173b8242461b3b24b2 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Tue, 16 Jun 2026 07:49:56 -0700 Subject: [PATCH 17/26] fix: enemies not interrupting QB's when they do damage (#1998) tested that stromlings in AG now correctly interrupt quickbuilds if the player takes damage --- dGame/dBehaviors/NpcCombatSkillBehavior.h | 2 ++ dGame/dBehaviors/VerifyBehavior.cpp | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/dGame/dBehaviors/NpcCombatSkillBehavior.h b/dGame/dBehaviors/NpcCombatSkillBehavior.h index 07826f48..993aed76 100644 --- a/dGame/dBehaviors/NpcCombatSkillBehavior.h +++ b/dGame/dBehaviors/NpcCombatSkillBehavior.h @@ -8,6 +8,8 @@ public: float m_npcSkillTime; + float m_maxRange{}; + /* * Inherited */ diff --git a/dGame/dBehaviors/VerifyBehavior.cpp b/dGame/dBehaviors/VerifyBehavior.cpp index c7ede52f..ec4b27a8 100644 --- a/dGame/dBehaviors/VerifyBehavior.cpp +++ b/dGame/dBehaviors/VerifyBehavior.cpp @@ -25,7 +25,7 @@ void VerifyBehavior::Calculate(BehaviorContext* context, RakNet::BitStream& bitS const auto distance = Vector3::DistanceSquared(self->GetPosition(), entity->GetPosition()); - if (distance > this->m_range * this->m_range) { + if (distance > this->m_range) { success = false; } } else if (this->m_blockCheck) { @@ -57,4 +57,5 @@ void VerifyBehavior::Load() { this->m_action = GetAction("action"); this->m_range = GetFloat("range"); + this->m_range = this->m_range * this->m_range * 0.9f; // Range checks are slightly smaller than the actual range to account for client/server discrepancies } From 0f17e1de3b644177125724f1629c9e5cf5f4980a Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Wed, 17 Jun 2026 23:07:36 -0700 Subject: [PATCH 18/26] feat: enemy npc pathing (#2000) * feat: enemy npc pathing they live :tada: tested that enemies path all around the world should they have a path configured. tested that the admiral in gf (at the first camp) paths now. fixes #1546 * feedback --- dGame/dComponents/BaseCombatAIComponent.cpp | 23 ++++-- dGame/dComponents/BaseCombatAIComponent.h | 2 + dGame/dComponents/MovementAIComponent.cpp | 78 +++++++++++++++++++-- dGame/dComponents/MovementAIComponent.h | 8 +++ dZoneManager/Zone.cpp | 16 +++-- 5 files changed, 111 insertions(+), 16 deletions(-) diff --git a/dGame/dComponents/BaseCombatAIComponent.cpp b/dGame/dComponents/BaseCombatAIComponent.cpp index b97211b8..ed4bf70d 100644 --- a/dGame/dComponents/BaseCombatAIComponent.cpp +++ b/dGame/dComponents/BaseCombatAIComponent.cpp @@ -210,8 +210,10 @@ void BaseCombatAIComponent::Update(const float deltaTime) { } if (stunnedThisFrame) { - m_MovementAI->Stop(); + if (!m_MovementAI->IsPaused()) m_MovementAI->Pause(); + // in this case we just become unstunned so check if we paused and resume if we did + if (!m_Stunned && m_MovementAI->IsPaused()) m_MovementAI->Resume(); return; } @@ -317,12 +319,14 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) { SetAiState(AiState::aggro); } else { SetAiState(AiState::idle); + if (m_MovementAI) m_MovementAI->SetMaxSpeed(1.0f); } if (!hasSkillToCast) return; if (m_Target == LWOOBJID_EMPTY) { SetAiState(AiState::idle); + if (m_MovementAI) m_MovementAI->SetMaxSpeed(1.0f); return; } @@ -618,6 +622,11 @@ void BaseCombatAIComponent::Wander() { return; } + // If we have a path to follow we should almost certainly do that instead of wandering. + if (m_MovementAI->HasPath()) { + return; + } + m_MovementAI->SetHaltDistance(0); const auto& info = m_MovementAI->GetInfo(); @@ -862,12 +871,12 @@ bool BaseCombatAIComponent::MsgGetObjectReportInfo(GameMessages::GetObjectReport // roundInfo.PushDebug("Combat Start Delay") = m_CombatStartDelay; std::string curState; switch (m_State) { - case idle: curState = "Idling"; break; - case aggro: curState = "Aggroed"; break; - case tether: curState = "Returning to Tether"; break; - case spawn: curState = "Spawn"; break; - case dead: curState = "Dead"; break; - default: curState = "Unknown or Undefined"; break; + case idle: curState = "Idling"; break; + case aggro: curState = "Aggroed"; break; + case tether: curState = "Returning to Tether"; break; + case spawn: curState = "Spawn"; break; + case dead: curState = "Dead"; break; + default: curState = "Unknown or Undefined"; break; } cmptType.PushDebug("Current Combat State") = curState; diff --git a/dGame/dComponents/BaseCombatAIComponent.h b/dGame/dComponents/BaseCombatAIComponent.h index 95eb75ce..3485ac16 100644 --- a/dGame/dComponents/BaseCombatAIComponent.h +++ b/dGame/dComponents/BaseCombatAIComponent.h @@ -236,6 +236,8 @@ public: bool MsgGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo); + void SetStartingPosition(const NiPoint3& pos) { m_StartPosition = pos; } + private: /** * Returns the current target or the target that currently is the largest threat to this entity diff --git a/dGame/dComponents/MovementAIComponent.cpp b/dGame/dComponents/MovementAIComponent.cpp index 3519712a..e885b4bb 100644 --- a/dGame/dComponents/MovementAIComponent.cpp +++ b/dGame/dComponents/MovementAIComponent.cpp @@ -19,6 +19,12 @@ #include "Amf3.h" #include "dNavMesh.h" +#include "eWaypointCommandType.h" +#include "StringifiedEnum.h" +#include "SkillComponent.h" +#include "GeneralUtils.h" +#include "RenderComponent.h" +#include "InventoryComponent.h" namespace { /** @@ -60,7 +66,7 @@ MovementAIComponent::MovementAIComponent(Entity* parent, const int32_t component RegisterMsg(&MovementAIComponent::OnGetObjectReportInfo); - if (!m_Parent->GetComponent()) SetPath(m_Parent->GetVarAsString(u"attached_path")); + SetPath(m_Parent->GetVarAsString(u"attached_path")); } void MovementAIComponent::SetPath(const std::string pathName) { @@ -162,6 +168,7 @@ void MovementAIComponent::Update(const float deltaTime) { } else { // Check if there are more waypoints in the queue, if so set our next destination to the next waypoint const auto waypointNum = m_IsBounced ? m_CurrentPath.size() : m_CurrentPathWaypointCount - m_CurrentPath.size() - 1; + RunWaypointCommands(waypointNum); if (m_CurrentPath.empty()) { if (m_Path) { if (m_Path->pathBehavior == PathBehavior::Loop) { @@ -172,17 +179,16 @@ void MovementAIComponent::Update(const float deltaTime) { if (m_IsBounced) std::ranges::reverse(waypoints); SetPath(waypoints); } else if (m_Path->pathBehavior == PathBehavior::Once) { - m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum); + // In this case we intended to follow a path and once we've followed it we camp there, otherwise we'd just wander home again. + if (m_BaseCombatAI) m_BaseCombatAI->SetStartingPosition(m_SourcePosition); Stop(); return; } } else { - m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum); Stop(); return; } } else { - m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum); SetDestination(m_CurrentPath.top().position); m_CurrentPath.pop(); @@ -423,7 +429,69 @@ NiPoint3 MovementAIComponent::GetDestination() const { void MovementAIComponent::SetMaxSpeed(const float value) { if (value == m_MaxSpeed) return; m_MaxSpeed = value; - m_Acceleration = value / 5; + m_Acceleration = value / 5.0f; +} + +void MovementAIComponent::RunWaypointCommands(uint32_t waypointNum) { + m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum); + + if (!m_Path || waypointNum >= m_Path->pathWaypoints.size()) return; + const auto& commands = m_Path->pathWaypoints[waypointNum].commands; + for (const auto& [command, data] : commands) { + LOG_DEBUG("%s %s %s", StringifiedEnum::ToString(command).data(), m_Path->pathName.c_str(), data.c_str()); + const auto dataSplit = GeneralUtils::SplitString(data, ','); + switch (command) { + case eWaypointCommandType::INVALID: break; + case eWaypointCommandType::BOUNCE: break; + case eWaypointCommandType::STOP: Pause(); break; + case eWaypointCommandType::GROUP_EMOTE: break; + case eWaypointCommandType::SET_VARIABLE: break; // Empty in the client + case eWaypointCommandType::CAST_SKILL: { + const auto skill = GeneralUtils::TryParse(data); + if (skill) { + auto* const skillComponent = m_Parent->GetComponent(); + if (skillComponent) skillComponent->CastSkill(skill.value()); + } + break; + } + case eWaypointCommandType::EQUIP_INVENTORY: { + auto* const inventoryComponent = m_Parent->GetComponent(); + if (inventoryComponent) { + // items should always exist + auto* const item = inventoryComponent->GetInventory(eInventoryType::ITEMS)->FindItemBySlot(0); + if (item) inventoryComponent->EquipItem(item); + } + break; + } + case eWaypointCommandType::UNEQUIP_INVENTORY: { + auto* const inventoryComponent = m_Parent->GetComponent(); + if (inventoryComponent) { + // items should always exist + auto* const item = inventoryComponent->GetInventory(eInventoryType::ITEMS)->FindItemBySlot(0); + if (item) inventoryComponent->UnEquipItem(item); + } + break; + } + case eWaypointCommandType::DELAY: { + // Pause(GeneralUtils::TryParse(data).value_or(0.0f)); + break; + } + case eWaypointCommandType::EMOTE: { + // m_Delay = RenderComponent::GetAnimationTime(m_Parent, data); + // const auto emoteID = GeneralUtils::TryParse(data); + // if (emoteID) GameMessages::SendPlayEmote(m_Parent->GetObjectID(), emoteID.value(), LWOOBJID_EMPTY, UNASSIGNED_SYSTEM_ADDRESS); + break; + } + case eWaypointCommandType::TELEPORT: break; + case eWaypointCommandType::PATH_SPEED: m_BaseSpeed = GetBaseSpeed(m_Parent->GetLOT()) * GeneralUtils::TryParse(data).value_or(1.0f); break; + case eWaypointCommandType::REMOVE_NPC: break; + case eWaypointCommandType::CHANGE_WAYPOINT: SetPath(dataSplit[0]); break; + case eWaypointCommandType::DELETE_SELF: break; + case eWaypointCommandType::KILL_SELF: m_Parent->Smash(); break; + case eWaypointCommandType::SPAWN_OBJECT: break; + case eWaypointCommandType::PLAY_SOUND: break; + } + } } bool MovementAIComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) { diff --git a/dGame/dComponents/MovementAIComponent.h b/dGame/dComponents/MovementAIComponent.h index 2a095716..af7da7f0 100644 --- a/dGame/dComponents/MovementAIComponent.h +++ b/dGame/dComponents/MovementAIComponent.h @@ -212,8 +212,16 @@ public: bool IsPaused() const { return m_Paused; } bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo); + + bool HasPath() const { return m_Path != nullptr; } private: + /** + * @brief + * Runs the commands on a waypoint if a path exists + */ + void RunWaypointCommands(uint32_t waypointNum); + /** * Sets the current position of the entity * @param value the position to set diff --git a/dZoneManager/Zone.cpp b/dZoneManager/Zone.cpp index 893d9598..fcb755f0 100644 --- a/dZoneManager/Zone.cpp +++ b/dZoneManager/Zone.cpp @@ -101,15 +101,23 @@ void Zone::LoadZoneIntoMemory() { m_Paths.reserve(pathCount); for (uint32_t i = 0; i < pathCount; ++i) LoadPath(file); - for (Path path : m_Paths) { + for (const Path& path : m_Paths) { if (path.pathType != PathType::Spawner) continue; - SpawnerInfo info = SpawnerInfo(); - for (PathWaypoint waypoint : path.pathWaypoints) { + SpawnerInfo info{}; + for (size_t i = 0; i < path.pathWaypoints.size(); i++) { + const auto& waypoint = path.pathWaypoints[i]; SpawnerNode* node = new SpawnerNode(); node->position = waypoint.position; node->rotation = waypoint.rotation; node->nodeID = 0; - node->config = waypoint.config; + node->config = path.pathWaypoints[0].config; + // All spawner waypoints get the config data of the first waypoint, but then we + // overwrite settings on this waypoint if we have another one defined of the same name + if (i != 0) { + for (const auto& [key, value] : waypoint.config) { + node->config.ParseInsert(value->GetString()); + } + } for (const auto& data : waypoint.config | std::views::values) { if (!data) continue; From ce9d4e823c09cfdca52cc6625dfaf0f7cd140dfc Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 18 Jun 2026 23:27:14 -0700 Subject: [PATCH 19/26] feat: enemies now use weights on their attacks (#2004) * feat: enemies now use weights on their attacks tested that 8 times out of 10, in close range, spiders did a web attack instead of a melee attack, vs the prior behavior of always following a pattern fixes #2002 * feedback --- .../CDClientTables/CDObjectSkillsTable.cpp | 8 ++ .../CDClientTables/CDObjectSkillsTable.h | 4 +- dGame/dComponents/BaseCombatAIComponent.cpp | 74 +++++++++++-------- dGame/dComponents/BaseCombatAIComponent.h | 18 ++++- 4 files changed, 68 insertions(+), 36 deletions(-) diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDObjectSkillsTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDObjectSkillsTable.cpp index a07446b5..522c3e88 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CDObjectSkillsTable.cpp +++ b/dDatabase/CDClientDatabase/CDClientTables/CDObjectSkillsTable.cpp @@ -38,3 +38,11 @@ std::vector CDObjectSkillsTable::Query(std::function CDObjectSkillsTable::Get(const LOT lot) const { + std::vector toReturn; + for (const auto& entry : GetEntries()) { + if (entry.objectTemplate == lot) toReturn.push_back(entry); + } + return toReturn; +} diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDObjectSkillsTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDObjectSkillsTable.h index 731f6657..ed314212 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CDObjectSkillsTable.h +++ b/dDatabase/CDClientDatabase/CDClientTables/CDObjectSkillsTable.h @@ -4,12 +4,13 @@ #include "CDTable.h" #include +#include struct CDObjectSkills { uint32_t objectTemplate; //!< The LOT of the item uint32_t skillID; //!< The Skill ID of the object uint32_t castOnType; //!< ??? - uint32_t AICombatWeight; //!< ??? + int32_t AICombatWeight; //!< ??? }; class CDObjectSkillsTable : public CDTable> { @@ -17,5 +18,6 @@ public: void LoadValuesFromDatabase(); // Queries the table with a custom "where" clause std::vector Query(std::function predicate); + std::vector Get(const LOT lot) const; }; diff --git a/dGame/dComponents/BaseCombatAIComponent.cpp b/dGame/dComponents/BaseCombatAIComponent.cpp index ed4bf70d..d4d70025 100644 --- a/dGame/dComponents/BaseCombatAIComponent.cpp +++ b/dGame/dComponents/BaseCombatAIComponent.cpp @@ -13,6 +13,8 @@ #include "CDClientDatabase.h" #include "CDClientManager.h" +#include "CDObjectSkillsTable.h" +#include "CDSkillBehaviorTable.h" #include "DestroyableComponent.h" #include @@ -43,7 +45,7 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const int32_t compo //Grab the aggro information from BaseCombatAI: auto componentQuery = CDClientDatabase::CreatePreppedStmt( - "SELECT aggroRadius, tetherSpeed, pursuitSpeed, softTetherRadius, hardTetherRadius FROM BaseCombatAIComponent WHERE id = ?;"); + "SELECT aggroRadius, tetherSpeed, pursuitSpeed, softTetherRadius, hardTetherRadius, minRoundLength, maxRoundLength, combatRoundLength FROM BaseCombatAIComponent WHERE id = ?;"); componentQuery.bind(1, static_cast(componentID)); auto componentResult = componentQuery.execQuery(); @@ -63,44 +65,37 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const int32_t compo if (!componentResult.fieldIsNull("hardTetherRadius")) m_HardTetherRadius = componentResult.getFloatField("hardTetherRadius"); + + m_MinRoundLength = componentResult.getFloatField("minRoundLength"); + m_MaxRoundLength = componentResult.getFloatField("maxRoundLength"); + m_CombatRoundLength = componentResult.getFloatField("combatRoundLength"); } - componentResult.finalize(); - // Get aggro and tether radius from settings and use this if it is present. Only overwrite the // radii if it is greater than the one in the database. - if (m_Parent) { - auto aggroRadius = m_Parent->GetVar(u"aggroRadius"); - m_AggroRadius = aggroRadius != 0 ? aggroRadius : m_AggroRadius; - auto tetherRadius = m_Parent->GetVar(u"tetherRadius"); - m_HardTetherRadius = tetherRadius != 0 ? tetherRadius : m_HardTetherRadius; - } + m_AggroRadius = m_Parent->HasVar(u"aggroRadius") ? m_Parent->GetVar(u"aggroRadius") : m_AggroRadius; + m_HardTetherRadius = m_Parent->HasVar(u"tetherRadius") ? m_Parent->GetVar(u"tetherRadius") : m_HardTetherRadius; /* * Find skills */ - auto skillQuery = CDClientDatabase::CreatePreppedStmt( - "SELECT skillID, cooldown, behaviorID FROM SkillBehavior WHERE skillID IN (SELECT skillID FROM ObjectSkills WHERE objectTemplate = ?);"); - skillQuery.bind(1, static_cast(parent->GetLOT())); + for (const auto objectSkill : CDClientManager::GetTable()->Get(parent->GetLOT())) { + const auto skillBehavior = CDClientManager::GetTable()->GetSkillByID(objectSkill.skillID); + if (skillBehavior.skillID == objectSkill.skillID) { + const auto skillId = skillBehavior.skillID; - auto result = skillQuery.execQuery(); + const auto abilityCooldown = skillBehavior.cooldown; - while (!result.eof()) { - const auto skillId = static_cast(result.getIntField("skillID")); + const auto behaviorId = skillBehavior.behaviorID; - const auto abilityCooldown = static_cast(result.getFloatField("cooldown")); + const auto combatWeight = objectSkill.AICombatWeight; - const auto behaviorId = static_cast(result.getIntField("behaviorID")); + auto* behavior = Behavior::CreateBehavior(behaviorId); - auto* behavior = Behavior::CreateBehavior(behaviorId); + AiSkillEntry entry = { .skillId = skillId, .cooldown = 0.0f, .abilityCooldown = abilityCooldown, .behavior = behavior, .combatWeight = combatWeight }; - std::stringstream behaviorQuery; - - AiSkillEntry entry = { skillId, 0, abilityCooldown, behavior }; - - m_SkillEntries.push_back(entry); - - result.nextRow(); + m_SkillEntries.push_back(entry); + } } Stun(1.0f); @@ -248,10 +243,12 @@ void BaseCombatAIComponent::Update(const float deltaTime) { void BaseCombatAIComponent::CalculateCombat(const float deltaTime) { bool hasSkillToCast = false; + int32_t maxSkillWeights = 0; for (auto& entry : m_SkillEntries) { if (entry.cooldown > 0.0f) { entry.cooldown -= deltaTime; } else { + maxSkillWeights += entry.combatWeight; hasSkillToCast = true; } } @@ -337,10 +334,19 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) { LookAt(target->GetPosition()); } - for (auto i = 0; i < m_SkillEntries.size(); ++i) { - auto entry = m_SkillEntries.at(i); + // Roll to find which skill we'll try to cast + auto randomizedWeight = GeneralUtils::GenerateRandomNumber(0, maxSkillWeights); - if (entry.cooldown > 0) { + for (auto& entry : m_SkillEntries) { + // Skill isn't cooled off yet + if (entry.cooldown > 0.0f) { + continue; + } + + randomizedWeight -= entry.combatWeight; + + // if the weight is still greater than 0 continue to the next rolled skill + if (randomizedWeight > 0) { continue; } @@ -359,8 +365,6 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) { entry.cooldown = entry.abilityCooldown + m_SkillTime; - m_SkillEntries[i] = entry; - break; } } @@ -914,8 +918,16 @@ bool BaseCombatAIComponent::MsgGetObjectReportInfo(GameMessages::GetObjectReport } auto& ignoredThreats = cmptType.PushDebug("Temp Ignored Threats"); - for (const auto& [id, threat] : m_ThreatEntries) { + for (const auto& [id, threat] : m_RemovedThreatList) { ignoredThreats.PushDebug(std::to_string(id) + " - Time") = threat; } + auto& skillInfo = cmptType.PushDebug("Skill Info"); + for (const auto& skill : m_SkillEntries) { + auto& skillDebug = skillInfo.PushDebug("Skill ID " + std::to_string(skill.skillId)); + skillDebug.PushDebug("Cooldown") = skill.cooldown; + skillDebug.PushDebug("Ability Cooldown") = skill.abilityCooldown; + skillDebug.PushDebug("AI Combat Weight") = skill.combatWeight; + } + return true; } diff --git a/dGame/dComponents/BaseCombatAIComponent.h b/dGame/dComponents/BaseCombatAIComponent.h index 3485ac16..18de88cb 100644 --- a/dGame/dComponents/BaseCombatAIComponent.h +++ b/dGame/dComponents/BaseCombatAIComponent.h @@ -33,13 +33,15 @@ enum class AiState : uint32_t { */ struct AiSkillEntry { - uint32_t skillId; + uint32_t skillId{}; - float cooldown; + float cooldown{}; - float abilityCooldown; + float abilityCooldown{}; - Behavior* behavior; + Behavior* behavior{}; + + int32_t combatWeight{}; }; /** @@ -396,9 +398,17 @@ private: */ bool m_DirtyStateOrTarget = false; + // Min amount of time to remain as in combat after casting a skill + float m_MinRoundLength = 0.0f; + + // max amount of time to remain as in combat after casting a skill + float m_MaxRoundLength = 0.0f; + // The amount of time the entity will be forced to tether for float m_ForcedTetherTime = 0.0f; + float m_CombatRoundLength = 0.0f; + // The amount of time a removed threat will be ignored for. std::map m_RemovedThreatList; From 56504d9447f41b376436e3d50f09e96045c5bb1b Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 18 Jun 2026 23:27:49 -0700 Subject: [PATCH 20/26] fix: add range checks to npc combat skill behavior (#2003) * fix: add range checks to npc combat skill behavior tested that all enemies now cast skills smartly based on range to targets, and do not cast skills if they are out of range. fixes an issue where the spider queen could attack you outside the normal range fixes an issue where entering happy flower caused you to need to restart the client fixes #965 * feedback --- dGame/dBehaviors/NpcCombatSkillBehavior.cpp | 30 +++++++++++--- dGame/dBehaviors/NpcCombatSkillBehavior.h | 1 + dGame/dComponents/BaseCombatAIComponent.cpp | 6 +-- dGame/dComponents/BaseCombatAIComponent.h | 3 +- .../Enemy/AG/BossSpiderQueenEnemyServer.cpp | 40 ++++++++++++++++++- .../Enemy/AG/BossSpiderQueenEnemyServer.h | 3 ++ dWorldServer/WorldServer.cpp | 1 + 7 files changed, 74 insertions(+), 10 deletions(-) diff --git a/dGame/dBehaviors/NpcCombatSkillBehavior.cpp b/dGame/dBehaviors/NpcCombatSkillBehavior.cpp index 69a6ea9d..433594a2 100644 --- a/dGame/dBehaviors/NpcCombatSkillBehavior.cpp +++ b/dGame/dBehaviors/NpcCombatSkillBehavior.cpp @@ -5,20 +5,40 @@ void NpcCombatSkillBehavior::Calculate(BehaviorContext* context, RakNet::BitStream& bit_stream, BehaviorBranchContext branch) { context->skillTime = this->m_npcSkillTime; + const auto* const targetEntity = Game::entityManager->GetEntity(branch.target); + const auto* const sourceEntity = Game::entityManager->GetEntity(context->caster); - for (auto* behavior : this->m_behaviors) { - behavior->Calculate(context, bit_stream, branch); + bool cast = true; + // Check that the target is within the cast range + if (targetEntity && sourceEntity && this->m_maxRange != 0.0f) { + const auto targetPos = targetEntity->GetPosition(); + const auto sourcePos = sourceEntity->GetPosition(); + const auto distance = NiPoint3::DistanceSquared(targetPos, sourcePos); + cast = distance >= this->m_minRange && distance <= this->m_maxRange; + } + + if (cast) { + for (auto* behavior : this->m_behaviors) { + behavior->Calculate(context, bit_stream, branch); + } + } else { + // We failed to find a valid target, do not continue the behavior + context->foundTarget = false; } } void NpcCombatSkillBehavior::Load() { this->m_npcSkillTime = GetFloat("npc skill time"); + this->m_minRange = GetFloat("min range") * 0.9f; // Make the min and max 10% smaller to account for server/client position disagreements + this->m_minRange *= this->m_minRange; + this->m_maxRange = GetFloat("max range") * 0.9f; // Make the min and max 10% smaller to account for server/client position disagreements + this->m_maxRange *= this->m_maxRange; const auto parameters = GetParameterNames(); - for (const auto& parameter : parameters) { - if (parameter.first.rfind("behavior", 0) == 0) { - auto* action = GetAction(parameter.second); + for (const auto& [parameter, value] : parameters) { + if (parameter.rfind("behavior", 0) == 0) { + auto* action = GetAction(value); this->m_behaviors.push_back(action); } diff --git a/dGame/dBehaviors/NpcCombatSkillBehavior.h b/dGame/dBehaviors/NpcCombatSkillBehavior.h index 993aed76..a1c7c0e9 100644 --- a/dGame/dBehaviors/NpcCombatSkillBehavior.h +++ b/dGame/dBehaviors/NpcCombatSkillBehavior.h @@ -9,6 +9,7 @@ public: float m_npcSkillTime; float m_maxRange{}; + float m_minRange{}; /* * Inherited diff --git a/dGame/dComponents/BaseCombatAIComponent.cpp b/dGame/dComponents/BaseCombatAIComponent.cpp index d4d70025..89c39c33 100644 --- a/dGame/dComponents/BaseCombatAIComponent.cpp +++ b/dGame/dComponents/BaseCombatAIComponent.cpp @@ -350,7 +350,7 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) { continue; } - const auto result = skillComponent->CalculateBehavior(entry.skillId, entry.behavior->m_behaviorId, LWOOBJID_EMPTY); + const auto result = skillComponent->CalculateBehavior(entry.skillId, entry.behavior->m_behaviorId, GetTarget()); if (result.success) { if (m_MovementAI != nullptr) { @@ -759,8 +759,8 @@ void BaseCombatAIComponent::SetTetherSpeed(float value) { m_TetherSpeed = value; } -void BaseCombatAIComponent::Stun(const float time) { - if (m_StunImmune || m_StunTime > time) { +void BaseCombatAIComponent::Stun(const float time, const bool force) { + if (!force && (m_StunImmune || m_StunTime > time)) { return; } diff --git a/dGame/dComponents/BaseCombatAIComponent.h b/dGame/dComponents/BaseCombatAIComponent.h index 18de88cb..728e0d4a 100644 --- a/dGame/dComponents/BaseCombatAIComponent.h +++ b/dGame/dComponents/BaseCombatAIComponent.h @@ -183,8 +183,9 @@ public: /** * Stuns the entity for a certain amount of time, will not work if the entity is stun immune * @param time the time to stun the entity, if stunnable + * @param force whether or not to force the stun and ignore checks */ - void Stun(float time); + void Stun(float time, const bool force = false); /** * Gets the radius that will cause this entity to get aggro'd, causing a target chase diff --git a/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.cpp b/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.cpp index b3938fd4..869e09ba 100644 --- a/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.cpp +++ b/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.cpp @@ -16,6 +16,7 @@ #include "eReplicaComponentType.h" #include "RenderComponent.h" #include "PlayerManager.h" +#include "eStateChangeType.h" #include @@ -48,10 +49,30 @@ void BossSpiderQueenEnemyServer::OnStartup(Entity* self) { combat->SetStunImmune(true); m_CurrentBossStage = 1; - + ToggleAttacking(*self, false); + self->SetProximityRadius(65.0f, "AggroRadius"); // Obtain faction and collision group to save for subsequent resets } +void BossSpiderQueenEnemyServer::OnProximityUpdate(Entity* self, Entity* entering, std::string name, std::string status) { + if (name != "AggroRadius" || !entering || !entering->IsPlayer()) return; + + auto playerCount = self->GetVar(u"player_count"); + + if (status == "ENTER") { + if (playerCount == 0) { + ToggleAttacking(*self, true); + } + playerCount++; + } else if (status == "LEAVE") { + playerCount--; + if (playerCount == 0) { + ToggleAttacking(*self, false); + } + } + self->SetVar(u"player_count", playerCount); +} + void BossSpiderQueenEnemyServer::OnDie(Entity* self, Entity* killer) { if (Game::zoneManager->GetZoneID().GetMapID() == instanceZoneID && killer) { for (const auto& player : PlayerManager::GetAllPlayers()) { @@ -71,6 +92,7 @@ void BossSpiderQueenEnemyServer::OnDie(Entity* self, Entity* killer) { self->SetPosition({ 10000, 0, 10000 }); Game::entityManager->SerializeEntity(self); + ToggleAttacking(*self, false); controller->OnFireEventServerSide(self, "ClearProperty"); } @@ -634,3 +656,19 @@ float BossSpiderQueenEnemyServer::PlayAnimAndReturnTime(Entity* self, const std: return animTimer; } + +void BossSpiderQueenEnemyServer::ToggleAttacking(Entity& self, bool on) { + const auto stoppedFlag = self.GetVarAs(u"stoppedFlag"); + + if (!on) { + if (stoppedFlag) return; + + self.SetVar(u"stoppedFlag", true); + combat->Stun(100000.0f, true); // forcibly stun so we stop attacking people trying to put on armor + } else { + if (!stoppedFlag) return; + + self.SetVar(u"stoppedFlag", false); + combat->Stun(0.0f, true); // forcibly turn off the stun we put on above + } +} diff --git a/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.h b/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.h index b5909000..0f975abe 100644 --- a/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.h +++ b/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.h @@ -46,7 +46,10 @@ public: void OnTimerDone(Entity* self, std::string timerName) override; + void OnProximityUpdate(Entity* self, Entity* entering, std::string name, std::string status); + private: + void ToggleAttacking(Entity& self, bool on); //Regular variables: DestroyableComponent* destroyable = nullptr; ControllablePhysicsComponent* controllable = nullptr; diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 8de1d4e5..e4d324f1 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -1019,6 +1019,7 @@ void HandlePacket(Packet* packet) { if (user) { Character* c = user->GetLastUsedChar(); if (c != nullptr) { + if (Game::entityManager->GetEntity(c->GetObjectID())) return; std::u16string username = GeneralUtils::ASCIIToUTF16(c->GetName()); Game::server->GetReplicaManager()->AddParticipant(packet->systemAddress); From 308412f46e66a6d86d8992dc7e0830056bb0991f Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Fri, 19 Jun 2026 02:12:47 -0700 Subject: [PATCH 21/26] fix: enemies snapping to the incorrect position if they had a path and trying to use the path if they were aggro'd to an enemy (#2005) * fix: enemies snapping to the incorrect position if they had a path tested that ags enemies no longer snap backwards a large amount * fix: move the home point so we can aggro correctly --- dGame/dComponents/MovementAIComponent.cpp | 60 ++++++++++++----------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/dGame/dComponents/MovementAIComponent.cpp b/dGame/dComponents/MovementAIComponent.cpp index e885b4bb..c02d3a00 100644 --- a/dGame/dComponents/MovementAIComponent.cpp +++ b/dGame/dComponents/MovementAIComponent.cpp @@ -37,8 +37,6 @@ MovementAIComponent::MovementAIComponent(Entity* parent, const int32_t component m_Info = info; m_AtFinalWaypoint = true; - m_BaseCombatAI = nullptr; - m_BaseCombatAI = m_Parent->GetComponent(); //Try and fix the insane values: @@ -131,7 +129,11 @@ void MovementAIComponent::Update(const float deltaTime) { m_TimeTravelled += deltaTime; - SetPosition(ApproximateLocation()); + const auto approxPos = ApproximateLocation(); + SetPosition(approxPos); + // Set the AIs new home based on where our current waypoint is IF we're idle, that way we can return to this + // when resuming the pathing after losing aggro while moving the aggro hitbox with us + if (m_BaseCombatAI && m_BaseCombatAI->GetState() == AiState::idle) m_BaseCombatAI->SetStartingPosition(approxPos); if (m_TimeTravelled < m_TimeToTravel) return; m_TimeTravelled = 0.0f; @@ -166,32 +168,34 @@ void MovementAIComponent::Update(const float deltaTime) { SetRotation(QuatUtils::LookAt(source, m_NextWaypoint)); } } else { - // Check if there are more waypoints in the queue, if so set our next destination to the next waypoint - const auto waypointNum = m_IsBounced ? m_CurrentPath.size() : m_CurrentPathWaypointCount - m_CurrentPath.size() - 1; - RunWaypointCommands(waypointNum); - if (m_CurrentPath.empty()) { - if (m_Path) { - if (m_Path->pathBehavior == PathBehavior::Loop) { - SetPath(m_Path->pathWaypoints); - } else if (m_Path->pathBehavior == PathBehavior::Bounce) { - m_IsBounced = !m_IsBounced; - std::vector waypoints = m_Path->pathWaypoints; - if (m_IsBounced) std::ranges::reverse(waypoints); - SetPath(waypoints); - } else if (m_Path->pathBehavior == PathBehavior::Once) { - // In this case we intended to follow a path and once we've followed it we camp there, otherwise we'd just wander home again. - if (m_BaseCombatAI) m_BaseCombatAI->SetStartingPosition(m_SourcePosition); + // Only try to renew or continue the path if we're in the idle or spawn state and we actually have a combatAI component + if (!m_BaseCombatAI || (m_BaseCombatAI && m_BaseCombatAI->GetState() == AiState::idle)) { + // Check if there are more waypoints in the queue, if so set our next destination to the next waypoint + const auto waypointNum = m_IsBounced ? m_CurrentPath.size() : m_CurrentPathWaypointCount - m_CurrentPath.size() - 1; + RunWaypointCommands(waypointNum); + if (m_CurrentPath.empty()) { + if (m_Path) { + if (m_Path->pathBehavior == PathBehavior::Loop) { + SetPath(m_Path->pathWaypoints); + } else if (m_Path->pathBehavior == PathBehavior::Bounce) { + m_IsBounced = !m_IsBounced; + std::vector waypoints = m_Path->pathWaypoints; + if (m_IsBounced) std::ranges::reverse(waypoints); + SetPath(waypoints); + } else if (m_Path->pathBehavior == PathBehavior::Once) { + // In this case we intended to follow a path and once we've followed it we camp there, otherwise we'd just wander home again. + Stop(); + return; + } + } else { Stop(); return; } } else { - Stop(); - return; - } - } else { - SetDestination(m_CurrentPath.top().position); + SetDestination(m_CurrentPath.top().position); - m_CurrentPath.pop(); + m_CurrentPath.pop(); + } } } @@ -218,8 +222,7 @@ NiPoint3 MovementAIComponent::GetCurrentWaypoint() const { NiPoint3 MovementAIComponent::ApproximateLocation() const { auto source = m_SourcePosition; - - if (AtFinalWaypoint()) return source; + if (AtFinalWaypoint()) return m_Parent->GetPosition(); auto destination = m_NextWaypoint; @@ -473,7 +476,7 @@ void MovementAIComponent::RunWaypointCommands(uint32_t waypointNum) { break; } case eWaypointCommandType::DELAY: { - // Pause(GeneralUtils::TryParse(data).value_or(0.0f)); + // Pause(GeneralUtils::TryParse(data).value_or(0.0f)); break; } case eWaypointCommandType::EMOTE: { @@ -521,12 +524,13 @@ bool MovementAIComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInf movementInfo.PushDebug("Lock Rotation") = m_LockRotation; movementInfo.PushDebug("Paused") = m_Paused; movementInfo.PushDebug("Pulling To Point") = m_PullingToPoint; + movementInfo.PushDebug("At Final Waypoint") = m_AtFinalWaypoint; auto& pullPointInfo = movementInfo.PushDebug("Pull Point"); pullPointInfo.PushDebug("X") = m_PullPoint.x; pullPointInfo.PushDebug("Y") = m_PullPoint.y; pullPointInfo.PushDebug("Z") = m_PullPoint.z; - + // movementInfo.PushDebug("Delay") = m_Delay; auto& waypoints = movementInfo.PushDebug("Interpolated Waypoints"); From 7456d6b5c11ea27c45f65f76d9f7fc2d6ef2cae8 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Fri, 19 Jun 2026 19:08:15 -0700 Subject: [PATCH 22/26] feat: spawner weights (#2006) * feat: spawner weights * remove ref * default weights to 1 --- dCommon/LDFFormat.h | 8 ++ .../Map/General/VisToggleNotifierServer.cpp | 2 +- dZoneManager/Spawner.cpp | 74 ++++++++++--------- dZoneManager/Spawner.h | 46 +++++++++--- dZoneManager/Zone.cpp | 17 +++-- 5 files changed, 94 insertions(+), 53 deletions(-) diff --git a/dCommon/LDFFormat.h b/dCommon/LDFFormat.h index 81a6648c..0f515d30 100644 --- a/dCommon/LDFFormat.h +++ b/dCommon/LDFFormat.h @@ -289,6 +289,14 @@ struct LwoNameValue { this->Erase(GeneralUtils::ASCIIToUTF16(key)); } + ValueType::iterator find(const ValueType::key_type& key) { + return this->values.find(key); + } + + ValueType::const_iterator find(const ValueType::key_type& key) const { + return this->values.find(key); + } + LwoNameValue() = default; LwoNameValue(const LwoNameValue& other) { diff --git a/dScripts/02_server/Map/General/VisToggleNotifierServer.cpp b/dScripts/02_server/Map/General/VisToggleNotifierServer.cpp index a02856ef..16d805eb 100644 --- a/dScripts/02_server/Map/General/VisToggleNotifierServer.cpp +++ b/dScripts/02_server/Map/General/VisToggleNotifierServer.cpp @@ -14,7 +14,7 @@ void VisToggleNotifierServer::OnMissionDialogueOK(Entity* self, Entity* target, auto spawners = Game::zoneManager->GetSpawnersByName(itr->second); if (spawners.empty()) return; for (const auto spawner : spawners) { - auto spawnedObjIds = spawner->GetSpawnedObjectIDs(); + const auto& spawnedObjIds = spawner->GetSpawnedObjectIDs(); for (const auto& objId : spawnedObjIds) { GameMessages::SendNotifyClientObject(objId, u"SetVisibility", visible); } diff --git a/dZoneManager/Spawner.cpp b/dZoneManager/Spawner.cpp index cccdfff0..e7045842 100644 --- a/dZoneManager/Spawner.cpp +++ b/dZoneManager/Spawner.cpp @@ -6,8 +6,10 @@ #include #include "GeneralUtils.h" #include "dZoneManager.h" +#include +#include -Spawner::Spawner(const SpawnerInfo info) { +Spawner::Spawner(const SpawnerInfo& info) { m_Info = info; m_Active = m_Info.activeOnLoad && info.spawnActivator; m_EntityInfo = EntityInfo(); @@ -62,10 +64,6 @@ Spawner::Spawner(const SpawnerInfo info) { } } -Spawner::~Spawner() { - -} - Entity* Spawner::Spawn() { std::vector freeNodes; for (SpawnerNode* node : m_Info.nodes) { @@ -77,9 +75,25 @@ Entity* Spawner::Spawn() { return Spawn(freeNodes); } -Entity* Spawner::Spawn(std::vector freeNodes, const bool force) { - if (force || ((m_Entities.size() < m_Info.amountMaintained) && (freeNodes.size() > 0) && (m_AmountSpawned < m_Info.maxToSpawn || m_Info.maxToSpawn == -1))) { - SpawnerNode* spawnNode = freeNodes[GeneralUtils::GenerateRandomNumber(0, freeNodes.size() - 1)]; +Entity* Spawner::Spawn(const std::vector& freeNodes, const bool force) { + Entity* spawnedEntity = nullptr; + if (force || ((m_Entities.size() < m_Info.amountMaintained) && !freeNodes.empty() && (m_AmountSpawned < m_Info.maxToSpawn || m_Info.maxToSpawn == -1))) { + // first sum the weights we were provided + int32_t spawnWeight = 0; + for (const auto* const node : freeNodes) spawnWeight += node->weight; + auto chosenWeight = GeneralUtils::GenerateRandomNumber(1, spawnWeight); + + // Default to 0 incase something goes wrong in this calc + // Roll the spawner nodes based on their weights, higher weights = more likely to spawn + SpawnerNode* spawnNode = freeNodes[0]; + for (auto* const node : freeNodes) { + chosenWeight -= node->weight; + if (chosenWeight <= 0) { + spawnNode = node; + break; // we rolled a spawner + } + } + ++m_AmountSpawned; m_EntityInfo.pos = spawnNode->position; m_EntityInfo.rot = spawnNode->rotation; @@ -93,26 +107,24 @@ Entity* Spawner::Spawn(std::vector freeNodes, const bool force) { m_EntityInfo.spawnerID = m_Info.spawnerID; } - Entity* rezdE = Game::entityManager->CreateEntity(m_EntityInfo, nullptr); + spawnedEntity = Game::entityManager->CreateEntity(m_EntityInfo, nullptr); - rezdE->GetGroups() = m_Info.groups; + spawnedEntity->GetGroups() = m_Info.groups; - Game::entityManager->ConstructEntity(rezdE); + Game::entityManager->ConstructEntity(spawnedEntity); - m_Entities.insert({ rezdE->GetObjectID(), spawnNode }); - spawnNode->entities.push_back(rezdE->GetObjectID()); + m_Entities[spawnedEntity->GetObjectID()] = spawnNode; + spawnNode->entities.push_back(spawnedEntity->GetObjectID()); if (m_Entities.size() == m_Info.amountMaintained) { m_NeedsUpdate = false; } for (const auto& cb : m_EntitySpawnedCallbacks) { - cb(rezdE); + cb(spawnedEntity); } - - return rezdE; } - return nullptr; + return spawnedEntity; } void Spawner::AddSpawnedEntityDieCallback(std::function callback) { @@ -148,18 +160,18 @@ void Spawner::SoftReset() { m_NeedsUpdate = true; } -void Spawner::SetRespawnTime(float time) { +void Spawner::SetRespawnTime(const float time) { m_Info.respawnTime = time; for (size_t i = 0; i < m_WaitTimes.size(); ++i) { m_WaitTimes[i] = 0; - }; + } m_Start = true; m_NeedsUpdate = true; } -void Spawner::SetNumToMaintain(int32_t value) { +void Spawner::SetNumToMaintain(const int32_t value) { m_Info.amountMaintained = value; } @@ -177,15 +189,8 @@ void Spawner::Update(const float deltaTime) { return; } - if (!m_NeedsUpdate) return; - if (!m_Active) return; - //if (m_Info.noTimedSpawn) return; - if (m_Info.spawnsOnSmash) { - if (!m_SpawnSmashFoundGroup) { + if (!m_NeedsUpdate || !m_Active || m_Info.spawnsOnSmash) return; - } - return; - } for (size_t i = 0; i < m_WaitTimes.size(); ) { m_WaitTimes[i] += deltaTime; if (m_WaitTimes[i] >= m_Info.respawnTime) { @@ -198,22 +203,21 @@ void Spawner::Update(const float deltaTime) { } } -std::vector Spawner::GetSpawnedObjectIDs() const { +const std::vector Spawner::GetSpawnedObjectIDs() const { std::vector ids; ids.reserve(m_Entities.size()); - for (const auto& [objId, spawnerNode] : m_Entities) { + for (const auto objId : m_Entities | std::views::keys) { ids.push_back(objId); } return ids; } void Spawner::NotifyOfEntityDeath(const LWOOBJID& objectID) { - for (std::function cb : m_SpawnedEntityDieCallbacks) { + for (const auto& cb : m_SpawnedEntityDieCallbacks) { cb(); } m_NeedsUpdate = true; - //m_RespawnTime = 10.0f; m_WaitTimes.push_back(0.0f); SpawnerNode* node; @@ -221,9 +225,7 @@ void Spawner::NotifyOfEntityDeath(const LWOOBJID& objectID) { if (it != m_Entities.end()) node = it->second; else return; - if (!node) { - return; - } + if (!node) return; for (size_t i = 0; i < node->entities.size();) { if (node->entities[i] && node->entities[i] == objectID) @@ -249,6 +251,6 @@ void Spawner::Activate() { } } -void Spawner::SetSpawnLot(LOT lot) { +void Spawner::SetSpawnLot(const LOT lot) { m_EntityInfo.lot = lot; } diff --git a/dZoneManager/Spawner.h b/dZoneManager/Spawner.h index 3f01161d..a5c9f355 100644 --- a/dZoneManager/Spawner.h +++ b/dZoneManager/Spawner.h @@ -11,12 +11,41 @@ #include "LDFFormat.h" #include "EntityInfo.h" +/** + * Any given spawner owns a certain number of spawner nodes + * these nodes are where entities are actually spawned + * The first spawner nodes waypoint in any given network contains the base config for all the spawner nodes + * Then each spawner node after the first may contain duplicate settings which override the base ones + * If spawner node 1 has an attached_path of "1", then all spawner nodes in this spawner network will have + * an attached_path of "1". + * Each spawner node can also specify attached_path of any other value and it will override the one provided by node 1. + * If a spawner node does NOT provide an override, the first one will be used + * I have no clue why the nodes are pointers, beats me + * sn = SpawnerNode + * Spawner + * ---------------- + * | sn | + * | sn | + * | sn | + * | | + * | sn | + * | sn | + * ----------------- + */ struct SpawnerNode { + // This spawner nodes position in the world NiPoint3 position = NiPoint3Constant::ZERO; + // The rotation of this spawner in the world NiQuaternion rotation = QuatUtils::IDENTITY; + // This spawners nodes ID in this spawner network uint32_t nodeID = 0; + // The max number of entities that can be spawned by this node uint32_t nodeMax = 1; + // The weight (chance) this spawner node has. Higher is more common + int32_t weight = 1; + // The IDs of entities spawned by this spawner node std::vector entities; + // The config of all entities spawned by this node LwoNameValue config; }; @@ -45,11 +74,10 @@ struct SpawnerInfo { class Spawner { public: - Spawner(SpawnerInfo info); - ~Spawner(); + Spawner(const SpawnerInfo& info); Entity* Spawn(); - Entity* Spawn(std::vector freeNodes, bool force = false); + Entity* Spawn(const std::vector& freeNodes, bool force = false); void Update(float deltaTime); void NotifyOfEntityDeath(const LWOOBJID& objectID); void Activate(); @@ -57,16 +85,16 @@ public: int32_t GetAmountSpawned() { return m_AmountSpawned; }; std::string GetName() { return m_Info.name; }; std::vector GetGroups() { return m_Info.groups; }; - void AddSpawnedEntityDieCallback(std::function callback); - void AddEntitySpawnedCallback(std::function callback); - void SetSpawnLot(LOT lot); + void AddSpawnedEntityDieCallback(const std::function callback); + void AddEntitySpawnedCallback(const std::function callback); + void SetSpawnLot(const LOT lot); void Reset(); void DestroyAllEntities(); void SoftReset(); - void SetRespawnTime(float time); - void SetNumToMaintain(int32_t value); + void SetRespawnTime(const float time); + void SetNumToMaintain(const int32_t value); bool GetIsSpawnSmashGroup() const { return m_SpawnSmashFoundGroup; }; - std::vector GetSpawnedObjectIDs() const; + const std::vector GetSpawnedObjectIDs() const; SpawnerInfo m_Info; bool m_Active = true; diff --git a/dZoneManager/Zone.cpp b/dZoneManager/Zone.cpp index fcb755f0..a5a8fa97 100644 --- a/dZoneManager/Zone.cpp +++ b/dZoneManager/Zone.cpp @@ -123,22 +123,25 @@ void Zone::LoadZoneIntoMemory() { if (!data) continue; if (data->GetKey() == u"spawner_node_id") { - node->nodeID = std::stoi(data->GetValueAsString()); + node->nodeID = GeneralUtils::TryParse(data->GetValueAsString(), 0); } else if (data->GetKey() == u"spawner_max_per_node") { - node->nodeMax = std::stoi(data->GetValueAsString()); + node->nodeMax = GeneralUtils::TryParse(data->GetValueAsString(), 0); } else if (data->GetKey() == u"groupID") { // Load object group - std::string groupStr = data->GetValueAsString(); - info.groups = GeneralUtils::SplitString(groupStr, ';'); + info.groups = GeneralUtils::SplitString(data->GetValueAsString(), ';'); if (info.groups.back().empty()) info.groups.erase(info.groups.end() - 1); } else if (data->GetKey() == u"grpNameQBShowBricks") { - if (data->GetValueAsString().empty()) continue; - /*std::string groupStr = data->GetValueAsString(); - info.groups.push_back(groupStr);*/ info.grpNameQBShowBricks = data->GetValueAsString(); } else if (data->GetKey() == u"spawner_name") { info.name = data->GetValueAsString(); + } else if (data->GetKey() == u"weight") { + node->weight = GeneralUtils::TryParse(data->GetValueAsString(), 1); + if (node->weight <= 0) { + LOG("Found a spawner with a weight of <= 0, is this intentional? %s:%i", info.name.c_str(), node->nodeID); + node->weight = 1; + } } } + info.nodes.push_back(node); } info.templateID = path.spawner.spawnedLOT; From a1891955e27450adaca611267673207762b70dc2 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sat, 20 Jun 2026 02:50:49 -0700 Subject: [PATCH 23/26] fix: leave team when fully logged out (#2007) * feat: spawner weights * remove ref * default weights to 1 * fix: remove team member if they've logged out tested that if i logout, after 20 seconds the team member is removed. --- dChatServer/PlayerContainer.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/dChatServer/PlayerContainer.cpp b/dChatServer/PlayerContainer.cpp index 148cab2a..56fc8974 100644 --- a/dChatServer/PlayerContainer.cpp +++ b/dChatServer/PlayerContainer.cpp @@ -105,15 +105,7 @@ void PlayerContainer::RemovePlayer(const LWOOBJID playerID) { auto* team = TeamContainer::GetTeam(playerID); if (team != nullptr) { - const auto memberName = GeneralUtils::UTF8ToUTF16(player.playerName); - - for (const auto memberId : team->memberIDs) { - const auto& otherMember = GetPlayerData(memberId); - - if (!otherMember) continue; - - TeamContainer::SendTeamSetOffWorldFlag(otherMember, playerID, { 0, 0, 0 }); - } + TeamContainer::RemoveMember(team, playerID, false, false, true); } ChatWeb::SendWSPlayerUpdate(player, eActivityType::PlayerLoggedOut); From 135aec8112d2aab24ff70785255be6e8eb9e16c8 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sat, 20 Jun 2026 15:10:00 -0700 Subject: [PATCH 24/26] feat: hatchlings (#2008) tested that hatchlings now function fixes #759 Update MovementAIComponent.cpp Update MovementAIComponent.cpp Update HatchlingPets.cpp --- dGame/Entity.cpp | 1 + dGame/dComponents/MovementAIComponent.cpp | 29 +++++++ dGame/dComponents/MovementAIComponent.h | 14 ++-- dScripts/02_server/CMakeLists.txt | 1 + dScripts/02_server/Objects/CMakeLists.txt | 11 ++- .../Objects/Hatchlings/CMakeLists.txt | 3 + .../Objects/Hatchlings/HatchlingPets.cpp | 83 +++++++++++++++++++ .../Objects/Hatchlings/HatchlingPets.h | 14 ++++ dScripts/CppScripts.cpp | 2 + 9 files changed, 151 insertions(+), 7 deletions(-) create mode 100644 dScripts/02_server/Objects/Hatchlings/CMakeLists.txt create mode 100644 dScripts/02_server/Objects/Hatchlings/HatchlingPets.cpp create mode 100644 dScripts/02_server/Objects/Hatchlings/HatchlingPets.h diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index c4265470..bad48b71 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -2237,6 +2237,7 @@ bool Entity::MsgRequestServerObjectInfo(GameMessages::RequestServerObjectInfo& r objectInfo.PushDebug("Template ID(LOT)") = GetLOT(); objectInfo.PushDebug("Object ID") = std::to_string(GetObjectID()); objectInfo.PushDebug("Spawner's Object ID") = std::to_string(GetSpawnerID()); + objectInfo.PushDebug("Owner override") = std::to_string(m_OwnerOverride); auto& componentDetails = objectInfo.PushDebug("Component Information"); for (const auto [id, component] : m_Components) { diff --git a/dGame/dComponents/MovementAIComponent.cpp b/dGame/dComponents/MovementAIComponent.cpp index c02d3a00..8a10df25 100644 --- a/dGame/dComponents/MovementAIComponent.cpp +++ b/dGame/dComponents/MovementAIComponent.cpp @@ -189,6 +189,15 @@ void MovementAIComponent::Update(const float deltaTime) { } } else { Stop(); + if (m_FollowedTarget != LWOOBJID_EMPTY) { + GameMessages::GetPosition getPos; + if (!getPos.Send(m_FollowedTarget)) { + LOG("Target %llu does not exist anymore to follow", m_FollowedTarget); + m_FollowedTarget = LWOOBJID_EMPTY; + } else { + SetDestination(getPos.pos); + } + } return; } } else { @@ -555,5 +564,25 @@ bool MovementAIComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInf pathCopy.pop(); } + movementInfo.PushDebug("Followed Target") = std::to_string(m_FollowedTarget); + return true; } + +void MovementAIComponent::FollowTarget(const LWOOBJID target) { + if (target == LWOOBJID_EMPTY) { + m_FollowedTarget = target; + return; + } + GameMessages::GetPosition getPos; + if (!getPos.Send(target)) { + LOG("Tried to follow target %llu but they don't exist", target); + m_FollowedTarget = LWOOBJID_EMPTY; + return; + } + + m_FollowedTarget = target; + SetMaxSpeed(1.0f); + m_CurrentSpeed = 1.0f; + SetDestination(getPos.pos); +} diff --git a/dGame/dComponents/MovementAIComponent.h b/dGame/dComponents/MovementAIComponent.h index af7da7f0..eeb871cf 100644 --- a/dGame/dComponents/MovementAIComponent.h +++ b/dGame/dComponents/MovementAIComponent.h @@ -31,27 +31,27 @@ struct MovementAIInfo { /** * The radius that the entity can wander in */ - float wanderRadius; + float wanderRadius{}; /** * The speed at which the entity wanders */ - float wanderSpeed; + float wanderSpeed{}; /** * This is only used for the emotes */ - float wanderChance; + float wanderChance{}; /** * The min amount of delay before wandering */ - float wanderDelayMin; + float wanderDelayMin{}; /** * The max amount of delay before wandering */ - float wanderDelayMax; + float wanderDelayMax{}; }; /** @@ -214,6 +214,8 @@ public: bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo); bool HasPath() const { return m_Path != nullptr; } + + void FollowTarget(const LWOOBJID target); private: /** @@ -337,6 +339,8 @@ private: // The number of waypoints that were on the path in the call to SetPath uint32_t m_CurrentPathWaypointCount{ 0 }; + + LWOOBJID m_FollowedTarget{ LWOOBJID_EMPTY }; }; #endif // MOVEMENTAICOMPONENT_H diff --git a/dScripts/02_server/CMakeLists.txt b/dScripts/02_server/CMakeLists.txt index 8114b226..048139a0 100644 --- a/dScripts/02_server/CMakeLists.txt +++ b/dScripts/02_server/CMakeLists.txt @@ -37,6 +37,7 @@ target_include_directories(dScriptsServerBase PUBLIC "." "Minigame" "Minigame/General" "Objects" + "Objects/Hatchlings" ) target_precompile_headers(dScriptsServerBase REUSE_FROM dScriptsBase) diff --git a/dScripts/02_server/Objects/CMakeLists.txt b/dScripts/02_server/Objects/CMakeLists.txt index 1b96d79f..da7e9b60 100644 --- a/dScripts/02_server/Objects/CMakeLists.txt +++ b/dScripts/02_server/Objects/CMakeLists.txt @@ -1,4 +1,11 @@ +add_subdirectory(Hatchlings) + set(DSCRIPTS_SOURCES_02_SERVER_OBJECTS "AgSurvivalBuffStation.cpp" - "StinkyFishTarget.cpp" - PARENT_SCOPE) + "StinkyFishTarget.cpp") + +foreach(file ${DSCRIPTS_SOURCES_02_SERVER_OBJECTS_HATCHLINGS}) + set(DSCRIPTS_SOURCES_02_SERVER_OBJECTS ${DSCRIPTS_SOURCES_02_SERVER_OBJECTS} "Hatchlings/${file}") +endforeach() + +set(DSCRIPTS_SOURCES_02_SERVER_OBJECTS ${DSCRIPTS_SOURCES_02_SERVER_OBJECTS} PARENT_SCOPE) diff --git a/dScripts/02_server/Objects/Hatchlings/CMakeLists.txt b/dScripts/02_server/Objects/Hatchlings/CMakeLists.txt new file mode 100644 index 00000000..0655ec64 --- /dev/null +++ b/dScripts/02_server/Objects/Hatchlings/CMakeLists.txt @@ -0,0 +1,3 @@ +set(DSCRIPTS_SOURCES_02_SERVER_OBJECTS_HATCHLINGS + "HatchlingPets.cpp" + PARENT_SCOPE) diff --git a/dScripts/02_server/Objects/Hatchlings/HatchlingPets.cpp b/dScripts/02_server/Objects/Hatchlings/HatchlingPets.cpp new file mode 100644 index 00000000..bc29ae8d --- /dev/null +++ b/dScripts/02_server/Objects/Hatchlings/HatchlingPets.cpp @@ -0,0 +1,83 @@ +#include "HatchlingPets.h" + +#include "Entity.h" +#include "MovementAIComponent.h" + +void HatchlingPets::OnStartup(Entity* self) { + self->SetVar(u"follow", false); + + self->SetProximityRadius(5, "StopFollow"); + self->SetProximityRadius(15, "Wander"); + self->SetProximityRadius(50, "Teleport"); + + Wander(*self, *self->GetOwner()); + self->AddComponent(-1, MovementAIInfo{ .wanderRadius = 2.5f }); +} + +void HatchlingPets::OnProximityUpdate(Entity* self, Entity* entering, std::string name, std::string status) { + auto* const parent = self->GetOwner(); + if (!entering || !entering->IsPlayer() || parent->GetObjectID() != entering->GetObjectID()) return; + + if (name == "StopFollow") { + if (status == "ENTER") { + if (self->GetVar(u"follow")) { + const auto randomWanderTime = GeneralUtils::GenerateRandomNumber(4, 9); + self->AddTimer("StartWander", randomWanderTime); + // stop following the player + auto* const movementAI = self->GetComponent(); + if (movementAI) { + movementAI->Stop(); + movementAI->FollowTarget(LWOOBJID_EMPTY); + } + self->SetVar(u"follow", false); + } + } + } else if (name == "Wander") { + if (status == "LEAVE") { + self->CancelAllTimers(); + // follow the player + auto* const movementAI = self->GetComponent(); + if (movementAI) { + movementAI->Stop(); + movementAI->FollowTarget(entering->GetObjectID()); + } + self->SetVar(u"follow", true); + } + } else if (name == "Teleport") { + if (status == "LEAVE") { + // stop following the player + auto* const movementAI = self->GetComponent(); + if (movementAI) { + movementAI->Stop(); + movementAI->FollowTarget(LWOOBJID_EMPTY); + } + GameMessages::GetPosition getPos; + getPos.Send(entering->GetObjectID()); + getPos.pos.z += 5.0f; + self->SetPosition(getPos.pos); + Game::entityManager->SerializeEntity(*self); + } + } +} + +void HatchlingPets::OnTimerDone(Entity* self, std::string timerName) { + if (timerName == "StartWander") { + Wander(*self, *self->GetOwner()); + } +} + +void HatchlingPets::Wander(Entity& self, Entity& player) { + GameMessages::GetPosition getPos; + if (!getPos.Send(player.GetObjectID())) { + LOG("Failed to get position for %llu", player.GetObjectID()); + return; + } + + const auto xWander = GeneralUtils::GenerateRandomNumber(0, 20) - 10.0f; + const auto zWander = GeneralUtils::GenerateRandomNumber(0, 20) - 10.0f; + getPos.pos.x += xWander; + getPos.pos.z += zWander; + auto* const movementAI = self.GetComponent(); + if (movementAI) movementAI->SetDestination(getPos.pos); + self.AddTimer("StartWander", GeneralUtils::GenerateRandomNumber(4, 9)); +} diff --git a/dScripts/02_server/Objects/Hatchlings/HatchlingPets.h b/dScripts/02_server/Objects/Hatchlings/HatchlingPets.h new file mode 100644 index 00000000..0dd2d5b6 --- /dev/null +++ b/dScripts/02_server/Objects/Hatchlings/HatchlingPets.h @@ -0,0 +1,14 @@ +#ifndef HATCHLINGPETS_H +#define HATCHLINGPETS_H + +#include "CppScripts.h" +#include "NiPoint3.h" + +class HatchlingPets : public CppScripts::Script { + void OnStartup(Entity* self) override; + void OnProximityUpdate(Entity* self, Entity* entering, std::string name, std::string status) override; + void OnTimerDone(Entity* self, std::string timerName) override; + void Wander(Entity& self, Entity& player); +}; + +#endif //!HATCHLINGPETS_H diff --git a/dScripts/CppScripts.cpp b/dScripts/CppScripts.cpp index a1f37153..fc955819 100644 --- a/dScripts/CppScripts.cpp +++ b/dScripts/CppScripts.cpp @@ -166,6 +166,7 @@ #include "AgSalutingNpcs.h" #include "BossSpiderQueenEnemyServer.h" #include "RockHydrantSmashable.h" +#include "HatchlingPets.h" // Misc Scripts #include "ExplodingAsset.h" @@ -423,6 +424,7 @@ namespace { {"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\\02_server\\Objects\\Hatchlings\\L_HATCHLING_PETS.lua", []() {return new HatchlingPets();}}, {"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();}}, From ccad52a5cea2c68291f9b77f31b993ce775cc8cd Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sat, 20 Jun 2026 15:10:13 -0700 Subject: [PATCH 25/26] feat: testmap improvements (#2009) * feat: testmap improvements fixes #1191 removed the force flag because it would only work to let you softlock your character. tested that taking the lego club door now spawns you at the lego club/ns lego club doors always vs letting you spawn where ever. tested that a testmap no longer spawns you where you last were in a zone and instead spawns you at the spawn point inspect allows you to inspect zoneControl and localCharacter now updated docs with the new info * Update Entity.cpp --- dGame/Entity.cpp | 7 +-- dGame/dUtilities/SlashCommandHandler.cpp | 4 +- .../SlashCommands/DEVGMCommands.cpp | 45 ++++++++++++------- dScripts/BaseConsoleTeleportServer.cpp | 3 ++ docs/Commands.md | 4 +- 5 files changed, 41 insertions(+), 22 deletions(-) diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index bad48b71..daffec1b 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -316,15 +316,16 @@ void Entity::Initialize() { controllablePhysics->LoadFromXml(m_Character->GetXMLDoc()); const auto mapID = Game::server->GetZoneID(); + const auto& targetSceneName = m_Character->GetTargetScene(); //If we came from another zone, put us in the starting loc - if (m_Character->GetZoneID() != Game::server->GetZoneID() || mapID == 1603) { // Exception for Moon Base as you tend to spawn on the roof. + // Exception for Moon Base as you tend to spawn on the roof. + // second exception if we have a specified targetScene since that would only be possible in a test map + if (m_Character->GetZoneID() != Game::server->GetZoneID() || mapID == 1603 || !targetSceneName.empty()) { NiPoint3 pos; NiQuaternion rot = QuatUtils::IDENTITY; - const auto& targetSceneName = m_Character->GetTargetScene(); auto* targetScene = Game::entityManager->GetSpawnPointEntity(targetSceneName); - if (m_Character->HasBeenToWorld(mapID) && targetSceneName.empty()) { pos = m_Character->GetRespawnPoint(mapID); rot = Game::zoneManager->GetZone()->GetSpawnRot(); diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 6c2d96ee..e4782db4 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -261,7 +261,7 @@ void SlashCommandHandler::Startup() { 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).", + .info = "Transfers you to the given zone by id and clone id and then spawns you at the specified spawn point if one was specified. Ignores instance-id for now.", .aliases = { "testmap", "tm" }, .handle = DEVGMCommands::TestMap, .requiredLevel = eGameMasterLevel::FORUM_MODERATOR @@ -468,7 +468,7 @@ void SlashCommandHandler::Startup() { 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", + .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 IDs of all components the entity has. Use `localCharacter` or `zoneControl` to inspect your current character or the zone control object.", .aliases = { "inspect" }, .handle = DEVGMCommands::Inspect, .requiredLevel = eGameMasterLevel::DEVELOPER diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp index 745798b8..5b464ef0 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp @@ -1051,7 +1051,8 @@ namespace DEVGMCommands { ChatPackets::SendSystemMessage(sysAddr, u"Requesting map change..."); LWOCLONEID cloneId = 0; - bool force = false; + LWOINSTANCEID instanceID{}; + std::string targetScene; const auto reqZoneOptional = GeneralUtils::TryParse(splitArgs[0]); if (!reqZoneOptional) { @@ -1061,29 +1062,34 @@ namespace DEVGMCommands { const LWOMAPID reqZone = reqZoneOptional.value(); if (splitArgs.size() > 1) { - auto index = 1; - - if (splitArgs[index] == "force") { - index++; - - force = true; + const auto cloneIdOptional = GeneralUtils::TryParse(splitArgs[1]); + if (!cloneIdOptional) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid clone id."); + return; } - if (splitArgs.size() > index) { - const auto cloneIdOptional = GeneralUtils::TryParse(splitArgs[index]); - if (!cloneIdOptional) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid clone id."); + cloneId = cloneIdOptional.value(); + + if (splitArgs.size() > 2) { + const auto instanceIDVal = GeneralUtils::TryParse(splitArgs[2]); + if (!instanceIDVal) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid instance id."); return; } - cloneId = cloneIdOptional.value(); + + instanceID = instanceIDVal.value(); + } + + if (splitArgs.size() > 3) { + targetScene = splitArgs[3]; } } const auto objid = entity->GetObjectID(); - if (force || Game::zoneManager->CheckIfAccessibleZone(reqZone)) { // to prevent tomfoolery + if (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) { + ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, reqZone, cloneId, false, [objid, targetScene](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; @@ -1101,6 +1107,7 @@ namespace DEVGMCommands { entity->GetCharacter()->SetZoneID(zoneID); entity->GetCharacter()->SetZoneInstance(zoneInstance); entity->GetCharacter()->SetZoneClone(zoneClone); + entity->GetCharacter()->SetTargetScene(targetScene); entity->GetComponent()->SetLastRocketConfig(u""); } @@ -1513,7 +1520,15 @@ namespace DEVGMCommands { void Inspect(Entity* entity, const SystemAddress& sysAddr, const std::string args) { const auto splitArgs = GeneralUtils::SplitString(args, ' '); if (splitArgs.empty()) return; - const auto idParsed = GeneralUtils::TryParse(splitArgs[0]); + std::optional idIntermed; + if (splitArgs[0] == "zoneControl") { + idIntermed = 0x3FFF'FFFFFFFE; + } else if (splitArgs[0] == "localCharacter") { + idIntermed = entity->GetObjectID(); + } else { + idIntermed = GeneralUtils::TryParse(splitArgs[0]); + } + const auto idParsed = idIntermed; // First try to get the object by its ID if provided. // Second try to get the object by player name. diff --git a/dScripts/BaseConsoleTeleportServer.cpp b/dScripts/BaseConsoleTeleportServer.cpp index d172696c..7392f759 100644 --- a/dScripts/BaseConsoleTeleportServer.cpp +++ b/dScripts/BaseConsoleTeleportServer.cpp @@ -94,6 +94,9 @@ void BaseConsoleTeleportServer::TransferPlayer(Entity* self, Entity* player, int const auto& teleportZone = self->GetVar(u"transferZoneID"); + auto* const character = player->GetCharacter(); + if (character && self->HasVar(u"spawnPoint")) character->SetTargetScene(self->GetVarAsString(u"spawnPoint")); + auto* characterComponent = player->GetComponent(); if (characterComponent) characterComponent->SendToZone(GeneralUtils::TryParse(GeneralUtils::UTF16ToWTF8(teleportZone), 0)); diff --git a/docs/Commands.md b/docs/Commands.md index 702f64cc..2d93adeb 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -122,7 +122,7 @@ These commands are primarily for development and testing. The usage of many of t |leave-zone|`/leave-zone`|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: `/leavezone`.|0| |resurrect|`/resurrect`|Resurrects the player.|8| |setminifig|`/setminifig `|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.|1| -|testmap|`/testmap (force) (clone-id)`|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: `/tm`.|1| +|testmap|`/testmap (clone-id) (instance-id) (spawn-point)`|Transfers you to the given zone by id and clone id and then spawns you at the specified spawn point if one was specified. Ignores instance-id for now. Aliases: `/tm`.|1| |reportproxphys|`/reportproxphys`|Prints to console the position and radius of proximity sensors.|9| |spawnphysicsverts|`/spawnphysicsverts`|Spawns a 1x1 brick at all vertices of phantom physics objects|8| |teleport|`/teleport (y) `|Teleports you. If no Y is given, you are teleported to the height of the terrain or physics object at (x, z). Any of the coordinates can use the syntax of an exact position (10.0), or a relative position (~+10.0). A ~ means use the current value of that axis as the base value. Addition or subtraction is supported (~+10) (~-10). If source player and target player are players that exist in the world, then the source player will be teleported to target player. Aliases: `/tele`, `/tp`.|6| @@ -145,7 +145,7 @@ These commands are primarily for development and testing. The usage of many of t |getnavmeshheight|`/getnavmeshheight`|Display the navmesh height at your current position|8| |giveuscore|`/giveuscore `|Gives uscore|8| |gmadditem|`/gmadditem (count)`|Adds the given item to your inventory by id. Aliases: `/give`.|8| -|inspect|`/inspect (-m | -a | -s | -p | -f (faction) | -t)`|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 IDs of all components the entity has. See detailed usage in the DLU docs|8| +|inspect|`/inspect (-m | -a | -s | -p | -f (faction) | -t)`|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 IDs of all components the entity has. Use `localCharacter` or `zoneControl` to inspect your current character or the zone control object.|8| |list-spawns|`/list-spawns`|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: `/listspawns`.|8| |locrow|`/locrow`|Prints your current position and rotation information to the console|8| |lookup|`/lookup `|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.|8| From ccc029424c96fca64a8a7cbc7521c041992a6ee2 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sat, 20 Jun 2026 16:53:49 -0700 Subject: [PATCH 26/26] fix: fv mpc in the tree (#2010) tested that it now spawns in the lower path point instead. fixes #741 --- dGame/Entity.cpp | 15 ++++++++------- dGame/dGameMessages/GameMessages.cpp | 15 ++++++++------- dGame/dGameMessages/GameMessages.h | 4 +++- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index daffec1b..069d984a 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -613,14 +613,15 @@ void Entity::Initialize() { if (rebuildResetTime != 0.0f) { quickBuildComponent->SetResetTime(rebuildResetTime); - - // Known bug with moving platform in FV that casues it to build at the end instead of the start. - // This extends the smash time so players can ride up the lift. - if (m_TemplateID == 9483) { - quickBuildComponent->SetResetTime(quickBuildComponent->GetResetTime() + 25); - } } + const auto objectID = GetObjectID(); + // FV tree handler for when built so it sets the state to moving at the correct time + if (GetLOT() == 9483) quickBuildComponent->AddQuickBuildCompleteCallback([objectID](Entity* user) { + auto* const entity = Game::entityManager->GetEntity(objectID); + if (entity) GameMessages::SendPlatformResync(entity, UNASSIGNED_SYSTEM_ADDRESS, false, 0, 1, 1, eMovementPlatformState::Moving, true); + }); + const auto activityID = GetVar(u"activityID"); if (activityID > 0) { @@ -1616,7 +1617,7 @@ void Entity::Kill(Entity* murderer, const eKillType killType) { const auto& grpNameQBShowBricks = GetVarAsString(u"grpNameQBShowBricks"); if (!grpNameQBShowBricks.empty()) { - for (auto* const spawner : Game::zoneManager->GetSpawnersByName(grpNameQBShowBricks)) if (spawner) spawner->Spawn(); + for (auto* const spawner : Game::zoneManager->GetSpawnersByName(grpNameQBShowBricks)) if (spawner) spawner->Spawn(); for (auto* const spawner : Game::zoneManager->GetSpawnersInGroup(grpNameQBShowBricks)) if (spawner) spawner->Spawn(); } diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 6b57e177..0acb4452 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -366,18 +366,19 @@ void GameMessages::SendResetMissions(Entity* entity, const SystemAddress& sysAdd void GameMessages::SendPlatformResync(Entity* entity, const SystemAddress& sysAddr, bool bStopAtDesiredWaypoint, int iIndex, int iDesiredWaypointIndex, int nextIndex, - eMovementPlatformState movementState) { + eMovementPlatformState movementState, bool special) { CBITSTREAM; CMSGHEADER; + const auto objID = entity->GetObjectID(); const auto lot = entity->GetLOT(); - if (lot == 12341 || lot == 5027 || lot == 5028 || lot == 14335 || lot == 14447 || lot == 14449 || lot == 11306 || lot == 11308) { + if (lot == 12341 || lot == 5027 || lot == 5028 || lot == 14335 || lot == 14447 || lot == 14449 || lot == 11306 || lot == 11308 || lot == 9483) { iDesiredWaypointIndex = (lot == 11306 || lot == 11308) ? 1 : 0; - iIndex = 0; - nextIndex = 0; + iIndex = lot == 9483 ? 1 : 0; + nextIndex = lot == 9483 && !special ? 1 : 0; bStopAtDesiredWaypoint = true; - movementState = eMovementPlatformState::Stationary; + movementState = lot == 9483 && !special ? eMovementPlatformState::Stopped : eMovementPlatformState::Stationary; } bitStream.Write(entity->GetObjectID()); @@ -6484,8 +6485,8 @@ namespace GameMessages { } void TeamPickupItem::Serialize(RakNet::BitStream& stream) const { - stream.Write(lootID); - stream.Write(lootOwnerID); + stream.Write(lootID); + stream.Write(lootOwnerID); } void ToggleGMInvis::Serialize(RakNet::BitStream& stream) const { diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index eef568d3..7a34de6c 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -103,9 +103,11 @@ namespace GameMessages { void SendPlayNDAudioEmitter(Entity* entity, const SystemAddress& sysAddr, std::string audioGUID); void SendStartPathing(Entity* entity); + + // special is for the FV tree platform, feature is complete if we just do that so meh void SendPlatformResync(Entity* entity, const SystemAddress& sysAddr, bool bStopAtDesiredWaypoint = false, int iIndex = 0, int iDesiredWaypointIndex = 1, int nextIndex = 1, - eMovementPlatformState movementState = eMovementPlatformState::Moving); + eMovementPlatformState movementState = eMovementPlatformState::Moving, bool special = false); void SendResetMissions(Entity* entity, const SystemAddress& sysAddr, const int32_t missionid = -1); void SendRestoreToPostLoadStats(Entity* entity, const SystemAddress& sysAddr);