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/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); 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..0f515d30 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,89 @@ 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)); + } + + 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) { + 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/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/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/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/Entity.cpp b/dGame/Entity.cpp index e9e4b4e8..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(); @@ -427,7 +428,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); @@ -949,13 +950,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 +988,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 @@ -1613,26 +1614,10 @@ 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); - - 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 @@ -2046,13 +2031,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 { @@ -2084,24 +2063,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() : ""; } @@ -2270,6 +2238,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) { @@ -2277,7 +2246,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/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..a7ca59c1 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; @@ -38,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(); } @@ -58,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++; } @@ -84,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/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/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 07826f48..a1c7c0e9 100644 --- a/dGame/dBehaviors/NpcCombatSkillBehavior.h +++ b/dGame/dBehaviors/NpcCombatSkillBehavior.h @@ -8,6 +8,9 @@ public: float m_npcSkillTime; + float m_maxRange{}; + float m_minRange{}; + /* * Inherited */ 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 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 } 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/dGame/dComponents/BaseCombatAIComponent.cpp b/dGame/dComponents/BaseCombatAIComponent.cpp index 5f6399da..89c39c33 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 @@ -23,7 +25,6 @@ #include "SkillComponent.h" #include "QuickBuildComponent.h" #include "DestroyableComponent.h" -#include "Metrics.hpp" #include "CDComponentsRegistryTable.h" #include "CDPhysicsComponentTable.h" #include "dNavMesh.h" @@ -44,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(); @@ -64,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); @@ -211,8 +205,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; } @@ -247,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; } } @@ -318,12 +316,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; } @@ -334,14 +334,23 @@ 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; } - const auto result = skillComponent->CalculateBehavior(entry.skillId, entry.behavior->m_behaviorId, LWOOBJID_EMPTY); + randomizedWeight -= entry.combatWeight; + + // if the weight is still greater than 0 continue to the next rolled skill + if (randomizedWeight > 0) { + continue; + } + + const auto result = skillComponent->CalculateBehavior(entry.skillId, entry.behavior->m_behaviorId, GetTarget()); if (result.success) { if (m_MovementAI != nullptr) { @@ -356,8 +365,6 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) { entry.cooldown = entry.abilityCooldown + m_SkillTime; - m_SkillEntries[i] = entry; - break; } } @@ -619,6 +626,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(); @@ -747,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; } @@ -863,12 +875,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; @@ -906,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 95eb75ce..728e0d4a 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{}; }; /** @@ -181,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 @@ -236,6 +239,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 @@ -394,9 +399,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; 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 f2b277df..9d9ffb80 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); @@ -740,18 +736,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); } @@ -778,7 +773,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()) { @@ -1076,7 +1071,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); @@ -1898,7 +1893,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(); } } @@ -1914,7 +1909,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 230d88d9..d7b67cfc 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/MovementAIComponent.cpp b/dGame/dComponents/MovementAIComponent.cpp index 3519712a..8a10df25 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 { /** @@ -31,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: @@ -60,7 +64,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) { @@ -125,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; @@ -160,32 +168,43 @@ 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; - 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) { - m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum); + // 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(); + 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 { - m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum); - Stop(); - return; - } - } else { - m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum); - SetDestination(m_CurrentPath.top().position); + SetDestination(m_CurrentPath.top().position); - m_CurrentPath.pop(); + m_CurrentPath.pop(); + } } } @@ -212,8 +231,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; @@ -423,7 +441,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) { @@ -453,12 +533,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"); @@ -483,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 2a095716..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{}; }; /** @@ -212,8 +212,18 @@ public: bool IsPaused() const { return m_Paused; } bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo); + + bool HasPath() const { return m_Path != nullptr; } + + void FollowTarget(const LWOOBJID target); 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 @@ -329,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/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/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/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/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 3af6cb48..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); @@ -3221,7 +3218,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 +3241,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 +3255,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 +3321,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; @@ -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..eef568d3 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 { @@ -961,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/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/Preconditions.cpp b/dGame/dUtilities/Preconditions.cpp index f0cacad0..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. } @@ -230,6 +243,7 @@ PreconditionExpression::PreconditionExpression(const std::string& conditions) { case '&': case ';': case '(': + case ':': b << conditions.substr(i + 1); done = true; break; 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 }; 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 f1b92e26..5b464ef0 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" @@ -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) { @@ -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""); } @@ -1268,10 +1275,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) { @@ -1288,18 +1295,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"; @@ -1517,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/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/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); } 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/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); }; 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); } } 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/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/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/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/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/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/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") { 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/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/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/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..8a649389 --- /dev/null +++ b/dScripts/02_server/Map/njhub/OldManNPC.cpp @@ -0,0 +1,29 @@ +#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) { + 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(); + 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/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/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/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/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) { 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/dScripts/CppScripts.cpp b/dScripts/CppScripts.cpp index 4c0ba749..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" @@ -278,6 +279,7 @@ #include "NjEarthPetServer.h" #include "NjDragonEmblemChestServer.h" #include "NjNyaMissionitems.h" +#include "OldManNPC.h" // Scripted equipment #include "AnvilOfArmor.h" @@ -338,6 +340,8 @@ #include "ImaginationBackPack.h" #include "NsWinterRaceServer.h" +#include "RegisterWithZoneControl.h" + #include #include #include @@ -420,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();}}, @@ -628,6 +633,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();}}, @@ -661,6 +667,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();}}, @@ -706,7 +713,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 = { @@ -730,6 +738,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 }; }; @@ -743,7 +756,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; 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/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 48a11cf2..e4d324f1 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" @@ -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); @@ -1538,7 +1539,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; 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.cpp b/dZoneManager/Spawner.cpp index df966055..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(); @@ -55,16 +57,13 @@ 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); }); } } } -Spawner::~Spawner() { - -} - Entity* Spawner::Spawn() { std::vector freeNodes; for (SpawnerNode* node : m_Info.nodes) { @@ -76,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; @@ -92,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) { @@ -147,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; } @@ -176,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) { @@ -197,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; @@ -220,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) @@ -248,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 701ceb29..a5c9f355 100644 --- a/dZoneManager/Spawner.h +++ b/dZoneManager/Spawner.h @@ -11,13 +11,42 @@ #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; - std::vector config; + // The config of all entities spawned by this node + LwoNameValue config; }; struct SpawnerInfo { @@ -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 282f4c45..a5a8fa97 100644 --- a/dZoneManager/Zone.cpp +++ b/dZoneManager/Zone.cpp @@ -101,36 +101,47 @@ 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 (LDFBaseData* data : waypoint.config) { + for (const auto& data : waypoint.config | std::views::values) { 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; @@ -465,7 +476,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/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: 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| 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