diff --git a/.env.example b/.env.example index 6ea77deb4..ca862dbfd 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,7 @@ # Full path to the LEGO Universe client CLIENT_PATH=./client # Updates NET_VERSION in CMakeVariables.txt -NET_VERSION=171022 +CLIENT_NET_VERSION=171022 # make sure this is a long random string # generate a "SHA 256-bit Key" from here: https://gchq.github.io/CyberChef/#recipe=Pseudo-Random_Number_Generator(256,'Hex') ACCOUNT_MANAGER_SECRET= diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index ec4fa8c95..660a502b4 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,29 +1,154 @@ -# GitHub Copilot Instructions - - * c++20 standard, please use the latest features except NO modules. - * use `.contains` for searching in associative containers - * use const as much as possible. If it can be const, it should be made const - * DO NOT USE const_cast EVER. - * use `cstdint` bitwidth types ALWAYS for integral types. - * NEVER use std::wstring. If wide strings are necessary, use std::u16string with conversion utilties in GeneralUtils.h. - * Functions are ALWAYS PascalCase. - * local variables are camelCase - * NEVER use snake case - * indentation is TABS, not SPACES. - * TABS are 4 spaces by default - * Use trailing braces ALWAYS - * global variables are prefixed with `g_` - * if global variables or functions are needed, they should be located in an anonymous namespace - * Use `GeneralUtils::TryParse` for ANY parsing of strings to integrals. - * Use brace initialization when possible. - * ALWAYS default initialize variables. - * Pointers should be avoided unless necessary. Use references when the pointer has been checked and should not be null - * headers should be as compact as possible. Do NOT include extra data that isnt needed. - * Remember to include logs (LOG macro uses printf style logging) while putting verbose logs under LOG_DEBUG. - * NEVER USE `RakNet::BitStream::ReadBit` - * NEVER assume pointers are good, always check if they are null. Once a pointer is checked and is known to be non-null, further accesses no longer need checking - * Be wary of TOCTOU. Prevent all possible issues relating to TOCTOU. - * new memory allocations should never be used unless absolutely necessary. - * new for reconstruction of objects is allowed - * Prefer following the format of the file over correct formatting. Consistency over correctness. - * When using auto, ALWAYS put a * for pointers. \ No newline at end of file +# Darkflame Universe Server Development Instructions + +Darkflame Universe (DLU) is a LEGO Universe server emulator written in C++ with a multi-server architecture (AuthServer, ChatServer, MasterServer, WorldServer). Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here. + +## Working Effectively + +### Bootstrap, Build, and Test - REQUIRED STEPS +Execute these commands in order for ANY development work. NEVER CANCEL builds - they take time but work reliably: + +```bash +# 1. Install system dependencies (Ubuntu/Debian) +sudo apt update && sudo apt install -y build-essential gcc zlib1g-dev libssl-dev openssl mariadb-server cmake + +# 2. Initialize git submodules (CRITICAL - project won't build without this) +git submodule update --init --recursive + +# 3. Build using the provided script +./build.sh -j2 +``` +- **Build time: ~6 minutes. NEVER CANCEL. Set timeout to 720+ seconds (12+ minutes).** +- **Uses CMake 3.25-3.31 (confirmed working with 3.31.6)** +- **Requires g++11+ (confirmed working with g++ 13.3.0)** + +### Alternative Build Methods +```bash +# Using CMake presets (CI-style) +cmake --workflow --preset ci-ubuntu-22.04 + +# Manual CMake (for custom configurations) +mkdir -p build && cd build +cmake -DCMAKE_BUILD_TYPE="Release" .. +cmake --build . --config Release -j2 +``` +- **Same timing: ~6 minutes. NEVER CANCEL. Set timeout to 720+ seconds.** + +### Run Tests +```bash +cd build +ctest --output-on-failure +``` +- **Test time: <4 seconds. Set timeout to 30+ seconds.** +- **91 tests run, all should pass** +- **Tests are built automatically when ENABLE_TESTING=1 in CMakeVariables.txt** + +### Database Setup (for runtime testing) +```bash +# Start MariaDB +sudo systemctl start mysql + +# Create test database and user +sudo mysql -e "CREATE USER 'testuser'@'localhost' IDENTIFIED BY 'testpass'; GRANT ALL ON *.* TO 'testuser'@'localhost' WITH GRANT OPTION; FLUSH PRIVILEGES; CREATE DATABASE testdarkflame;" +``` + +## Validation + +### Build Validation +- **ALWAYS run the bootstrapping steps first** before making any code changes +- **ALWAYS build and test your changes** before considering them complete +- Build output should include all server binaries: AuthServer, ChatServer, MasterServer, WorldServer +- Build directory contains required files: `*.ini` configs, `navmeshes/`, `migrations/`, `vanity/`, `blocklist.dcf`, `libmariadbcpp.so` + +### Runtime Validation +The servers can be started for basic validation: +```bash +cd build +./MasterServer +``` +- **Server will start but complain about missing client files (this is expected)** +- **Database connections work with proper configuration in sharedconfig.ini** +- **For full server testing, LEGO Universe client files are required (not available in this repository)** + +### Code Validation +**ALWAYS validate your changes by**: +1. Building successfully with no new compilation errors +2. Running the test suite and confirming all tests pass +3. Starting MasterServer to verify basic functionality +4. **Use .editorconfig** - code style uses tabs (width=4), Unix line endings, trailing whitespace removal + +## Common Tasks + +### Project Structure +``` +/home/runner/work/DarkflameServer/DarkflameServer/ +├── dAuthServer/ # Authentication server code +├── dChatServer/ # Chat server code +├── dMasterServer/ # Master server (main coordinator) +├── dWorldServer/ # World/game server code +├── dCommon/ # Shared common utilities +├── dDatabase/ # Database abstraction layer +├── dGame/ # Core game logic, components, behaviors +├── dScripts/ # Game scripts (NPCs, quests, etc.) +├── dNet/ # Network utilities +├── dPhysics/ # Physics integration +├── tests/ # Unit tests (GoogleTest) +├── migrations/ # Database schema migrations +├── thirdparty/ # External dependencies +├── build.sh # Main build script +├── CMakeVariables.txt # Build configuration variables +└── CMakePresets.json # CMake preset configurations +``` + +### Key Files to Know +- **CMakeVariables.txt**: Build configuration (testing enabled, MariaDB jobs, etc.) +- **build/sharedconfig.ini**: Database connection, client location, server settings +- **build/masterconfig.ini**: Master server port and startup configuration +- **CONTRIBUTING.md**: Code style guidelines and commit message format +- **docs/Commands.md**: Complete list of in-game server commands + +### Build Configuration +Located in `CMakeVariables.txt`: +- `ENABLE_TESTING=1` - Unit tests enabled (keep enabled) +- `MARIADB_CONNECTOR_COMPILE_JOBS=1` - Parallel compilation jobs for MariaDB connector +- `CDCLIENT_CACHE_ALL=0` - Database caching strategy + +### Common Commands Reference +```bash +# Build from clean state +rm -rf build && ./build.sh -j2 + +# Run specific test +cd build && ctest -R "TestName" --output-on-failure + +# Check which servers were built +cd build && ls -la *Server + +# View build configuration +cat CMakeVariables.txt + +# Check git submodules status +git submodule status +``` + +### Important Notes +- **Client files are NOT included** - this is only the server emulator +- **Database can use SQLite or MariaDB** - SQLite recommended for development since it's lighter and doesn't require an external service +- **Multi-server architecture** requires all 4 servers to run a complete setup +- **Network ports**: Auth (1001), Chat (2005), Master (2000), World (3000+) +- **Development uses Debug builds**, production uses Release builds +- **GM level 0** = normal player, **GM level 8-9** = admin privileges + +## Troubleshooting +- **"Asset bundle not found"**: Expected without LEGO Universe client files +- **"Submodule errors"**: Run `git submodule update --init --recursive` +- **"CMake version errors"**: Requires CMake 3.25-3.31 +- **"MariaDB connection errors"**: Check database setup and sharedconfig.ini +- **"Permission denied on port"**: Run `sudo setcap 'cap_net_bind_service=+ep' AuthServer` for ports <1024 + +### CI Information +- **GitHub Actions** runs builds on Windows, Ubuntu, and macOS +- **Build matrix** tests multiple configurations via CMake presets +- **All tests must pass** for CI to succeed +- **Build artifacts** are automatically generated and uploaded + +**Remember: This is a complex game server requiring LEGO Universe client files for full functionality, but the server has the capability to mock everything that's needed to test without the client since cdclient can be mocked and the database can be mocked as well.** diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index c26f0dee5..3963fdbb8 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 769303989..cd7a75cee 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 @@ -219,7 +215,7 @@ file(ARCHIVE_EXTRACT INPUT ${PROJECT_BINARY_DIR}/navmeshes.zip DESTINATION ${PRO file(REMOVE ${PROJECT_BINARY_DIR}/navmeshes.zip) # Copy vanity files on first build -set(VANITY_FILES "CREDITS.md" "INFO.md" "TESTAMENT.md" "root.xml" "dev-tribute.xml" "atm.xml" "demo.xml") +set(VANITY_FILES "CREDITS.md" "INFO.md" "TESTAMENT.md" "dev-tribute.xml" "atm.xml" "demo.xml") foreach(file ${VANITY_FILES}) configure_file("${CMAKE_SOURCE_DIR}/vanity/${file}" "${CMAKE_BINARY_DIR}/vanity/${file}" COPYONLY) diff --git a/CMakePresets.json b/CMakePresets.json index 2819f3207..bc6c8dad6 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/README.md b/README.md index 9816db917..60198f3b6 100644 --- a/README.md +++ b/README.md @@ -419,8 +419,8 @@ at once. For that: - [Install Docker Desktop](https://docs.docker.com/get-docker/) - Open the directory that contains your LU Client - - If the `legouniverse.exe` is in a subfolder called `client`, you're good to go. There may also be a folder `versions`. - - Otherwise, create a new `client` folder and move the exe and everything else (e.g. `res` and `locale`) in there. This is necessary to work around a bug in the client that will prevent that you to log back in after getting disconnected. + - If the `legouniverse.exe` is in a subfolder called `client`, copy & paste it to the current directory. + - Create the `client` folder if it does not exist. If you created the `client` folder, move everything (e.g. `res` and `locale`) in there. This is necessary to work around a bug in the client that will prevent that you to log back in after getting disconnected. - Download the [docker-compose.yml](docker-compose.yml) file and place it next to `client`. - Download the [.env.example](.env.example) file and place it next to `client` with the file name `.env` - You may get warnings that this name starts with a dot, acknowledge those, this is intentional. Depending on your operating system, you may need to activate showing hidden files (e.g. Ctrl-H in Gnome on Linux) and/or file extensions ("File name extensions" in the "View" tab on Windows). @@ -429,10 +429,23 @@ at once. For that: - Avoid `:` and `@` characters - Once the database user is created, changing the password will not update it, so the server will just fail to connect. - Set `EXTERNAL_IP` to your LAN IP or public IP if you want to host the game for friends & family +- The directory structure should now be: +``` + your-client-directory/ + ├── docker-compose.yml + ├── .env + └── client/ + ├── legouniverse.exe + ├── client/ + │ ├── res/ + │ └── locale/ + ├── versions/ + └── patcher/ +``` - Open a terminal in the folder with the `docker-compose.yml` and `client` - Run `docker compose up -d` - This might require `sudo` on Linux, and a recent version of [docker compose](https://docs.docker.com/compose/install/) -- Run `docker exec -it dlu-darkflameserver-1 /app/MasterServer -a` and follow the instructions to create the initial admin account +- Run `docker exec -it darkflameserver /app/MasterServer -a` and follow the instructions to create the initial admin account - Open to access Nexus Dashboard with the admin account to create normal users - Set `AUTHSERVERIP=0:localhost` in `client/boot.cfg` - Replace `localhost` with the value of `EXTERNAL_IP` if you changed that earlier. diff --git a/dCommon/CMakeLists.txt b/dCommon/CMakeLists.txt index ba67974e8..0e7c7d52f 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 da28ae6e0..04ba643ac 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; - - std::pair keyValue; - keyValue.first = format.substr(0, equalsPosition); - keyValue.second = format.substr(equalsPosition + 1, format.size()); - - std::u16string key = GeneralUtils::ASCIIToUTF16(keyValue.first); - - auto colonPosition = keyValue.second.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; - - 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 (ldfTypeAndValue.second.size() == 0 && !(ldfTypeAndValue.first == "0" || ldfTypeAndValue.first == "13")) return nullptr; - - 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; - } - - 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_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()); - - break; - } - - 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; - } - - 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_U32: - { - uint32_t data; - - 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; - } - 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; + 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)) { + + const std::string_view keyValue = format.substr(0, equalsPosition); + const std::string_view typeAndValue = format.substr(equalsPosition + 1, format.size()); + + const auto key = GeneralUtils::ASCIIToUTF16(keyValue); + + 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) { + const std::string_view ldfType = typeAndValue.substr(0, colonPosition); + const std::string_view ldfValue = typeAndValue.substr(colonPosition + 1, typeAndValue.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; + } + + 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()); + } + + 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_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; + } + + 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(); + } + } + + if (parsed) toReturn.reset(new LDFData(key, data)); + 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(); + } + } + + if (parsed) toReturn.reset(new LDFData(key, data)); + break; + } + + 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_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 7c2e939b6..81a6648c6 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 @@ class LDFBaseData { 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 @@ class LDFData: public LDFBaseData { return this->GetValueString(); } - LDFBaseData* Copy() const override { - return new LDFData(key, value); + std::unique_ptr Copy() const override { + return std::make_unique>(key, value); } inline static const T Default = {}; @@ -226,4 +227,81 @@ template<> inline std::string LDFData::GetValueString() const { return template<> inline std::string LDFData::GetValueString() const { return this->value; } -#endif //!__LDFFORMAT__H__ +struct LwoNameValue { + using LDFPtr = std::unique_ptr; + using ValueType = std::map; + + LwoNameValue& operator=(const LwoNameValue& other) { + this->values = other.Copy(); + return *this; + } + + template + void Insert(const std::u16string& key, const T& value) { + this->values.insert_or_assign(key, std::unique_ptr(std::make_unique>(key, value))); + } + + void Insert(const std::u16string& key, const char* value) { + this->Insert(key, value); + } + + void Insert(const std::u16string& key, const char16_t* value) { + this->Insert(key, value); + } + + template + void Insert(const std::string& key, const T& value) { + this->Insert(GeneralUtils::UTF8ToUTF16(key), value); + } + + void Insert(const std::string& key, const char* value) { + this->Insert(GeneralUtils::UTF8ToUTF16(key), value); + } + + void Insert(const std::string& key, const char16_t* value) { + this->Insert(GeneralUtils::UTF8ToUTF16(key), value); + } + + const LDFPtr& ParseInsert(const std::string& data) { + LDFPtr toInsert(LDFBaseData::DataFromString(data)); + return toInsert ? + this->values.insert_or_assign(toInsert->GetKey(), std::move(toInsert)).first->second : + this->values.insert_or_assign(u"FAILED_TO_PARSE_" + GeneralUtils::UTF8ToUTF16(data), std::make_unique>("", "")).first->second; + } + + const LDFPtr& ParseInsert(const std::u16string& data) { + return this->ParseInsert(GeneralUtils::UTF16ToWTF8(data)); + } + + ValueType::const_iterator begin() const { + return this->values.cbegin(); + } + + ValueType::const_iterator end() const { + return this->values.cend(); + } + + void Erase(const std::u16string& key) { + this->values.erase(key); + } + + void Erase(const std::string& key) { + this->Erase(GeneralUtils::ASCIIToUTF16(key)); + } + + LwoNameValue() = default; + + LwoNameValue(const LwoNameValue& other) { + this->values = other.Copy(); + } + + ValueType values; +private: + ValueType Copy() const { + ValueType copy; + for (const auto& [key, value] : this->values) copy.insert_or_assign(key, value->Copy()); + return copy; + } +}; + +#endif //!LDFFORMAT_H diff --git a/dCommon/NiPoint3.h b/dCommon/NiPoint3.h index b968a4dea..d1b65c93c 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 a4546e2cd..c4ef06616 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 fc1ccdeda..c3ed1b145 100644 --- a/dCommon/dEnums/dCommonVars.h +++ b/dCommon/dEnums/dCommonVars.h @@ -111,18 +111,6 @@ struct LWOZONEID { constexpr LWOSCENEID LWOSCENEID_INVALID = -1; -struct LWONameValue { - uint32_t length = 0; //!< The length of the name - std::u16string name; //!< The name - - LWONameValue() = default; - - LWONameValue(const std::u16string& name) { - this->name = name; - this->length = static_cast(name.length()); - } -}; - struct FriendData { public: bool isOnline = false; diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index d88aa1d52..c42654709 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -949,13 +949,13 @@ void Entity::SetGMLevel(eGameMasterLevel value) { } } -void Entity::WriteLDFData(const std::vector& ldf, RakNet::BitStream& outBitStream) const { +void Entity::WriteLDFData(const LwoNameValue& ldf, RakNet::BitStream& outBitStream) const { RakNet::BitStream settingStream; - int32_t numberOfValidKeys = ldf.size(); + int32_t numberOfValidKeys = ldf.values.size(); // Writing keys value pairs the client does not expect to receive or interpret will result in undefined behavior, // so we need to filter out any keys that are not valid and fix the number of valid keys to be correct. - for (LDFBaseData* data : ldf) { + for (const auto& data : ldf.values | std::views::values) { if (data && data->GetValueType() != eLDFType::LDF_TYPE_UNKNOWN) { data->WriteToPacket(settingStream); } else { @@ -987,16 +987,16 @@ void Entity::WriteBaseReplicaData(RakNet::BitStream& outBitStream, eReplicaPacke const auto& syncLDF = GetVar>(u"syncLDF"); // Only sync for models. - if (!m_Settings.empty() && (GetComponent() && !GetComponent())) { + if (!m_Settings.values.empty() && (GetComponent() && !GetComponent())) { outBitStream.Write1(); // Has ldf data WriteLDFData(m_Settings, outBitStream); } else if (!syncLDF.empty()) { // Find all the ldf data we need to write - std::vector ldfData; - ldfData.reserve(m_Settings.size()); + LwoNameValue ldfData; for (const auto& data : syncLDF) { - ldfData.push_back(GetVarData(data)); + const auto* toInsert = GetVarData(data); + if (toInsert) ldfData.values.insert_or_assign(data, toInsert->Copy()); } outBitStream.Write1(); // Has ldf data @@ -1615,23 +1615,8 @@ void Entity::Kill(Entity* murderer, const eKillType killType) { const auto& grpNameQBShowBricks = GetVarAsString(u"grpNameQBShowBricks"); if (!grpNameQBShowBricks.empty()) { - auto spawners = Game::zoneManager->GetSpawnersByName(grpNameQBShowBricks); - - Spawner* spawner = nullptr; - - if (!spawners.empty()) { - spawner = spawners[0]; - } else { - spawners = Game::zoneManager->GetSpawnersInGroup(grpNameQBShowBricks); - - if (!spawners.empty()) { - spawner = spawners[0]; - } - } - - if (spawner != nullptr) { - spawner->Spawn(); - } + for (auto* const spawner : Game::zoneManager->GetSpawnersByName(grpNameQBShowBricks)) if (spawner) spawner->Spawn(); + for (auto* const spawner : Game::zoneManager->GetSpawnersInGroup(grpNameQBShowBricks)) if (spawner) spawner->Spawn(); } // Track a player being smashed @@ -2045,13 +2030,7 @@ void Entity::SetI64(const std::u16string& name, const int64_t value) { } bool Entity::HasVar(const std::u16string& name) const { - for (auto* data : m_Settings) { - if (data->GetKey() == name) { - return true; - } - } - - return false; + return m_Settings.values.contains(name); } uint16_t Entity::GetNetworkId() const { @@ -2083,24 +2062,13 @@ void Entity::SendNetworkVar(const std::string& data, const SystemAddress& sysAdd GameMessages::SendSetNetworkScriptVar(this, sysAddr, data); } -LDFBaseData* Entity::GetVarData(const std::u16string& name) const { - for (auto* data : m_Settings) { - if (data == nullptr) { - continue; - } - - if (data->GetKey() != name) { - continue; - } - - return data; - } - - return nullptr; +const LDFBaseData* const Entity::GetVarData(const std::u16string& name) const { + const auto itr = m_Settings.values.find(name); + return itr != m_Settings.values.cend() ? itr->second.get() : nullptr; } std::string Entity::GetVarAsString(const std::u16string& name) const { - auto* data = GetVarData(name); + const auto* const data = GetVarData(name); return data ? data->GetValueAsString() : ""; } @@ -2276,7 +2244,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 c07bd3f42..a8781d425 100644 --- a/dGame/Entity.h +++ b/dGame/Entity.h @@ -97,9 +97,9 @@ class Entity { 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 @@ class Entity { 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 @@ class Entity { 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 @@ class Entity { // 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 @@ class Entity { 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); - - if (data == nullptr) { - auto* data = new LDFData(name, value); - - m_Settings.push_back(data); + InsertLnvData(m_Settings, name, value); +} - 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); - - if (typed == nullptr) { - return; - } + return itr; +} - 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; - - for (auto* data : m_NetworkSettings) { - if (data->GetKey() != name) - continue; - - 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; - } + const auto itr = InsertNetworkVar(name, value); - if (newData == nullptr) { - newData = new LDFData(name, value); - } + SendNetworkVar(itr->second->GetString(), sysAddr); +} - 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; - - auto* typed = dynamic_cast*>(data); - if (typed == nullptr) - continue; + T toReturn = LDFData::Default; - 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 fee2b66ad..81b1e1c92 100644 --- a/dGame/EntityManager.cpp +++ b/dGame/EntityManager.cpp @@ -304,6 +304,19 @@ std::vector EntityManager::GetEntitiesInGroup(const std::string& group) return entitiesInGroup; } +std::vector EntityManager::GetEntitiesBySpawnerID(const LWOOBJID& objectID) const { + std::vector entitiesSpawnedBy; + if (!GetEntity(objectID)) return entitiesSpawnedBy; + + for (auto* entity : m_Entities | std::views::values) { + if (entity->GetSpawnerID() == objectID) { + entitiesSpawnedBy.push_back(entity); + } + } + + return entitiesSpawnedBy; +} + std::vector EntityManager::GetEntitiesByComponent(const eReplicaComponentType componentType) const { std::vector withComp; if (componentType != eReplicaComponentType::INVALID) { diff --git a/dGame/EntityManager.h b/dGame/EntityManager.h index 26daf7508..c8e450a70 100644 --- a/dGame/EntityManager.h +++ b/dGame/EntityManager.h @@ -30,6 +30,7 @@ class EntityManager { void DestroyEntity(Entity* entity); Entity* GetEntity(const LWOOBJID& objectId) const; std::vector GetEntitiesInGroup(const std::string& group); + std::vector GetEntitiesBySpawnerID(const LWOOBJID& objectID) const; std::vector GetEntitiesByComponent(eReplicaComponentType componentType) const; std::vector GetEntitiesByLOT(const LOT& lot) const; std::vector GetEntitiesByProximity(NiPoint3 reference, float radius) const; diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index b8e40154c..a7ca59c1e 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -39,10 +39,10 @@ Leaderboard::~Leaderboard() { } void Leaderboard::Clear() { - for (auto& entry : entries) for (auto ldfData : entry) delete ldfData; + entries.clear(); } -inline void WriteLeaderboardRow(std::ostringstream& leaderboard, const uint32_t& index, LDFBaseData* data) { +inline void WriteLeaderboardRow(std::ostringstream& leaderboard, const uint32_t& index, const std::unique_ptr& data) { leaderboard << "\nResult[0].Row[" << index << "]." << data->GetString(); } @@ -59,8 +59,8 @@ void Leaderboard::Serialize(RakNet::BitStream& bitStream) const { int32_t rowNumber = 0; for (auto& entry : entries) { - for (auto* data : entry) { - WriteLeaderboardRow(leaderboard, rowNumber, data); + for (const auto& data : entry.values | std::views::values) { + if (data) WriteLeaderboardRow(leaderboard, rowNumber, data); } rowNumber++; } @@ -85,57 +85,56 @@ void QueryToLdf(Leaderboard& leaderboard, const std::vector for (const auto& leaderboardEntry : leaderboardEntries) { constexpr int32_t MAX_NUM_DATA_PER_ROW = 9; auto& entry = leaderboard.PushBackEntry(); - entry.reserve(MAX_NUM_DATA_PER_ROW); - entry.push_back(new LDFData(u"CharacterID", leaderboardEntry.charId)); - entry.push_back(new LDFData(u"LastPlayed", leaderboardEntry.lastPlayedTimestamp)); - entry.push_back(new LDFData(u"NumPlayed", leaderboardEntry.numTimesPlayed)); - entry.push_back(new LDFData(u"name", GeneralUtils::ASCIIToUTF16(leaderboardEntry.name))); - entry.push_back(new LDFData(u"RowNumber", leaderboardEntry.ranking)); + entry.Insert(u"CharacterID", leaderboardEntry.charId); + entry.Insert(u"LastPlayed", leaderboardEntry.lastPlayedTimestamp); + entry.Insert(u"NumPlayed", leaderboardEntry.numTimesPlayed); + entry.Insert(u"name", GeneralUtils::ASCIIToUTF16(leaderboardEntry.name)); + entry.Insert(u"RowNumber", leaderboardEntry.ranking); switch (leaderboard.GetLeaderboardType()) { case ShootingGallery: - entry.push_back(new LDFData(u"Score", leaderboardEntry.primaryScore)); + entry.Insert(u"Score", leaderboardEntry.primaryScore); // Score:1 - entry.push_back(new LDFData(u"Streak", leaderboardEntry.secondaryScore)); + entry.Insert(u"Streak", leaderboardEntry.secondaryScore); // Streak:1 - entry.push_back(new LDFData(u"HitPercentage", leaderboardEntry.tertiaryScore)); + entry.Insert(u"HitPercentage", leaderboardEntry.tertiaryScore); // HitPercentage:3 between 0 and 1 break; case Racing: - entry.push_back(new LDFData(u"BestTime", leaderboardEntry.primaryScore)); + entry.Insert(u"BestTime", leaderboardEntry.primaryScore); // BestLapTime:3 - entry.push_back(new LDFData(u"BestLapTime", leaderboardEntry.secondaryScore)); + entry.Insert(u"BestLapTime", leaderboardEntry.secondaryScore); // BestTime:3 - entry.push_back(new LDFData(u"License", 1)); + entry.Insert(u"License", 1); // License:1 - 1 if player has completed mission 637 and 0 otherwise - entry.push_back(new LDFData(u"NumWins", leaderboardEntry.numWins)); + entry.Insert(u"NumWins", leaderboardEntry.numWins); // NumWins:1 break; case UnusedLeaderboard4: - entry.push_back(new LDFData(u"Points", leaderboardEntry.primaryScore)); + entry.Insert(u"Points", leaderboardEntry.primaryScore); // Points:1 break; case MonumentRace: - entry.push_back(new LDFData(u"Time", leaderboardEntry.primaryScore)); + entry.Insert(u"Time", leaderboardEntry.primaryScore); // Time:1(?) break; case FootRace: - entry.push_back(new LDFData(u"Time", leaderboardEntry.primaryScore)); + entry.Insert(u"Time", leaderboardEntry.primaryScore); // Time:1 break; case Survival: - entry.push_back(new LDFData(u"Points", leaderboardEntry.primaryScore)); + entry.Insert(u"Points", leaderboardEntry.primaryScore); // Points:1 - entry.push_back(new LDFData(u"Time", leaderboardEntry.secondaryScore)); + entry.Insert(u"Time", leaderboardEntry.secondaryScore); // Time:1 break; case SurvivalNS: - entry.push_back(new LDFData(u"Wave", leaderboardEntry.primaryScore)); + entry.Insert(u"Wave", leaderboardEntry.primaryScore); // Wave:1 - entry.push_back(new LDFData(u"Time", leaderboardEntry.secondaryScore)); + entry.Insert(u"Time", leaderboardEntry.secondaryScore); // Time:1 break; case Donations: - entry.push_back(new LDFData(u"Score", leaderboardEntry.primaryScore)); + entry.Insert(u"Score", leaderboardEntry.primaryScore); // Score:1 break; case None: diff --git a/dGame/LeaderboardManager.h b/dGame/LeaderboardManager.h index 760d282e6..c99634c17 100644 --- a/dGame/LeaderboardManager.h +++ b/dGame/LeaderboardManager.h @@ -70,8 +70,7 @@ class Leaderboard { private: - using LeaderboardEntry = std::vector; - using LeaderboardEntries = std::vector; + using LeaderboardEntries = std::vector; LeaderboardEntries entries; LWOOBJID relatedPlayer; @@ -81,7 +80,7 @@ class Leaderboard { bool weekly; uint32_t numResults; public: - LeaderboardEntry& PushBackEntry() { + LwoNameValue& PushBackEntry() { return entries.emplace_back(); } diff --git a/dGame/dBehaviors/TacArcBehavior.cpp b/dGame/dBehaviors/TacArcBehavior.cpp index bea246aa9..076fc5157 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/dComponents/CharacterComponent.cpp b/dGame/dComponents/CharacterComponent.cpp index a703d884c..0c5230f86 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 ebccc662a..905913671 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 a7e8d5edb..0d410e82d 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,15 @@ void InventoryComponent::AddItem( auto* inventory = GetInventory(inventoryType); - if (!config.empty() || bound) { + if (!config.values.empty() || bound) { + if (inventoryType == eInventoryType::VENDOR_BUYBACK && buybackItems.size() >= 27) { + const auto oldestId = buybackItems.back(); + buybackItems.pop_back(); + if (auto* oldItem = FindItemById(oldestId)) { + oldItem->SetCount(0, true); + } + } + const auto slot = preferredSlot != -1 && inventory->IsSlotEmpty(preferredSlot) ? preferredSlot : inventory->FindEmptySlot(); if (slot == -1) { @@ -215,6 +223,10 @@ void InventoryComponent::AddItem( auto* item = new Item(lot, inventory, slot, count, config, parent, showFlyingLoot, isModMoveAndEquip, subKey, bound, lootSourceType); + if (inventoryType == eInventoryType::VENDOR_BUYBACK) { + buybackItems.push_front(item->GetId()); + } + if (missions != nullptr && !IsTransferInventory(inventoryType)) { missions->Progress(eMissionTaskType::GATHER, lot, LWOOBJID_EMPTY, "", count, IsTransferInventory(inventorySourceType)); } @@ -261,6 +273,14 @@ void InventoryComponent::AddItem( left -= size; + if (inventoryType == eInventoryType::VENDOR_BUYBACK && buybackItems.size() >= 27) { + const auto oldestId = buybackItems.back(); + buybackItems.pop_back(); + if (auto* oldItem = FindItemById(oldestId)) { + oldItem->SetCount(0, true); + } + } + int32_t slot; if (preferredSlot != -1 && inventory->IsSlotEmpty(preferredSlot)) { @@ -301,6 +321,10 @@ void InventoryComponent::AddItem( } auto* item = new Item(lot, inventory, slot, size, {}, parent, showFlyingLoot, isModMoveAndEquip, subKey, false, lootSourceType); + if (inventoryType == eInventoryType::VENDOR_BUYBACK) { + buybackItems.push_front(item->GetId()); + } + isModMoveAndEquip = false; } @@ -352,11 +376,15 @@ void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType in auto* origin = item->GetInventory(); + if (origin->GetType() == eInventoryType::VENDOR_BUYBACK) { + buybackItems.erase(std::ranges::find(buybackItems, item->GetId())); + } + const auto lot = item->GetLot(); 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 +407,7 @@ void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType in isModMoveAndEquip = false; } } else { - std::vector config; - - for (auto* const data : item->GetConfig()) { - config.push_back(data->Copy()); - } + const auto config = item->GetConfig(); const auto delta = std::min(item->GetCount(), count); @@ -744,18 +768,17 @@ void InventoryComponent::Serialize(RakNet::BitStream& outBitStream, const bool b outBitStream.Write0(); - bool flag = !item.config.empty(); + bool flag = !item.config.values.empty(); outBitStream.Write(flag); if (flag) { RakNet::BitStream ldfStream; - ldfStream.Write(item.config.size()); // Key count - for (LDFBaseData* data : item.config) { + ldfStream.Write(item.config.values.size()); // Key count + for (const auto& data : item.config.values | std::views::values) { if (data->GetKey() == u"assemblyPartLOTs") { std::string newRocketStr = data->GetValueAsString() + ";"; GeneralUtils::ReplaceInString(newRocketStr, "+", ";"); - LDFData* ldf_data = new LDFData(u"assemblyPartLOTs", GeneralUtils::ASCIIToUTF16(newRocketStr)); - ldf_data->WriteToPacket(ldfStream); - delete ldf_data; + LDFData ldf_data(u"assemblyPartLOTs", GeneralUtils::ASCIIToUTF16(newRocketStr)); + ldf_data.WriteToPacket(ldfStream); } else { data->WriteToPacket(ldfStream); } @@ -782,7 +805,7 @@ void InventoryComponent::Update(float deltaTime) { } } -void InventoryComponent::UpdateSlot(const std::string& location, EquippedItem item, bool keepCurrent) { +void InventoryComponent::UpdateSlot(const std::string& location, const EquippedItem& item, bool keepCurrent) { const auto index = m_Equipped.find(location); if (index != m_Equipped.end()) { @@ -1080,7 +1103,7 @@ void InventoryComponent::PushEquippedItems() { } void InventoryComponent::PopEquippedItems() { - auto current = m_Equipped; + const auto current = m_Equipped; for (const auto& pair : current) { auto* const item = FindItemById(pair.second.id); @@ -1876,7 +1899,7 @@ bool InventoryComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo slot.PushDebug("Bind on equip") = item->GetInfo().isBOE; slot.PushDebug("Is currently bound") = item->GetBound(); auto& extra = slot.PushDebug("Extra Info"); - for (const auto* const setting : item->GetConfig()) { + for (const auto& setting : item->GetConfig().values | std::views::values) { if (setting) extra.PushDebug(GeneralUtils::UTF16ToWTF8(setting->GetKey())) = setting->GetValueAsString(); } } @@ -1892,7 +1915,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 beaf0efb1..e05fb870c 100644 --- a/dGame/dComponents/InventoryComponent.h +++ b/dGame/dComponents/InventoryComponent.h @@ -4,6 +4,7 @@ #define INVENTORYCOMPONENT_H #include +#include #include @@ -134,7 +135,7 @@ class InventoryComponent final : public Component { 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 +214,7 @@ class InventoryComponent final : public Component { * @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 @@ -531,6 +532,8 @@ class InventoryComponent final : public Component { void LoadGroupXml(const tinyxml2::XMLElement& groups); void UpdateGroupXml(tinyxml2::XMLElement& groups) const; + + std::deque buybackItems; }; #endif diff --git a/dGame/dComponents/ModelComponent.cpp b/dGame/dComponents/ModelComponent.cpp index 14093b153..48585237b 100644 --- a/dGame/dComponents/ModelComponent.cpp +++ b/dGame/dComponents/ModelComponent.cpp @@ -221,8 +221,8 @@ void ModelComponent::RemoveBehavior(MoveToInventoryMessage& msg, const bool keep auto* const inventoryComponent = playerEntity->GetComponent(); if (inventoryComponent && !behavior.GetIsLoot()) { // config is owned by the item - std::vector config; - config.push_back(new LDFData(u"userModelName", behavior.GetName())); + LwoNameValue config; + config.Insert(u"userModelName", behavior.GetName()); inventoryComponent->AddItem(7965, 1, eLootSourceType::PROPERTY, eInventoryType::BEHAVIORS, config, LWOOBJID_EMPTY, true, false, msg.GetBehaviorId()); } } diff --git a/dGame/dComponents/PhantomPhysicsComponent.h b/dGame/dComponents/PhantomPhysicsComponent.h index 115783aca..9a9e18801 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 3c62dc528..54371fde3 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); - - 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); + 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); + + inventoryComponent->AddItem(6662, 1, eLootSourceType::DELETION, eInventoryType::MODELS_IN_BBB, actualConfig, LWOOBJID_EMPTY, false, false, spawnerId); auto* item = inventoryComponent->FindItemBySubKey(spawnerId); if (item == nullptr) { @@ -615,23 +603,23 @@ void PropertyManagementComponent::Load() { info.spawnerID = databaseModel.id; - std::vector settings; + LwoNameValue& settings = node->config; //BBB property models need to have extra stuff set for them: if (databaseModel.lot == 14) { LWOOBJID blueprintID = databaseModel.ugcId; - settings.push_back(new LDFData(u"blueprintid", blueprintID)); - settings.push_back(new LDFData(u"componentWhitelist", 1)); - settings.push_back(new LDFData(u"modelType", 2)); - settings.push_back(new LDFData(u"propertyObjectID", true)); - settings.push_back(new LDFData(u"userModelID", databaseModel.id)); + settings.Insert(u"blueprintid", blueprintID); + settings.Insert(u"componentWhitelist", 1); + settings.Insert(u"modelType", 2); + settings.Insert(u"propertyObjectID", true); + settings.Insert(u"userModelID", databaseModel.id); } else { - settings.push_back(new LDFData(u"modelType", 2)); - settings.push_back(new LDFData(u"userModelID", databaseModel.id)); - settings.push_back(new LDFData(u"modelBehaviors", 0)); - settings.push_back(new LDFData(u"propertyObjectID", true)); - settings.push_back(new LDFData(u"componentWhitelist", 1)); + settings.Insert(u"modelType", 2); + settings.Insert(u"userModelID", databaseModel.id); + settings.Insert(u"modelBehaviors", 0); + settings.Insert(u"propertyObjectID", true); + settings.Insert(u"componentWhitelist", 1); } std::ostringstream userModelBehavior; @@ -646,9 +634,7 @@ void PropertyManagementComponent::Load() { firstAdded = true; } - settings.push_back(new LDFData(u"userModelBehaviors", userModelBehavior.str())); - - node->config = settings; + settings.Insert(u"userModelBehaviors", userModelBehavior.str()); const auto spawnerId = Game::zoneManager->MakeSpawner(info); diff --git a/dGame/dComponents/RacingControlComponent.cpp b/dGame/dComponents/RacingControlComponent.cpp index 06cd02be0..9eb43b9e3 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 b4300d945..bedb636b6 100644 --- a/dGame/dComponents/RacingControlComponent.h +++ b/dGame/dComponents/RacingControlComponent.h @@ -152,7 +152,7 @@ class RacingControlComponent final : public ActivityComponent { */ 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 87b6e6e1c..2c9be34b2 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 5ac27bb60..1d4eb29d6 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 e16c315d1..68eaa95d2 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 5edd47f24..6b57e177b 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #include "RakString.h" //CDB includes: @@ -465,20 +466,20 @@ void GameMessages::SendAddItemToInventoryClientSync(Entity* entity, const System bitStream.Write(lootSourceType != eLootSourceType::NONE); // Loot source if (lootSourceType != eLootSourceType::NONE) bitStream.Write(lootSourceType); - LWONameValue extraInfo; + std::u16string extraInfo; - auto config = item->GetConfig(); + const auto& config = item->GetConfig(); - for (auto* data : config) { - extraInfo.name += GeneralUtils::ASCIIToUTF16(data->GetString()) + u","; + for (const auto& data : config.values | std::views::values) { + extraInfo += GeneralUtils::ASCIIToUTF16(data->GetString()) + u","; } - if (extraInfo.name.length() > 0) extraInfo.name.pop_back(); // remove the last comma + if (extraInfo.length() > 0) extraInfo.pop_back(); // remove the last comma - bitStream.Write(extraInfo.name.size()); - if (extraInfo.name.size() > 0) { - for (uint32_t i = 0; i < extraInfo.name.size(); ++i) { - bitStream.Write(extraInfo.name[i]); + bitStream.Write(extraInfo.size()); + if (extraInfo.size() > 0) { + for (uint32_t i = 0; i < extraInfo.size(); ++i) { + bitStream.Write(extraInfo[i]); } bitStream.Write(0x00); } @@ -743,13 +744,9 @@ void GameMessages::SendBroadcastTextToChatbox(Entity* entity, const SystemAddres bitStream.Write(entity->GetObjectID()); bitStream.Write(MessageType::Game::BROADCAST_TEXT_TO_CHATBOX); - LWONameValue attribs; - attribs.name = attrs; - attribs.length = attrs.size(); - - bitStream.Write(attribs.length); - for (uint32_t i = 0; i < attribs.length; ++i) { - bitStream.Write(attribs.name[i]); + bitStream.Write(attrs.size()); + for (uint32_t i = 0; i < attrs.size(); ++i) { + bitStream.Write(attrs[i]); } bitStream.Write(0x00); // Null Terminator @@ -2621,11 +2618,11 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent info.spawnerID = entity->GetObjectID(); info.spawnerNodeID = 0; - info.settings.push_back(new LDFData(u"blueprintid", blueprintIDs[i])); - info.settings.push_back(new LDFData(u"componentWhitelist", 1)); - info.settings.push_back(new LDFData(u"modelType", 2)); - info.settings.push_back(new LDFData(u"propertyObjectID", true)); - info.settings.push_back(new LDFData(u"userModelID", modelIDs[i])); + info.settings.Insert(u"blueprintid", blueprintIDs[i]); + info.settings.Insert(u"componentWhitelist", 1); + info.settings.Insert(u"modelType", 2); + info.settings.Insert(u"propertyObjectID", true); + info.settings.Insert(u"userModelID", modelIDs[i]); Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr); if (newEntity) { Game::entityManager->ConstructEntity(newEntity); @@ -5266,7 +5263,8 @@ void GameMessages::HandleRemoveItemFromInventory(RakNet::BitStream& inStream, En int eInvType = INVENTORY_MAX; bool eLootTypeSourceIsDefault = false; int eLootTypeSource = LOOTTYPE_NONE; - LWONameValue extraInfo; + int32_t extraInfoLength = 0; + std::u16string extraInfo; bool forceDeletion = true; bool iLootTypeSourceIsDefault = false; LWOOBJID iLootTypeSource = LWOOBJID_EMPTY; @@ -5292,12 +5290,12 @@ void GameMessages::HandleRemoveItemFromInventory(RakNet::BitStream& inStream, En if (eInvTypeIsDefault) inStream.Read(eInvType); inStream.Read(eLootTypeSourceIsDefault); if (eLootTypeSourceIsDefault) inStream.Read(eLootTypeSource); - inStream.Read(extraInfo.length); - if (extraInfo.length > 0) { - for (uint32_t i = 0; i < extraInfo.length; ++i) { + inStream.Read(extraInfoLength); + if (extraInfoLength > 0) { + for (uint32_t i = 0; i < extraInfoLength; ++i) { uint16_t character; inStream.Read(character); - extraInfo.name.push_back(character); + extraInfo.push_back(character); } uint16_t nullTerm; inStream.Read(nullTerm); @@ -5505,10 +5503,8 @@ void GameMessages::HandleModularBuildFinish(RakNet::BitStream& inStream, Entity* //inv->UnequipItem(inv->GetItemStackByLOT(6086, eInventoryType::ITEMS)); // take off the thinking cap //Game::entityManager->SerializeEntity(entity); - const auto moduleAssembly = new LDFData(u"assemblyPartLOTs", modules); - - std::vector config; - config.push_back(moduleAssembly); + LwoNameValue config; + config.Insert(u"assemblyPartLOTs", modules); LWOOBJID newID = ObjectIDManager::GetPersistentID(); @@ -5754,7 +5750,6 @@ void GameMessages::HandleUseNonEquipmentItem(RakNet::BitStream& inStream, Entity void GameMessages::HandleMatchRequest(RakNet::BitStream& inStream, Entity* entity) { LWOOBJID activator; - //std::map additionalPlayers; uint32_t playerChoicesLen; std::string playerChoices; int type; @@ -6309,7 +6304,7 @@ namespace GameMessages { bitStream.Write(id); std::string toWrite; - for (const auto* item : localizeParams) { + for (const auto& item : localizeParams | std::views::values) { toWrite += item->GetString() + "\n"; } if (!toWrite.empty()) toWrite.pop_back(); diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index b7558d4bf..8937e0287 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -13,6 +13,7 @@ #include "Brick.h" #include "MessageType/Game.h" #include "eGameMasterLevel.h" +#include "LDFFormat.h" class AMFBaseValue; class AMFArrayValue; @@ -711,7 +712,7 @@ namespace GameMessages { bool translate{}; int32_t time{}; std::u16string id{}; - std::vector localizeParams{}; + LwoNameValue localizeParams{}; std::u16string imageName{}; std::u16string text{}; void Serialize(RakNet::BitStream& bitStream) const override; @@ -734,7 +735,7 @@ namespace GameMessages { struct ConfigureRacingControl : public GameMsg { ConfigureRacingControl() : GameMsg(MessageType::Game::CONFIGURE_RACING_CONTROL) {} - std::vector> racingSettings{}; + LwoNameValue racingSettings{}; }; struct SetModelToBuild : public GameMsg { @@ -754,7 +755,7 @@ namespace GameMessages { struct ActivityNotify : public GameMsg { ActivityNotify() : GameMsg(MessageType::Game::ACTIVITY_NOTIFY) {} - std::vector> notification{}; + LwoNameValue notification{}; }; struct ShootingGalleryFire : public GameMsg { diff --git a/dGame/dInventory/EquippedItem.h b/dGame/dInventory/EquippedItem.h index 78da9e8d9..ec10f95b4 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/Inventory.cpp b/dGame/dInventory/Inventory.cpp index f5742165b..7be38a423 100644 --- a/dGame/dInventory/Inventory.cpp +++ b/dGame/dInventory/Inventory.cpp @@ -86,13 +86,11 @@ void Inventory::SetSize(const uint32_t value) { int32_t Inventory::FindEmptySlot() { if (free <= 6) // Up from 1 { - if (type != ITEMS && type != VAULT_ITEMS && type != eInventoryType::VAULT_MODELS) { + if (type != ITEMS && type != VAULT_ITEMS && type != eInventoryType::VAULT_MODELS && type != eInventoryType::VENDOR_BUYBACK) { uint32_t newSize = size; if (type == MODELS) { newSize = 240; - } else if (type == eInventoryType::VENDOR_BUYBACK) { - newSize += 9u; } else { newSize += 10u; } diff --git a/dGame/dInventory/Item.cpp b/dGame/dInventory/Item.cpp index 19b536a42..f18cd0b3d 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 846a7aa73..baccf28fb 100644 --- a/dGame/dInventory/Item.h +++ b/dGame/dInventory/Item.h @@ -40,7 +40,7 @@ class Item final 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 @@ class Item final 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 @@ class Item final * 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 @@ class Item final /** * 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 872796a8b..f23deb6e4 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/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 6c2d96ee3..bf1bcda46 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -475,6 +475,15 @@ void SlashCommandHandler::Startup() { }; RegisterCommand(InspectCommand); + Command InspectDespawnCommand{ + .help = "Despawn the closest inspected entity", + .info = "Finds the closest entity with the given component or LDF variable and despawns it", + .aliases = { "inspect-despawn", "inspect-destroy" }, + .handle = DEVGMCommands::InspectDespawn, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(InspectDespawnCommand); + Command ListSpawnsCommand{ .help = "List spawn points for players", .info = "Lists all the character spawn points in the zone. Additionally, this command will display the current scene that plays when the character lands in the next zone, if there is one.", @@ -638,8 +647,8 @@ void SlashCommandHandler::Startup() { RegisterCommand(SetUiStateCommand); Command SpawnCommand{ - .help = "Spawns an object at your location by id", - .info = "Spawns an object at your location by id", + .help = "Spawns an object by id", + .info = "Spawns an object by id at your position, or with optional position, rotation, and group name", .aliases = { "spawn" }, .handle = DEVGMCommands::Spawn, .requiredLevel = eGameMasterLevel::DEVELOPER @@ -808,6 +817,150 @@ void SlashCommandHandler::Startup() { }; RegisterCommand(DeleteInvenCommand); + Command GetPlayerIDCommand{ + .help = "Gets a player's object ID", + .info = "Gets your object ID, or that of another character by name if provided", + .aliases = { "playerid", "getplayerid" }, + .handle = DEVGMCommands::GetPlayerID, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(GetPlayerIDCommand); + + Command ListGroupCommand{ + .help = "Lists all entities in the specified group", + .info = "Lists all entities in the specified group", + .aliases = { "listgroup", "list-group" }, + .handle = DEVGMCommands::ListGroup, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ListGroupCommand); + + Command ListSpawnedByPlayerCommand{ + .help = "Lists all entities spawned by a player", + .info = "Lists all entities spawned by a player", + .aliases = { "listspawnedbyplayer", "list-spawned-by-player" }, + .handle = DEVGMCommands::ListSpawnedByPlayer, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ListSpawnedByPlayerCommand); + + Command ListSpawnedBySenderCommand{ + .help = "Lists all entities you have spawned", + .info = "Lists all entities you have spawned", + .aliases = { "myspawns", "my-spawns", "listmyspawns", "list-my-spawns" }, + .handle = DEVGMCommands::ListSpawnedBySender, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ListSpawnedBySenderCommand); + + Command ListSpawnedBySpawnerIDCommand{ + .help = "Lists all entities spawned by the specified object ID", + .info = "Lists all entities spawned by the specified object ID", + .aliases = { "listspawnedbyid", "list-spawned-by-id" }, + .handle = DEVGMCommands::ListSpawnedBySpawnerID, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ListSpawnedBySpawnerIDCommand); + + Command ListAllPlayerSpawnsCommand{ + .help = "Lists all entities spawned by current players", + .info = "Lists all entities spawned by current players", + .aliases = { "listallplayerspawns", "list-all-player-spawns" }, + .handle = DEVGMCommands::ListAllPlayerSpawns, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ListAllPlayerSpawnsCommand); + + Command DespawnGroupCommand{ + .help = "Despawns all entities in the specified group", + .info = "Despawns all entities in the specified group", + .aliases = { "despawngroup", "despawn-group", "deletegroup", "delete-group" }, + .handle = DEVGMCommands::DespawnGroup, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(DespawnGroupCommand); + + Command DespawnSpawnedByPlayerCommand{ + .help = "Despawns all entities spawned by a player", + .info = "Despawns all entities spawned by a player", + .aliases = { "despawnplayerspawns", "despawn-player-spawns", "deleteplayerspawns", "delete-player-spawns" }, + .handle = DEVGMCommands::DespawnSpawnedByPlayer, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(DespawnSpawnedByPlayerCommand); + + Command DespawnSpawnedBySenderCommand{ + .help = "Despawns all entities you have spawned", + .info = "Despawns all entities you have spawned", + .aliases = { "despawnmyspawns", "despawn-my-spawns", "deletemyspawns", "delete-my-spawns" }, + .handle = DEVGMCommands::DespawnSpawnedBySender, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(DespawnSpawnedBySenderCommand); + + Command DespawnSpawnedBySpawnerIDCommand{ + .help = "Despawns all entities spawned by the specified object ID", + .info = "Despawns all entities spawned by the specified object ID", + .aliases = { "despawnbyspawnerid", "despawn-by-spawner-id", "deletebyspawnerid", "delete-by-spawner-id" }, + .handle = DEVGMCommands::DespawnSpawnedBySpawnerID, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(DespawnSpawnedBySpawnerIDCommand); + + Command DespawnAllPlayerSpawnsCommand{ + .help = "Despawns all entities spawned by current players", + .info = "Despawns all entities spawned by current players", + .aliases = { "despawnallplayerspawns", "despawn-all-player-spawns", "deleteallplayerspawns", "delete-all-player-spawns" }, + .handle = DEVGMCommands::DespawnAllPlayerSpawns, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(DespawnAllPlayerSpawnsCommand); + + Command SaveGroupCommand{ + .help = "Saves all entities in the specified group to the Bug Report Table", + .info = "Saves all entities in the specified group to the Bug Report Table", + .aliases = { "savegroup", "save-group" }, + .handle = DEVGMCommands::SaveGroup, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SaveGroupCommand); + + Command SaveSpawnedByPlayerCommand{ + .help = "Saves all entities spawned by a player to the Bug Report Table", + .info = "Saves all entities spawned by a player to the Bug Report Table", + .aliases = { "saveplayerspawns", "save-player-spawns" }, + .handle = DEVGMCommands::SaveSpawnedByPlayer, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SaveSpawnedByPlayerCommand); + + Command SaveSpawnedBySenderCommand{ + .help = "Saves all entities you have spawned to the Bug Report Table", + .info = "Saves all entities you have spawned to the Bug Report Table", + .aliases = { "savemyspawns", "save-my-spawns" }, + .handle = DEVGMCommands::SaveSpawnedBySender, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SaveSpawnedBySenderCommand); + + Command SaveSpawnedBySpawnerIDCommand{ + .help = "Saves all entities spawned by the specified object ID to the Bug Report Table", + .info = "Saves all entities spawned by the specified object ID to the Bug Report Table", + .aliases = { "savebyspawnerid", "save-by-spawner-id" }, + .handle = DEVGMCommands::SaveSpawnedBySpawnerID, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SaveSpawnedBySpawnerIDCommand); + + Command SaveAllPlayerSpawnsCommand{ + .help = "Saves all entities spawned by current players to the Bug Report Table", + .info = "Saves all entities spawned by current players to the Bug Report Table", + .aliases = { "saveallplayerspawns", "save-all-player-spawns" }, + .handle = DEVGMCommands::SaveAllPlayerSpawns, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(SaveAllPlayerSpawnsCommand); + Command ExecuteCommand{ .help = "Execute commands with modified context (Minecraft-style)", .info = "Execute commands as different entities or from different positions. Usage: /execute ... run . Subcommands: as , at , positioned ", diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp index f5dbd29fd..e01e9b3c1 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp @@ -24,6 +24,7 @@ // Database #include "Database.h" +#include "IBugReports.h" #include "CDObjectsTable.h" #include "CDRewardCodesTable.h" #include "CDLootMatrixTable.h" @@ -782,35 +783,87 @@ namespace DEVGMCommands { } void Spawn(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (args.empty()) { + ChatPackets::SendSystemMessage(sysAddr, u"No lot provided."); + return; + } + const auto splitArgs = GeneralUtils::SplitString(args, ' '); - if (splitArgs.empty()) return; + const auto argCount = splitArgs.size(); ControllablePhysicsComponent* comp = static_cast(entity->GetComponent(eReplicaComponentType::CONTROLLABLE_PHYSICS)); if (!comp) return; const auto lot = GeneralUtils::TryParse(splitArgs[0]); - if (!lot) { - ChatPackets::SendSystemMessage(sysAddr, u"Invalid lot."); + ChatPackets::SendSystemMessage(sysAddr, u"Failed to parse lot."); return; } + std::string groupName; + if (argCount == 2) { + groupName = splitArgs[1]; + } else if (argCount == 5) { + groupName = splitArgs[4]; + } else if (argCount == 9) { + groupName = splitArgs[8]; + } + + NiPoint3 pos{}; + if (argCount >= 4) { + const auto x = GeneralUtils::TryParse(splitArgs[1]); + const auto y = GeneralUtils::TryParse(splitArgs[2]); + const auto z = GeneralUtils::TryParse(splitArgs[3]); + if (!x || !y || !z) { + ChatPackets::SendSystemMessage(sysAddr, u"Failed to parse position."); + return; + } + + pos.SetX(x.value()); + pos.SetY(y.value()); + pos.SetZ(z.value()); + } else { + pos = comp->GetPosition(); + } + + NiQuaternion rot{}; + if (argCount >= 8) { + const auto w = GeneralUtils::TryParse(splitArgs[4]); + const auto x = GeneralUtils::TryParse(splitArgs[5]); + const auto y = GeneralUtils::TryParse(splitArgs[6]); + const auto z = GeneralUtils::TryParse(splitArgs[7]); + if (!w || !x || !y || !z) { + ChatPackets::SendSystemMessage(sysAddr, u"Failed to parse rotation quaternion."); + return; + } + + rot.SetW(w.value()); + rot.SetX(x.value()); + rot.SetY(y.value()); + rot.SetZ(z.value()); + } else { + rot = comp->GetRotation(); + } + EntityInfo info; info.lot = lot.value(); - info.pos = comp->GetPosition(); - info.rot = comp->GetRotation(); + info.pos = pos; + info.rot = rot; 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); - if (newEntity == nullptr) { ChatPackets::SendSystemMessage(sysAddr, u"Failed to spawn entity."); return; } + if (!groupName.empty()) { + newEntity->AddToGroup(groupName); + } + Game::entityManager->ConstructEntity(newEntity); } @@ -844,7 +897,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) { @@ -867,6 +920,209 @@ namespace DEVGMCommands { } } + void GetPlayerID(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + auto* targetPlayer = entity; + std::string targetName = "Your character"; + + if (!args.empty()) { + targetName = splitArgs[0]; + targetPlayer = PlayerManager::GetPlayer(targetName); + } + + if (!targetPlayer) { + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16("Player not found.")); + return; + } + + std::stringstream msg; + msg << targetName << " has object ID " << targetPlayer->GetObjectID() << "."; + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(msg.str())); + } + + static void SendEntityList(const SystemAddress& sysAddr, const std::vector& entityList) { + for (auto* listEntity : entityList) { + std::stringstream entry; + entry << "[" << listEntity->GetObjectID() << "], LOT " << listEntity->GetLOT(); + const auto spawnerID = listEntity->GetSpawnerID(); + if (auto* spawner = Game::entityManager->GetEntity(spawnerID)) { + if (spawner->IsPlayer()) { + entry << ", Spawned by " << spawner->GetCharacter()->GetName() << "."; + } else { + entry << ", SpawnerID " << spawnerID << "."; + } + } + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(entry.str())); + } + } + + static void DespawnEntityList(const std::vector& entityList) { + for (auto* listEntity : entityList) { + Game::entityManager->DestroyEntity(listEntity); + } + } + + static void SaveEntityList(Entity* entity, const SystemAddress& sysAddr, const std::vector& entityList) { + if (entityList.empty()) { + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16("No spawns found.")); + return; + } + + IBugReports::Info reportInfo; + std::stringstream entityInfo; + for (auto* listEntity : entityList) { + const auto& entityPosition = listEntity->GetPosition(); + const auto& entityRotation = listEntity->GetRotation(); + entityInfo << "/spawn " << listEntity->GetLOT() << " "; + entityInfo << entityPosition.GetX() << " " << entityPosition.GetY() << " " << entityPosition.GetZ() << " "; + entityInfo << entityRotation.GetW() << " " << entityRotation.GetX() << " " << entityRotation.GetY() << " " << entityRotation.GetZ() << " "; + entityInfo << "groupName\n "; + } + + reportInfo.body = entityInfo.str(); + reportInfo.clientVersion = "N/A"; + reportInfo.otherPlayer = "N/A"; + reportInfo.selection = "Saved spawn command."; + if (auto* character = entity->GetCharacter()) { + reportInfo.characterId = character->GetID(); + } + + Database::Get()->InsertNewBugReport(reportInfo); + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16("List saved to Bug Report table.")); + } + + static void SendEntitiesSpawnedByID(const SystemAddress& sysAddr, const LWOOBJID spawnerID) { + if (!Game::entityManager->GetEntity(spawnerID)) return; + SendEntityList(sysAddr, Game::entityManager->GetEntitiesBySpawnerID(spawnerID)); + } + + static void DespawnEntitiesSpawnedByID(const LWOOBJID spawnerID) { + if (!Game::entityManager->GetEntity(spawnerID)) return; + DespawnEntityList(Game::entityManager->GetEntitiesBySpawnerID(spawnerID)); + } + + static void SaveEntitiesSpawnedByID(Entity* entity, const SystemAddress& sysAddr, const LWOOBJID spawnerID) { + if (!Game::entityManager->GetEntity(spawnerID)) return; + SaveEntityList(entity, sysAddr, Game::entityManager->GetEntitiesBySpawnerID(spawnerID)); + } + + void ListGroup(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (args.empty()) return; + + const auto groupName = GeneralUtils::SplitString(args, ' ')[0]; + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16("Listing entities in group " + groupName + ":")); + SendEntityList(sysAddr, Game::entityManager->GetEntitiesInGroup(groupName)); + } + + void DespawnGroup(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (args.empty()) return; + DespawnEntityList(Game::entityManager->GetEntitiesInGroup(GeneralUtils::SplitString(args, ' ')[0])); + } + + void SaveGroup(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (args.empty()) return; + SaveEntityList(entity, sysAddr, Game::entityManager->GetEntitiesInGroup(GeneralUtils::SplitString(args, ' ')[0])); + } + + void ListSpawnedByPlayer(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto* spawner = entity; + if (!args.empty()) { + spawner = PlayerManager::GetPlayer(GeneralUtils::SplitString(args, ' ')[0]); + } + if (!spawner) { + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16("Player not found.")); + return; + } + SendEntitiesSpawnedByID(sysAddr, spawner->GetObjectID()); + } + + void DespawnSpawnedByPlayer(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto* spawner = entity; + if (!args.empty()) { + spawner = PlayerManager::GetPlayer(GeneralUtils::SplitString(args, ' ')[0]); + } + if (!spawner) { + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16("Player not found.")); + return; + } + DespawnEntitiesSpawnedByID(spawner->GetObjectID()); + } + + void SaveSpawnedByPlayer(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + auto* spawner = entity; + if (!args.empty()) { + spawner = PlayerManager::GetPlayer(GeneralUtils::SplitString(args, ' ')[0]); + } + if (!spawner) { + ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16("Player not found.")); + return; + } + SaveEntitiesSpawnedByID(entity, sysAddr, spawner->GetObjectID()); + } + + void ListSpawnedBySender(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + SendEntitiesSpawnedByID(sysAddr, entity->GetObjectID()); + } + + void DespawnSpawnedBySender(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + DespawnEntitiesSpawnedByID(entity->GetObjectID()); + } + + void SaveSpawnedBySender(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + SaveEntitiesSpawnedByID(entity, sysAddr, entity->GetObjectID()); + } + + void ListSpawnedBySpawnerID(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (args.empty()) return; + const auto spawnerID = GeneralUtils::TryParse(GeneralUtils::SplitString(args, ' ')[0]); + if (!spawnerID) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid object ID."); + return; + } + SendEntitiesSpawnedByID(sysAddr, spawnerID.value()); + } + + void DespawnSpawnedBySpawnerID(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (args.empty()) return; + const auto spawnerID = GeneralUtils::TryParse(GeneralUtils::SplitString(args, ' ')[0]); + if (!spawnerID) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid object ID."); + return; + } + DespawnEntitiesSpawnedByID(spawnerID.value()); + } + + void SaveSpawnedBySpawnerID(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (args.empty()) return; + const auto spawnerID = GeneralUtils::TryParse(GeneralUtils::SplitString(args, ' ')[0]); + if (!spawnerID) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid object ID."); + return; + } + SaveEntitiesSpawnedByID(entity, sysAddr, spawnerID.value()); + } + + void ListAllPlayerSpawns(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + for (auto* player : PlayerManager::GetAllPlayers()) { + SendEntitiesSpawnedByID(sysAddr, player->GetObjectID()); + } + } + + void DespawnAllPlayerSpawns(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + for (auto* player : PlayerManager::GetAllPlayers()) { + DespawnEntitiesSpawnedByID(player->GetObjectID()); + } + } + + void SaveAllPlayerSpawns(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + std::vector fullEntityList; + for (auto* player : PlayerManager::GetAllPlayers()) { + const auto entityList = Game::entityManager->GetEntitiesBySpawnerID(player->GetObjectID()); + fullEntityList.insert(fullEntityList.end(), entityList.begin(), entityList.end()); + } + SaveEntityList(entity, sysAddr, fullEntityList); + } + void GiveUScore(Entity* entity, const SystemAddress& sysAddr, const std::string args) { const auto splitArgs = GeneralUtils::SplitString(args, ' '); if (splitArgs.empty()) return; @@ -1268,10 +1524,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) { @@ -1675,6 +1931,48 @@ namespace DEVGMCommands { } } + void InspectDespawn(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (args.empty()) return; + + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + Entity* closest = nullptr; + if (const auto idParsed = GeneralUtils::TryParse(splitArgs[0])) { + closest = Game::entityManager->GetEntity(idParsed.value()); + } + + if (!closest) closest = PlayerManager::GetPlayer(splitArgs[0]); + if (!closest) { + std::u16string ldf; + bool isLDF = false; + auto component = GeneralUtils::TryParse(splitArgs[0]); + if (!component) { + component = eReplicaComponentType::INVALID; + ldf = GeneralUtils::UTF8ToUTF16(splitArgs[0]); + isLDF = true; + } + + const auto reference = entity->GetPosition(); + float closestDistance = 0.0f; + for (auto* candidate : Game::entityManager->GetEntitiesByComponent(component.value())) { + if (candidate->GetLOT() == 1 || candidate->GetLOT() == 8092) continue; + if (isLDF && !candidate->HasVar(ldf)) continue; + + const auto distance = NiPoint3::Distance(candidate->GetPosition(), reference); + if (!closest || distance < closestDistance) { + closest = candidate; + closestDistance = distance; + } + } + } + + if (!closest) { + ChatPackets::SendSystemMessage(sysAddr, u"No entities found with that component."); + return; + } + + Game::entityManager->DestroyEntity(closest); + } + void Shutdown(Entity* entity, const SystemAddress& sysAddr, const std::string args) { auto* character = entity->GetCharacter(); if (character) LOG("Mythran (%s) has shutdown the world", character->GetName().c_str()); @@ -1699,7 +1997,10 @@ namespace DEVGMCommands { const auto splitArgs = GeneralUtils::SplitString(args, ' '); const auto objId = GeneralUtils::TryParse(splitArgs[0]); - if (!objId) return; + if (!objId) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid object ID."); + return; + } auto* const target = Game::entityManager->GetEntity(*objId); if (!target) { @@ -1707,13 +2008,7 @@ namespace DEVGMCommands { return; } - if (!target->GetVar(u"SpawnedFromSlashCommand")) { - ChatPackets::SendSystemMessage(sysAddr, u"You cannot despawn this entity as it was not despawned from a slash command."); - return; - } - - target->Smash(LWOOBJID_EMPTY, eKillType::SILENT); - LOG("Despawned entity (%llu)", target->GetObjectID()); + Game::entityManager->DestroyEntity(target); ChatPackets::SendSystemMessage(sysAddr, u"Despawned entity: " + GeneralUtils::to_u16string(target->GetObjectID())); } diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.h b/dGame/dUtilities/SlashCommands/DEVGMCommands.h index 64783e24f..a23aa82f8 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.h +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.h @@ -77,6 +77,23 @@ namespace DEVGMCommands { void Barfight(Entity* entity, const SystemAddress& sysAddr, const std::string args); void Despawn(Entity* entity, const SystemAddress& sysAddr, const std::string args); void Execute(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void GetPlayerID(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void InspectDespawn(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void DespawnGroup(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void DespawnSpawnedByPlayer(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void DespawnSpawnedBySender(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void DespawnSpawnedBySpawnerID(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void DespawnAllPlayerSpawns(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ListGroup(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ListSpawnedByPlayer(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ListSpawnedBySender(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ListSpawnedBySpawnerID(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void ListAllPlayerSpawns(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SaveGroup(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SaveSpawnedByPlayer(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SaveSpawnedBySender(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SaveSpawnedBySpawnerID(Entity* entity, const SystemAddress& sysAddr, const std::string args); + void SaveAllPlayerSpawns(Entity* entity, const SystemAddress& sysAddr, const std::string args); } #endif //!DEVGMCOMMANDS_H diff --git a/dGame/dUtilities/VanityUtilities.cpp b/dGame/dUtilities/VanityUtilities.cpp index 48d3c0da5..950f26e23 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 2afd4a6be..4da74479d 100644 --- a/dGame/dUtilities/VanityUtilities.h +++ b/dGame/dUtilities/VanityUtilities.h @@ -19,7 +19,7 @@ struct VanityObject { std::vector m_Equipment; std::vector m_Phrases; std::map> m_Locations; - std::vector m_Config; + LwoNameValue m_Config; }; diff --git a/dNet/WorldPackets.cpp b/dNet/WorldPackets.cpp index 911170569..e963b10cb 100644 --- a/dNet/WorldPackets.cpp +++ b/dNet/WorldPackets.cpp @@ -11,6 +11,7 @@ #include "BitStreamUtils.h" #include +#include void HTTPMonitorInfo::Serialize(RakNet::BitStream& bitStream) const { bitStream.Write(port); @@ -88,18 +89,18 @@ void WorldPackets::SendCreateCharacter(const SystemAddress& sysAddr, int64_t rep RakNet::BitStream data; - std::vector> ldfData; - ldfData.push_back(std::move(make_unique>(u"objid", player))); - ldfData.push_back(std::move(make_unique>(u"template", 1))); - ldfData.push_back(std::move(make_unique>(u"xmlData", xmlData))); - ldfData.push_back(std::move(make_unique>(u"name", username))); - ldfData.push_back(std::move(make_unique>(u"gmlevel", static_cast(gm)))); - ldfData.push_back(std::move(make_unique>(u"chatmode", static_cast(gm)))); - ldfData.push_back(std::move(make_unique>(u"reputation", reputation))); - ldfData.push_back(std::move(make_unique>(u"propertycloneid", cloneID))); + LwoNameValue ldfData; + ldfData.Insert(u"objid", player); + ldfData.Insert(u"template", 1); + ldfData.Insert(u"xmlData", xmlData); + ldfData.Insert(u"name", username); + ldfData.Insert(u"gmlevel", static_cast(gm)); + ldfData.Insert(u"chatmode", static_cast(gm)); + ldfData.Insert(u"reputation", reputation); + ldfData.Insert(u"propertycloneid", cloneID); - data.Write(ldfData.size()); - for (const auto& toSerialize : ldfData) toSerialize->WriteToPacket(data); + data.Write(ldfData.values.size()); + for (const auto& toSerialize : ldfData | std::views::values) toSerialize->WriteToPacket(data); //Compress the data before sending: const uint32_t reservedSize = ZCompression::GetMaxCompressedLength(data.GetNumberOfBytesUsed()); diff --git a/dScripts/02_server/Enemy/AM/AmDarklingDragon.cpp b/dScripts/02_server/Enemy/AM/AmDarklingDragon.cpp index 61d254360..5fbbda20b 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 b4b038b84..2ddc63099 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 9c71c284d..26948c571 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 8e70d4c3e..45f1e9ad1 100644 --- a/dScripts/02_server/Enemy/General/BaseEnemyMech.cpp +++ b/dScripts/02_server/Enemy/General/BaseEnemyMech.cpp @@ -23,7 +23,7 @@ void BaseEnemyMech::OnDie(Entity* self, Entity* killer) { NiPoint3 newLoc = { controlPhys->GetPosition().x, dpWorld::GetNavMesh()->GetHeightAtPoint(controlPhys->GetPosition()), controlPhys->GetPosition().z }; EntityInfo info = EntityInfo(); - std::vector cfg; + LwoNameValue cfg; std::u16string activatorPosStr; activatorPosStr += (GeneralUtils::to_u16string(controlPhys->GetPosition().x)); activatorPosStr.push_back(0x1f); @@ -31,8 +31,7 @@ void BaseEnemyMech::OnDie(Entity* self, Entity* killer) { activatorPosStr.push_back(0x1f); activatorPosStr += (GeneralUtils::to_u16string(controlPhys->GetPosition().z)); - LDFBaseData* activatorPos = new LDFData(u"rebuild_activators", activatorPosStr); - cfg.push_back(activatorPos); + cfg.Insert(u"rebuild_activators", activatorPosStr); info.lot = qbTurretLOT; info.pos = newLoc; info.rot = controlPhys->GetRotation(); diff --git a/dScripts/02_server/Map/AG_Spider_Queen/ZoneAgSpiderQueen.cpp b/dScripts/02_server/Map/AG_Spider_Queen/ZoneAgSpiderQueen.cpp index 2091041bb..2b367ed2b 100644 --- a/dScripts/02_server/Map/AG_Spider_Queen/ZoneAgSpiderQueen.cpp +++ b/dScripts/02_server/Map/AG_Spider_Queen/ZoneAgSpiderQueen.cpp @@ -71,9 +71,7 @@ void ZoneAgSpiderQueen::OnTimerDone(Entity* self, std::string timerName) { info.pos = spawnTarget->GetPosition(); info.rot = spawnTarget->GetRotation(); info.lot = chestObject; - info.settings = { - new LDFData(u"parent_tag", self->GetObjectID()) - }; + info.settings.Insert(u"parent_tag", self->GetObjectID()); auto* chest = Game::entityManager->CreateEntity(info); Game::entityManager->ConstructEntity(chest); diff --git a/dScripts/02_server/Map/AM/AmSkullkinTower.cpp b/dScripts/02_server/Map/AM/AmSkullkinTower.cpp index 17ae2ea9e..19c39f8b5 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 77a50e5af..e20194a4d 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 0562fdd83..9081ec577 100644 --- a/dScripts/02_server/Map/General/QbSpawner.cpp +++ b/dScripts/02_server/Map/General/QbSpawner.cpp @@ -66,14 +66,12 @@ void QbSpawner::OnTimerDone(Entity* self, std::string timerName) { info.rot = newRot; info.spawnerID = self->GetObjectID(); info.spawnerNodeID = 0; - info.settings = { - new LDFData(u"no_timed_spawn", true), - new LDFData(u"aggroRadius", 70), - new LDFData(u"softtetherRadius", 80), - new LDFData(u"tetherRadius", 90), - new LDFData(u"wanderRadius", 5), - new LDFData(u"mobTableLoc", i) - }; + info.settings.Insert(u"no_timed_spawn", true); + info.settings.Insert(u"aggroRadius", 70); + info.settings.Insert(u"softtetherRadius", 80); + info.settings.Insert(u"tetherRadius", 90); + info.settings.Insert(u"wanderRadius", 5); + info.settings.Insert(u"mobTableLoc", i); auto* child = Game::entityManager->CreateEntity(info, nullptr, self); Game::entityManager->ConstructEntity(child); diff --git a/dScripts/02_server/Map/NS/NsConcertChoiceBuildManager.cpp b/dScripts/02_server/Map/NS/NsConcertChoiceBuildManager.cpp index 5306a1ddb..aab664494 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 2b2f16f9f..c036b0236 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 39fae3aa2..180543650 100644 --- a/dScripts/02_server/Map/Property/AG_Small/ZoneAgProperty.cpp +++ b/dScripts/02_server/Map/Property/AG_Small/ZoneAgProperty.cpp @@ -95,10 +95,8 @@ void ZoneAgProperty::LoadInstance(Entity* self) { for (auto* spawner : Game::zoneManager->GetSpawnersByName(self->GetVar(InstancerSpawner))) { for (auto* spawnerNode : spawner->m_Info.nodes) { - spawnerNode->config.push_back( - new LDFData(u"custom_script_server", - R"(scripts\ai\GENERAL\L_INSTANCE_EXIT_TRANSFER_PLAYER_TO_LAST_NON_INSTANCE.lua)")); - spawnerNode->config.push_back(new LDFData(u"transferText", u"SPIDER_QUEEN_EXIT_QUESTION")); + spawnerNode->config.Insert(u"custom_script_server", R"(scripts\ai\GENERAL\L_INSTANCE_EXIT_TRANSFER_PLAYER_TO_LAST_NON_INSTANCE.lua)"); + spawnerNode->config.Insert(u"transferText", u"SPIDER_QUEEN_EXIT_QUESTION"); } } diff --git a/dScripts/02_server/Map/njhub/boss_instance/NjMonastryBossInstance.cpp b/dScripts/02_server/Map/njhub/boss_instance/NjMonastryBossInstance.cpp index d7be9d32d..be47b53b3 100644 --- a/dScripts/02_server/Map/njhub/boss_instance/NjMonastryBossInstance.cpp +++ b/dScripts/02_server/Map/njhub/boss_instance/NjMonastryBossInstance.cpp @@ -515,9 +515,7 @@ void NjMonastryBossInstance::FightOver(Entity* self) { info.pos = treasureChest->GetPosition(); info.rot = treasureChest->GetRotation(); info.spawnerID = self->GetObjectID(); - info.settings = { - new LDFData(u"parent_tag", self->GetObjectID()) - }; + info.settings.Insert(u"parent_tag", self->GetObjectID()); // Finally spawn a treasure chest at the correct spawn point auto* chestObject = Game::entityManager->CreateEntity(info); diff --git a/dScripts/02_server/Objects/StinkyFishTarget.cpp b/dScripts/02_server/Objects/StinkyFishTarget.cpp index 9840235d8..ae595b890 100644 --- a/dScripts/02_server/Objects/StinkyFishTarget.cpp +++ b/dScripts/02_server/Objects/StinkyFishTarget.cpp @@ -21,9 +21,7 @@ void StinkyFishTarget::OnSkillEventFired(Entity* self, Entity* caster, const std entityInfo.pos = self->GetPosition(); entityInfo.rot = self->GetRotation(); entityInfo.spawnerID = self->GetObjectID(); - entityInfo.settings = { - new LDFData(u"no_timed_spawn", true) - }; + entityInfo.settings.Insert(u"no_timed_spawn", true); auto* fish = Game::entityManager->CreateEntity(entityInfo); Game::entityManager->ConstructEntity(fish); diff --git a/dScripts/SpawnPetBaseServer.cpp b/dScripts/SpawnPetBaseServer.cpp index d23741624..04a24ae68 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 bc9f1c8a7..e02bcb553 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 0b4363961..e7ebdca84 100644 --- a/dScripts/ai/GF/GfBanana.cpp +++ b/dScripts/ai/GF/GfBanana.cpp @@ -4,6 +4,9 @@ #include "DestroyableComponent.h" #include "EntityInfo.h" #include "EntityManager.h" +#include "dConfig.h" + +#include "GeneralUtils.h" void GfBanana::SpawnBanana(Entity* self) { auto position = self->GetPosition(); @@ -44,7 +47,36 @@ void GfBanana::OnHit(Entity* self, Entity* attacker) { const auto bananaId = self->GetVar(u"banana"); - if (bananaId == LWOOBJID_EMPTY) return; + if (bananaId == LWOOBJID_EMPTY) { + if (GeneralUtils::GenerateRandomNumber(1, 100) == 100 && self->GetVar(u"apeId") == LWOOBJID_EMPTY && Game::config->GetValue("angry_apes") == "1") { + // ape appears if you hurt the trees feeling while it has no banana + + // play fx effect + GameMessages::SendPlayEmbeddedEffectOnAllClientsNearObject(self, u"camshake-bridge", self->GetObjectID(), 100.0f); + + auto position = self->GetPosition(); + auto rotation = QuatUtils::LookAt(self->GetPosition(), position); + + EntityInfo info {}; + + info.pos = position; + info.rot = rotation; + info.lot = m_GorillaLOT; + info.spawner = nullptr; + info.spawnerID = self->GetObjectID(); + info.spawnerNodeID = 0; + + auto* entity = Game::entityManager->CreateEntity(info); + + Game::entityManager->ConstructEntity(entity, UNASSIGNED_SYSTEM_ADDRESS); + + self->SetVar(u"apeId", entity->GetObjectID()); + + self->AddTimer("apeTimer", 10); + } + + return; + } auto* bananaEntity = Game::entityManager->GetEntity(bananaId); @@ -55,24 +87,30 @@ void GfBanana::OnHit(Entity* self, Entity* attacker) { return; } - bananaEntity->Smash(LWOOBJID_EMPTY, eKillType::SILENT); - const auto rotation = self->GetRotation(); - EntityInfo info{}; - info.lot = 6718; - info.pos = self->GetPosition(); - info.pos.y += 12; - info.pos.x -= QuatUtils::Right(rotation).x * 5; - info.pos.z -= QuatUtils::Right(rotation).z * 5; - info.rot = rotation; - info.spawnerID = self->GetObjectID(); - info.settings = { new LDFData(u"motionType", 5) }; - auto* const newEn = Game::entityManager->CreateEntity(info, nullptr, self); - Game::entityManager->ConstructEntity(newEn); + auto bananaFloor = (bananaEntity->GetPosition() - NiPoint3Constant::UNIT_Y * 8); + bananaEntity->SetPosition(bananaFloor); + + auto* bananaDestroyable = bananaEntity->GetComponent(); + bananaDestroyable->SetHealth(0); + bananaDestroyable->Smash(attacker->GetObjectID()); + + Game::entityManager->SerializeEntity(self); } void GfBanana::OnTimerDone(Entity* self, std::string timerName) { if (timerName == "bananaTimer") { SpawnBanana(self); + } else if (timerName == "apeTimer") { + if (self->GetVar(u"apeId") == LWOOBJID_EMPTY) return; + + auto entity = Game::entityManager->GetEntity(self->GetVar(u"apeId")); + + if (entity) { + GameMessages::SendPlayEmbeddedEffectOnAllClientsNearObject(self, u"camshake-bridge", self->GetObjectID(), 100.0f); + entity->GetComponent()->Smash(self->GetObjectID(), eKillType::SILENT); // to destroy the anchor too + Game::entityManager->DestroyEntity(entity); + } + self->SetVar(u"apeId", LWOOBJID_EMPTY); } } diff --git a/dScripts/ai/GF/GfBanana.h b/dScripts/ai/GF/GfBanana.h index a33bddf68..996a88748 100644 --- a/dScripts/ai/GF/GfBanana.h +++ b/dScripts/ai/GF/GfBanana.h @@ -11,4 +11,7 @@ class GfBanana final : public CppScripts::Script void OnHit(Entity* self, Entity* attacker) override; void OnTimerDone(Entity* self, std::string timerName) override; + +private: + LOT m_GorillaLOT = 13524; }; diff --git a/dScripts/ai/GF/PetDigBuild.cpp b/dScripts/ai/GF/PetDigBuild.cpp index 9caf2f8c8..b720907e3 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 3dd2773f6..473cd7c52 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 d388baacf..6549a85b5 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 fc83a5d34..9e7706158 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 5dd43f48a..9c53a9907 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.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.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.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.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)); - - const std::vector racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL); - for (auto* const racingController : racingControllers) { - auto* racingComponent = racingController->GetComponent(); - if (racingComponent) racingComponent->MsgConfigureRacingControl(config); - } + 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.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.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.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.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); + + config.Send(self->GetObjectID()); } diff --git a/dScripts/ai/RACING/TRACK_GF/GfRaceServer.cpp b/dScripts/ai/RACING/TRACK_GF/GfRaceServer.cpp index d465d57fd..c7a4cf947 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.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.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.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.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)); - - const std::vector racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL); - for (auto* const racingController : racingControllers) { - auto* racingComponent = racingController->GetComponent(); - if (racingComponent) racingComponent->MsgConfigureRacingControl(config); - } + 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.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.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.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.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); + + config.Send(self->GetObjectID()); } diff --git a/dScripts/ai/RACING/TRACK_NS/NsRaceServer.cpp b/dScripts/ai/RACING/TRACK_NS/NsRaceServer.cpp index 5ef6c6d1d..3c9efa3ae 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.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.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.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.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)); - - std::vector racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL); - for (auto* const racingController : racingControllers) { - auto* racingComponent = racingController->GetComponent(); - if (racingComponent) racingComponent->MsgConfigureRacingControl(config); - } + 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.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.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.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.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); + + 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 9a7712a13..8baaa4c6f 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.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.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.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.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)); - - const auto racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL); - for (auto* const racingController : racingControllers) { - auto* const racingComponent = racingController->GetComponent(); - if (racingComponent) racingComponent->MsgConfigureRacingControl(config); - } + 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.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.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.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.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); + + config.Send(self->GetObjectID()); } diff --git a/dZoneManager/Level.cpp b/dZoneManager/Level.cpp index 40ee6d30b..72fbdcdd7 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 1f0b4f2e1..22860fe72 100644 --- a/dZoneManager/Level.h +++ b/dZoneManager/Level.h @@ -40,7 +40,7 @@ class Level { public: Level(Zone* parentZone, const std::string& filepath); - static void MakeSpawner(SceneObject obj); + static void MakeSpawner(const SceneObject& obj); std::map m_ChunkHeaders; private: diff --git a/dZoneManager/Spawner.h b/dZoneManager/Spawner.h index 701ceb29c..3f01161d0 100644 --- a/dZoneManager/Spawner.h +++ b/dZoneManager/Spawner.h @@ -17,7 +17,7 @@ struct SpawnerNode { uint32_t nodeID = 0; uint32_t nodeMax = 1; std::vector entities; - std::vector config; + LwoNameValue config; }; struct SpawnerInfo { diff --git a/dZoneManager/Zone.cpp b/dZoneManager/Zone.cpp index 282f4c454..893d9598b 100644 --- a/dZoneManager/Zone.cpp +++ b/dZoneManager/Zone.cpp @@ -111,7 +111,7 @@ void Zone::LoadZoneIntoMemory() { node->nodeID = 0; node->config = waypoint.config; - for (LDFBaseData* data : waypoint.config) { + for (const auto& data : waypoint.config | std::views::values) { if (!data) continue; if (data->GetKey() == u"spawner_node_id") { @@ -465,7 +465,7 @@ void Zone::LoadPath(std::istream& file) { command.data = value; } else LOG("Tried to load invalid waypoint command '%s'", parameter.c_str()); } else { - waypoint.config.emplace_back(LDFBaseData::DataFromString(parameter + "=" + value)); + waypoint.config.ParseInsert(parameter + "=" + value); } } diff --git a/dZoneManager/Zone.h b/dZoneManager/Zone.h index 20010c2cb..4cb74efaa 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 5acdc6b71..53f1d3c4a 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 8e4cb7608..9158ad720 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,7 @@ name: dlu services: darkflamedb: + container_name: darkflamedb networks: - darkflame image: mariadb:latest @@ -21,6 +22,7 @@ services: retries: 3 darkflameserver: + container_name: darkflameserver networks: - darkflame image: ghcr.io/darkflameuniverse/darkflameserver:latest @@ -54,6 +56,7 @@ services: test: ["CMD", "ls", "/app/resServer/CDServer.sqlite"] darkflameweb: + container_name: darkflameweb networks: - darkflame image: ghcr.io/darkflameuniverse/nexusdashboard:latest diff --git a/docs/Commands.md b/docs/Commands.md index 702f64cc4..1603cb5e9 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -146,6 +146,7 @@ These commands are primarily for development and testing. The usage of many of t |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-despawn|`/inspect-despawn `|Finds the closest entity with the given component or LDF variable and despawns it. Alias: `/inspect-destroy`|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| @@ -164,7 +165,7 @@ These commands are primarily for development and testing. The usage of many of t |setflag|`/setflag (value) `|Sets the given inventory or health flag to the given value, where value can be one of "on" or "off". If no value is given, by default this adds the flag to your character (equivalent of calling `/setflag on `)|8| |setinventorysize|`/setinventorysize (inventory)`|Sets your inventory size to the given size. If `inventory` is provided, the number or string will be used to set that inventory to the requested size. Aliases: `/setinvsize`, `/setinvensize`.|8| |setuistate|`/setuistate `|Changes UI state|8| -|spawn|`/spawn `|Spawns an object at your location by id|8| +|spawn|`/spawn ( ( )) ()`|Spawns an object by id at your position, or with optional position, rotation, and group name|8| |spawngroup|`/spawngroup `|Spawns `` of object `` within the given `` from your location|8| |speedboost|`/speedboost `|Sets the speed multiplier to the given amount. `/speedboost 1.5` will set the speed multiplier to 1.5x the normal speed|8| |startcelebration|`/startcelebration `|Starts a celebration effect on your character|8| @@ -180,7 +181,23 @@ These commands are primarily for development and testing. The usage of many of t |getfactions|`/getfactions`|Shows the player's factions|8| |setrewardcode|`/setrewardcode `|Sets the rewardcode for the account you are logged into if it's a valid rewardcode, See cdclient table `RewardCodes`|8| |barfight|`/barfight`|Starts a barfight (turns everyones pvp on)|8| -|despawn|`/despawn `|Despawns an object by id|8| +|despawn|`/despawn `|Despawns an object by object id|8| +|despawn-group|`/despawn-group `|Despawns all entities in a given group. Alias: `/despawngroup`|8| +|despawn-player-spawns|`/despawn-player-spawns `|Despawns all entities spawned by a given player. Alias: `/despawnplayerspawns`|8| +|despawn-my-spawns|`/despawn-my-spawns`|Despawns all entities spawned by you. Alias: `/despawnmyspawns`|8| +|despawn-by-spawner-id|`/despawn-by-spawner-id `|Despawns all entities with a given spawner id. Alias: `/despawnbyspawnerid`|8| +|despawn-all-player-spawns|`/despawn-all-player-spawns`|Despawns all entities spawned by online players. Alias: `/despawnallplayerspawns`|8| +|list-group|`/list-group `|Lists all entities in a given group. Alias: `/listgroup`|8| +|list-spawned-by-player|`/list-spawned-by-player `|Lists all entities spawned by a given player. Alias: `/listspawnedbyplayer`|8| +|list-my-spawns|`/list-my-spawns`|Lists all entities spawned by you. Aliases: `/listmyspawns`, `/my-spawns`|8| +|list-spawned-by-id|`/list-spawned-by-id `|Lists all entities with a given spawner id. Alias: `/listspawnedbyid`|8| +|list-all-player-spawns|`/list-all-player-spawns`|Lists all entities spawned by online players. Alias: `/listallplayerspawns`|8| +|playerid|`/playerid (player name)`|Gets your player's object id, or that of another character by name. Alias: `/getplayerid`|8| +|save-group|`/save-group `|Saves all entities in a given group to the bug report table. Alias: `/savegroup`|8| +|save-player-spawns|`/save-player-spawns `|Saves all entities spawned by a given player. Alias: `/saveplayerspawns`|8| +|save-my-spawns|`/save-my-spawns`|Saves all entities spawned by you. Alias: `/savemyspawns`|8| +|save-by-spawner-id|`/save-by-spawner-id `|Saves all entities with a given spawner id. Alias: `/savebyspawnerid`|8| +|save-all-player-spawns|`/save-all-player-spawns`|Saves all entities spawned by online players. Alias: `/saveallplayerspawns`|8| |execute|`/execute ... run `|Execute commands as different entities or from different positions. Usage: /execute ... run . Subcommands: as , at , positioned . Aliases: `/exec`.|8| |crash|`/crash`|Crashes the server. Aliases: `/pumpkin`.|9| |rollloot|`/rollloot `|Given a `loot matrix index`, look for `item id` in that matrix `amount` times and print to the chat box statistics of rolling that loot matrix. Aliases: `/roll-loot`.|8| diff --git a/resources/sharedconfig.ini b/resources/sharedconfig.ini index 333f22494..00bd2acfa 100644 --- a/resources/sharedconfig.ini +++ b/resources/sharedconfig.ini @@ -24,7 +24,7 @@ dump_folder= # The location of the client # Either the folder with /res or with /client and /versions -client_location= +client_location=.. # The maximum outgoing bandwidth in bits. If your clients are having # issues with enemies taking a while to catch up to them, increse this value. diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index fe72f2da9..61c13de2d 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 c5cbd5302..9f4ce6445 160000 --- a/thirdparty/recastnavigation +++ b/thirdparty/recastnavigation @@ -1 +1 @@ -Subproject commit c5cbd53024c8a9d8d097a4371215e3342d2fdc87 +Subproject commit 9f4ce64458dfae86e1239c525ddc219c4e9e06f1