From 6d3bf2fdc38d8ae2d2705af4ef9d6d36d1984b6e Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 8 Sep 2025 20:50:22 -0700 Subject: [PATCH 01/39] fix: need to create account twice due to commit latency?? (#1873) idk fixes the issue --- dMasterServer/MasterServer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 38abdc71..2d108a2d 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -159,6 +159,7 @@ int main(int argc, char** argv) { } MigrationRunner::RunMigrations(); + Database::Get()->Commit(); const auto resServerPath = BinaryPathFinder::GetBinaryDir() / "resServer"; std::filesystem::create_directories(resServerPath); const bool cdServerExists = std::filesystem::exists(resServerPath / "CDServer.sqlite"); From 154112050f31a29b3a4b2f07d7f53a8b25ed797c Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Sep 2025 22:35:18 -0700 Subject: [PATCH 02/39] feat: Implement Minecraft-style execute command with relative positioning (#1864) * Initial plan * Implement Minecraft-style execute command Co-authored-by: aronwk-aaron <26027722+aronwk-aaron@users.noreply.github.com> * Add relative positioning support to execute command using ~ syntax Co-authored-by: aronwk-aaron <26027722+aronwk-aaron@users.noreply.github.com> * update the parsing and fix chat response --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: aronwk-aaron <26027722+aronwk-aaron@users.noreply.github.com> Co-authored-by: Aaron Kimbrell --- dGame/dUtilities/SlashCommandHandler.cpp | 9 + .../SlashCommands/DEVGMCommands.cpp | 193 ++++++++++++++++-- .../dUtilities/SlashCommands/DEVGMCommands.h | 1 + docs/Commands.md | 1 + 4 files changed, 188 insertions(+), 16 deletions(-) diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index f65fae32..218ccfa8 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -808,6 +808,15 @@ void SlashCommandHandler::Startup() { }; RegisterCommand(DeleteInvenCommand); + 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 ", + .aliases = { "execute", "exec" }, + .handle = DEVGMCommands::Execute, + .requiredLevel = eGameMasterLevel::DEVELOPER + }; + RegisterCommand(ExecuteCommand); + // Register Greater Than Zero Commands Command KickCommand{ diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp index 6963ebff..447d5155 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp @@ -559,23 +559,25 @@ namespace DEVGMCommands { } } - std::optional ParseRelativeAxis(const float sourcePos, const std::string& toParse) { - if (toParse.empty()) return std::nullopt; - - // relative offset from current position - if (toParse[0] == '~') { - if (toParse.size() == 1) return sourcePos; - - if (toParse.size() < 3 || !(toParse[1] != '+' || toParse[1] != '-')) return std::nullopt; - - const auto offset = GeneralUtils::TryParse(toParse.substr(2)); - if (!offset.has_value()) return std::nullopt; - - bool isNegative = toParse[1] == '-'; - return isNegative ? sourcePos - offset.value() : sourcePos + offset.value(); + // Parse coordinates with support for relative positioning (~) + std::optional ParseRelativeAxis(const float currentValue, const std::string& rawCoord) { + if (rawCoord.empty()) return std::nullopt; + std::string coord = rawCoord; + // Remove any '+' characters to simplify parsing, since they don't affect the value + coord.erase(std::remove(coord.begin(), coord.end(), '+'), coord.end()); + if (coord[0] == '~') { + if (coord.length() == 1) { + return currentValue; + } else { + auto offsetOpt = GeneralUtils::TryParse(coord.substr(1)); + if (!offsetOpt) return std::nullopt; + return currentValue + offsetOpt.value(); + } + } else { + auto absoluteOpt = GeneralUtils::TryParse(coord); + if (!absoluteOpt) return std::nullopt; + return absoluteOpt.value(); } - - return GeneralUtils::TryParse(toParse); } void Teleport(Entity* entity, const SystemAddress& sysAddr, const std::string args) { @@ -1664,4 +1666,163 @@ namespace DEVGMCommands { LOG("Despawned entity (%llu)", target->GetObjectID()); ChatPackets::SendSystemMessage(sysAddr, u"Despawned entity: " + GeneralUtils::to_u16string(target->GetObjectID())); } + + void Execute(Entity* entity, const SystemAddress& sysAddr, const std::string args) { + if (args.empty()) { + ChatPackets::SendSystemMessage(sysAddr, + u"Usage: /execute ... run \n" + u"Subcommands:\n" + u" as - Execute as different player\n" + u" at - Execute from player's position\n" + u" positioned - Execute from coordinates (absolute or relative with ~)\n" + u"Examples:\n" + u" /execute as Player1 run pos\n" + u" /execute at Player2 positioned 100 200 300 run spawn 1234\n" + u" /execute positioned ~5 ~10 ~ run spawn 1234" + ); + return; + } + + const auto splitArgs = GeneralUtils::SplitString(args, ' '); + + // Prevent execute command recursion by checking if this is already an execute command + for (const auto& arg : splitArgs) { + if (arg == "execute" || arg == "exec") { + ChatPackets::SendSystemMessage(sysAddr, u"Error: Recursive execute commands are not allowed"); + return; + } + } + + // Context variables for execution + Entity* execEntity = entity; // Entity to execute as + NiPoint3 execPosition = entity->GetPosition(); // Position to execute from + bool positionOverridden = false; + std::string finalCommand; + + // Parse subcommands + size_t i = 0; + while (i < splitArgs.size()) { + const std::string& subcommand = splitArgs[i]; + + if (subcommand == "as") { + if (i + 1 >= splitArgs.size()) { + ChatPackets::SendSystemMessage(sysAddr, u"Error: 'as' requires a player name"); + return; + } + + const std::string& targetName = splitArgs[i + 1]; + auto* targetPlayer = PlayerManager::GetPlayer(targetName); + if (!targetPlayer) { + ChatPackets::SendSystemMessage(sysAddr, u"Error: Player '" + GeneralUtils::ASCIIToUTF16(targetName) + u"' not found"); + return; + } + + execEntity = targetPlayer; + i += 2; + + } else if (subcommand == "at") { + if (i + 1 >= splitArgs.size()) { + ChatPackets::SendSystemMessage(sysAddr, u"Error: 'at' requires a player name"); + return; + } + + const std::string& targetName = splitArgs[i + 1]; + auto* targetPlayer = PlayerManager::GetPlayer(targetName); + if (!targetPlayer) { + ChatPackets::SendSystemMessage(sysAddr, u"Error: Player '" + GeneralUtils::ASCIIToUTF16(targetName) + u"' not found"); + return; + } + + execPosition = targetPlayer->GetPosition(); + positionOverridden = true; + i += 2; + + } else if (subcommand == "positioned") { + if (i + 3 >= splitArgs.size()) { + ChatPackets::SendSystemMessage(sysAddr, u"Error: 'positioned' requires x, y, z coordinates"); + return; + } + + auto xOpt = ParseRelativeAxis(execPosition.x, splitArgs[i + 1]); + auto yOpt = ParseRelativeAxis(execPosition.y, splitArgs[i + 2]); + auto zOpt = ParseRelativeAxis(execPosition.z, splitArgs[i + 3]); + + if (!xOpt || !yOpt || !zOpt) { + ChatPackets::SendSystemMessage(sysAddr, u"Error: Invalid coordinates for 'positioned'. Use numeric values or relative coordinates with ~."); + return; + } + + execPosition = NiPoint3(xOpt.value(), yOpt.value(), zOpt.value()); + positionOverridden = true; + + i += 4; + + } else if (subcommand == "run") { + // Everything after "run" is the command to execute + if (i + 1 >= splitArgs.size()) { + ChatPackets::SendSystemMessage(sysAddr, u"Error: 'run' requires a command"); + return; + } + + // Reconstruct the command from remaining args + for (size_t j = i + 1; j < splitArgs.size(); ++j) { + if (!finalCommand.empty()) finalCommand += " "; + finalCommand += splitArgs[j]; + } + break; + + } else { + ChatPackets::SendSystemMessage(sysAddr, u"Error: Unknown subcommand '" + GeneralUtils::ASCIIToUTF16(subcommand) + u"'"); + ChatPackets::SendSystemMessage(sysAddr, u"Valid subcommands: as, at, positioned, run"); + return; + } + } + + if (finalCommand.empty()) { + ChatPackets::SendSystemMessage(sysAddr, u"Error: No command specified to run. Use 'run ' at the end."); + return; + } + + // Validate that the command starts with a valid character + if (finalCommand.empty() || finalCommand[0] == '/') { + ChatPackets::SendSystemMessage(sysAddr, u"Error: Command should not start with '/'. Just specify the command name."); + return; + } + + // Store original position if we need to restore it + NiPoint3 originalPosition; + bool needToRestore = false; + + if (positionOverridden && execEntity == entity) { + // If we're executing as ourselves but from a different position, + // temporarily move the entity + originalPosition = entity->GetPosition(); + needToRestore = true; + + // Set the position temporarily for the command execution + auto* controllable = entity->GetComponent(); + if (controllable) { + controllable->SetPosition(execPosition); + } + } + + // Provide feedback about what we're executing + std::string execAsName = execEntity->GetCharacter() ? execEntity->GetCharacter()->GetName() : "Unknown"; + ChatPackets::SendSystemMessage(sysAddr, u"[Execute] Running as '" + GeneralUtils::ASCIIToUTF16(execAsName) + + u"' from <" + GeneralUtils::to_u16string(execPosition.x) + u", " + + GeneralUtils::to_u16string(execPosition.y) + u", " + + GeneralUtils::to_u16string(execPosition.z) + u">: /" + + GeneralUtils::ASCIIToUTF16(finalCommand)); + + // Execute the command through the slash command handler + SlashCommandHandler::HandleChatCommand(GeneralUtils::ASCIIToUTF16("/" + finalCommand), execEntity, sysAddr); + + // Restore original position if needed + if (needToRestore) { + auto* controllable = entity->GetComponent(); + if (controllable) { + controllable->SetPosition(originalPosition); + } + } + } }; diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.h b/dGame/dUtilities/SlashCommands/DEVGMCommands.h index b1abb07e..64783e24 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.h +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.h @@ -76,6 +76,7 @@ namespace DEVGMCommands { void Shutdown(Entity* entity, const SystemAddress& sysAddr, const std::string args); 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); } #endif //!DEVGMCOMMANDS_H diff --git a/docs/Commands.md b/docs/Commands.md index 2a93e3de..54d5e531 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -116,6 +116,7 @@ These commands are primarily for development and testing. The usage of many of t |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 start`|Starts a barfight (turns everyones pvp on)|8| |despawn|`/despawn `|Despawns the entity objectID IF it was spawned in through a slash command.|8| +|execute|`/execute ... run `|Execute commands with modified context (Minecraft-style). Subcommands: `as ` (execute as different player), `at ` (execute from player's position), `positioned ` (execute from coordinates - supports absolute coordinates like `100 200 300` or relative coordinates like `~5 ~10 ~` where `~` means current position). Example: `/execute as Player1 run pos`, `/execute positioned ~5 ~ ~-3 run spawn 1234`|8| |crash|`/crash`|Crashes the server.|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.|9| |castskill|`/castskill `|Casts the skill as the player|9| From b798da8ef84e545ed7a909a17731100fc422ced3 Mon Sep 17 00:00:00 2001 From: HailStorm32 Date: Mon, 8 Sep 2025 23:07:08 -0700 Subject: [PATCH 03/39] fix: Update mute expiry from database (#1871) * Update mute expiry from database * Address review comments * Address review comment Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> --------- Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> --- dDatabase/GameDatabase/ITables/IAccounts.h | 1 + .../GameDatabase/MySQL/Tables/Accounts.cpp | 3 +- .../GameDatabase/SQLite/Tables/Accounts.cpp | 1 + dGame/User.cpp | 30 +++++++++++++++++-- dGame/User.h | 6 ++-- 5 files changed, 35 insertions(+), 6 deletions(-) diff --git a/dDatabase/GameDatabase/ITables/IAccounts.h b/dDatabase/GameDatabase/ITables/IAccounts.h index 13ecf29b..a58f3a25 100644 --- a/dDatabase/GameDatabase/ITables/IAccounts.h +++ b/dDatabase/GameDatabase/ITables/IAccounts.h @@ -14,6 +14,7 @@ public: std::string bcryptPassword; uint32_t id{}; uint32_t playKeyId{}; + uint64_t muteExpire{}; bool banned{}; bool locked{}; eGameMasterLevel maxGmLevel{}; diff --git a/dDatabase/GameDatabase/MySQL/Tables/Accounts.cpp b/dDatabase/GameDatabase/MySQL/Tables/Accounts.cpp index f4310dd8..b96c9c48 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/Accounts.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/Accounts.cpp @@ -3,7 +3,7 @@ #include "eGameMasterLevel.h" std::optional MySQLDatabase::GetAccountInfo(const std::string_view username) { - auto result = ExecuteSelect("SELECT id, password, banned, locked, play_key_id, gm_level FROM accounts WHERE name = ? LIMIT 1;", username); + auto result = ExecuteSelect("SELECT id, password, banned, locked, play_key_id, gm_level, mute_expire FROM accounts WHERE name = ? LIMIT 1;", username); if (!result->next()) { return std::nullopt; @@ -16,6 +16,7 @@ std::optional MySQLDatabase::GetAccountInfo(const std::string_v toReturn.banned = result->getBoolean("banned"); toReturn.locked = result->getBoolean("locked"); toReturn.playKeyId = result->getUInt("play_key_id"); + toReturn.muteExpire = result->getUInt64("mute_expire"); return toReturn; } diff --git a/dDatabase/GameDatabase/SQLite/Tables/Accounts.cpp b/dDatabase/GameDatabase/SQLite/Tables/Accounts.cpp index 9431d407..72572f89 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/Accounts.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/Accounts.cpp @@ -17,6 +17,7 @@ std::optional SQLiteDatabase::GetAccountInfo(const std::string_ toReturn.banned = result.getIntField("banned"); toReturn.locked = result.getIntField("locked"); toReturn.playKeyId = result.getIntField("play_key_id"); + toReturn.muteExpire = static_cast(result.getInt64Field("mute_expire")); return toReturn; } diff --git a/dGame/User.cpp b/dGame/User.cpp index 806d4611..7c03daa8 100644 --- a/dGame/User.cpp +++ b/dGame/User.cpp @@ -7,6 +7,10 @@ #include "dZoneManager.h" #include "eServerDisconnectIdentifiers.h" #include "eGameMasterLevel.h" +#include "BitStreamUtils.h" +#include "MessageType/Chat.h" +#include +#include User::User(const SystemAddress& sysAddr, const std::string& username, const std::string& sessionKey) { m_AccountID = 0; @@ -28,7 +32,7 @@ User::User(const SystemAddress& sysAddr, const std::string& username, const std: if (userInfo) { m_AccountID = userInfo->id; m_MaxGMLevel = userInfo->maxGmLevel; - m_MuteExpire = 0; //res->getUInt64(3); + m_MuteExpire = userInfo->muteExpire; } //If we're loading a zone, we'll load the last used (aka current) character: @@ -91,8 +95,28 @@ Character* User::GetLastUsedChar() { } } -bool User::GetIsMuted() const { - return m_MuteExpire == 1 || m_MuteExpire > time(NULL); +bool User::GetIsMuted() { + using namespace std::chrono; + constexpr auto refreshInterval = seconds{ 60 }; + const auto now = steady_clock::now(); + if (now - m_LastMuteCheck >= refreshInterval) { + m_LastMuteCheck = now; + if (const auto info = Database::Get()->GetAccountInfo(m_Username)) { + const auto expire = static_cast(info->muteExpire); + if (expire != m_MuteExpire) { + m_MuteExpire = expire; + + if (Game::chatServer && m_LoggedInCharID != 0) { + RakNet::BitStream bitStream; + BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::GM_MUTE); + bitStream.Write(m_LoggedInCharID); + bitStream.Write(m_MuteExpire); + Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false); + } + } + } + } + return m_MuteExpire == 1 || m_MuteExpire > std::time(nullptr); } time_t User::GetMuteExpire() const { diff --git a/dGame/User.h b/dGame/User.h index 662842a8..7fe8d335 100644 --- a/dGame/User.h +++ b/dGame/User.h @@ -3,6 +3,7 @@ #include #include +#include #include "RakNetTypes.h" #include "dCommonVars.h" @@ -46,7 +47,7 @@ public: const std::unordered_map& GetIsBestFriendMap() { return m_IsBestFriendMap; } void UpdateBestFriendValue(const std::string_view playerName, const bool newValue); - bool GetIsMuted() const; + bool GetIsMuted(); time_t GetMuteExpire() const; void SetMuteExpire(time_t value); @@ -72,7 +73,8 @@ private: bool m_LastChatMessageApproved = false; int m_AmountOfTimesOutOfSync = 0; const int m_MaxDesyncAllowed = 12; - time_t m_MuteExpire; + uint64_t m_MuteExpire; + std::chrono::steady_clock::time_point m_LastMuteCheck{}; }; #endif // USER_H From 68f2e2dee2d2ee79ba8e064fc367039dc7795fca Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Fri, 12 Sep 2025 01:32:15 -0700 Subject: [PATCH 04/39] Fix FetchContent_Declare speed (#1875) --- cmake/FindGoogleTest.cmake | 1 + dCommon/CMakeLists.txt | 1 + thirdparty/CMakeLists.txt | 2 ++ 3 files changed, 4 insertions(+) diff --git a/cmake/FindGoogleTest.cmake b/cmake/FindGoogleTest.cmake index db0bfb77..7c2ad9e0 100644 --- a/cmake/FindGoogleTest.cmake +++ b/cmake/FindGoogleTest.cmake @@ -7,6 +7,7 @@ FetchContent_Declare( GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG release-1.12.1 GIT_PROGRESS TRUE + GIT_SHALLOW 1 ) # For Windows: Prevent overriding the parent project's compiler/linker settings diff --git a/dCommon/CMakeLists.txt b/dCommon/CMakeLists.txt index 2945d3db..ba67974e 100644 --- a/dCommon/CMakeLists.txt +++ b/dCommon/CMakeLists.txt @@ -55,6 +55,7 @@ elseif (WIN32) URL https://github.com/madler/zlib/archive/refs/tags/v1.2.11.zip URL_HASH MD5=9d6a627693163bbbf3f26403a3a0b0b1 GIT_PROGRESS TRUE + GIT_SHALLOW 1 ) # Disable warning about no project version. diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 46f9e45c..fe72f2da 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -49,6 +49,7 @@ if(UNIX AND NOT APPLE) backtrace GIT_REPOSITORY https://github.com/ianlancetaylor/libbacktrace.git GIT_PROGRESS TRUE + GIT_SHALLOW 1 ) FetchContent_MakeAvailable(backtrace) @@ -71,6 +72,7 @@ FetchContent_Declare( GIT_REPOSITORY https://github.com/g-truc/glm.git GIT_TAG bf71a834948186f4097caa076cd2663c69a10e1e #refs/tags/1.0.1 GIT_PROGRESS TRUE + GIT_SHALLOW 1 ) FetchContent_MakeAvailable(glm) From 6389876c6e28eb3276e8cad8875676fcdc04d6e1 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 18 Sep 2025 23:12:23 -0700 Subject: [PATCH 05/39] feat: convert character ids to 64 bits (#1878) * feat: convert character ids to 64 bits remove all usages of the PERSISTENT bit with regards to storing of playerIDs on the server. the bit does not exist and was a phantom in the first place. Tested that a full playthrough of ag, ns and gf was still doable. slash commands work, ugc works, friends works, ignore list works, properties work and have names, teaming works. migrating an old mysql database works . need to test an old sqlite database * fix sqlite migration * remove nd specific column migration --- dChatServer/ChatIgnoreList.cpp | 8 +-- dChatServer/ChatPacketHandler.cpp | 4 -- dDatabase/GameDatabase/GameDatabase.h | 2 +- dDatabase/GameDatabase/ITables/IActivityLog.h | 2 +- dDatabase/GameDatabase/ITables/IBehaviors.h | 2 +- dDatabase/GameDatabase/ITables/IBugReports.h | 2 +- dDatabase/GameDatabase/ITables/ICharInfo.h | 12 ++-- dDatabase/GameDatabase/ITables/ICharXml.h | 6 +- dDatabase/GameDatabase/ITables/ICommandLog.h | 2 +- dDatabase/GameDatabase/ITables/IFriends.h | 14 ++-- dDatabase/GameDatabase/ITables/IIgnoreList.h | 8 +-- dDatabase/GameDatabase/ITables/ILeaderboard.h | 13 ++-- dDatabase/GameDatabase/ITables/IMail.h | 4 +- dDatabase/GameDatabase/ITables/IProperty.h | 4 +- .../GameDatabase/ITables/IPropertyContents.h | 2 +- .../GameDatabase/ITables/IUgcModularBuild.h | 2 +- .../GameDatabase/MySQL/MySQLDatabase.cpp | 2 +- dDatabase/GameDatabase/MySQL/MySQLDatabase.h | 67 +++++++++++-------- .../GameDatabase/MySQL/Tables/ActivityLog.cpp | 2 +- .../GameDatabase/MySQL/Tables/CharInfo.cpp | 16 ++--- .../GameDatabase/MySQL/Tables/CharXml.cpp | 6 +- .../GameDatabase/MySQL/Tables/CommandLog.cpp | 2 +- .../GameDatabase/MySQL/Tables/Friends.cpp | 16 ++--- .../GameDatabase/MySQL/Tables/IgnoreList.cpp | 8 +-- .../GameDatabase/MySQL/Tables/Leaderboard.cpp | 12 ++-- dDatabase/GameDatabase/MySQL/Tables/Mail.cpp | 4 +- .../GameDatabase/MySQL/Tables/Property.cpp | 4 +- dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp | 2 +- .../MySQL/Tables/UgcModularBuild.cpp | 2 +- .../GameDatabase/SQLite/SQLiteDatabase.cpp | 2 +- .../GameDatabase/SQLite/SQLiteDatabase.h | 67 +++++++++++-------- .../SQLite/Tables/ActivityLog.cpp | 2 +- .../GameDatabase/SQLite/Tables/CharInfo.cpp | 16 ++--- .../GameDatabase/SQLite/Tables/CharXml.cpp | 6 +- .../GameDatabase/SQLite/Tables/CommandLog.cpp | 2 +- .../GameDatabase/SQLite/Tables/Friends.cpp | 16 ++--- .../GameDatabase/SQLite/Tables/IgnoreList.cpp | 8 +-- .../SQLite/Tables/Leaderboard.cpp | 12 ++-- dDatabase/GameDatabase/SQLite/Tables/Mail.cpp | 4 +- dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp | 2 +- .../SQLite/Tables/UgcModularBuild.cpp | 2 +- .../GameDatabase/TestSQL/TestSQLDatabase.cpp | 44 ++++++------ .../GameDatabase/TestSQL/TestSQLDatabase.h | 56 ++++++++-------- dGame/Character.cpp | 15 +++-- dGame/Character.h | 8 +-- dGame/LeaderboardManager.cpp | 6 +- dGame/User.cpp | 4 +- dGame/UserManager.cpp | 24 +++---- dGame/UserManager.h | 2 +- .../PropertyManagementComponent.cpp | 1 - dGame/dUtilities/CheatDetection.cpp | 6 +- .../GMGreaterThanZeroCommands.cpp | 1 - dNet/MailInfo.h | 4 +- dWorldServer/WorldServer.cpp | 13 ++-- .../23_store_character_id_as_objectid.sql | 22 ++++++ .../6_store_character_id_as_objectid.sql | 22 ++++++ 56 files changed, 327 insertions(+), 270 deletions(-) create mode 100644 migrations/dlu/mysql/23_store_character_id_as_objectid.sql create mode 100644 migrations/dlu/sqlite/6_store_character_id_as_objectid.sql diff --git a/dChatServer/ChatIgnoreList.cpp b/dChatServer/ChatIgnoreList.cpp index 025d56dc..6433da70 100644 --- a/dChatServer/ChatIgnoreList.cpp +++ b/dChatServer/ChatIgnoreList.cpp @@ -34,7 +34,7 @@ void ChatIgnoreList::GetIgnoreList(Packet* packet) { if (!receiver.ignoredPlayers.empty()) { LOG_DEBUG("Player %llu already has an ignore list, but is requesting it again.", playerId); } else { - auto ignoreList = Database::Get()->GetIgnoreList(static_cast(playerId)); + auto ignoreList = Database::Get()->GetIgnoreList(playerId); if (ignoreList.empty()) { LOG_DEBUG("Player %llu has no ignores", playerId); return; @@ -43,7 +43,6 @@ void ChatIgnoreList::GetIgnoreList(Packet* packet) { for (auto& ignoredPlayer : ignoreList) { receiver.ignoredPlayers.emplace_back(ignoredPlayer.name, ignoredPlayer.id); GeneralUtils::SetBit(receiver.ignoredPlayers.back().playerId, eObjectBits::CHARACTER); - GeneralUtils::SetBit(receiver.ignoredPlayers.back().playerId, eObjectBits::PERSISTENT); } } @@ -114,9 +113,8 @@ void ChatIgnoreList::AddIgnore(Packet* packet) { } if (ignoredPlayerId != LWOOBJID_EMPTY) { - Database::Get()->AddIgnore(static_cast(playerId), static_cast(ignoredPlayerId)); + Database::Get()->AddIgnore(playerId, ignoredPlayerId); GeneralUtils::SetBit(ignoredPlayerId, eObjectBits::CHARACTER); - GeneralUtils::SetBit(ignoredPlayerId, eObjectBits::PERSISTENT); receiver.ignoredPlayers.emplace_back(toIgnoreStr, ignoredPlayerId); LOG_DEBUG("Player %llu is ignoring %s", playerId, toIgnoreStr.c_str()); @@ -157,7 +155,7 @@ void ChatIgnoreList::RemoveIgnore(Packet* packet) { return; } - Database::Get()->RemoveIgnore(static_cast(playerId), static_cast(toRemove->playerId)); + Database::Get()->RemoveIgnore(playerId, toRemove->playerId); receiver.ignoredPlayers.erase(toRemove, receiver.ignoredPlayers.end()); CBITSTREAM; diff --git a/dChatServer/ChatPacketHandler.cpp b/dChatServer/ChatPacketHandler.cpp index 80d1a26f..844631b4 100644 --- a/dChatServer/ChatPacketHandler.cpp +++ b/dChatServer/ChatPacketHandler.cpp @@ -35,7 +35,6 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) { FriendData fd; fd.isFTP = false; // not a thing in DLU fd.friendID = friendData.friendID; - GeneralUtils::SetBit(fd.friendID, eObjectBits::PERSISTENT); GeneralUtils::SetBit(fd.friendID, eObjectBits::CHARACTER); fd.isBestFriend = friendData.isBestFriend; //0 = friends, 1 = left_requested, 2 = right_requested, 3 = both_accepted - are now bffs @@ -161,9 +160,7 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) { // Set the bits GeneralUtils::SetBit(queryPlayerID, eObjectBits::CHARACTER); - GeneralUtils::SetBit(queryPlayerID, eObjectBits::PERSISTENT); GeneralUtils::SetBit(queryFriendID, eObjectBits::CHARACTER); - GeneralUtils::SetBit(queryFriendID, eObjectBits::PERSISTENT); // Since this player can either be the friend of someone else or be friends with someone else // their column in the database determines what bit gets set. When the value hits 3, they @@ -318,7 +315,6 @@ void ChatPacketHandler::HandleRemoveFriend(Packet* packet) { } // Convert friendID to LWOOBJID - GeneralUtils::SetBit(friendID, eObjectBits::PERSISTENT); GeneralUtils::SetBit(friendID, eObjectBits::CHARACTER); Database::Get()->RemoveFriend(playerID, friendID); diff --git a/dDatabase/GameDatabase/GameDatabase.h b/dDatabase/GameDatabase/GameDatabase.h index d0b5c866..2126b9be 100644 --- a/dDatabase/GameDatabase/GameDatabase.h +++ b/dDatabase/GameDatabase/GameDatabase.h @@ -48,7 +48,7 @@ public: virtual void Commit() = 0; virtual bool GetAutoCommit() = 0; virtual void SetAutoCommit(bool value) = 0; - virtual void DeleteCharacter(const uint32_t characterId) = 0; + virtual void DeleteCharacter(const LWOOBJID characterId) = 0; }; #endif //!__GAMEDATABASE__H__ diff --git a/dDatabase/GameDatabase/ITables/IActivityLog.h b/dDatabase/GameDatabase/ITables/IActivityLog.h index 10a97e97..19022e2b 100644 --- a/dDatabase/GameDatabase/ITables/IActivityLog.h +++ b/dDatabase/GameDatabase/ITables/IActivityLog.h @@ -14,7 +14,7 @@ enum class eActivityType : uint32_t { class IActivityLog { public: // Update the activity log for the given account. - virtual void UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) = 0; + virtual void UpdateActivityLog(const LWOOBJID characterId, const eActivityType activityType, const LWOMAPID mapId) = 0; }; #endif //!__IACTIVITYLOG__H__ diff --git a/dDatabase/GameDatabase/ITables/IBehaviors.h b/dDatabase/GameDatabase/ITables/IBehaviors.h index e791ebef..e3f087f3 100644 --- a/dDatabase/GameDatabase/ITables/IBehaviors.h +++ b/dDatabase/GameDatabase/ITables/IBehaviors.h @@ -9,7 +9,7 @@ class IBehaviors { public: struct Info { LWOOBJID behaviorId{}; - uint32_t characterId{}; + LWOOBJID characterId{}; std::string behaviorInfo; }; diff --git a/dDatabase/GameDatabase/ITables/IBugReports.h b/dDatabase/GameDatabase/ITables/IBugReports.h index 29a6180f..083b58aa 100644 --- a/dDatabase/GameDatabase/ITables/IBugReports.h +++ b/dDatabase/GameDatabase/ITables/IBugReports.h @@ -11,7 +11,7 @@ public: std::string clientVersion; std::string otherPlayer; std::string selection; - uint32_t characterId{}; + LWOOBJID characterId{}; }; // Add a new bug report to the database. diff --git a/dDatabase/GameDatabase/ITables/ICharInfo.h b/dDatabase/GameDatabase/ITables/ICharInfo.h index d908d993..d28017f3 100644 --- a/dDatabase/GameDatabase/ITables/ICharInfo.h +++ b/dDatabase/GameDatabase/ITables/ICharInfo.h @@ -14,7 +14,7 @@ public: struct Info { std::string name; std::string pendingName; - uint32_t id{}; + LWOOBJID id{}; uint32_t accountId{}; bool needsRename{}; LWOCLONEID cloneId{}; @@ -25,25 +25,25 @@ public: virtual std::vector GetApprovedCharacterNames() = 0; // Get the character info for the given character id. - virtual std::optional GetCharacterInfo(const uint32_t charId) = 0; + virtual std::optional GetCharacterInfo(const LWOOBJID charId) = 0; // Get the character info for the given character name. virtual std::optional GetCharacterInfo(const std::string_view name) = 0; // Get the character ids for the given account. - virtual std::vector GetAccountCharacterIds(const uint32_t accountId) = 0; + virtual std::vector GetAccountCharacterIds(const LWOOBJID accountId) = 0; // Insert a new character into the database. virtual void InsertNewCharacter(const ICharInfo::Info info) = 0; // Set the name of the given character. - virtual void SetCharacterName(const uint32_t characterId, const std::string_view name) = 0; + virtual void SetCharacterName(const LWOOBJID characterId, const std::string_view name) = 0; // Set the pending name of the given character. - virtual void SetPendingCharacterName(const uint32_t characterId, const std::string_view name) = 0; + virtual void SetPendingCharacterName(const LWOOBJID characterId, const std::string_view name) = 0; // Updates the given character ids last login to be right now. - virtual void UpdateLastLoggedInCharacter(const uint32_t characterId) = 0; + virtual void UpdateLastLoggedInCharacter(const LWOOBJID characterId) = 0; virtual bool IsNameInUse(const std::string_view name) = 0; }; diff --git a/dDatabase/GameDatabase/ITables/ICharXml.h b/dDatabase/GameDatabase/ITables/ICharXml.h index c7ada075..498d964b 100644 --- a/dDatabase/GameDatabase/ITables/ICharXml.h +++ b/dDatabase/GameDatabase/ITables/ICharXml.h @@ -8,13 +8,13 @@ class ICharXml { public: // Get the character xml for the given character id. - virtual std::string GetCharacterXml(const uint32_t charId) = 0; + virtual std::string GetCharacterXml(const LWOOBJID charId) = 0; // Update the character xml for the given character id. - virtual void UpdateCharacterXml(const uint32_t charId, const std::string_view lxfml) = 0; + virtual void UpdateCharacterXml(const LWOOBJID charId, const std::string_view lxfml) = 0; // Insert the character xml for the given character id. - virtual void InsertCharacterXml(const uint32_t characterId, const std::string_view lxfml) = 0; + virtual void InsertCharacterXml(const LWOOBJID characterId, const std::string_view lxfml) = 0; }; #endif //!__ICHARXML__H__ diff --git a/dDatabase/GameDatabase/ITables/ICommandLog.h b/dDatabase/GameDatabase/ITables/ICommandLog.h index 63595360..a466d4f6 100644 --- a/dDatabase/GameDatabase/ITables/ICommandLog.h +++ b/dDatabase/GameDatabase/ITables/ICommandLog.h @@ -8,7 +8,7 @@ class ICommandLog { public: // Insert a new slash command log entry. - virtual void InsertSlashCommandUsage(const uint32_t characterId, const std::string_view command) = 0; + virtual void InsertSlashCommandUsage(const LWOOBJID characterId, const std::string_view command) = 0; }; #endif //!__ICOMMANDLOG__H__ diff --git a/dDatabase/GameDatabase/ITables/IFriends.h b/dDatabase/GameDatabase/ITables/IFriends.h index 6f96f441..7179bb8b 100644 --- a/dDatabase/GameDatabase/ITables/IFriends.h +++ b/dDatabase/GameDatabase/ITables/IFriends.h @@ -8,25 +8,25 @@ class IFriends { public: struct BestFriendStatus { - uint32_t playerCharacterId{}; - uint32_t friendCharacterId{}; + LWOOBJID playerCharacterId{}; + LWOOBJID friendCharacterId{}; uint32_t bestFriendStatus{}; }; // Get the friends list for the given character id. - virtual std::vector GetFriendsList(const uint32_t charId) = 0; + virtual std::vector GetFriendsList(const LWOOBJID charId) = 0; // Get the best friend status for the given player and friend character ids. - virtual std::optional GetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId) = 0; + virtual std::optional GetBestFriendStatus(const LWOOBJID playerCharacterId, const LWOOBJID friendCharacterId) = 0; // Set the best friend status for the given player and friend character ids. - virtual void SetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId, const uint32_t bestFriendStatus) = 0; + virtual void SetBestFriendStatus(const LWOOBJID playerCharacterId, const LWOOBJID friendCharacterId, const uint32_t bestFriendStatus) = 0; // Add a friend to the given character id. - virtual void AddFriend(const uint32_t playerCharacterId, const uint32_t friendCharacterId) = 0; + virtual void AddFriend(const LWOOBJID playerCharacterId, const LWOOBJID friendCharacterId) = 0; // Remove a friend from the given character id. - virtual void RemoveFriend(const uint32_t playerCharacterId, const uint32_t friendCharacterId) = 0; + virtual void RemoveFriend(const LWOOBJID playerCharacterId, const LWOOBJID friendCharacterId) = 0; }; #endif //!__IFRIENDS__H__ diff --git a/dDatabase/GameDatabase/ITables/IIgnoreList.h b/dDatabase/GameDatabase/ITables/IIgnoreList.h index cf831203..02da9276 100644 --- a/dDatabase/GameDatabase/ITables/IIgnoreList.h +++ b/dDatabase/GameDatabase/ITables/IIgnoreList.h @@ -9,12 +9,12 @@ class IIgnoreList { public: struct Info { std::string name; - uint32_t id; + LWOOBJID id; }; - virtual std::vector GetIgnoreList(const uint32_t playerId) = 0; - virtual void AddIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) = 0; - virtual void RemoveIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) = 0; + virtual std::vector GetIgnoreList(const LWOOBJID playerId) = 0; + virtual void AddIgnore(const LWOOBJID playerId, const LWOOBJID ignoredPlayerId) = 0; + virtual void RemoveIgnore(const LWOOBJID playerId, const LWOOBJID ignoredPlayerId) = 0; }; #endif //!__IIGNORELIST__H__ diff --git a/dDatabase/GameDatabase/ITables/ILeaderboard.h b/dDatabase/GameDatabase/ITables/ILeaderboard.h index 0a7922e4..90e9260d 100644 --- a/dDatabase/GameDatabase/ITables/ILeaderboard.h +++ b/dDatabase/GameDatabase/ITables/ILeaderboard.h @@ -5,12 +5,13 @@ #include #include #include +#include "dCommonVars.h" class ILeaderboard { public: struct Entry { - uint32_t charId{}; + LWOOBJID charId{}; uint32_t lastPlayedTimestamp{}; float primaryScore{}; float secondaryScore{}; @@ -36,12 +37,12 @@ public: virtual std::vector GetAscendingLeaderboard(const uint32_t activityId) = 0; virtual std::vector GetNsLeaderboard(const uint32_t activityId) = 0; virtual std::vector GetAgsLeaderboard(const uint32_t activityId) = 0; - virtual std::optional GetPlayerScore(const uint32_t playerId, const uint32_t gameId) = 0; + virtual std::optional GetPlayerScore(const LWOOBJID playerId, const uint32_t gameId) = 0; - virtual void SaveScore(const uint32_t playerId, const uint32_t gameId, const Score& score) = 0; - virtual void UpdateScore(const uint32_t playerId, const uint32_t gameId, const Score& score) = 0; - virtual void IncrementNumWins(const uint32_t playerId, const uint32_t gameId) = 0; - virtual void IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) = 0; + virtual void SaveScore(const LWOOBJID playerId, const uint32_t gameId, const Score& score) = 0; + virtual void UpdateScore(const LWOOBJID playerId, const uint32_t gameId, const Score& score) = 0; + virtual void IncrementNumWins(const LWOOBJID playerId, const uint32_t gameId) = 0; + virtual void IncrementTimesPlayed(const LWOOBJID playerId, const uint32_t gameId) = 0; }; #endif //!__ILEADERBOARD__H__ diff --git a/dDatabase/GameDatabase/ITables/IMail.h b/dDatabase/GameDatabase/ITables/IMail.h index 239f3cf2..6fb657c9 100644 --- a/dDatabase/GameDatabase/ITables/IMail.h +++ b/dDatabase/GameDatabase/ITables/IMail.h @@ -16,13 +16,13 @@ public: virtual void InsertNewMail(const MailInfo& mail) = 0; // Get the mail for the given character id. - virtual std::vector GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) = 0; + virtual std::vector GetMailForPlayer(const LWOOBJID characterId, const uint32_t numberOfMail) = 0; // Get the mail for the given mail id. virtual std::optional GetMail(const uint64_t mailId) = 0; // Get the number of unread mail for the given character id. - virtual uint32_t GetUnreadMailCount(const uint32_t characterId) = 0; + virtual uint32_t GetUnreadMailCount(const LWOOBJID characterId) = 0; // Mark the given mail as read. virtual void MarkMailRead(const uint64_t mailId) = 0; diff --git a/dDatabase/GameDatabase/ITables/IProperty.h b/dDatabase/GameDatabase/ITables/IProperty.h index 9019c176..78f58284 100644 --- a/dDatabase/GameDatabase/ITables/IProperty.h +++ b/dDatabase/GameDatabase/ITables/IProperty.h @@ -13,7 +13,7 @@ public: std::string description; std::string rejectionReason; LWOOBJID id{}; - uint32_t ownerId{}; + LWOOBJID ownerId{}; LWOCLONEID cloneId{}; int32_t privacyOption{}; uint32_t modApproved{}; @@ -27,7 +27,7 @@ public: uint32_t mapId{}; std::string searchString; ePropertySortType sortChoice{}; - uint32_t playerId{}; + LWOOBJID playerId{}; uint32_t numResults{}; uint32_t startIndex{}; uint32_t playerSort{}; diff --git a/dDatabase/GameDatabase/ITables/IPropertyContents.h b/dDatabase/GameDatabase/ITables/IPropertyContents.h index 1c51d00c..52f1a672 100644 --- a/dDatabase/GameDatabase/ITables/IPropertyContents.h +++ b/dDatabase/GameDatabase/ITables/IPropertyContents.h @@ -25,7 +25,7 @@ public: std::stringstream& sd0Data, const uint32_t blueprintId, const uint32_t accountId, - const uint32_t characterId) = 0; + const LWOOBJID characterId) = 0; // Get the property models for the given property id. virtual std::vector GetPropertyModels(const LWOOBJID& propertyId) = 0; diff --git a/dDatabase/GameDatabase/ITables/IUgcModularBuild.h b/dDatabase/GameDatabase/ITables/IUgcModularBuild.h index 4aa2e312..7ac69fe3 100644 --- a/dDatabase/GameDatabase/ITables/IUgcModularBuild.h +++ b/dDatabase/GameDatabase/ITables/IUgcModularBuild.h @@ -7,7 +7,7 @@ class IUgcModularBuild { public: - virtual void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional characterId) = 0; + virtual void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional characterId) = 0; virtual void DeleteUgcBuild(const LWOOBJID bigId) = 0; }; diff --git a/dDatabase/GameDatabase/MySQL/MySQLDatabase.cpp b/dDatabase/GameDatabase/MySQL/MySQLDatabase.cpp index 26693631..7bbd2812 100644 --- a/dDatabase/GameDatabase/MySQL/MySQLDatabase.cpp +++ b/dDatabase/GameDatabase/MySQL/MySQLDatabase.cpp @@ -100,7 +100,7 @@ void MySQLDatabase::SetAutoCommit(bool value) { con->setAutoCommit(value); } -void MySQLDatabase::DeleteCharacter(const uint32_t characterId) { +void MySQLDatabase::DeleteCharacter(const LWOOBJID characterId) { ExecuteDelete("DELETE FROM charxml WHERE id=? LIMIT 1;", characterId); ExecuteDelete("DELETE FROM command_log WHERE character_id=?;", characterId); ExecuteDelete("DELETE FROM friends WHERE player_id=? OR friend_id=?;", characterId, characterId); diff --git a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h index 13f84e9a..7a3a53b5 100644 --- a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h +++ b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h @@ -40,31 +40,31 @@ public: std::vector GetApprovedCharacterNames() override; - std::vector GetFriendsList(uint32_t charID) override; + std::vector GetFriendsList(LWOOBJID charID) override; - std::optional GetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId) override; - void SetBestFriendStatus(const uint32_t playerAccountId, const uint32_t friendAccountId, const uint32_t bestFriendStatus) override; - void AddFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) override; - void RemoveFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) override; - void UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) override; + std::optional GetBestFriendStatus(const LWOOBJID playerCharacterId, const LWOOBJID friendCharacterId) override; + void SetBestFriendStatus(const LWOOBJID playerAccountId, const LWOOBJID friendAccountId, const uint32_t bestFriendStatus) override; + void AddFriend(const LWOOBJID playerAccountId, const LWOOBJID friendAccountId) override; + void RemoveFriend(const LWOOBJID playerAccountId, const LWOOBJID friendAccountId) override; + void UpdateActivityLog(const LWOOBJID characterId, const eActivityType activityType, const LWOMAPID mapId) override; void DeleteUgcModelData(const LWOOBJID& modelId) override; void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) override; std::vector GetAllUgcModels() override; void CreateMigrationHistoryTable() override; bool IsMigrationRun(const std::string_view str) override; void InsertMigration(const std::string_view str) override; - std::optional GetCharacterInfo(const uint32_t charId) override; + std::optional GetCharacterInfo(const LWOOBJID charId) override; std::optional GetCharacterInfo(const std::string_view charId) override; - std::string GetCharacterXml(const uint32_t accountId) override; - void UpdateCharacterXml(const uint32_t characterId, const std::string_view lxfml) override; + std::string GetCharacterXml(const LWOOBJID accountId) override; + void UpdateCharacterXml(const LWOOBJID characterId, const std::string_view lxfml) override; std::optional GetAccountInfo(const std::string_view username) override; void InsertNewCharacter(const ICharInfo::Info info) override; - void InsertCharacterXml(const uint32_t accountId, const std::string_view lxfml) override; - std::vector GetAccountCharacterIds(uint32_t accountId) override; - void DeleteCharacter(const uint32_t characterId) override; - void SetCharacterName(const uint32_t characterId, const std::string_view name) override; - void SetPendingCharacterName(const uint32_t characterId, const std::string_view name) override; - void UpdateLastLoggedInCharacter(const uint32_t characterId) override; + void InsertCharacterXml(const LWOOBJID accountId, const std::string_view lxfml) override; + std::vector GetAccountCharacterIds(LWOOBJID accountId) override; + void DeleteCharacter(const LWOOBJID characterId) override; + void SetCharacterName(const LWOOBJID characterId, const std::string_view name) override; + void SetPendingCharacterName(const LWOOBJID characterId, const std::string_view name) override; + void UpdateLastLoggedInCharacter(const LWOOBJID characterId) override; void SetPetNameModerationStatus(const LWOOBJID& petId, const IPetNames::Info& info) override; std::optional GetPetNameInfo(const LWOOBJID& petId) override; std::optional GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) override; @@ -85,14 +85,14 @@ public: std::stringstream& sd0Data, const uint32_t blueprintId, const uint32_t accountId, - const uint32_t characterId) override; - std::vector GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override; + const LWOOBJID characterId) override; + std::vector GetMailForPlayer(const LWOOBJID characterId, const uint32_t numberOfMail) override; std::optional GetMail(const uint64_t mailId) override; - uint32_t GetUnreadMailCount(const uint32_t characterId) override; + uint32_t GetUnreadMailCount(const LWOOBJID characterId) override; void MarkMailRead(const uint64_t mailId) override; void DeleteMail(const uint64_t mailId) override; void ClaimMailItem(const uint64_t mailId) override; - void InsertSlashCommandUsage(const uint32_t characterId, const std::string_view command) override; + void InsertSlashCommandUsage(const LWOOBJID characterId, const std::string_view command) override; void UpdateAccountUnmuteTime(const uint32_t accountId, const uint64_t timeToUnmute) override; void UpdateAccountBan(const uint32_t accountId, const bool banned) override; void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override; @@ -104,9 +104,9 @@ public: std::optional GetDonationTotal(const uint32_t activityId) override; std::optional IsPlaykeyActive(const int32_t playkeyId) override; std::vector GetUgcModels(const LWOOBJID& propertyId) override; - void AddIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) override; - void RemoveIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) override; - std::vector GetIgnoreList(const uint32_t playerId) override; + void AddIgnore(const LWOOBJID playerId, const LWOOBJID ignoredPlayerId) override; + void RemoveIgnore(const LWOOBJID playerId, const LWOOBJID ignoredPlayerId) override; + std::vector GetIgnoreList(const LWOOBJID playerId) override; void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override; std::vector GetRewardCodesByAccountID(const uint32_t account_id) override; void AddBehavior(const IBehaviors::Info& info) override; @@ -118,12 +118,12 @@ public: std::vector GetAscendingLeaderboard(const uint32_t activityId) override; std::vector GetNsLeaderboard(const uint32_t activityId) override; std::vector GetAgsLeaderboard(const uint32_t activityId) override; - void SaveScore(const uint32_t playerId, const uint32_t gameId, const Score& score) override; - void UpdateScore(const uint32_t playerId, const uint32_t gameId, const Score& score) override; - std::optional GetPlayerScore(const uint32_t playerId, const uint32_t gameId) override; - void IncrementNumWins(const uint32_t playerId, const uint32_t gameId) override; - void IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) override; - void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional characterId) override; + void SaveScore(const LWOOBJID playerId, const uint32_t gameId, const Score& score) override; + void UpdateScore(const LWOOBJID playerId, const uint32_t gameId, const Score& score) override; + std::optional GetPlayerScore(const LWOOBJID playerId, const uint32_t gameId) override; + void IncrementNumWins(const LWOOBJID playerId, const uint32_t gameId) override; + void IncrementTimesPlayed(const LWOOBJID playerId, const uint32_t gameId) override; + void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional characterId) override; void DeleteUgcBuild(const LWOOBJID bigId) override; uint32_t GetAccountCount() override; bool IsNameInUse(const std::string_view name) override; @@ -268,4 +268,15 @@ inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::opti } } +template<> +inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::optional param) { + if (param) { + // LOG("%d", param.value()); + stmt->setInt64(index, param.value()); + } else { + // LOG("Null"); + stmt->setNull(index, sql::DataType::SQLNULL); + } +} + #endif //!__MYSQLDATABASE__H__ diff --git a/dDatabase/GameDatabase/MySQL/Tables/ActivityLog.cpp b/dDatabase/GameDatabase/MySQL/Tables/ActivityLog.cpp index 50fd6b79..a84be699 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/ActivityLog.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/ActivityLog.cpp @@ -1,6 +1,6 @@ #include "MySQLDatabase.h" -void MySQLDatabase::UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) { +void MySQLDatabase::UpdateActivityLog(const LWOOBJID characterId, const eActivityType activityType, const LWOMAPID mapId) { ExecuteInsert("INSERT INTO activity_log (character_id, activity, time, map_id) VALUES (?, ?, ?, ?);", characterId, static_cast(activityType), static_cast(time(NULL)), mapId); } diff --git a/dDatabase/GameDatabase/MySQL/Tables/CharInfo.cpp b/dDatabase/GameDatabase/MySQL/Tables/CharInfo.cpp index ca8103d6..5d744b3d 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/CharInfo.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/CharInfo.cpp @@ -19,7 +19,7 @@ std::optional CharInfoFromQueryResult(std::unique_ptrgetUInt("id"); + toReturn.id = stmt->getInt64("id"); toReturn.name = stmt->getString("name").c_str(); toReturn.pendingName = stmt->getString("pending_name").c_str(); toReturn.needsRename = stmt->getBoolean("needs_rename"); @@ -30,7 +30,7 @@ std::optional CharInfoFromQueryResult(std::unique_ptr MySQLDatabase::GetCharacterInfo(const uint32_t charId) { +std::optional MySQLDatabase::GetCharacterInfo(const LWOOBJID charId) { return CharInfoFromQueryResult( ExecuteSelect("SELECT name, pending_name, needs_rename, prop_clone_id, permission_map, id, account_id FROM charinfo WHERE id = ? LIMIT 1;", charId) ); @@ -42,13 +42,13 @@ std::optional MySQLDatabase::GetCharacterInfo(const std::string ); } -std::vector MySQLDatabase::GetAccountCharacterIds(const uint32_t accountId) { +std::vector MySQLDatabase::GetAccountCharacterIds(const LWOOBJID accountId) { auto result = ExecuteSelect("SELECT id FROM charinfo WHERE account_id = ? ORDER BY last_login DESC LIMIT 4;", accountId); - std::vector toReturn; + std::vector toReturn; toReturn.reserve(result->rowsCount()); while (result->next()) { - toReturn.push_back(result->getUInt("id")); + toReturn.push_back(result->getInt64("id")); } return toReturn; @@ -65,15 +65,15 @@ void MySQLDatabase::InsertNewCharacter(const ICharInfo::Info info) { static_cast(time(NULL))); } -void MySQLDatabase::SetCharacterName(const uint32_t characterId, const std::string_view name) { +void MySQLDatabase::SetCharacterName(const LWOOBJID characterId, const std::string_view name) { ExecuteUpdate("UPDATE charinfo SET name = ?, pending_name = '', needs_rename = 0, last_login = ? WHERE id = ? LIMIT 1;", name, static_cast(time(NULL)), characterId); } -void MySQLDatabase::SetPendingCharacterName(const uint32_t characterId, const std::string_view name) { +void MySQLDatabase::SetPendingCharacterName(const LWOOBJID characterId, const std::string_view name) { ExecuteUpdate("UPDATE charinfo SET pending_name = ?, needs_rename = 0, last_login = ? WHERE id = ? LIMIT 1", name, static_cast(time(NULL)), characterId); } -void MySQLDatabase::UpdateLastLoggedInCharacter(const uint32_t characterId) { +void MySQLDatabase::UpdateLastLoggedInCharacter(const LWOOBJID characterId) { ExecuteUpdate("UPDATE charinfo SET last_login = ? WHERE id = ? LIMIT 1", static_cast(time(NULL)), characterId); } diff --git a/dDatabase/GameDatabase/MySQL/Tables/CharXml.cpp b/dDatabase/GameDatabase/MySQL/Tables/CharXml.cpp index 91a6351e..1de69166 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/CharXml.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/CharXml.cpp @@ -1,6 +1,6 @@ #include "MySQLDatabase.h" -std::string MySQLDatabase::GetCharacterXml(const uint32_t charId) { +std::string MySQLDatabase::GetCharacterXml(const LWOOBJID charId) { auto result = ExecuteSelect("SELECT xml_data FROM charxml WHERE id = ? LIMIT 1;", charId); if (!result->next()) { @@ -10,10 +10,10 @@ std::string MySQLDatabase::GetCharacterXml(const uint32_t charId) { return result->getString("xml_data").c_str(); } -void MySQLDatabase::UpdateCharacterXml(const uint32_t charId, const std::string_view lxfml) { +void MySQLDatabase::UpdateCharacterXml(const LWOOBJID charId, const std::string_view lxfml) { ExecuteUpdate("UPDATE charxml SET xml_data = ? WHERE id = ?;", lxfml, charId); } -void MySQLDatabase::InsertCharacterXml(const uint32_t characterId, const std::string_view lxfml) { +void MySQLDatabase::InsertCharacterXml(const LWOOBJID characterId, const std::string_view lxfml) { ExecuteInsert("INSERT INTO `charxml` (`id`, `xml_data`) VALUES (?,?)", characterId, lxfml); } diff --git a/dDatabase/GameDatabase/MySQL/Tables/CommandLog.cpp b/dDatabase/GameDatabase/MySQL/Tables/CommandLog.cpp index c8ae365a..10d83b2e 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/CommandLog.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/CommandLog.cpp @@ -1,5 +1,5 @@ #include "MySQLDatabase.h" -void MySQLDatabase::InsertSlashCommandUsage(const uint32_t characterId, const std::string_view command) { +void MySQLDatabase::InsertSlashCommandUsage(const LWOOBJID characterId, const std::string_view command) { ExecuteInsert("INSERT INTO command_log (character_id, command) VALUES (?, ?);", characterId, command); } diff --git a/dDatabase/GameDatabase/MySQL/Tables/Friends.cpp b/dDatabase/GameDatabase/MySQL/Tables/Friends.cpp index da9b34a3..7355ecde 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/Friends.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/Friends.cpp @@ -1,6 +1,6 @@ #include "MySQLDatabase.h" -std::vector MySQLDatabase::GetFriendsList(const uint32_t charId) { +std::vector MySQLDatabase::GetFriendsList(const LWOOBJID charId) { auto friendsList = ExecuteSelect( R"QUERY( SELECT fr.requested_player AS player, best_friend AS bff, ci.name AS name FROM @@ -19,7 +19,7 @@ std::vector MySQLDatabase::GetFriendsList(const uint32_t charId) { while (friendsList->next()) { FriendData fd; - fd.friendID = friendsList->getUInt("player"); + fd.friendID = friendsList->getUInt64("player"); fd.isBestFriend = friendsList->getInt("bff") == 3; // 0 = friends, 1 = left_requested, 2 = right_requested, 3 = both_accepted - are now bffs fd.friendName = friendsList->getString("name").c_str(); @@ -29,7 +29,7 @@ std::vector MySQLDatabase::GetFriendsList(const uint32_t charId) { return toReturn; } -std::optional MySQLDatabase::GetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId) { +std::optional MySQLDatabase::GetBestFriendStatus(const LWOOBJID playerCharacterId, const LWOOBJID friendCharacterId) { auto result = ExecuteSelect("SELECT * FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;", playerCharacterId, friendCharacterId, @@ -42,14 +42,14 @@ std::optional MySQLDatabase::GetBestFriendStatus(con } IFriends::BestFriendStatus toReturn; - toReturn.playerCharacterId = result->getUInt("player_id"); - toReturn.friendCharacterId = result->getUInt("friend_id"); + toReturn.playerCharacterId = result->getUInt64("player_id"); + toReturn.friendCharacterId = result->getUInt64("friend_id"); toReturn.bestFriendStatus = result->getUInt("best_friend"); return toReturn; } -void MySQLDatabase::SetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId, const uint32_t bestFriendStatus) { +void MySQLDatabase::SetBestFriendStatus(const LWOOBJID playerCharacterId, const LWOOBJID friendCharacterId, const uint32_t bestFriendStatus) { ExecuteUpdate("UPDATE friends SET best_friend = ? WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;", bestFriendStatus, playerCharacterId, @@ -59,11 +59,11 @@ void MySQLDatabase::SetBestFriendStatus(const uint32_t playerCharacterId, const ); } -void MySQLDatabase::AddFriend(const uint32_t playerCharacterId, const uint32_t friendCharacterId) { +void MySQLDatabase::AddFriend(const LWOOBJID playerCharacterId, const LWOOBJID friendCharacterId) { ExecuteInsert("INSERT IGNORE INTO friends (player_id, friend_id, best_friend) VALUES (?, ?, 0);", playerCharacterId, friendCharacterId); } -void MySQLDatabase::RemoveFriend(const uint32_t playerCharacterId, const uint32_t friendCharacterId) { +void MySQLDatabase::RemoveFriend(const LWOOBJID playerCharacterId, const LWOOBJID friendCharacterId) { ExecuteDelete("DELETE FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;", playerCharacterId, friendCharacterId, diff --git a/dDatabase/GameDatabase/MySQL/Tables/IgnoreList.cpp b/dDatabase/GameDatabase/MySQL/Tables/IgnoreList.cpp index 283df324..ec1f378c 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/IgnoreList.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/IgnoreList.cpp @@ -1,22 +1,22 @@ #include "MySQLDatabase.h" -std::vector MySQLDatabase::GetIgnoreList(const uint32_t playerId) { +std::vector MySQLDatabase::GetIgnoreList(const LWOOBJID playerId) { auto result = ExecuteSelect("SELECT ci.name AS name, il.ignored_player_id AS ignore_id FROM ignore_list AS il JOIN charinfo AS ci ON il.ignored_player_id = ci.id WHERE il.player_id = ?", playerId); std::vector ignoreList; ignoreList.reserve(result->rowsCount()); while (result->next()) { - ignoreList.push_back(IIgnoreList::Info{ result->getString("name").c_str(), result->getUInt("ignore_id") }); + ignoreList.push_back(IIgnoreList::Info{ result->getString("name").c_str(), result->getInt64("ignore_id") }); } return ignoreList; } -void MySQLDatabase::AddIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) { +void MySQLDatabase::AddIgnore(const LWOOBJID playerId, const LWOOBJID ignoredPlayerId) { ExecuteInsert("INSERT IGNORE INTO ignore_list (player_id, ignored_player_id) VALUES (?, ?)", playerId, ignoredPlayerId); } -void MySQLDatabase::RemoveIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) { +void MySQLDatabase::RemoveIgnore(const LWOOBJID playerId, const LWOOBJID ignoredPlayerId) { ExecuteDelete("DELETE FROM ignore_list WHERE player_id = ? AND ignored_player_id = ?", playerId, ignoredPlayerId); } diff --git a/dDatabase/GameDatabase/MySQL/Tables/Leaderboard.cpp b/dDatabase/GameDatabase/MySQL/Tables/Leaderboard.cpp index 14ac121a..eb60bf08 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/Leaderboard.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/Leaderboard.cpp @@ -21,7 +21,7 @@ std::vector ProcessQuery(UniqueResultSet& rows) { while (rows->next()) { auto& entry = entries.emplace_back(); - entry.charId = rows->getUInt("character_id"); + entry.charId = rows->getUInt64("character_id"); entry.lastPlayedTimestamp = rows->getUInt("lp_unix"); entry.primaryScore = rows->getFloat("primaryScore"); entry.secondaryScore = rows->getFloat("secondaryScore"); @@ -58,21 +58,21 @@ std::vector MySQLDatabase::GetNsLeaderboard(const uint32_t return ProcessQuery(leaderboard); } -void MySQLDatabase::SaveScore(const uint32_t playerId, const uint32_t gameId, const Score& score) { +void MySQLDatabase::SaveScore(const LWOOBJID playerId, const uint32_t gameId, const Score& score) { ExecuteInsert("INSERT leaderboard SET primaryScore = ?, secondaryScore = ?, tertiaryScore = ?, character_id = ?, game_id = ?;", score.primaryScore, score.secondaryScore, score.tertiaryScore, playerId, gameId); } -void MySQLDatabase::UpdateScore(const uint32_t playerId, const uint32_t gameId, const Score& score) { +void MySQLDatabase::UpdateScore(const LWOOBJID playerId, const uint32_t gameId, const Score& score) { ExecuteInsert("UPDATE leaderboard SET primaryScore = ?, secondaryScore = ?, tertiaryScore = ?, timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;", score.primaryScore, score.secondaryScore, score.tertiaryScore, playerId, gameId); } -void MySQLDatabase::IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) { +void MySQLDatabase::IncrementTimesPlayed(const LWOOBJID playerId, const uint32_t gameId) { ExecuteUpdate("UPDATE leaderboard SET timesPlayed = timesPlayed + 1 WHERE character_id = ? AND game_id = ?;", playerId, gameId); } -std::optional MySQLDatabase::GetPlayerScore(const uint32_t playerId, const uint32_t gameId) { +std::optional MySQLDatabase::GetPlayerScore(const LWOOBJID playerId, const uint32_t gameId) { std::optional toReturn = std::nullopt; auto res = ExecuteSelect("SELECT * FROM leaderboard WHERE character_id = ? AND game_id = ?;", playerId, gameId); if (res->next()) { @@ -86,6 +86,6 @@ std::optional MySQLDatabase::GetPlayerScore(const uint32_t return toReturn; } -void MySQLDatabase::IncrementNumWins(const uint32_t playerId, const uint32_t gameId) { +void MySQLDatabase::IncrementNumWins(const LWOOBJID playerId, const uint32_t gameId) { ExecuteUpdate("UPDATE leaderboard SET numWins = numWins + 1 WHERE character_id = ? AND game_id = ?;", playerId, gameId); } diff --git a/dDatabase/GameDatabase/MySQL/Tables/Mail.cpp b/dDatabase/GameDatabase/MySQL/Tables/Mail.cpp index 74acb54f..27d76899 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/Mail.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/Mail.cpp @@ -19,7 +19,7 @@ void MySQLDatabase::InsertNewMail(const MailInfo& mail) { mail.itemCount); } -std::vector MySQLDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) { +std::vector MySQLDatabase::GetMailForPlayer(const LWOOBJID characterId, const uint32_t numberOfMail) { auto res = ExecuteSelect( "SELECT id, subject, body, sender_name, attachment_id, attachment_lot, attachment_subkey, attachment_count, was_read, time_sent" " FROM mail WHERE receiver_id=? limit ?;", @@ -61,7 +61,7 @@ std::optional MySQLDatabase::GetMail(const uint64_t mailId) { return toReturn; } -uint32_t MySQLDatabase::GetUnreadMailCount(const uint32_t characterId) { +uint32_t MySQLDatabase::GetUnreadMailCount(const LWOOBJID characterId) { auto res = ExecuteSelect("SELECT COUNT(*) AS number_unread FROM mail WHERE receiver_id=? AND was_read=0;", characterId); if (!res->next()) { diff --git a/dDatabase/GameDatabase/MySQL/Tables/Property.cpp b/dDatabase/GameDatabase/MySQL/Tables/Property.cpp index fcb35751..662070df 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/Property.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/Property.cpp @@ -119,7 +119,7 @@ std::optional MySQLDatabase::GetProperties(co if (!result) result = IProperty::PropertyEntranceResult(); auto& entry = result->entries.emplace_back(); entry.id = properties->getUInt64("id"); - entry.ownerId = properties->getUInt64("owner_id"); + entry.ownerId = properties->getInt64("owner_id"); entry.cloneId = properties->getUInt64("clone_id"); entry.name = properties->getString("name").c_str(); entry.description = properties->getString("description").c_str(); @@ -146,7 +146,7 @@ std::optional MySQLDatabase::GetPropertyInfo(const LWOMAPID map IProperty::Info toReturn; toReturn.id = propertyEntry->getUInt64("id"); - toReturn.ownerId = propertyEntry->getUInt64("owner_id"); + toReturn.ownerId = propertyEntry->getInt64("owner_id"); toReturn.cloneId = propertyEntry->getUInt64("clone_id"); toReturn.name = propertyEntry->getString("name").c_str(); toReturn.description = propertyEntry->getString("description").c_str(); diff --git a/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp b/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp index 2d2655f4..a7570adb 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp @@ -48,7 +48,7 @@ void MySQLDatabase::InsertNewUgcModel( std:: stringstream& sd0Data, // cant be const sad const uint32_t blueprintId, const uint32_t accountId, - const uint32_t characterId) { + const LWOOBJID characterId) { const std::istream stream(sd0Data.rdbuf()); ExecuteInsert( "INSERT INTO `ugc`(`id`, `account_id`, `character_id`, `is_optimized`, `lxfml`, `bake_ao`, `filename`) VALUES (?,?,?,?,?,?,?)", diff --git a/dDatabase/GameDatabase/MySQL/Tables/UgcModularBuild.cpp b/dDatabase/GameDatabase/MySQL/Tables/UgcModularBuild.cpp index a9573515..6336a32f 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/UgcModularBuild.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/UgcModularBuild.cpp @@ -1,6 +1,6 @@ #include "MySQLDatabase.h" -void MySQLDatabase::InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional characterId) { +void MySQLDatabase::InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional characterId) { ExecuteInsert("INSERT INTO ugc_modular_build (ugc_id, ldf_config, character_id) VALUES (?,?,?)", bigId, modules, characterId); } diff --git a/dDatabase/GameDatabase/SQLite/SQLiteDatabase.cpp b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.cpp index 0f2b97a2..00726be5 100644 --- a/dDatabase/GameDatabase/SQLite/SQLiteDatabase.cpp +++ b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.cpp @@ -67,7 +67,7 @@ void SQLiteDatabase::SetAutoCommit(bool value) { } } -void SQLiteDatabase::DeleteCharacter(const uint32_t characterId) { +void SQLiteDatabase::DeleteCharacter(const LWOOBJID characterId) { ExecuteDelete("DELETE FROM charxml WHERE id=?;", characterId); ExecuteDelete("DELETE FROM command_log WHERE character_id=?;", characterId); ExecuteDelete("DELETE FROM friends WHERE player_id=? OR friend_id=?;", characterId, characterId); diff --git a/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h index 4112f87d..f46b7bc2 100644 --- a/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h +++ b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h @@ -38,31 +38,31 @@ public: std::vector GetApprovedCharacterNames() override; - std::vector GetFriendsList(uint32_t charID) override; + std::vector GetFriendsList(LWOOBJID charID) override; - std::optional GetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId) override; - void SetBestFriendStatus(const uint32_t playerAccountId, const uint32_t friendAccountId, const uint32_t bestFriendStatus) override; - void AddFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) override; - void RemoveFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) override; - void UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) override; + std::optional GetBestFriendStatus(const LWOOBJID playerCharacterId, const LWOOBJID friendCharacterId) override; + void SetBestFriendStatus(const LWOOBJID playerAccountId, const LWOOBJID friendAccountId, const uint32_t bestFriendStatus) override; + void AddFriend(const LWOOBJID playerAccountId, const LWOOBJID friendAccountId) override; + void RemoveFriend(const LWOOBJID playerAccountId, const LWOOBJID friendAccountId) override; + void UpdateActivityLog(const LWOOBJID characterId, const eActivityType activityType, const LWOMAPID mapId) override; void DeleteUgcModelData(const LWOOBJID& modelId) override; void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) override; std::vector GetAllUgcModels() override; void CreateMigrationHistoryTable() override; bool IsMigrationRun(const std::string_view str) override; void InsertMigration(const std::string_view str) override; - std::optional GetCharacterInfo(const uint32_t charId) override; + std::optional GetCharacterInfo(const LWOOBJID charId) override; std::optional GetCharacterInfo(const std::string_view charId) override; - std::string GetCharacterXml(const uint32_t accountId) override; - void UpdateCharacterXml(const uint32_t characterId, const std::string_view lxfml) override; + std::string GetCharacterXml(const LWOOBJID accountId) override; + void UpdateCharacterXml(const LWOOBJID characterId, const std::string_view lxfml) override; std::optional GetAccountInfo(const std::string_view username) override; void InsertNewCharacter(const ICharInfo::Info info) override; - void InsertCharacterXml(const uint32_t accountId, const std::string_view lxfml) override; - std::vector GetAccountCharacterIds(uint32_t accountId) override; - void DeleteCharacter(const uint32_t characterId) override; - void SetCharacterName(const uint32_t characterId, const std::string_view name) override; - void SetPendingCharacterName(const uint32_t characterId, const std::string_view name) override; - void UpdateLastLoggedInCharacter(const uint32_t characterId) override; + void InsertCharacterXml(const LWOOBJID accountId, const std::string_view lxfml) override; + std::vector GetAccountCharacterIds(LWOOBJID accountId) override; + void DeleteCharacter(const LWOOBJID characterId) override; + void SetCharacterName(const LWOOBJID characterId, const std::string_view name) override; + void SetPendingCharacterName(const LWOOBJID characterId, const std::string_view name) override; + void UpdateLastLoggedInCharacter(const LWOOBJID characterId) override; void SetPetNameModerationStatus(const LWOOBJID& petId, const IPetNames::Info& info) override; std::optional GetPetNameInfo(const LWOOBJID& petId) override; std::optional GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) override; @@ -83,14 +83,14 @@ public: std::stringstream& sd0Data, const uint32_t blueprintId, const uint32_t accountId, - const uint32_t characterId) override; - std::vector GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override; + const LWOOBJID characterId) override; + std::vector GetMailForPlayer(const LWOOBJID characterId, const uint32_t numberOfMail) override; std::optional GetMail(const uint64_t mailId) override; - uint32_t GetUnreadMailCount(const uint32_t characterId) override; + uint32_t GetUnreadMailCount(const LWOOBJID characterId) override; void MarkMailRead(const uint64_t mailId) override; void DeleteMail(const uint64_t mailId) override; void ClaimMailItem(const uint64_t mailId) override; - void InsertSlashCommandUsage(const uint32_t characterId, const std::string_view command) override; + void InsertSlashCommandUsage(const LWOOBJID characterId, const std::string_view command) override; void UpdateAccountUnmuteTime(const uint32_t accountId, const uint64_t timeToUnmute) override; void UpdateAccountBan(const uint32_t accountId, const bool banned) override; void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override; @@ -102,9 +102,9 @@ public: std::optional GetDonationTotal(const uint32_t activityId) override; std::optional IsPlaykeyActive(const int32_t playkeyId) override; std::vector GetUgcModels(const LWOOBJID& propertyId) override; - void AddIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) override; - void RemoveIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) override; - std::vector GetIgnoreList(const uint32_t playerId) override; + void AddIgnore(const LWOOBJID playerId, const LWOOBJID ignoredPlayerId) override; + void RemoveIgnore(const LWOOBJID playerId, const LWOOBJID ignoredPlayerId) override; + std::vector GetIgnoreList(const LWOOBJID playerId) override; void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override; std::vector GetRewardCodesByAccountID(const uint32_t account_id) override; void AddBehavior(const IBehaviors::Info& info) override; @@ -116,12 +116,12 @@ public: std::vector GetAscendingLeaderboard(const uint32_t activityId) override; std::vector GetNsLeaderboard(const uint32_t activityId) override; std::vector GetAgsLeaderboard(const uint32_t activityId) override; - void SaveScore(const uint32_t playerId, const uint32_t gameId, const Score& score) override; - void UpdateScore(const uint32_t playerId, const uint32_t gameId, const Score& score) override; - std::optional GetPlayerScore(const uint32_t playerId, const uint32_t gameId) override; - void IncrementNumWins(const uint32_t playerId, const uint32_t gameId) override; - void IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) override; - void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional characterId) override; + void SaveScore(const LWOOBJID playerId, const uint32_t gameId, const Score& score) override; + void UpdateScore(const LWOOBJID playerId, const uint32_t gameId, const Score& score) override; + std::optional GetPlayerScore(const LWOOBJID playerId, const uint32_t gameId) override; + void IncrementNumWins(const LWOOBJID playerId, const uint32_t gameId) override; + void IncrementTimesPlayed(const LWOOBJID playerId, const uint32_t gameId) override; + void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional characterId) override; void DeleteUgcBuild(const LWOOBJID bigId) override; uint32_t GetAccountCount() override; bool IsNameInUse(const std::string_view name) override; @@ -270,4 +270,15 @@ inline void SetParam(PreppedStmtRef stmt, const int index, const std::optional +inline void SetParam(PreppedStmtRef stmt, const int index, const std::optional param) { + if (param) { + LOG("%d", param.value()); + stmt.bind(index, static_cast(param.value())); + } else { + LOG("Null"); + stmt.bindNull(index); + } +} + #endif //!SQLITEDATABASE_H diff --git a/dDatabase/GameDatabase/SQLite/Tables/ActivityLog.cpp b/dDatabase/GameDatabase/SQLite/Tables/ActivityLog.cpp index 33f81429..e5953925 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/ActivityLog.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/ActivityLog.cpp @@ -1,6 +1,6 @@ #include "SQLiteDatabase.h" -void SQLiteDatabase::UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) { +void SQLiteDatabase::UpdateActivityLog(const LWOOBJID characterId, const eActivityType activityType, const LWOMAPID mapId) { ExecuteInsert("INSERT INTO activity_log (character_id, activity, time, map_id) VALUES (?, ?, ?, ?);", characterId, static_cast(activityType), static_cast(time(NULL)), mapId); } diff --git a/dDatabase/GameDatabase/SQLite/Tables/CharInfo.cpp b/dDatabase/GameDatabase/SQLite/Tables/CharInfo.cpp index efe5e1ab..6b3dab3b 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/CharInfo.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/CharInfo.cpp @@ -20,7 +20,7 @@ std::optional CharInfoFromQueryResult(CppSQLite3Query stmt) { ICharInfo::Info toReturn; - toReturn.id = stmt.getIntField("id"); + toReturn.id = stmt.getInt64Field("id"); toReturn.name = stmt.getStringField("name"); toReturn.pendingName = stmt.getStringField("pending_name"); toReturn.needsRename = stmt.getIntField("needs_rename"); @@ -31,7 +31,7 @@ std::optional CharInfoFromQueryResult(CppSQLite3Query stmt) { return toReturn; } -std::optional SQLiteDatabase::GetCharacterInfo(const uint32_t charId) { +std::optional SQLiteDatabase::GetCharacterInfo(const LWOOBJID charId) { return CharInfoFromQueryResult( ExecuteSelect("SELECT name, pending_name, needs_rename, prop_clone_id, permission_map, id, account_id FROM charinfo WHERE id = ? LIMIT 1;", charId).second ); @@ -43,12 +43,12 @@ std::optional SQLiteDatabase::GetCharacterInfo(const std::strin ); } -std::vector SQLiteDatabase::GetAccountCharacterIds(const uint32_t accountId) { +std::vector SQLiteDatabase::GetAccountCharacterIds(const LWOOBJID accountId) { auto [_, result] = ExecuteSelect("SELECT id FROM charinfo WHERE account_id = ? ORDER BY last_login DESC LIMIT 4;", accountId); - std::vector toReturn; + std::vector toReturn; while (!result.eof()) { - toReturn.push_back(result.getIntField("id")); + toReturn.push_back(result.getInt64Field("id")); result.nextRow(); } @@ -66,15 +66,15 @@ void SQLiteDatabase::InsertNewCharacter(const ICharInfo::Info info) { static_cast(time(NULL))); } -void SQLiteDatabase::SetCharacterName(const uint32_t characterId, const std::string_view name) { +void SQLiteDatabase::SetCharacterName(const LWOOBJID characterId, const std::string_view name) { ExecuteUpdate("UPDATE charinfo SET name = ?, pending_name = '', needs_rename = 0, last_login = ? WHERE id = ?;", name, static_cast(time(NULL)), characterId); } -void SQLiteDatabase::SetPendingCharacterName(const uint32_t characterId, const std::string_view name) { +void SQLiteDatabase::SetPendingCharacterName(const LWOOBJID characterId, const std::string_view name) { ExecuteUpdate("UPDATE charinfo SET pending_name = ?, needs_rename = 0, last_login = ? WHERE id = ?;", name, static_cast(time(NULL)), characterId); } -void SQLiteDatabase::UpdateLastLoggedInCharacter(const uint32_t characterId) { +void SQLiteDatabase::UpdateLastLoggedInCharacter(const LWOOBJID characterId) { ExecuteUpdate("UPDATE charinfo SET last_login = ? WHERE id = ?;", static_cast(time(NULL)), characterId); } diff --git a/dDatabase/GameDatabase/SQLite/Tables/CharXml.cpp b/dDatabase/GameDatabase/SQLite/Tables/CharXml.cpp index 56085101..2fcbdc84 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/CharXml.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/CharXml.cpp @@ -1,6 +1,6 @@ #include "SQLiteDatabase.h" -std::string SQLiteDatabase::GetCharacterXml(const uint32_t charId) { +std::string SQLiteDatabase::GetCharacterXml(const LWOOBJID charId) { auto [_, result] = ExecuteSelect("SELECT xml_data FROM charxml WHERE id = ? LIMIT 1;", charId); if (result.eof()) { @@ -10,10 +10,10 @@ std::string SQLiteDatabase::GetCharacterXml(const uint32_t charId) { return result.getStringField("xml_data"); } -void SQLiteDatabase::UpdateCharacterXml(const uint32_t charId, const std::string_view lxfml) { +void SQLiteDatabase::UpdateCharacterXml(const LWOOBJID charId, const std::string_view lxfml) { ExecuteUpdate("UPDATE charxml SET xml_data = ? WHERE id = ?;", lxfml, charId); } -void SQLiteDatabase::InsertCharacterXml(const uint32_t characterId, const std::string_view lxfml) { +void SQLiteDatabase::InsertCharacterXml(const LWOOBJID characterId, const std::string_view lxfml) { ExecuteInsert("INSERT INTO `charxml` (`id`, `xml_data`) VALUES (?,?)", characterId, lxfml); } diff --git a/dDatabase/GameDatabase/SQLite/Tables/CommandLog.cpp b/dDatabase/GameDatabase/SQLite/Tables/CommandLog.cpp index db39046f..e644e55b 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/CommandLog.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/CommandLog.cpp @@ -1,5 +1,5 @@ #include "SQLiteDatabase.h" -void SQLiteDatabase::InsertSlashCommandUsage(const uint32_t characterId, const std::string_view command) { +void SQLiteDatabase::InsertSlashCommandUsage(const LWOOBJID characterId, const std::string_view command) { ExecuteInsert("INSERT INTO command_log (character_id, command) VALUES (?, ?);", characterId, command); } diff --git a/dDatabase/GameDatabase/SQLite/Tables/Friends.cpp b/dDatabase/GameDatabase/SQLite/Tables/Friends.cpp index 7ac41459..f3d11ace 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/Friends.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/Friends.cpp @@ -1,6 +1,6 @@ #include "SQLiteDatabase.h" -std::vector SQLiteDatabase::GetFriendsList(const uint32_t charId) { +std::vector SQLiteDatabase::GetFriendsList(const LWOOBJID charId) { auto [_, friendsList] = ExecuteSelect( R"QUERY( SELECT fr.requested_player AS player, best_friend AS bff, ci.name AS name FROM @@ -18,7 +18,7 @@ std::vector SQLiteDatabase::GetFriendsList(const uint32_t charId) { while (!friendsList.eof()) { FriendData fd; - fd.friendID = friendsList.getIntField("player"); + fd.friendID = friendsList.getInt64Field("player"); fd.isBestFriend = friendsList.getIntField("bff") == 3; // 0 = friends, 1 = left_requested, 2 = right_requested, 3 = both_accepted - are now bffs fd.friendName = friendsList.getStringField("name"); @@ -29,7 +29,7 @@ std::vector SQLiteDatabase::GetFriendsList(const uint32_t charId) { return toReturn; } -std::optional SQLiteDatabase::GetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId) { +std::optional SQLiteDatabase::GetBestFriendStatus(const LWOOBJID playerCharacterId, const LWOOBJID friendCharacterId) { auto [_, result] = ExecuteSelect("SELECT * FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;", playerCharacterId, friendCharacterId, @@ -42,14 +42,14 @@ std::optional SQLiteDatabase::GetBestFriendStatus(co } IFriends::BestFriendStatus toReturn; - toReturn.playerCharacterId = result.getIntField("player_id"); - toReturn.friendCharacterId = result.getIntField("friend_id"); + toReturn.playerCharacterId = result.getInt64Field("player_id"); + toReturn.friendCharacterId = result.getInt64Field("friend_id"); toReturn.bestFriendStatus = result.getIntField("best_friend"); return toReturn; } -void SQLiteDatabase::SetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId, const uint32_t bestFriendStatus) { +void SQLiteDatabase::SetBestFriendStatus(const LWOOBJID playerCharacterId, const LWOOBJID friendCharacterId, const uint32_t bestFriendStatus) { ExecuteUpdate("UPDATE friends SET best_friend = ? WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?);", bestFriendStatus, playerCharacterId, @@ -59,11 +59,11 @@ void SQLiteDatabase::SetBestFriendStatus(const uint32_t playerCharacterId, const ); } -void SQLiteDatabase::AddFriend(const uint32_t playerCharacterId, const uint32_t friendCharacterId) { +void SQLiteDatabase::AddFriend(const LWOOBJID playerCharacterId, const LWOOBJID friendCharacterId) { ExecuteInsert("INSERT OR IGNORE INTO friends (player_id, friend_id, best_friend) VALUES (?, ?, 0);", playerCharacterId, friendCharacterId); } -void SQLiteDatabase::RemoveFriend(const uint32_t playerCharacterId, const uint32_t friendCharacterId) { +void SQLiteDatabase::RemoveFriend(const LWOOBJID playerCharacterId, const LWOOBJID friendCharacterId) { ExecuteDelete("DELETE FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?);", playerCharacterId, friendCharacterId, diff --git a/dDatabase/GameDatabase/SQLite/Tables/IgnoreList.cpp b/dDatabase/GameDatabase/SQLite/Tables/IgnoreList.cpp index e7f5a3e0..e7fd7a14 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/IgnoreList.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/IgnoreList.cpp @@ -1,22 +1,22 @@ #include "SQLiteDatabase.h" -std::vector SQLiteDatabase::GetIgnoreList(const uint32_t playerId) { +std::vector SQLiteDatabase::GetIgnoreList(const LWOOBJID playerId) { auto [_, result] = ExecuteSelect("SELECT ci.name AS name, il.ignored_player_id AS ignore_id FROM ignore_list AS il JOIN charinfo AS ci ON il.ignored_player_id = ci.id WHERE il.player_id = ?", playerId); std::vector ignoreList; while (!result.eof()) { - ignoreList.push_back(IIgnoreList::Info{ result.getStringField("name"), static_cast(result.getIntField("ignore_id")) }); + ignoreList.push_back(IIgnoreList::Info{ result.getStringField("name"), result.getInt64Field("ignore_id") }); result.nextRow(); } return ignoreList; } -void SQLiteDatabase::AddIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) { +void SQLiteDatabase::AddIgnore(const LWOOBJID playerId, const LWOOBJID ignoredPlayerId) { ExecuteInsert("INSERT OR IGNORE INTO ignore_list (player_id, ignored_player_id) VALUES (?, ?)", playerId, ignoredPlayerId); } -void SQLiteDatabase::RemoveIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) { +void SQLiteDatabase::RemoveIgnore(const LWOOBJID playerId, const LWOOBJID ignoredPlayerId) { ExecuteDelete("DELETE FROM ignore_list WHERE player_id = ? AND ignored_player_id = ?", playerId, ignoredPlayerId); } diff --git a/dDatabase/GameDatabase/SQLite/Tables/Leaderboard.cpp b/dDatabase/GameDatabase/SQLite/Tables/Leaderboard.cpp index ee0423dd..a03e62a3 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/Leaderboard.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/Leaderboard.cpp @@ -20,7 +20,7 @@ std::vector ProcessQuery(CppSQLite3Query& rows) { while (!rows.eof()) { auto& entry = entries.emplace_back(); - entry.charId = rows.getIntField("character_id"); + entry.charId = rows.getInt64Field("character_id"); entry.lastPlayedTimestamp = rows.getIntField("lp_unix"); entry.primaryScore = rows.getFloatField("primaryScore"); entry.secondaryScore = rows.getFloatField("secondaryScore"); @@ -58,17 +58,17 @@ std::vector SQLiteDatabase::GetNsLeaderboard(const uint32_t return ProcessQuery(result); } -void SQLiteDatabase::SaveScore(const uint32_t playerId, const uint32_t gameId, const Score& score) { +void SQLiteDatabase::SaveScore(const LWOOBJID playerId, const uint32_t gameId, const Score& score) { ExecuteInsert("INSERT INTO leaderboard (primaryScore, secondaryScore, tertiaryScore, character_id, game_id, last_played) VALUES (?,?,?,?,?,CURRENT_TIMESTAMP) ;", score.primaryScore, score.secondaryScore, score.tertiaryScore, playerId, gameId); } -void SQLiteDatabase::UpdateScore(const uint32_t playerId, const uint32_t gameId, const Score& score) { +void SQLiteDatabase::UpdateScore(const LWOOBJID playerId, const uint32_t gameId, const Score& score) { ExecuteInsert("UPDATE leaderboard SET primaryScore = ?, secondaryScore = ?, tertiaryScore = ?, timesPlayed = timesPlayed + 1, last_played = CURRENT_TIMESTAMP WHERE character_id = ? AND game_id = ?;", score.primaryScore, score.secondaryScore, score.tertiaryScore, playerId, gameId); } -std::optional SQLiteDatabase::GetPlayerScore(const uint32_t playerId, const uint32_t gameId) { +std::optional SQLiteDatabase::GetPlayerScore(const LWOOBJID playerId, const uint32_t gameId) { std::optional toReturn = std::nullopt; auto [_, res] = ExecuteSelect("SELECT * FROM leaderboard WHERE character_id = ? AND game_id = ?;", playerId, gameId); if (!res.eof()) { @@ -82,10 +82,10 @@ std::optional SQLiteDatabase::GetPlayerScore(const uint32_t return toReturn; } -void SQLiteDatabase::IncrementNumWins(const uint32_t playerId, const uint32_t gameId) { +void SQLiteDatabase::IncrementNumWins(const LWOOBJID playerId, const uint32_t gameId) { ExecuteUpdate("UPDATE leaderboard SET numWins = numWins + 1, last_played = CURRENT_TIMESTAMP WHERE character_id = ? AND game_id = ?;", playerId, gameId); } -void SQLiteDatabase::IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) { +void SQLiteDatabase::IncrementTimesPlayed(const LWOOBJID playerId, const uint32_t gameId) { ExecuteUpdate("UPDATE leaderboard SET timesPlayed = timesPlayed + 1, last_played = CURRENT_TIMESTAMP WHERE character_id = ? AND game_id = ?;", playerId, gameId); } diff --git a/dDatabase/GameDatabase/SQLite/Tables/Mail.cpp b/dDatabase/GameDatabase/SQLite/Tables/Mail.cpp index edd4b672..7b0a4860 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/Mail.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/Mail.cpp @@ -18,7 +18,7 @@ void SQLiteDatabase::InsertNewMail(const MailInfo& mail) { mail.itemCount); } -std::vector SQLiteDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) { +std::vector SQLiteDatabase::GetMailForPlayer(const LWOOBJID characterId, const uint32_t numberOfMail) { auto [_, res] = ExecuteSelect( "SELECT id, subject, body, sender_name, attachment_id, attachment_lot, attachment_subkey, attachment_count, was_read, time_sent" " FROM mail WHERE receiver_id=? limit ?;", @@ -60,7 +60,7 @@ std::optional SQLiteDatabase::GetMail(const uint64_t mailId) { return toReturn; } -uint32_t SQLiteDatabase::GetUnreadMailCount(const uint32_t characterId) { +uint32_t SQLiteDatabase::GetUnreadMailCount(const LWOOBJID characterId) { auto [_, res] = ExecuteSelect("SELECT COUNT(*) AS number_unread FROM mail WHERE receiver_id=? AND was_read=0;", characterId); if (res.eof()) { diff --git a/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp b/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp index c6410a42..df0b3d43 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp @@ -49,7 +49,7 @@ void SQLiteDatabase::InsertNewUgcModel( std::stringstream& sd0Data, // cant be const sad const uint32_t blueprintId, const uint32_t accountId, - const uint32_t characterId) { + const LWOOBJID characterId) { const std::istream stream(sd0Data.rdbuf()); ExecuteInsert( "INSERT INTO `ugc`(`id`, `account_id`, `character_id`, `is_optimized`, `lxfml`, `bake_ao`, `filename`) VALUES (?,?,?,?,?,?,?)", diff --git a/dDatabase/GameDatabase/SQLite/Tables/UgcModularBuild.cpp b/dDatabase/GameDatabase/SQLite/Tables/UgcModularBuild.cpp index 4e806384..f501d0e0 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/UgcModularBuild.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/UgcModularBuild.cpp @@ -1,6 +1,6 @@ #include "SQLiteDatabase.h" -void SQLiteDatabase::InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional characterId) { +void SQLiteDatabase::InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional characterId) { ExecuteInsert("INSERT INTO ugc_modular_build (ugc_id, ldf_config, character_id) VALUES (?,?,?)", bigId, modules, characterId); } diff --git a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.cpp b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.cpp index 8dd03f27..9381c07b 100644 --- a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.cpp +++ b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.cpp @@ -32,27 +32,27 @@ std::vector TestSQLDatabase::GetApprovedCharacterNames() { return {}; } -std::vector TestSQLDatabase::GetFriendsList(uint32_t charID) { +std::vector TestSQLDatabase::GetFriendsList(LWOOBJID charID) { return {}; } -std::optional TestSQLDatabase::GetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId) { +std::optional TestSQLDatabase::GetBestFriendStatus(const LWOOBJID playerCharacterId, const LWOOBJID friendCharacterId) { return {}; } -void TestSQLDatabase::SetBestFriendStatus(const uint32_t playerAccountId, const uint32_t friendAccountId, const uint32_t bestFriendStatus) { +void TestSQLDatabase::SetBestFriendStatus(const LWOOBJID playerAccountId, const LWOOBJID friendAccountId, const uint32_t bestFriendStatus) { } -void TestSQLDatabase::AddFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) { +void TestSQLDatabase::AddFriend(const LWOOBJID playerAccountId, const LWOOBJID friendAccountId) { } -void TestSQLDatabase::RemoveFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) { +void TestSQLDatabase::RemoveFriend(const LWOOBJID playerAccountId, const LWOOBJID friendAccountId) { } -void TestSQLDatabase::UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) { +void TestSQLDatabase::UpdateActivityLog(const LWOOBJID characterId, const eActivityType activityType, const LWOMAPID mapId) { } @@ -80,7 +80,7 @@ void TestSQLDatabase::InsertMigration(const std::string_view str) { } -std::optional TestSQLDatabase::GetCharacterInfo(const uint32_t charId) { +std::optional TestSQLDatabase::GetCharacterInfo(const LWOOBJID charId) { return {}; } @@ -88,11 +88,11 @@ std::optional TestSQLDatabase::GetCharacterInfo(const std::stri return {}; } -std::string TestSQLDatabase::GetCharacterXml(const uint32_t accountId) { +std::string TestSQLDatabase::GetCharacterXml(const LWOOBJID accountId) { return {}; } -void TestSQLDatabase::UpdateCharacterXml(const uint32_t characterId, const std::string_view lxfml) { +void TestSQLDatabase::UpdateCharacterXml(const LWOOBJID characterId, const std::string_view lxfml) { } @@ -104,27 +104,27 @@ void TestSQLDatabase::InsertNewCharacter(const ICharInfo::Info info) { } -void TestSQLDatabase::InsertCharacterXml(const uint32_t accountId, const std::string_view lxfml) { +void TestSQLDatabase::InsertCharacterXml(const LWOOBJID accountId, const std::string_view lxfml) { } -std::vector TestSQLDatabase::GetAccountCharacterIds(uint32_t accountId) { +std::vector TestSQLDatabase::GetAccountCharacterIds(LWOOBJID accountId) { return {}; } -void TestSQLDatabase::DeleteCharacter(const uint32_t characterId) { +void TestSQLDatabase::DeleteCharacter(const LWOOBJID characterId) { } -void TestSQLDatabase::SetCharacterName(const uint32_t characterId, const std::string_view name) { +void TestSQLDatabase::SetCharacterName(const LWOOBJID characterId, const std::string_view name) { } -void TestSQLDatabase::SetPendingCharacterName(const uint32_t characterId, const std::string_view name) { +void TestSQLDatabase::SetPendingCharacterName(const LWOOBJID characterId, const std::string_view name) { } -void TestSQLDatabase::UpdateLastLoggedInCharacter(const uint32_t characterId) { +void TestSQLDatabase::UpdateLastLoggedInCharacter(const LWOOBJID characterId) { } @@ -192,11 +192,11 @@ void TestSQLDatabase::InsertNewMail(const MailInfo& mail) { } -void TestSQLDatabase::InsertNewUgcModel(std::stringstream& sd0Data, const uint32_t blueprintId, const uint32_t accountId, const uint32_t characterId) { +void TestSQLDatabase::InsertNewUgcModel(std::stringstream& sd0Data, const uint32_t blueprintId, const uint32_t accountId, const LWOOBJID characterId) { } -std::vector TestSQLDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) { +std::vector TestSQLDatabase::GetMailForPlayer(const LWOOBJID characterId, const uint32_t numberOfMail) { return {}; } @@ -204,7 +204,7 @@ std::optional TestSQLDatabase::GetMail(const uint64_t mailId) { return {}; } -uint32_t TestSQLDatabase::GetUnreadMailCount(const uint32_t characterId) { +uint32_t TestSQLDatabase::GetUnreadMailCount(const LWOOBJID characterId) { return {}; } @@ -220,7 +220,7 @@ void TestSQLDatabase::ClaimMailItem(const uint64_t mailId) { } -void TestSQLDatabase::InsertSlashCommandUsage(const uint32_t characterId, const std::string_view command) { +void TestSQLDatabase::InsertSlashCommandUsage(const LWOOBJID characterId, const std::string_view command) { } @@ -268,15 +268,15 @@ std::vector TestSQLDatabase::GetUgcModels(const LWOOBJID& propertyI return {}; } -void TestSQLDatabase::AddIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) { +void TestSQLDatabase::AddIgnore(const LWOOBJID playerId, const LWOOBJID ignoredPlayerId) { } -void TestSQLDatabase::RemoveIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) { +void TestSQLDatabase::RemoveIgnore(const LWOOBJID playerId, const LWOOBJID ignoredPlayerId) { } -std::vector TestSQLDatabase::GetIgnoreList(const uint32_t playerId) { +std::vector TestSQLDatabase::GetIgnoreList(const LWOOBJID playerId) { return {}; } diff --git a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h index 59f7fe08..8d808e42 100644 --- a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h +++ b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h @@ -17,31 +17,31 @@ class TestSQLDatabase : public GameDatabase { std::vector GetApprovedCharacterNames() override; - std::vector GetFriendsList(uint32_t charID) override; + std::vector GetFriendsList(LWOOBJID charID) override; - std::optional GetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId) override; - void SetBestFriendStatus(const uint32_t playerAccountId, const uint32_t friendAccountId, const uint32_t bestFriendStatus) override; - void AddFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) override; - void RemoveFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) override; - void UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) override; + std::optional GetBestFriendStatus(const LWOOBJID playerCharacterId, const LWOOBJID friendCharacterId) override; + void SetBestFriendStatus(const LWOOBJID playerAccountId, const LWOOBJID friendAccountId, const uint32_t bestFriendStatus) override; + void AddFriend(const LWOOBJID playerAccountId, const LWOOBJID friendAccountId) override; + void RemoveFriend(const LWOOBJID playerAccountId, const LWOOBJID friendAccountId) override; + void UpdateActivityLog(const LWOOBJID characterId, const eActivityType activityType, const LWOMAPID mapId) override; void DeleteUgcModelData(const LWOOBJID& modelId) override; void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) override; std::vector GetAllUgcModels() override; void CreateMigrationHistoryTable() override; bool IsMigrationRun(const std::string_view str) override; void InsertMigration(const std::string_view str) override; - std::optional GetCharacterInfo(const uint32_t charId) override; + std::optional GetCharacterInfo(const LWOOBJID charId) override; std::optional GetCharacterInfo(const std::string_view charId) override; - std::string GetCharacterXml(const uint32_t accountId) override; - void UpdateCharacterXml(const uint32_t characterId, const std::string_view lxfml) override; + std::string GetCharacterXml(const LWOOBJID accountId) override; + void UpdateCharacterXml(const LWOOBJID characterId, const std::string_view lxfml) override; std::optional GetAccountInfo(const std::string_view username) override; void InsertNewCharacter(const ICharInfo::Info info) override; - void InsertCharacterXml(const uint32_t accountId, const std::string_view lxfml) override; - std::vector GetAccountCharacterIds(uint32_t accountId) override; - void DeleteCharacter(const uint32_t characterId) override; - void SetCharacterName(const uint32_t characterId, const std::string_view name) override; - void SetPendingCharacterName(const uint32_t characterId, const std::string_view name) override; - void UpdateLastLoggedInCharacter(const uint32_t characterId) override; + void InsertCharacterXml(const LWOOBJID accountId, const std::string_view lxfml) override; + std::vector GetAccountCharacterIds(LWOOBJID accountId) override; + void DeleteCharacter(const LWOOBJID characterId) override; + void SetCharacterName(const LWOOBJID characterId, const std::string_view name) override; + void SetPendingCharacterName(const LWOOBJID characterId, const std::string_view name) override; + void UpdateLastLoggedInCharacter(const LWOOBJID characterId) override; void SetPetNameModerationStatus(const LWOOBJID& petId, const IPetNames::Info& info) override; std::optional GetPetNameInfo(const LWOOBJID& petId) override; std::optional GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) override; @@ -62,14 +62,14 @@ class TestSQLDatabase : public GameDatabase { std::stringstream& sd0Data, const uint32_t blueprintId, const uint32_t accountId, - const uint32_t characterId) override; - std::vector GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override; + const LWOOBJID characterId) override; + std::vector GetMailForPlayer(const LWOOBJID characterId, const uint32_t numberOfMail) override; std::optional GetMail(const uint64_t mailId) override; - uint32_t GetUnreadMailCount(const uint32_t characterId) override; + uint32_t GetUnreadMailCount(const LWOOBJID characterId) override; void MarkMailRead(const uint64_t mailId) override; void DeleteMail(const uint64_t mailId) override; void ClaimMailItem(const uint64_t mailId) override; - void InsertSlashCommandUsage(const uint32_t characterId, const std::string_view command) override; + void InsertSlashCommandUsage(const LWOOBJID characterId, const std::string_view command) override; void UpdateAccountUnmuteTime(const uint32_t accountId, const uint64_t timeToUnmute) override; void UpdateAccountBan(const uint32_t accountId, const bool banned) override; void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override; @@ -81,9 +81,9 @@ class TestSQLDatabase : public GameDatabase { std::optional GetDonationTotal(const uint32_t activityId) override; std::optional IsPlaykeyActive(const int32_t playkeyId) override; std::vector GetUgcModels(const LWOOBJID& propertyId) override; - void AddIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) override; - void RemoveIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) override; - std::vector GetIgnoreList(const uint32_t playerId) override; + void AddIgnore(const LWOOBJID playerId, const LWOOBJID ignoredPlayerId) override; + void RemoveIgnore(const LWOOBJID playerId, const LWOOBJID ignoredPlayerId) override; + std::vector GetIgnoreList(const LWOOBJID playerId) override; void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override; std::vector GetRewardCodesByAccountID(const uint32_t account_id) override; void AddBehavior(const IBehaviors::Info& info) override; @@ -95,12 +95,12 @@ class TestSQLDatabase : public GameDatabase { std::vector GetAscendingLeaderboard(const uint32_t activityId) override { return {}; }; std::vector GetNsLeaderboard(const uint32_t activityId) override { return {}; }; std::vector GetAgsLeaderboard(const uint32_t activityId) override { return {}; }; - void SaveScore(const uint32_t playerId, const uint32_t gameId, const Score& score) override {}; - void UpdateScore(const uint32_t playerId, const uint32_t gameId, const Score& score) override {}; - std::optional GetPlayerScore(const uint32_t playerId, const uint32_t gameId) override { return {}; }; - void IncrementNumWins(const uint32_t playerId, const uint32_t gameId) override {}; - void IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) override {}; - void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional characterId) override {}; + void SaveScore(const LWOOBJID playerId, const uint32_t gameId, const Score& score) override {}; + void UpdateScore(const LWOOBJID playerId, const uint32_t gameId, const Score& score) override {}; + std::optional GetPlayerScore(const LWOOBJID playerId, const uint32_t gameId) override { return {}; }; + void IncrementNumWins(const LWOOBJID playerId, const uint32_t gameId) override {}; + void IncrementTimesPlayed(const LWOOBJID playerId, const uint32_t gameId) override {}; + void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional characterId) override {}; void DeleteUgcBuild(const LWOOBJID bigId) override {}; uint32_t GetAccountCount() override { return 0; }; diff --git a/dGame/Character.cpp b/dGame/Character.cpp index 0af51a1c..250466a9 100644 --- a/dGame/Character.cpp +++ b/dGame/Character.cpp @@ -23,7 +23,7 @@ #include "ePlayerFlag.h" #include "CDPlayerFlagsTable.h" -Character::Character(uint32_t id, User* parentUser) { +Character::Character(LWOOBJID id, User* parentUser) { //First load the name, etc: m_ID = id; m_ParentUser = parentUser; @@ -50,6 +50,10 @@ void Character::UpdateInfoFromDatabase() { //Load the xmlData now: m_XMLData = Database::Get()->GetCharacterXml(m_ID); + if (m_XMLData.empty()) { + LOG("Character %s (%llu) has no xml data!", m_Name.c_str(), m_ID); + return; + } m_ZoneID = 0; //TEMP! Set back to 0 when done. This is so we can see loading screen progress for testing. m_ZoneInstanceID = 0; //These values don't really matter, these are only used on the char select screen and seem unused. @@ -61,7 +65,6 @@ void Character::UpdateInfoFromDatabase() { //Set our objectID: m_ObjectID = m_ID; GeneralUtils::SetBit(m_ObjectID, eObjectBits::CHARACTER); - GeneralUtils::SetBit(m_ObjectID, eObjectBits::PERSISTENT); m_OurEntity = nullptr; m_BuildMode = false; @@ -75,7 +78,7 @@ void Character::DoQuickXMLDataParse() { if (m_XMLData.size() == 0) return; if (m_Doc.Parse(m_XMLData.c_str(), m_XMLData.size()) == 0) { - LOG("Loaded xmlData for character %s (%i)!", m_Name.c_str(), m_ID); + LOG("Loaded xmlData for character %s (%llu)!", m_Name.c_str(), m_ID); } else { LOG("Failed to load xmlData (%i) (%s) (%s)!", m_Doc.ErrorID(), m_Doc.ErrorIDToName(m_Doc.ErrorID()), m_Doc.ErrorStr()); //Server::rakServer->CloseConnection(m_ParentUser->GetSystemAddress(), true); @@ -238,7 +241,7 @@ void Character::SetBuildMode(bool buildMode) { void Character::SaveXMLToDatabase() { // Check that we can actually _save_ before saving if (!m_OurEntity) { - LOG("%i:%s didn't have an entity set while saving! CHARACTER WILL NOT BE SAVED!", this->GetID(), this->GetName().c_str()); + LOG("%llu:%s didn't have an entity set while saving! CHARACTER WILL NOT BE SAVED!", this->GetID(), this->GetName().c_str()); return; } @@ -308,7 +311,7 @@ void Character::SaveXMLToDatabase() { //For metrics, log the time it took to save: auto end = std::chrono::system_clock::now(); std::chrono::duration elapsed = end - start; - LOG("%i:%s Saved character to Database in: %fs", this->GetID(), this->GetName().c_str(), elapsed.count()); + LOG("%llu:%s Saved character to Database in: %fs", this->GetID(), this->GetName().c_str(), elapsed.count()); } void Character::SetIsNewLogin() { @@ -320,7 +323,7 @@ void Character::SetIsNewLogin() { while (currentChild) { auto* nextChild = currentChild->NextSiblingElement(); if (currentChild->Attribute("si")) { - LOG("Removed session flag (%s) from character %i:%s, saving character to database", currentChild->Attribute("si"), GetID(), GetName().c_str()); + LOG("Removed session flag (%s) from character %llu:%s, saving character to database", currentChild->Attribute("si"), GetID(), GetName().c_str()); flags->DeleteChild(currentChild); WriteToDatabase(); } diff --git a/dGame/Character.h b/dGame/Character.h index bcddfd3e..2c88fd0d 100644 --- a/dGame/Character.h +++ b/dGame/Character.h @@ -23,7 +23,7 @@ enum class eLootSourceType : uint32_t; */ class Character { public: - Character(uint32_t id, User* parentUser); + Character(LWOOBJID id, User* parentUser); ~Character(); /** @@ -53,7 +53,7 @@ public: * Gets the database ID of the character * @return the database ID of the character */ - uint32_t GetID() const { return m_ID; } + LWOOBJID GetID() const { return m_ID; } /** * Gets the (custom) name of the character @@ -467,9 +467,9 @@ public: private: void UpdateInfoFromDatabase(); /** - * The ID of this character. First 32 bits of the ObjectID. + * The ID of this character. */ - uint32_t m_ID{}; + LWOOBJID m_ID{}; /** * The 64-bit unique ID used in the game. diff --git a/dGame/LeaderboardManager.cpp b/dGame/LeaderboardManager.cpp index b08bbd70..488bc2b0 100644 --- a/dGame/LeaderboardManager.cpp +++ b/dGame/LeaderboardManager.cpp @@ -145,7 +145,7 @@ void QueryToLdf(Leaderboard& leaderboard, const std::vector } } -std::vector FilterToNumResults(const std::vector& leaderboard, const uint32_t relatedPlayer, const Leaderboard::InfoType infoType, const uint32_t numResults) { +std::vector FilterToNumResults(const std::vector& leaderboard, const LWOOBJID relatedPlayer, const Leaderboard::InfoType infoType, const uint32_t numResults) { std::vector toReturn; int32_t index = 0; @@ -197,7 +197,7 @@ std::vector FilterWeeklies(const std::vector FilterFriends(const std::vector& leaderboard, const uint32_t relatedPlayer) { +std::vector FilterFriends(const std::vector& leaderboard, const LWOOBJID relatedPlayer) { // Filter the leaderboard to only include friends of the player auto friendOfPlayer = Database::Get()->GetFriendsList(relatedPlayer); std::vector friendsLeaderboard; @@ -217,7 +217,7 @@ std::vector ProcessLeaderboard( const std::vector& leaderboard, const bool weekly, const Leaderboard::InfoType infoType, - const uint32_t relatedPlayer, + const LWOOBJID relatedPlayer, const uint32_t numResults) { std::vector toReturn; diff --git a/dGame/User.cpp b/dGame/User.cpp index 7c03daa8..1f8a41fa 100644 --- a/dGame/User.cpp +++ b/dGame/User.cpp @@ -39,11 +39,11 @@ User::User(const SystemAddress& sysAddr, const std::string& username, const std: if (Game::server->GetZoneID() != 0) { auto characterList = Database::Get()->GetAccountCharacterIds(m_AccountID); if (!characterList.empty()) { - const uint32_t lastUsedCharacterId = characterList.front(); + const auto lastUsedCharacterId = characterList.front(); Character* character = new Character(lastUsedCharacterId, this); character->UpdateFromDatabase(); m_Characters.push_back(character); - LOG("Loaded %i as it is the last used char", lastUsedCharacterId); + LOG("Loaded %llu as it is the last used char", lastUsedCharacterId); } } } diff --git a/dGame/UserManager.cpp b/dGame/UserManager.cpp index 6b742b27..9bd6d844 100644 --- a/dGame/UserManager.cpp +++ b/dGame/UserManager.cpp @@ -325,7 +325,9 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet) } //Now that the name is ok, we can get an objectID from Master: - ObjectIDManager::RequestPersistentID([=, this](uint32_t objectID) { + ObjectIDManager::RequestPersistentID([=, this](uint32_t persistentID) { + LWOOBJID objectID = persistentID; + GeneralUtils::SetBit(objectID, eObjectBits::CHARACTER); if (Database::Get()->GetCharacterInfo(objectID)) { LOG("Character object id unavailable, check object_id_tracker!"); WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::OBJECT_ID_UNAVAILABLE); @@ -409,9 +411,8 @@ void UserManager::DeleteCharacter(const SystemAddress& sysAddr, Packet* packet) CINSTREAM_SKIP_HEADER; LWOOBJID objectID; inStream.Read(objectID); - uint32_t charID = static_cast(objectID); - LOG("Received char delete req for ID: %llu (%u)", objectID, charID); + LOG("Received char delete req for ID: %llu", objectID); bool hasCharacter = CheatDetection::VerifyLwoobjidIsSender( objectID, @@ -423,8 +424,8 @@ void UserManager::DeleteCharacter(const SystemAddress& sysAddr, Packet* packet) if (!hasCharacter) { WorldPackets::SendCharacterDeleteResponse(sysAddr, false); } else { - LOG("Deleting character %i", charID); - Database::Get()->DeleteCharacter(charID); + LOG("Deleting character %llu", objectID); + Database::Get()->DeleteCharacter(objectID); CBITSTREAM; BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::UNEXPECTED_DISCONNECT); @@ -445,11 +446,8 @@ void UserManager::RenameCharacter(const SystemAddress& sysAddr, Packet* packet) CINSTREAM_SKIP_HEADER; LWOOBJID objectID; inStream.Read(objectID); - GeneralUtils::ClearBit(objectID, eObjectBits::CHARACTER); - GeneralUtils::ClearBit(objectID, eObjectBits::PERSISTENT); - uint32_t charID = static_cast(objectID); - LOG("Received char rename request for ID: %llu (%u)", objectID, charID); + LOG("Received char rename request for ID: %llu", objectID); LUWString LUWStringName; inStream.Read(LUWStringName); @@ -466,7 +464,7 @@ void UserManager::RenameCharacter(const SystemAddress& sysAddr, Packet* packet) u->GetAccountID()); auto unusedItr = std::find_if(u->GetCharacters().begin(), u->GetCharacters().end(), [&](Character* c) { - if (c->GetID() == charID) { + if (c->GetID() == objectID) { character = c; return true; } @@ -483,12 +481,12 @@ void UserManager::RenameCharacter(const SystemAddress& sysAddr, Packet* packet) if (!Database::Get()->GetCharacterInfo(newName)) { if (IsNamePreapproved(newName)) { - Database::Get()->SetCharacterName(charID, newName); + Database::Get()->SetCharacterName(objectID, newName); LOG("Character %s now known as %s", character->GetName().c_str(), newName.c_str()); WorldPackets::SendCharacterRenameResponse(sysAddr, eRenameResponse::SUCCESS); UserManager::RequestCharacterList(sysAddr); } else { - Database::Get()->SetPendingCharacterName(charID, newName); + Database::Get()->SetPendingCharacterName(objectID, newName); LOG("Character %s has been renamed to %s and is pending approval by a moderator.", character->GetName().c_str(), newName.c_str()); WorldPackets::SendCharacterRenameResponse(sysAddr, eRenameResponse::SUCCESS); UserManager::RequestCharacterList(sysAddr); @@ -502,7 +500,7 @@ void UserManager::RenameCharacter(const SystemAddress& sysAddr, Packet* packet) } } -void UserManager::LoginCharacter(const SystemAddress& sysAddr, uint32_t playerID) { +void UserManager::LoginCharacter(const SystemAddress& sysAddr, LWOOBJID playerID) { User* u = GetUser(sysAddr); if (!u) { LOG("Couldn't get user to log in character"); diff --git a/dGame/UserManager.h b/dGame/UserManager.h index 94bf5e95..9b4af26e 100644 --- a/dGame/UserManager.h +++ b/dGame/UserManager.h @@ -35,7 +35,7 @@ public: void CreateCharacter(const SystemAddress& sysAddr, Packet* packet); void DeleteCharacter(const SystemAddress& sysAddr, Packet* packet); void RenameCharacter(const SystemAddress& sysAddr, Packet* packet); - void LoginCharacter(const SystemAddress& sysAddr, uint32_t playerID); + void LoginCharacter(const SystemAddress& sysAddr, LWOOBJID playerID); void SaveAllActiveCharacters(); diff --git a/dGame/dComponents/PropertyManagementComponent.cpp b/dGame/dComponents/PropertyManagementComponent.cpp index ccef695f..d8d00787 100644 --- a/dGame/dComponents/PropertyManagementComponent.cpp +++ b/dGame/dComponents/PropertyManagementComponent.cpp @@ -64,7 +64,6 @@ PropertyManagementComponent::PropertyManagementComponent(Entity* parent) : Compo this->propertyId = propertyInfo->id; this->owner = propertyInfo->ownerId; GeneralUtils::SetBit(this->owner, eObjectBits::CHARACTER); - GeneralUtils::SetBit(this->owner, eObjectBits::PERSISTENT); this->clone_Id = propertyInfo->cloneId; this->propertyName = propertyInfo->name; this->propertyDescription = propertyInfo->description; diff --git a/dGame/dUtilities/CheatDetection.cpp b/dGame/dUtilities/CheatDetection.cpp index 504b3d53..a0f8c53a 100644 --- a/dGame/dUtilities/CheatDetection.cpp +++ b/dGame/dUtilities/CheatDetection.cpp @@ -76,8 +76,8 @@ void LogAndSaveFailedAntiCheatCheck(const LWOOBJID& id, const SystemAddress& sys auto* user = UserManager::Instance()->GetUser(sysAddr); if (user) { - LOG("User at system address (%s) (%s) (%llu) sent a packet as (%i) which is not an id they own.", - sysAddr.ToString(), user->GetLastUsedChar()->GetName().c_str(), user->GetLastUsedChar()->GetObjectID(), static_cast(id)); + LOG("User at system address (%s) (%s) (%llu) sent a packet as (%llu) which is not an id they own.", + sysAddr.ToString(), user->GetLastUsedChar()->GetName().c_str(), user->GetLastUsedChar()->GetObjectID(), id); // Can't know sending player. Just log system address for IP banning. } else { LOG("No user found for system address (%s).", sysAddr.ToString()); @@ -117,7 +117,7 @@ bool CheatDetection::VerifyLwoobjidIsSender(const LWOOBJID& id, const SystemAddr return false; } invalidPacket = true; - const uint32_t characterId = static_cast(id); + const auto characterId = id; // Check to make sure the ID provided is one of the user's characters. for (const auto& character : sendingUser->GetCharacters()) { if (character && character->GetID() == characterId) { diff --git a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp index b31291fe..7d017cf0 100644 --- a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp +++ b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp @@ -141,7 +141,6 @@ namespace GMGreaterThanZeroCommands { characterId = characterInfo->id; GeneralUtils::SetBit(characterId, eObjectBits::CHARACTER); - GeneralUtils::SetBit(characterId, eObjectBits::PERSISTENT); } if (accountId == 0) { diff --git a/dNet/MailInfo.h b/dNet/MailInfo.h index c74eee87..ac8edb06 100644 --- a/dNet/MailInfo.h +++ b/dNet/MailInfo.h @@ -15,8 +15,8 @@ struct MailInfo { std::string subject; std::string body; uint64_t id{}; - uint32_t senderId{}; - uint32_t receiverId{}; + LWOOBJID senderId{}; + LWOOBJID receiverId{}; uint64_t timeSent{}; bool wasRead{}; uint16_t languageCode{}; diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index cb7fedd5..ec3ca128 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -861,7 +861,7 @@ void HandlePacket(Packet* packet) { } if (luBitStream.connectionType != ServiceType::WORLD) return; - + LOG_DEBUG("Got world packet %s", StringifiedEnum::ToString(static_cast(luBitStream.internalPacketID)).data()); switch (static_cast(luBitStream.internalPacketID)) { case MessageType::World::VALIDATION: { CINSTREAM_SKIP_HEADER; @@ -983,7 +983,7 @@ void HandlePacket(Packet* packet) { LWOOBJID playerID = 0; inStream.Read(playerID); - + LOG("User is requesting to login with character %llu", playerID); bool valid = CheatDetection::VerifyLwoobjidIsSender( playerID, packet->systemAddress, @@ -991,18 +991,15 @@ void HandlePacket(Packet* packet) { "Sending login request with a sending player that does not match their own. Player ID: %llu", playerID ); - + LOG("Login request for player %llu is %s", playerID, valid ? "valid" : "invalid"); if (!valid) return; - GeneralUtils::ClearBit(playerID, eObjectBits::CHARACTER); - GeneralUtils::ClearBit(playerID, eObjectBits::PERSISTENT); - auto user = UserManager::Instance()->GetUser(packet->systemAddress); if (user) { auto lastCharacter = user->GetLoggedInChar(); // This means we swapped characters and we need to remove the previous player from the container. - if (static_cast(lastCharacter) != playerID) { + if (lastCharacter != playerID) { CBITSTREAM; BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::UNEXPECTED_DISCONNECT); bitStream.Write(lastCharacter); @@ -1010,7 +1007,7 @@ void HandlePacket(Packet* packet) { } } - UserManager::Instance()->LoginCharacter(packet->systemAddress, static_cast(playerID)); + UserManager::Instance()->LoginCharacter(packet->systemAddress, playerID); break; } diff --git a/migrations/dlu/mysql/23_store_character_id_as_objectid.sql b/migrations/dlu/mysql/23_store_character_id_as_objectid.sql new file mode 100644 index 00000000..a05003ef --- /dev/null +++ b/migrations/dlu/mysql/23_store_character_id_as_objectid.sql @@ -0,0 +1,22 @@ +START TRANSACTION; +ALTER TABLE mail MODIFY COLUMN sender_id BIGINT NOT NULL; +ALTER TABLE bug_reports MODIFY COLUMN reporter_id BIGINT; +/* This is done to prevent all entries on the leaderboard from updating */ +ALTER TABLE leaderboard CHANGE last_played last_played TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP(); +SET foreign_key_checks = 0; +UPDATE activity_log SET character_id = character_id | 0x1000000000000000; +UPDATE behaviors SET character_id = character_id | 0x1000000000000000; +UPDATE bug_reports SET reporter_id = reporter_id | 0x1000000000000000; +UPDATE charinfo SET id = id | 0x1000000000000000; +UPDATE charxml SET id = id | 0x1000000000000000; +UPDATE command_log SET character_id = character_id | 0x1000000000000000; +UPDATE friends SET player_id = player_id | 0x1000000000000000, friend_id = friend_id | 0x1000000000000000; +UPDATE ignore_list SET player_id = player_id | 0x1000000000000000, ignored_player_id = ignored_player_id | 0x1000000000000000; +UPDATE leaderboard SET character_id = character_id | 0x1000000000000000; +UPDATE mail SET sender_id = sender_id | 0x1000000000000000, receiver_id = receiver_id | 0x1000000000000000; +UPDATE properties SET owner_id = owner_id | 0x1000000000000000; +UPDATE ugc SET character_id = character_id | 0x1000000000000000; +UPDATE ugc_modular_build SET character_id = character_id | 0x1000000000000000; +SET foreign_key_checks = 1; +ALTER TABLE leaderboard CHANGE last_played last_played TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP() ON UPDATE CURRENT_TIMESTAMP(); +COMMIT; diff --git a/migrations/dlu/sqlite/6_store_character_id_as_objectid.sql b/migrations/dlu/sqlite/6_store_character_id_as_objectid.sql new file mode 100644 index 00000000..8db89ee6 --- /dev/null +++ b/migrations/dlu/sqlite/6_store_character_id_as_objectid.sql @@ -0,0 +1,22 @@ +ALTER TABLE mail ADD COLUMN sender_id_1 BIGINT DEFAULT 0; +ALTER TABLE bug_reports ADD COLUMN reporter_id_1 BIGINT DEFAULT 0; +/* The leaderboard last_played change is not needed here since sqlite does not have ON UPDATE */ +UPDATE activity_log SET character_id = character_id | 0x1000000000000000; +UPDATE behaviors SET character_id = character_id | 0x1000000000000000; +UPDATE bug_reports SET reporter_id_1 = reporter_id | 0x1000000000000000; +UPDATE charinfo SET id = id | 0x1000000000000000; +UPDATE charxml SET id = id | 0x1000000000000000; +UPDATE command_log SET character_id = character_id | 0x1000000000000000; +UPDATE friends SET player_id = player_id | 0x1000000000000000, friend_id = friend_id | 0x1000000000000000; +UPDATE ignore_list SET player_id = player_id | 0x1000000000000000, ignored_player_id = ignored_player_id | 0x1000000000000000; +UPDATE leaderboard SET character_id = character_id | 0x1000000000000000; +UPDATE mail SET sender_id_1 = sender_id | 0x1000000000000000, receiver_id = receiver_id | 0x1000000000000000; +UPDATE properties SET owner_id = owner_id | 0x1000000000000000; +UPDATE ugc SET character_id = character_id | 0x1000000000000000; +UPDATE ugc_modular_build SET character_id = character_id | 0x1000000000000000; + +ALTER TABLE mail DROP COLUMN sender_id; +ALTER TABLE mail RENAME COLUMN sender_id_1 TO sender_id; + +ALTER TABLE bug_reports DROP COLUMN reporter_id; +ALTER TABLE bug_reports RENAME COLUMN reporter_id_1 TO reporter_id; From 06022e4b192ffed00a0f4b214b46ecb13939badd Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 18 Sep 2025 23:12:34 -0700 Subject: [PATCH 06/39] fix: use after free in TCPInterface (#1879) lol --- thirdparty/raknet/Source/TCPInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thirdparty/raknet/Source/TCPInterface.cpp b/thirdparty/raknet/Source/TCPInterface.cpp index c8238c1b..59d2320f 100644 --- a/thirdparty/raknet/Source/TCPInterface.cpp +++ b/thirdparty/raknet/Source/TCPInterface.cpp @@ -211,8 +211,8 @@ SystemAddress TCPInterface::Connect(const char* host, unsigned short remotePort, int errorCode = RakNet::RakThread::Create(ConnectionAttemptLoop, s); if (errorCode!=0) { - delete s; failedConnectionAttempts.Push(s->systemAddress); + delete s; } return UNASSIGNED_SYSTEM_ADDRESS; } From bb05b3ac0d84a893b662343b32b6d4a836d52444 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sat, 20 Sep 2025 17:22:16 -0700 Subject: [PATCH 07/39] feat: Add logging for testing Johnny missions (#1880) --- dGame/dComponents/MissionComponent.cpp | 2 ++ dGame/dMission/Mission.cpp | 13 ++++++++++++- dGame/dMission/Mission.h | 3 +++ dGame/dMission/MissionTask.cpp | 7 ++++++- 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/dGame/dComponents/MissionComponent.cpp b/dGame/dComponents/MissionComponent.cpp index e430aefc..b5a6572a 100644 --- a/dGame/dComponents/MissionComponent.cpp +++ b/dGame/dComponents/MissionComponent.cpp @@ -19,6 +19,7 @@ #include "MissionPrerequisites.h" #include "AchievementCacheKey.h" #include "eMissionState.h" +#include "StringifiedEnum.h" // MARK: Mission Component @@ -136,6 +137,7 @@ void MissionComponent::RemoveMission(uint32_t missionId) { } void MissionComponent::Progress(eMissionTaskType type, int32_t value, LWOOBJID associate, const std::string& targets, int32_t count, bool ignoreAchievements) { + LOG("Progressing missions %s %i %llu %s %s", StringifiedEnum::ToString(type).data(), value, associate, targets.c_str(), ignoreAchievements ? "(ignoring achievements)" : ""); std::vector acceptedAchievements; if (count > 0 && !ignoreAchievements) { acceptedAchievements = LookForAchievements(type, value, true, associate, targets, count); diff --git a/dGame/dMission/Mission.cpp b/dGame/dMission/Mission.cpp index ec430f3e..72641d80 100644 --- a/dGame/dMission/Mission.cpp +++ b/dGame/dMission/Mission.cpp @@ -29,6 +29,11 @@ #include "CDMissionEmailTable.h" #include "ChatPackets.h" #include "PlayerManager.h" +#include "StringifiedEnum.h" + +namespace { + std::set g_TestedMissions = {773, 774, 775, 776, 777}; // TODO Figure out why these missions are broken sometimes +} Mission::Mission(MissionComponent* missionComponent, const uint32_t missionId) { m_MissionComponent = missionComponent; @@ -563,12 +568,14 @@ void Mission::YieldRewards() { void Mission::Progress(eMissionTaskType type, int32_t value, LWOOBJID associate, const std::string& targets, int32_t count) { const auto isRemoval = count < 0; - + const bool testedMission = GetTestedMissions().contains(GetMissionId()); + if (testedMission) LOG("%i Removal: %s complete: %s achievement: %s", GetMissionId(), isRemoval ? "true" : "false", IsComplete() ? "true" : "false", IsAchievement() ? "true" : "false"); if (isRemoval && (IsComplete() || IsAchievement())) { return; } for (auto* task : m_Tasks) { + if (testedMission) LOG("Complete: %s Type: %s TaskType: %s", task->IsComplete() ? "true" : "false", StringifiedEnum::ToString(type).data(), StringifiedEnum::ToString(task->GetType()).data()); if (task->IsComplete() && !isRemoval) { continue; } @@ -618,3 +625,7 @@ Mission::~Mission() { m_Tasks.clear(); } + +const std::set& Mission::GetTestedMissions() const { + return g_TestedMissions; +} diff --git a/dGame/dMission/Mission.h b/dGame/dMission/Mission.h index 77f24181..9526c40a 100644 --- a/dGame/dMission/Mission.h +++ b/dGame/dMission/Mission.h @@ -241,6 +241,9 @@ public: * Sets the unique mission order ID of this mission */ void SetUniqueMissionOrderID(uint32_t value) { m_UniqueMissionID = value; }; + + const std::set& GetTestedMissions() const; + private: /** * Progresses all the newly accepted tasks for this mission after it has been accepted to reflect the state of the diff --git a/dGame/dMission/MissionTask.cpp b/dGame/dMission/MissionTask.cpp index aa2b9bca..801eaeda 100644 --- a/dGame/dMission/MissionTask.cpp +++ b/dGame/dMission/MissionTask.cpp @@ -15,6 +15,7 @@ #include "MissionComponent.h" #include "eMissionTaskType.h" #include "eReplicaComponentType.h" +#include "StringifiedEnum.h" MissionTask::MissionTask(Mission* mission, CDMissionTasks* info, uint32_t mask) { this->info = info; @@ -99,6 +100,7 @@ Mission* MissionTask::GetMission() const { uint32_t MissionTask::GetTarget() const { + if (mission->GetTestedMissions().contains(mission->GetMissionId())) LOG("Target: %i", info->targetValue); return info->target; } @@ -158,6 +160,7 @@ bool MissionTask::InParameters(const uint32_t value) const { bool MissionTask::IsComplete() const { + if (mission->GetTestedMissions().contains(mission->GetMissionId())) LOG("uid: %i target: %i", info->uid, info->targetValue); // Mission 668 has task uid 984 which is a bit mask. Its completion value is 3. if (info->uid == 984) { return progress >= 3; @@ -168,6 +171,7 @@ bool MissionTask::IsComplete() const { void MissionTask::Complete() { + if (mission->GetTestedMissions().contains(mission->GetMissionId())) LOG("target: %i", info->targetValue); SetProgress(info->targetValue); } @@ -180,6 +184,7 @@ void MissionTask::CheckCompletion() const { void MissionTask::Progress(int32_t value, LWOOBJID associate, const std::string& targets, int32_t count) { + if (mission->GetTestedMissions().contains(mission->GetMissionId())) LOG("Progressing mission %s %i", StringifiedEnum::ToString(GetType()).data(), value); if (IsComplete() && count > 0) return; const auto type = GetType(); @@ -229,7 +234,7 @@ void MissionTask::Progress(int32_t value, LWOOBJID associate, const std::string& entity = Game::entityManager->GetEntity(associate); if (entity == nullptr) { if (associate != LWOOBJID_EMPTY) { - LOG("Failed to find associated entity (%llu)!", associate); + if (mission->GetTestedMissions().contains(mission->GetMissionId())) LOG("Failed to find associated entity (%llu)!", associate); } break; } From 4a577f233d22c1496d02c24ebd156a187293eb02 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 21 Sep 2025 18:12:50 -0700 Subject: [PATCH 08/39] fix: hardcore mode fixes (#1883) fixes hardcore modes uscore drops adds config option for excluded item drops f feat: Add logging for config options on load and reload feat: Add logging for config options on load and reload --- dAuthServer/AuthServer.cpp | 1 + dChatServer/ChatServer.cpp | 1 + dCommon/dConfig.cpp | 9 +++++++++ dCommon/dConfig.h | 4 +++- dMasterServer/MasterServer.cpp | 1 + dWorldServer/WorldServer.cpp | 1 + 6 files changed, 16 insertions(+), 1 deletion(-) diff --git a/dAuthServer/AuthServer.cpp b/dAuthServer/AuthServer.cpp index 1905fa73..505b1f1a 100644 --- a/dAuthServer/AuthServer.cpp +++ b/dAuthServer/AuthServer.cpp @@ -52,6 +52,7 @@ int main(int argc, char** argv) { //Create all the objects we need to run our service: Server::SetupLogger("AuthServer"); if (!Game::logger) return EXIT_FAILURE; + Game::config->LogSettings(); LOG("Starting Auth server..."); LOG("Version: %s", PROJECT_VERSION); diff --git a/dChatServer/ChatServer.cpp b/dChatServer/ChatServer.cpp index 9cabbf28..34b4d6e3 100644 --- a/dChatServer/ChatServer.cpp +++ b/dChatServer/ChatServer.cpp @@ -59,6 +59,7 @@ int main(int argc, char** argv) { //Create all the objects we need to run our service: Server::SetupLogger("ChatServer"); if (!Game::logger) return EXIT_FAILURE; + Game::config->LogSettings(); //Read our config: diff --git a/dCommon/dConfig.cpp b/dCommon/dConfig.cpp index bed274b0..72a07d5b 100644 --- a/dCommon/dConfig.cpp +++ b/dCommon/dConfig.cpp @@ -47,6 +47,7 @@ void dConfig::LoadConfig() { void dConfig::ReloadConfig() { this->m_ConfigValues.clear(); LoadConfig(); + LogSettings(); } const std::string& dConfig::GetValue(std::string key) { @@ -58,6 +59,14 @@ const std::string& dConfig::GetValue(std::string key) { return this->m_ConfigValues[key]; } +void dConfig::LogSettings() const { + LOG("Configuration settings:"); + for (const auto& [key, value] : m_ConfigValues) { + const auto& valueLog = key.find("password") != std::string::npos ? "" : value; + LOG(" %s = %s", key.c_str(), valueLog.c_str()); + } +} + void dConfig::ProcessLine(const std::string& line) { auto splitLoc = line.find('='); auto key = line.substr(0, splitLoc); diff --git a/dCommon/dConfig.h b/dCommon/dConfig.h index 65415eea..59923b78 100644 --- a/dCommon/dConfig.h +++ b/dCommon/dConfig.h @@ -29,10 +29,12 @@ public: * Reloads the config file to reset values */ void ReloadConfig(); + + void LogSettings() const; + private: void ProcessLine(const std::string& line); -private: std::map m_ConfigValues; std::string m_ConfigFilePath; }; diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 2d108a2d..34d44ea8 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -99,6 +99,7 @@ int main(int argc, char** argv) { //Create all the objects we need to run our service: Server::SetupLogger("MasterServer"); if (!Game::logger) return EXIT_FAILURE; + Game::config->LogSettings(); auto folders = { "navmeshes", "migrations", "vanity" }; diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index ec3ca128..f655bac7 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -159,6 +159,7 @@ int main(int argc, char** argv) { //Create all the objects we need to run our service: Server::SetupLogger("WorldServer_" + std::to_string(zoneID) + "_" + std::to_string(g_InstanceID)); if (!Game::logger) return EXIT_FAILURE; + Game::config->LogSettings(); LOG("Starting World server..."); LOG("Version: %s", Game::projectVersion.c_str()); From 4a5dd68e87241ae531575cddd8701b43da6dc141 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 21 Sep 2025 18:36:32 -0700 Subject: [PATCH 09/39] fix: hardcore mode fixes (#1882) fixes hardcore modes uscore drops adds config option for excluded item drops --- dCommon/dConfig.cpp | 5 ++++ dCommon/dConfig.h | 5 ++++ dGame/EntityManager.cpp | 30 +++++++++++++++------- dGame/EntityManager.h | 3 +++ dGame/dComponents/DestroyableComponent.cpp | 25 ++++++++---------- resources/worldconfig.ini | 3 +++ 6 files changed, 47 insertions(+), 24 deletions(-) diff --git a/dCommon/dConfig.cpp b/dCommon/dConfig.cpp index 72a07d5b..7ebfad25 100644 --- a/dCommon/dConfig.cpp +++ b/dCommon/dConfig.cpp @@ -47,6 +47,7 @@ void dConfig::LoadConfig() { void dConfig::ReloadConfig() { this->m_ConfigValues.clear(); LoadConfig(); + for (const auto& handler : m_ConfigHandlers) handler(); LogSettings(); } @@ -59,6 +60,10 @@ const std::string& dConfig::GetValue(std::string key) { return this->m_ConfigValues[key]; } +void dConfig::AddConfigHandler(std::function handler) { + m_ConfigHandlers.push_back(handler); +} + void dConfig::LogSettings() const { LOG("Configuration settings:"); for (const auto& [key, value] : m_ConfigValues) { diff --git a/dCommon/dConfig.h b/dCommon/dConfig.h index 59923b78..36c94148 100644 --- a/dCommon/dConfig.h +++ b/dCommon/dConfig.h @@ -1,5 +1,7 @@ #pragma once + #include +#include #include #include @@ -30,11 +32,14 @@ public: */ void ReloadConfig(); + // Adds a function to be called when the config is (re)loaded + void AddConfigHandler(std::function handler); void LogSettings() const; private: void ProcessLine(const std::string& line); std::map m_ConfigValues; + std::vector> m_ConfigHandlers; std::string m_ConfigFilePath; }; diff --git a/dGame/EntityManager.cpp b/dGame/EntityManager.cpp index 762529f3..f7faa5f1 100644 --- a/dGame/EntityManager.cpp +++ b/dGame/EntityManager.cpp @@ -52,6 +52,25 @@ std::vector EntityManager::m_GhostingExcludedLOTs = { 4967 }; +void EntityManager::ReloadConfig() { + auto hcmode = Game::config->GetValue("hardcore_mode"); + m_HardcoreMode = hcmode.empty() ? false : (hcmode == "1"); + auto hcUscorePercent = Game::config->GetValue("hardcore_lose_uscore_on_death_percent"); + m_HardcoreLoseUscoreOnDeathPercent = hcUscorePercent.empty() ? 10 : std::stoi(hcUscorePercent); + auto hcUscoreMult = Game::config->GetValue("hardcore_uscore_enemies_multiplier"); + m_HardcoreUscoreEnemiesMultiplier = hcUscoreMult.empty() ? 2 : std::stoi(hcUscoreMult); + auto hcDropInv = Game::config->GetValue("hardcore_dropinventory_on_death"); + m_HardcoreDropinventoryOnDeath = hcDropInv.empty() ? false : (hcDropInv == "1"); + auto hcExcludedItemDrops = Game::config->GetValue("hardcore_excluded_item_drops"); + m_HardcoreExcludedItemDrops.clear(); + for (const auto& strLot : GeneralUtils::SplitString(hcExcludedItemDrops, ',')) { + const auto lot = GeneralUtils::TryParse(strLot); + if (lot) { + m_HardcoreExcludedItemDrops.insert(lot.value()); + } + } +} + void EntityManager::Initialize() { // Check if this zone has ghosting enabled m_GhostingEnabled = std::find( @@ -61,15 +80,8 @@ void EntityManager::Initialize() { ) == m_GhostingExcludedZones.end(); // grab hardcore mode settings and load them with sane defaults - auto hcmode = Game::config->GetValue("hardcore_mode"); - m_HardcoreMode = hcmode.empty() ? false : (hcmode == "1"); - auto hcUscorePercent = Game::config->GetValue("hardcore_lose_uscore_on_death_percent"); - m_HardcoreLoseUscoreOnDeathPercent = hcUscorePercent.empty() ? 10 : std::stoi(hcUscorePercent); - auto hcUscoreMult = Game::config->GetValue("hardcore_uscore_enemies_multiplier"); - m_HardcoreUscoreEnemiesMultiplier = hcUscoreMult.empty() ? 2 : std::stoi(hcUscoreMult); - auto hcDropInv = Game::config->GetValue("hardcore_dropinventory_on_death"); - m_HardcoreDropinventoryOnDeath = hcDropInv.empty() ? false : (hcDropInv == "1"); - + Game::config->AddConfigHandler([]() {Game::entityManager->ReloadConfig();}); + Game::entityManager->ReloadConfig(); // If cloneID is not zero, then hardcore mode is disabled // aka minigames and props if (Game::zoneManager->GetZoneID().GetCloneID() != 0) m_HardcoreMode = false; diff --git a/dGame/EntityManager.h b/dGame/EntityManager.h index 3ea97676..066ebb66 100644 --- a/dGame/EntityManager.h +++ b/dGame/EntityManager.h @@ -75,11 +75,13 @@ public: const uint32_t GetHardcoreLoseUscoreOnDeathPercent() { return m_HardcoreLoseUscoreOnDeathPercent; }; const bool GetHardcoreDropinventoryOnDeath() { return m_HardcoreDropinventoryOnDeath; }; const uint32_t GetHardcoreUscoreEnemiesMultiplier() { return m_HardcoreUscoreEnemiesMultiplier; }; + const std::set& GetHardcoreExcludedItemDrops() { return m_HardcoreExcludedItemDrops; }; // Messaging bool SendMessage(GameMessages::GameMsg& msg) const; private: + void ReloadConfig(); void SerializeEntities(); void KillEntities(); void DeleteEntities(); @@ -112,6 +114,7 @@ private: uint32_t m_HardcoreLoseUscoreOnDeathPercent; bool m_HardcoreDropinventoryOnDeath; uint32_t m_HardcoreUscoreEnemiesMultiplier; + std::set m_HardcoreExcludedItemDrops; }; #endif // ENTITYMANAGER_H diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 39d91045..18319a3e 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -37,6 +37,7 @@ #include "eMissionTaskType.h" #include "eStateChangeType.h" #include "eGameActivity.h" +#include #include "CDComponentsRegistryTable.h" @@ -981,7 +982,8 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) { auto* character = m_Parent->GetComponent(); auto uscore = character->GetUScore(); - auto uscoreToLose = uscore * (Game::entityManager->GetHardcoreLoseUscoreOnDeathPercent() / 100); + auto uscoreToLose = static_cast(uscore * (Game::entityManager->GetHardcoreLoseUscoreOnDeathPercent() / 100.0f)); + LOG("Player %llu has lost %llu uscore!", m_Parent->GetObjectID(), uscoreToLose); character->SetUScore(uscore - uscoreToLose); GameMessages::SendModifyLEGOScore(m_Parent, m_Parent->GetSystemAddress(), -uscoreToLose, eLootSourceType::MISSION); @@ -995,13 +997,11 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) { if (items) { auto itemMap = items->GetItems(); if (!itemMap.empty()) { - for (const auto& item : itemMap) { - //drop the item: - if (!item.second) continue; - // don't drop the thinkng cap - if (item.second->GetLot() == 6086) continue; - GameMessages::SendDropClientLoot(m_Parent, source, item.second->GetLot(), 0, m_Parent->GetPosition(), item.second->GetCount()); - item.second->SetCount(0, false, false); + for (const auto item : itemMap | std::views::values) { + // Don't drop excluded items or null ones + if (!item || Game::entityManager->GetHardcoreExcludedItemDrops().contains(item->GetLot())) continue; + GameMessages::SendDropClientLoot(m_Parent, source, item->GetLot(), 0, m_Parent->GetPosition(), item->GetCount()); + item->SetCount(0, false, false); } Game::entityManager->SerializeEntity(m_Parent); } @@ -1020,11 +1020,6 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) { //drop all coins: GameMessages::SendDropClientLoot(m_Parent, source, LOT_NULL, coins, m_Parent->GetPosition()); } - - // Reload the player since we can't normally reduce uscore from the server and we want the UI to update - // do this last so we don't get killed.... again - Game::entityManager->DestructEntity(m_Parent); - Game::entityManager->ConstructEntity(m_Parent); return; } @@ -1032,12 +1027,12 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) { auto* player = Game::entityManager->GetEntity(source); if (player && player->IsPlayer()) { auto* playerStats = player->GetComponent(); - if (playerStats) { + if (playerStats && GetMaxHealth() > 0) { //get the maximum health from this enemy: auto maxHealth = GetMaxHealth(); int uscore = maxHealth * Game::entityManager->GetHardcoreUscoreEnemiesMultiplier(); - + LOG("Rewarding player %llu with %i uscore for killing enemy %i", player->GetObjectID(), uscore, m_Parent->GetLOT()); playerStats->SetUScore(playerStats->GetUScore() + uscore); GameMessages::SendModifyLEGOScore(player, player->GetSystemAddress(), uscore, eLootSourceType::MISSION); diff --git a/resources/worldconfig.ini b/resources/worldconfig.ini index 1e73ceea..3579be89 100644 --- a/resources/worldconfig.ini +++ b/resources/worldconfig.ini @@ -80,3 +80,6 @@ cdclient_mismatch_message=We detected that your client is out of date. Please up # Auto reject properties which contain no models | must be 1 in order to auto reject. auto_reject_empty_properties=0 + +# comma delimited list of items to not drop in hardcore mode +hardcore_excluded_item_drops=6086,7044 From 64faac714c1a84321cd6630873ce8488b0e127ad Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 22 Sep 2025 21:41:38 -0700 Subject: [PATCH 10/39] feat: Remove PERSISTENT ObjectID bit because it's not an ObjectID bit (#1881) * feat: Remove PERSISTENT ObjectID bit because it's not an ObjectID bit TODO: Need to add character save migration for the pet subkey in the inventory Tested that the migrations work on mysql and sqlite and that properties have all their contents as before. Need to test pets still * fix: ugc, pet ids. remove persistent bit --- dCommon/dEnums/eCharacterVersion.h | 3 +- dCommon/dEnums/eObjectBits.h | 7 +-- .../GameDatabase/ITables/IPropertyContents.h | 2 +- dDatabase/GameDatabase/MySQL/MySQLDatabase.h | 38 ++++++------ dGame/Character.cpp | 5 +- dGame/UserManager.cpp | 2 - dGame/dComponents/InventoryComponent.h | 3 +- dGame/dComponents/PetComponent.cpp | 1 - .../PropertyManagementComponent.cpp | 1 - dGame/dGameMessages/GameMessages.cpp | 2 - dGame/dInventory/Item.cpp | 1 - dWorldServer/WorldServer.cpp | 60 +++++++++++++------ .../dlu/mysql/24_remove_persistent_bit.sql | 37 ++++++++++++ migrations/dlu/mysql/25_fix_pet_ids.sql | 1 + .../dlu/sqlite/7_remove_persistent_bit.sql | 37 ++++++++++++ migrations/dlu/sqlite/8_fix_pet_ids.sql | 2 + 16 files changed, 151 insertions(+), 51 deletions(-) create mode 100644 migrations/dlu/mysql/24_remove_persistent_bit.sql create mode 100644 migrations/dlu/mysql/25_fix_pet_ids.sql create mode 100644 migrations/dlu/sqlite/7_remove_persistent_bit.sql create mode 100644 migrations/dlu/sqlite/8_fix_pet_ids.sql diff --git a/dCommon/dEnums/eCharacterVersion.h b/dCommon/dEnums/eCharacterVersion.h index 51e6e5e5..477467b8 100644 --- a/dCommon/dEnums/eCharacterVersion.h +++ b/dCommon/dEnums/eCharacterVersion.h @@ -18,7 +18,8 @@ enum class eCharacterVersion : uint32_t { SPEED_BASE, // Fixes nexus force explorer missions NJ_JAYMISSIONS, - UP_TO_DATE, // will become NEXUS_FORCE_EXPLORER + NEXUS_FORCE_EXPLORER, // Fixes pet ids in player inventories + UP_TO_DATE, // will become PET_IDS }; #endif //!__ECHARACTERVERSION__H__ diff --git a/dCommon/dEnums/eObjectBits.h b/dCommon/dEnums/eObjectBits.h index b978aad6..121c58b9 100644 --- a/dCommon/dEnums/eObjectBits.h +++ b/dCommon/dEnums/eObjectBits.h @@ -1,13 +1,12 @@ -#ifndef __EOBJECTBITS__H__ -#define __EOBJECTBITS__H__ +#ifndef EOBJECTBITS_H +#define EOBJECTBITS_H #include enum class eObjectBits : size_t { - PERSISTENT = 32, CLIENT = 46, SPAWNED = 58, CHARACTER = 60 }; -#endif //!__EOBJECTBITS__H__ +#endif //!EOBJECTBITS_H diff --git a/dDatabase/GameDatabase/ITables/IPropertyContents.h b/dDatabase/GameDatabase/ITables/IPropertyContents.h index 52f1a672..94636801 100644 --- a/dDatabase/GameDatabase/ITables/IPropertyContents.h +++ b/dDatabase/GameDatabase/ITables/IPropertyContents.h @@ -16,7 +16,7 @@ public: NiQuaternion rotation = QuatUtils::IDENTITY; LWOOBJID id{}; LOT lot{}; - uint32_t ugcId{}; + LWOOBJID ugcId{}; std::array behaviors{}; }; diff --git a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h index 7a3a53b5..3546d668 100644 --- a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h +++ b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h @@ -168,91 +168,91 @@ private: template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::string_view param) { - // LOG("%s", param.data()); + LOG_DEBUG("%s", param.data()); stmt->setString(index, param.data()); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const char* param) { - // LOG("%s", param); + LOG_DEBUG("%s", param); stmt->setString(index, param); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::string param) { - // LOG("%s", param.c_str()); + LOG_DEBUG("%s", param.c_str()); stmt->setString(index, param.c_str()); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int8_t param) { - // LOG("%u", param); + LOG_DEBUG("%u", param); stmt->setByte(index, param); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint8_t param) { - // LOG("%d", param); + LOG_DEBUG("%d", param); stmt->setByte(index, param); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int16_t param) { - // LOG("%u", param); + LOG_DEBUG("%u", param); stmt->setShort(index, param); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint16_t param) { - // LOG("%d", param); + LOG_DEBUG("%d", param); stmt->setShort(index, param); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint32_t param) { - // LOG("%u", param); + LOG_DEBUG("%u", param); stmt->setUInt(index, param); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int32_t param) { - // LOG("%d", param); + LOG_DEBUG("%d", param); stmt->setInt(index, param); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int64_t param) { - // LOG("%llu", param); + LOG_DEBUG("%llu", param); stmt->setInt64(index, param); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint64_t param) { - // LOG("%llu", param); + LOG_DEBUG("%llu", param); stmt->setUInt64(index, param); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const float param) { - // LOG("%f", param); + LOG_DEBUG("%f", param); stmt->setFloat(index, param); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const double param) { - // LOG("%f", param); + LOG_DEBUG("%f", param); stmt->setDouble(index, param); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const bool param) { - // LOG("%d", param); + LOG_DEBUG("%s", param ? "true" : "false"); stmt->setBoolean(index, param); } template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::istream* param) { - // LOG("Blob"); + LOG_DEBUG("Blob"); // This is the one time you will ever see me use const_cast. stmt->setBlob(index, const_cast(param)); } @@ -260,10 +260,10 @@ inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::istr template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::optional param) { if (param) { - // LOG("%d", param.value()); + LOG_DEBUG("%d", param.value()); stmt->setInt(index, param.value()); } else { - // LOG("Null"); + LOG_DEBUG("Null"); stmt->setNull(index, sql::DataType::SQLNULL); } } @@ -271,10 +271,10 @@ inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::opti template<> inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::optional param) { if (param) { - // LOG("%d", param.value()); + LOG_DEBUG("%d", param.value()); stmt->setInt64(index, param.value()); } else { - // LOG("Null"); + LOG_DEBUG("Null"); stmt->setNull(index, sql::DataType::SQLNULL); } } diff --git a/dGame/Character.cpp b/dGame/Character.cpp index 250466a9..102db91a 100644 --- a/dGame/Character.cpp +++ b/dGame/Character.cpp @@ -336,8 +336,11 @@ void Character::WriteToDatabase() { tinyxml2::XMLPrinter printer(0, true, 0); m_Doc.Print(&printer); + // Update the xml on the character for future use if needed + m_XMLData = printer.CStr(); + //Finally, save to db: - Database::Get()->UpdateCharacterXml(m_ID, printer.CStr()); + Database::Get()->UpdateCharacterXml(m_ID, m_XMLData); } void Character::SetPlayerFlag(const uint32_t flagId, const bool value) { diff --git a/dGame/UserManager.cpp b/dGame/UserManager.cpp index 9bd6d844..82afb063 100644 --- a/dGame/UserManager.cpp +++ b/dGame/UserManager.cpp @@ -360,9 +360,7 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet) } while (lwoidforpants == lwoidforshirt); //Make sure we don't have the same ID for both shirt and pants GeneralUtils::SetBit(lwoidforshirt, eObjectBits::CHARACTER); - GeneralUtils::SetBit(lwoidforshirt, eObjectBits::PERSISTENT); GeneralUtils::SetBit(lwoidforpants, eObjectBits::CHARACTER); - GeneralUtils::SetBit(lwoidforpants, eObjectBits::PERSISTENT); xml << ""; xml << ""; diff --git a/dGame/dComponents/InventoryComponent.h b/dGame/dComponents/InventoryComponent.h index 395dd5d6..d67cf3c1 100644 --- a/dGame/dComponents/InventoryComponent.h +++ b/dGame/dComponents/InventoryComponent.h @@ -402,7 +402,8 @@ public: bool SetSkill(BehaviorSlot slot, uint32_t skillId); void UpdateGroup(const GroupUpdate& groupUpdate); - void RemoveGroup(const std::string& groupId); + + std::unordered_map& GetPetsMut() { return m_Pets; }; void FixInvisibleItems(); diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index de805e10..5081db6c 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -482,7 +482,6 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) { LWOOBJID petSubKey = ObjectIDManager::GenerateRandomObjectID(); GeneralUtils::SetBit(petSubKey, eObjectBits::CHARACTER); - GeneralUtils::SetBit(petSubKey, eObjectBits::PERSISTENT); m_DatabaseId = petSubKey; diff --git a/dGame/dComponents/PropertyManagementComponent.cpp b/dGame/dComponents/PropertyManagementComponent.cpp index d8d00787..4d3c1a3e 100644 --- a/dGame/dComponents/PropertyManagementComponent.cpp +++ b/dGame/dComponents/PropertyManagementComponent.cpp @@ -622,7 +622,6 @@ void PropertyManagementComponent::Load() { if (databaseModel.lot == 14) { LWOOBJID blueprintID = databaseModel.ugcId; GeneralUtils::SetBit(blueprintID, eObjectBits::CHARACTER); - GeneralUtils::SetBit(blueprintID, eObjectBits::PERSISTENT); settings.push_back(new LDFData(u"blueprintid", blueprintID)); settings.push_back(new LDFData(u"componentWhitelist", 1)); diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 08b1817e..c2ae363a 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -2587,12 +2587,10 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent if (!entity || !entity->GetCharacter() || !entity->GetCharacter()->GetParentUser()) return; LWOOBJID newIDL = newID; GeneralUtils::SetBit(newIDL, eObjectBits::CHARACTER); - GeneralUtils::SetBit(newIDL, eObjectBits::PERSISTENT); uint32_t blueprintIDSmall = ObjectIDManager::GenerateRandomObjectID(); LWOOBJID blueprintID = blueprintIDSmall; GeneralUtils::SetBit(blueprintID, eObjectBits::CHARACTER); - GeneralUtils::SetBit(blueprintID, eObjectBits::PERSISTENT); //We need to get the propertyID: (stolen from Wincent's propertyManagementComp) const auto& worldId = Game::zoneManager->GetZone()->GetZoneID(); diff --git a/dGame/dInventory/Item.cpp b/dGame/dInventory/Item.cpp index ca93cd9a..1a12c25f 100644 --- a/dGame/dInventory/Item.cpp +++ b/dGame/dInventory/Item.cpp @@ -101,7 +101,6 @@ Item::Item( LWOOBJID id = ObjectIDManager::GenerateRandomObjectID(); GeneralUtils::SetBit(id, eObjectBits::CHARACTER); - GeneralUtils::SetBit(id, eObjectBits::PERSISTENT); const auto type = static_cast(info->itemType); diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index f655bac7..3a22bbf0 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -82,6 +82,7 @@ #include "MissionComponent.h" #include "SlashCommandHandler.h" #include "InventoryComponent.h" +#include "Item.h" namespace Game { Logger* logger = nullptr; @@ -1038,21 +1039,6 @@ void HandlePacket(Packet* packet) { auto* characterComponent = player->GetComponent(); if (!characterComponent) return; - WorldPackets::SendCreateCharacter(packet->systemAddress, player->GetComponent()->GetReputation(), player->GetObjectID(), c->GetXMLData(), username, c->GetGMLevel(), c->GetPropertyCloneID()); - WorldPackets::SendServerState(packet->systemAddress); - - const auto respawnPoint = player->GetCharacter()->GetRespawnPoint(Game::zoneManager->GetZone()->GetWorldID()); - - Game::entityManager->ConstructEntity(player, UNASSIGNED_SYSTEM_ADDRESS); - - if (respawnPoint != NiPoint3Constant::ZERO) { - GameMessages::SendPlayerReachedRespawnCheckpoint(player, respawnPoint, QuatUtils::IDENTITY); - } - - Game::entityManager->ConstructAllEntities(packet->systemAddress); - - characterComponent->RocketUnEquip(player); - // Do charxml fixes here auto* levelComponent = player->GetComponent(); auto* const inventoryComponent = player->GetComponent(); @@ -1060,6 +1046,7 @@ void HandlePacket(Packet* packet) { if (!levelComponent || !missionComponent || !inventoryComponent) return; auto version = levelComponent->GetCharacterVersion(); + LOG("Updating character from version %s", StringifiedEnum::ToString(version).data()); switch (version) { case eCharacterVersion::RELEASE: // TODO: Implement, super low priority @@ -1108,6 +1095,29 @@ void HandlePacket(Packet* packet) { } if (complete) missionComponent->CompleteMission(937 /* Nexus Force explorer */); + levelComponent->SetCharacterVersion(eCharacterVersion::NEXUS_FORCE_EXPLORER); + [[fallthrough]]; + } + case eCharacterVersion::NEXUS_FORCE_EXPLORER: { + LOG("Fixing pet IDs"); + + // First copy the original ids + const auto pets = inventoryComponent->GetPetsMut(); + + // Then clear the pets so we can re-add them with the updated IDs + auto& invPets = inventoryComponent->GetPetsMut(); + invPets.clear(); + for (auto& [id, databasePet] : pets) { + const auto originalID = id; + const auto newId = GeneralUtils::ClearBit(id, 32); // Persistent bit that didn't exist + LOG("New ID %llu", newId); + auto* item = inventoryComponent->FindItemBySubKey(originalID); + if (item) { + LOG("item subkey %llu", item->GetSubKey()); + item->SetSubKey(newId); + invPets[newId] = databasePet; + } + } levelComponent->SetCharacterVersion(eCharacterVersion::UP_TO_DATE); [[fallthrough]]; } @@ -1115,6 +1125,24 @@ void HandlePacket(Packet* packet) { break; } + // Update the characters xml to ensure the update above is not only saved, but so the client picks up on the changes. + c->SaveXMLToDatabase(); + + WorldPackets::SendCreateCharacter(packet->systemAddress, characterComponent->GetReputation(), player->GetObjectID(), c->GetXMLData(), username, c->GetGMLevel(), c->GetPropertyCloneID()); + WorldPackets::SendServerState(packet->systemAddress); + + const auto respawnPoint = player->GetCharacter()->GetRespawnPoint(Game::zoneManager->GetZone()->GetWorldID()); + + Game::entityManager->ConstructEntity(player, UNASSIGNED_SYSTEM_ADDRESS); + + if (respawnPoint != NiPoint3Constant::ZERO) { + GameMessages::SendPlayerReachedRespawnCheckpoint(player, respawnPoint, QuatUtils::IDENTITY); + } + + Game::entityManager->ConstructAllEntities(packet->systemAddress); + + characterComponent->RocketUnEquip(player); + player->GetCharacter()->SetTargetScene(""); // Fix the destroyable component @@ -1149,8 +1177,6 @@ void HandlePacket(Packet* packet) { //Send message: LWOOBJID blueprintID = bbbModel.id; - GeneralUtils::SetBit(blueprintID, eObjectBits::CHARACTER); - GeneralUtils::SetBit(blueprintID, eObjectBits::PERSISTENT); // Workaround for not having a UGC server to get model LXFML onto the client so it // can generate the physics and nif for the object. diff --git a/migrations/dlu/mysql/24_remove_persistent_bit.sql b/migrations/dlu/mysql/24_remove_persistent_bit.sql new file mode 100644 index 00000000..2ed84947 --- /dev/null +++ b/migrations/dlu/mysql/24_remove_persistent_bit.sql @@ -0,0 +1,37 @@ +START TRANSACTION; +CREATE TABLE `ugc_2` ( + `id` BIGINT NOT NULL PRIMARY KEY, + `account_id` INT(11) NOT NULL REFERENCES `accounts` (`id`), + `character_id` BIGINT(20) NOT NULL REFERENCES `charinfo` (`id`), + `is_optimized` TINYINT(1) NOT NULL DEFAULT 0, + `lxfml` MEDIUMBLOB NOT NULL, + `bake_ao` TINYINT(1) NOT NULL DEFAULT 0, + `filename` TEXT NOT NULL DEFAULT '' +); +CREATE TABLE `properties_contents_2` ( + `id` BIGINT PRIMARY KEY NOT NULL, + `property_id` BIGINT NOT NULL REFERENCES `properties`(`id`), + `ugc_id` BIGINT DEFAULT NULL REFERENCES `ugc_2`(`id`), + `lot` INT NOT NULL, + `x` FLOAT NOT NULL, + `y` FLOAT NOT NULL, + `z` FLOAT NOT NULL, + `rx` FLOAT NOT NULL, + `ry` FLOAT NOT NULL, + `rz` FLOAT NOT NULL, + `rw` FLOAT NOT NULL, + `model_name` TEXT NOT NULL DEFAULT '', + `model_description` TEXT NOT NULL DEFAULT '', + `behavior_1` BIGINT DEFAULT 0, + `behavior_2` BIGINT DEFAULT 0, + `behavior_3` BIGINT DEFAULT 0, + `behavior_4` BIGINT DEFAULT 0, + `behavior_5` BIGINT DEFAULT 0 +); +INSERT INTO `ugc_2` SELECT `id`|0x1000000000000000,`account_id`,`character_id`,`is_optimized`,`lxfml`,`bake_ao`,`filename` FROM `ugc`; +INSERT INTO `properties_contents_2` SELECT `id`,`property_id`,`ugc_id`|0x1000000000000000,`lot`,`x`,`y`,`z`,`rx`,`ry`,`rz`,`rw`,`model_name`,`model_description`,`behavior_1`,`behavior_2`,`behavior_3`,`behavior_4`,`behavior_5` FROM `properties_contents`; +DROP TABLE `properties_contents`; +DROP TABLE `ugc`; +RENAME TABLE `properties_contents_2` TO `properties_contents`; +RENAME TABLE `ugc_2` TO `ugc`; +COMMIT; diff --git a/migrations/dlu/mysql/25_fix_pet_ids.sql b/migrations/dlu/mysql/25_fix_pet_ids.sql new file mode 100644 index 00000000..1ad79043 --- /dev/null +++ b/migrations/dlu/mysql/25_fix_pet_ids.sql @@ -0,0 +1 @@ +update pet_names set id = id % 0x100000000 | 0x1000000000000000; diff --git a/migrations/dlu/sqlite/7_remove_persistent_bit.sql b/migrations/dlu/sqlite/7_remove_persistent_bit.sql new file mode 100644 index 00000000..69feef3b --- /dev/null +++ b/migrations/dlu/sqlite/7_remove_persistent_bit.sql @@ -0,0 +1,37 @@ +BEGIN TRANSACTION; +CREATE TABLE `ugc_2` ( + `id` BIGINT NOT NULL PRIMARY KEY, + `account_id` INT NOT NULL REFERENCES `accounts` (`id`), + `character_id` BIGINT NOT NULL REFERENCES `charinfo` (`id`), + `is_optimized` INT NOT NULL DEFAULT 0, + `lxfml` BLOB NOT NULL, + `bake_ao` INT NOT NULL DEFAULT 0, + `filename` TEXT NOT NULL DEFAULT '' +); +CREATE TABLE `properties_contents_2` ( + `id` BIGINT PRIMARY KEY NOT NULL, + `property_id` BIGINT NOT NULL REFERENCES `properties`(`id`), + `ugc_id` BIGINT DEFAULT NULL REFERENCES `ugc_2`(`id`), + `lot` INT NOT NULL, + `x` DOUBLE NOT NULL, + `y` DOUBLE NOT NULL, + `z` DOUBLE NOT NULL, + `rx` DOUBLE NOT NULL, + `ry` DOUBLE NOT NULL, + `rz` DOUBLE NOT NULL, + `rw` DOUBLE NOT NULL, + `model_name` TEXT NOT NULL DEFAULT '', + `model_description` TEXT NOT NULL DEFAULT '', + `behavior_1` BIGINT DEFAULT 0, + `behavior_2` BIGINT DEFAULT 0, + `behavior_3` BIGINT DEFAULT 0, + `behavior_4` BIGINT DEFAULT 0, + `behavior_5` BIGINT DEFAULT 0 +); +INSERT INTO `ugc_2` SELECT `id`|0x1000000000000000,`account_id`,`character_id`,`is_optimized`,`lxfml`,`bake_ao`,`filename` FROM `ugc`; +INSERT INTO `properties_contents_2` SELECT `id`,`property_id`,`ugc_id`|0x1000000000000000,`lot`,`x`,`y`,`z`,`rx`,`ry`,`rz`,`rw`,`model_name`,`model_description`,`behavior_1`,`behavior_2`,`behavior_3`,`behavior_4`,`behavior_5` FROM `properties_contents`; +DROP TABLE `properties_contents`; +DROP TABLE `ugc`; +ALTER TABLE `properties_contents_2` RENAME TO `properties_contents`; +ALTER TABLE `ugc_2` RENAME TO `ugc`; +COMMIT; diff --git a/migrations/dlu/sqlite/8_fix_pet_ids.sql b/migrations/dlu/sqlite/8_fix_pet_ids.sql new file mode 100644 index 00000000..4b2b9283 --- /dev/null +++ b/migrations/dlu/sqlite/8_fix_pet_ids.sql @@ -0,0 +1,2 @@ +/* Unset the fake persistent bit alongside the Character bit and then re-set the Character bit */ +update pet_names set id = id % 0x100000000 | 0x1000000000000000; From 74e1d36bb1c22f8262cbba23ca303a20cc3ccf8e Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Tue, 23 Sep 2025 05:02:29 -0700 Subject: [PATCH 11/39] feat: Hardcore mode settings (#1884) --- dGame/EntityManager.cpp | 38 ++++++++++++++++------ dGame/EntityManager.h | 10 ++++++ dGame/dComponents/DestroyableComponent.cpp | 22 ++++++++----- resources/worldconfig.ini | 16 +++++++++ 4 files changed, 68 insertions(+), 18 deletions(-) diff --git a/dGame/EntityManager.cpp b/dGame/EntityManager.cpp index f7faa5f1..026fc02a 100644 --- a/dGame/EntityManager.cpp +++ b/dGame/EntityManager.cpp @@ -52,23 +52,41 @@ std::vector EntityManager::m_GhostingExcludedLOTs = { 4967 }; +template +void ParseDelimSetting(std::set& setting, const std::string_view settingName, const char delim = ',') { + const auto str = Game::config->GetValue(settingName.data()); + setting.clear(); + for (const auto& strVal : GeneralUtils::SplitString(str, delim)) { + const auto val = GeneralUtils::TryParse(strVal); + if (val) { + setting.insert(val.value()); + } + } +} + void EntityManager::ReloadConfig() { auto hcmode = Game::config->GetValue("hardcore_mode"); m_HardcoreMode = hcmode.empty() ? false : (hcmode == "1"); auto hcUscorePercent = Game::config->GetValue("hardcore_lose_uscore_on_death_percent"); - m_HardcoreLoseUscoreOnDeathPercent = hcUscorePercent.empty() ? 10 : std::stoi(hcUscorePercent); + m_HardcoreLoseUscoreOnDeathPercent = hcUscorePercent.empty() ? 10 : GeneralUtils::TryParse(hcUscorePercent).value_or(10); auto hcUscoreMult = Game::config->GetValue("hardcore_uscore_enemies_multiplier"); - m_HardcoreUscoreEnemiesMultiplier = hcUscoreMult.empty() ? 2 : std::stoi(hcUscoreMult); + m_HardcoreUscoreEnemiesMultiplier = hcUscoreMult.empty() ? 2 : GeneralUtils::TryParse(hcUscoreMult).value_or(2); auto hcDropInv = Game::config->GetValue("hardcore_dropinventory_on_death"); m_HardcoreDropinventoryOnDeath = hcDropInv.empty() ? false : (hcDropInv == "1"); - auto hcExcludedItemDrops = Game::config->GetValue("hardcore_excluded_item_drops"); - m_HardcoreExcludedItemDrops.clear(); - for (const auto& strLot : GeneralUtils::SplitString(hcExcludedItemDrops, ',')) { - const auto lot = GeneralUtils::TryParse(strLot); - if (lot) { - m_HardcoreExcludedItemDrops.insert(lot.value()); - } - } + ParseDelimSetting(m_HardcoreExcludedItemDrops, "hardcore_excluded_item_drops"); + + // We don't need to save the worlds, just need to check if this one is in the list + std::set worlds; + ParseDelimSetting(worlds, "hardcore_uscore_reduced_worlds"); + m_HardcoreUscoreReduced = worlds.contains(Game::zoneManager->GetZoneID().GetMapID()); + + ParseDelimSetting(m_HardcoreUscoreReducedLots, "hardcore_uscore_reduced_lots"); + ParseDelimSetting(m_HardcoreUscoreExcludedEnemies, "hardcore_uscore_excluded_enemies"); + ParseDelimSetting(m_HardcoreDisabledWorlds, "hardcore_disabled_worlds"); + + auto hcXpReduction = Game::config->GetValue("hardcore_uscore_reduction"); + m_HardcoreUscoreReduction = hcXpReduction.empty() ? 1.0f : GeneralUtils::TryParse(hcXpReduction).value_or(1.0f); + m_HardcoreMode = GetHardcoreDisabledWorlds().contains(Game::zoneManager->GetZoneID().GetMapID()) ? false : m_HardcoreMode; } void EntityManager::Initialize() { diff --git a/dGame/EntityManager.h b/dGame/EntityManager.h index 066ebb66..549c922e 100644 --- a/dGame/EntityManager.h +++ b/dGame/EntityManager.h @@ -76,6 +76,11 @@ public: const bool GetHardcoreDropinventoryOnDeath() { return m_HardcoreDropinventoryOnDeath; }; const uint32_t GetHardcoreUscoreEnemiesMultiplier() { return m_HardcoreUscoreEnemiesMultiplier; }; const std::set& GetHardcoreExcludedItemDrops() { return m_HardcoreExcludedItemDrops; }; + const float& GetHardcoreUscoreReduction() const { return m_HardcoreUscoreReduction; }; + bool GetHardcoreUscoreReduced() const { return m_HardcoreUscoreReduced; }; + const std::set& GetHardcoreUscoreReducedLots() const { return m_HardcoreUscoreReducedLots; }; + const std::set& GetHardcoreUscoreExcludedEnemies() const { return m_HardcoreUscoreExcludedEnemies; }; + const std::set& GetHardcoreDisabledWorlds() const { return m_HardcoreDisabledWorlds; }; // Messaging bool SendMessage(GameMessages::GameMsg& msg) const; @@ -115,6 +120,11 @@ private: bool m_HardcoreDropinventoryOnDeath; uint32_t m_HardcoreUscoreEnemiesMultiplier; std::set m_HardcoreExcludedItemDrops; + float m_HardcoreUscoreReduction{}; + bool m_HardcoreUscoreReduced{}; + std::set m_HardcoreUscoreReducedLots{}; + std::set m_HardcoreUscoreExcludedEnemies{}; + std::set m_HardcoreDisabledWorlds{}; }; #endif // ENTITYMANAGER_H diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 18319a3e..512ac150 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -667,11 +667,6 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32 return; } - //check if hardcore mode is enabled - if (Game::entityManager->GetHardcoreMode()) { - DoHardcoreModeDrops(source); - } - Smash(source, eKillType::VIOLENT, u"", skillID); } @@ -699,6 +694,11 @@ void DestroyableComponent::NotifySubscribers(Entity* attacker, uint32_t damage) } void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType, const std::u16string& deathType, uint32_t skillID) { + //check if hardcore mode is enabled + if (Game::entityManager->GetHardcoreMode()) { + DoHardcoreModeDrops(source); + } + if (m_iHealth > 0) { SetArmor(0); SetHealth(0); @@ -1026,13 +1026,19 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) { //award the player some u-score: auto* player = Game::entityManager->GetEntity(source); if (player && player->IsPlayer()) { + const auto lot = m_Parent->GetLOT(); auto* playerStats = player->GetComponent(); - if (playerStats && GetMaxHealth() > 0) { + if (playerStats && GetMaxHealth() > 0 && !Game::entityManager->GetHardcoreUscoreExcludedEnemies().contains(lot)) { //get the maximum health from this enemy: auto maxHealth = GetMaxHealth(); + const auto uscoreMultiplier = Game::entityManager->GetHardcoreUscoreEnemiesMultiplier(); + const bool isUscoreReducedLot = + Game::entityManager->GetHardcoreUscoreReducedLots().contains(lot) || + Game::entityManager->GetHardcoreUscoreReduced(); + const auto uscoreReduction = isUscoreReducedLot ? Game::entityManager->GetHardcoreUscoreReduction() : 1.0f; - int uscore = maxHealth * Game::entityManager->GetHardcoreUscoreEnemiesMultiplier(); - LOG("Rewarding player %llu with %i uscore for killing enemy %i", player->GetObjectID(), uscore, m_Parent->GetLOT()); + int uscore = maxHealth * Game::entityManager->GetHardcoreUscoreEnemiesMultiplier() * uscoreReduction; + LOG("Rewarding player %llu with %i uscore for killing enemy %i", player->GetObjectID(), uscore, lot); playerStats->SetUScore(playerStats->GetUScore() + uscore); GameMessages::SendModifyLEGOScore(player, player->GetSystemAddress(), uscore, eLootSourceType::MISSION); diff --git a/resources/worldconfig.ini b/resources/worldconfig.ini index 3579be89..9631183a 100644 --- a/resources/worldconfig.ini +++ b/resources/worldconfig.ini @@ -83,3 +83,19 @@ auto_reject_empty_properties=0 # comma delimited list of items to not drop in hardcore mode hardcore_excluded_item_drops=6086,7044 + +# Reduces the UScore gained from the below uscore_reduction config options by x (i.e. uscoreGained * hardcore_uscore_reduction) +hardcore_uscore_reduction= + +# Any given enemy will only have its uscore gain reduced 1 time +# Worlds that should have their uscore reduction applied at a global level (i.e. for worlds with lots of skeletons) +hardcore_uscore_reduced_worlds= + +# Specific LOTs that should have their uscore reduced (i.e. for worlds with a mix of skeletons and normal enemies) +hardcore_uscore_reduced_lots= + +# Excludes this comma delimited list of LOTs from giving UScore in hardcore mode +hardcore_uscore_excluded_enemies= + +# Disables hardcore mode for specific worlds, if hardcore is enabled +hardcore_disabled_worlds= From b5a3cc91870ba7970c3edbb1f86b83ce251f21f6 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Wed, 24 Sep 2025 04:01:46 -0700 Subject: [PATCH 12/39] fix: Extend saved ugc id to 64 bits (#1885) Tetsed that ugc models are saved and loaded with the correct bits --- dDatabase/GameDatabase/ITables/IPropertyContents.h | 2 +- dDatabase/GameDatabase/MySQL/MySQLDatabase.h | 2 +- dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp | 2 +- dDatabase/GameDatabase/SQLite/SQLiteDatabase.h | 2 +- dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp | 2 +- dDatabase/GameDatabase/TestSQL/TestSQLDatabase.cpp | 2 +- dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h | 2 +- dGame/dComponents/PropertyManagementComponent.cpp | 1 - dGame/dGameMessages/GameMessages.cpp | 7 +++---- 9 files changed, 10 insertions(+), 12 deletions(-) diff --git a/dDatabase/GameDatabase/ITables/IPropertyContents.h b/dDatabase/GameDatabase/ITables/IPropertyContents.h index 94636801..b2bb1a97 100644 --- a/dDatabase/GameDatabase/ITables/IPropertyContents.h +++ b/dDatabase/GameDatabase/ITables/IPropertyContents.h @@ -23,7 +23,7 @@ public: // Inserts a new UGC model into the database. virtual void InsertNewUgcModel( std::stringstream& sd0Data, - const uint32_t blueprintId, + const uint64_t blueprintId, const uint32_t accountId, const LWOOBJID characterId) = 0; diff --git a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h index 3546d668..e355ce62 100644 --- a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h +++ b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h @@ -83,7 +83,7 @@ public: void InsertNewMail(const MailInfo& mail) override; void InsertNewUgcModel( std::stringstream& sd0Data, - const uint32_t blueprintId, + const uint64_t blueprintId, const uint32_t accountId, const LWOOBJID characterId) override; std::vector GetMailForPlayer(const LWOOBJID characterId, const uint32_t numberOfMail) override; diff --git a/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp b/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp index a7570adb..031ae6c7 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp @@ -46,7 +46,7 @@ void MySQLDatabase::RemoveUnreferencedUgcModels() { void MySQLDatabase::InsertNewUgcModel( std:: stringstream& sd0Data, // cant be const sad - const uint32_t blueprintId, + const uint64_t blueprintId, const uint32_t accountId, const LWOOBJID characterId) { const std::istream stream(sd0Data.rdbuf()); diff --git a/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h index f46b7bc2..54dc39d3 100644 --- a/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h +++ b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h @@ -81,7 +81,7 @@ public: void InsertNewMail(const MailInfo& mail) override; void InsertNewUgcModel( std::stringstream& sd0Data, - const uint32_t blueprintId, + const uint64_t blueprintId, const uint32_t accountId, const LWOOBJID characterId) override; std::vector GetMailForPlayer(const LWOOBJID characterId, const uint32_t numberOfMail) override; diff --git a/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp b/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp index df0b3d43..31a6a2e7 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp @@ -47,7 +47,7 @@ void SQLiteDatabase::RemoveUnreferencedUgcModels() { void SQLiteDatabase::InsertNewUgcModel( std::stringstream& sd0Data, // cant be const sad - const uint32_t blueprintId, + const uint64_t blueprintId, const uint32_t accountId, const LWOOBJID characterId) { const std::istream stream(sd0Data.rdbuf()); diff --git a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.cpp b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.cpp index 9381c07b..f9c9fb09 100644 --- a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.cpp +++ b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.cpp @@ -192,7 +192,7 @@ void TestSQLDatabase::InsertNewMail(const MailInfo& mail) { } -void TestSQLDatabase::InsertNewUgcModel(std::stringstream& sd0Data, const uint32_t blueprintId, const uint32_t accountId, const LWOOBJID characterId) { +void TestSQLDatabase::InsertNewUgcModel(std::stringstream& sd0Data, const uint64_t blueprintId, const uint32_t accountId, const LWOOBJID characterId) { } diff --git a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h index 8d808e42..87e08e91 100644 --- a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h +++ b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h @@ -60,7 +60,7 @@ class TestSQLDatabase : public GameDatabase { void InsertNewMail(const MailInfo& mail) override; void InsertNewUgcModel( std::stringstream& sd0Data, - const uint32_t blueprintId, + const uint64_t blueprintId, const uint32_t accountId, const LWOOBJID characterId) override; std::vector GetMailForPlayer(const LWOOBJID characterId, const uint32_t numberOfMail) override; diff --git a/dGame/dComponents/PropertyManagementComponent.cpp b/dGame/dComponents/PropertyManagementComponent.cpp index 4d3c1a3e..06792897 100644 --- a/dGame/dComponents/PropertyManagementComponent.cpp +++ b/dGame/dComponents/PropertyManagementComponent.cpp @@ -621,7 +621,6 @@ void PropertyManagementComponent::Load() { //BBB property models need to have extra stuff set for them: if (databaseModel.lot == 14) { LWOOBJID blueprintID = databaseModel.ugcId; - GeneralUtils::SetBit(blueprintID, eObjectBits::CHARACTER); settings.push_back(new LDFData(u"blueprintid", blueprintID)); settings.push_back(new LDFData(u"componentWhitelist", 1)); diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index c2ae363a..168469c9 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -2588,8 +2588,7 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent LWOOBJID newIDL = newID; GeneralUtils::SetBit(newIDL, eObjectBits::CHARACTER); - uint32_t blueprintIDSmall = ObjectIDManager::GenerateRandomObjectID(); - LWOOBJID blueprintID = blueprintIDSmall; + LWOOBJID blueprintID = ObjectIDManager::GenerateRandomObjectID(); GeneralUtils::SetBit(blueprintID, eObjectBits::CHARACTER); //We need to get the propertyID: (stolen from Wincent's propertyManagementComp) @@ -2614,12 +2613,12 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent // Recompress the data and save to the database sd0.FromData(reinterpret_cast(newLxfml.data()), newLxfml.size()); auto sd0AsStream = sd0.GetAsStream(); - Database::Get()->InsertNewUgcModel(sd0AsStream, blueprintIDSmall, entity->GetCharacter()->GetParentUser()->GetAccountID(), entity->GetCharacter()->GetID()); + Database::Get()->InsertNewUgcModel(sd0AsStream, blueprintID, entity->GetCharacter()->GetParentUser()->GetAccountID(), entity->GetCharacter()->GetID()); //Insert into the db as a BBB model: IPropertyContents::Model model; model.id = newIDL; - model.ugcId = blueprintIDSmall; + model.ugcId = blueprintID; model.position = newCenter; model.rotation = NiQuaternion(0.0f, 0.0f, 0.0f, 0.0f); model.lot = 14; From 76c2f380bf230bd94ce4d5df234e09f28766c0a5 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 29 Sep 2025 06:54:37 -0700 Subject: [PATCH 13/39] feat: re-write persistent object ID tracker (#1888) * feat: re-write persistent object ID tracker Features: - Remove random objectIDs entirely - Replace random objectIDs with persistentIDs - Remove the need to contact the MASTER server for a persistent ID - Add persistent ID logic to WorldServers that use transactions to guarantee unique IDs no matter when they are generated - Default character xml version to be the most recent one Fixes: - Return optional from GetModel (and check for nullopt where it may exist) - Regenerate inventory item ids on first login to be unique item IDs (fixes all those random IDs Pet IDs and subkeys are left alone and are assumed to be reserved (checks are there to prevent this) There is also duplicate check logic in place for properties and UGC/Models * Update comment and log * fix: sqlite transaction bug * fix colliding temp item ids temp items should not be saved. would cause issues between worlds as experienced before this commit --- dCommon/dEnums/MessageType/Master.h | 4 +- dCommon/dEnums/eCharacterVersion.h | 3 +- .../GameDatabase/ITables/IObjectIdTracker.h | 11 +- dDatabase/GameDatabase/ITables/IProperty.h | 3 + .../GameDatabase/ITables/IPropertyContents.h | 2 +- dDatabase/GameDatabase/ITables/IUgc.h | 2 + dDatabase/GameDatabase/MySQL/MySQLDatabase.h | 8 +- .../MySQL/Tables/ObjectIdTracker.cpp | 33 ++- .../GameDatabase/MySQL/Tables/Property.cpp | 59 ++-- .../MySQL/Tables/PropertyContents.cpp | 35 +-- dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp | 43 +-- .../GameDatabase/SQLite/SQLiteDatabase.cpp | 4 +- .../GameDatabase/SQLite/SQLiteDatabase.h | 8 +- .../SQLite/Tables/ObjectIdTracker.cpp | 31 ++- .../GameDatabase/SQLite/Tables/Property.cpp | 59 ++-- .../SQLite/Tables/PropertyContents.cpp | 35 +-- dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp | 41 +-- .../GameDatabase/TestSQL/TestSQLDatabase.cpp | 9 +- .../GameDatabase/TestSQL/TestSQLDatabase.h | 8 +- dDatabase/ModelNormalizeMigration.cpp | 16 +- dGame/UserManager.cpp | 111 ++++---- dGame/dComponents/InventoryComponent.cpp | 9 +- dGame/dComponents/InventoryComponent.h | 3 + dGame/dComponents/PetComponent.cpp | 14 +- .../dComponents/PropertyEntranceComponent.cpp | 2 +- .../PropertyManagementComponent.cpp | 73 ++--- dGame/dGameMessages/GameMessages.cpp | 253 +++++++++--------- dGame/dInventory/Inventory.cpp | 16 ++ dGame/dInventory/Inventory.h | 2 + dGame/dInventory/Item.cpp | 42 ++- dGame/dInventory/Item.h | 2 + dGame/dPropertyBehaviors/ControlBehaviors.cpp | 28 +- dGame/dUtilities/ObjectIDManager.cpp | 56 ++-- dGame/dUtilities/ObjectIDManager.h | 39 +-- dMasterServer/CMakeLists.txt | 1 - dMasterServer/MasterServer.cpp | 16 -- dMasterServer/PersistentIDManager.cpp | 45 ---- dMasterServer/PersistentIDManager.h | 23 -- dNet/MasterPackets.cpp | 17 -- dNet/MasterPackets.h | 5 - .../ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp | 1 - dWorldServer/WorldServer.cpp | 164 ++++++------ .../mysql/26_update_property_contents_ids.sql | 1 + .../sqlite/9_update_property_contents_ids.sql | 1 + 44 files changed, 679 insertions(+), 659 deletions(-) delete mode 100644 dMasterServer/PersistentIDManager.cpp delete mode 100644 dMasterServer/PersistentIDManager.h create mode 100644 migrations/dlu/mysql/26_update_property_contents_ids.sql create mode 100644 migrations/dlu/sqlite/9_update_property_contents_ids.sql diff --git a/dCommon/dEnums/MessageType/Master.h b/dCommon/dEnums/MessageType/Master.h index b6054d7f..1529ca51 100644 --- a/dCommon/dEnums/MessageType/Master.h +++ b/dCommon/dEnums/MessageType/Master.h @@ -3,9 +3,7 @@ namespace MessageType { enum class Master : uint32_t { - REQUEST_PERSISTENT_ID = 1, - REQUEST_PERSISTENT_ID_RESPONSE, - REQUEST_ZONE_TRANSFER, + REQUEST_ZONE_TRANSFER = 1, REQUEST_ZONE_TRANSFER_RESPONSE, SERVER_INFO, REQUEST_SESSION_KEY, diff --git a/dCommon/dEnums/eCharacterVersion.h b/dCommon/dEnums/eCharacterVersion.h index 477467b8..15610c3a 100644 --- a/dCommon/dEnums/eCharacterVersion.h +++ b/dCommon/dEnums/eCharacterVersion.h @@ -19,7 +19,8 @@ enum class eCharacterVersion : uint32_t { // Fixes nexus force explorer missions NJ_JAYMISSIONS, NEXUS_FORCE_EXPLORER, // Fixes pet ids in player inventories - UP_TO_DATE, // will become PET_IDS + PET_IDS, // Fixes pet ids in player inventories + UP_TO_DATE, // will become INVENTORY_PERSISTENT_IDS }; #endif //!__ECHARACTERVERSION__H__ diff --git a/dDatabase/GameDatabase/ITables/IObjectIdTracker.h b/dDatabase/GameDatabase/ITables/IObjectIdTracker.h index cbe34b6d..6344f2e7 100644 --- a/dDatabase/GameDatabase/ITables/IObjectIdTracker.h +++ b/dDatabase/GameDatabase/ITables/IObjectIdTracker.h @@ -6,14 +6,19 @@ class IObjectIdTracker { public: + // Only the first 48 bits of the ids are the id, the last 16 bits are reserved for flags. + struct Range { + uint64_t minID{}; // Only the first 48 bits are the id, the last 16 bits are reserved for flags. + uint64_t maxID{}; // Only the first 48 bits are the id, the last 16 bits are reserved for flags. + }; + // Get the current persistent id. - virtual std::optional GetCurrentPersistentId() = 0; + virtual std::optional GetCurrentPersistentId() = 0; // Insert the default persistent id. virtual void InsertDefaultPersistentId() = 0; - // Update the persistent id. - virtual void UpdatePersistentId(const uint32_t newId) = 0; + virtual Range GetPersistentIdRange() = 0; }; #endif //!__IOBJECTIDTRACKER__H__ diff --git a/dDatabase/GameDatabase/ITables/IProperty.h b/dDatabase/GameDatabase/ITables/IProperty.h index 78f58284..c4957e5b 100644 --- a/dDatabase/GameDatabase/ITables/IProperty.h +++ b/dDatabase/GameDatabase/ITables/IProperty.h @@ -39,6 +39,9 @@ public: std::vector entries; }; + // Get the property info for the given property id. + virtual std::optional GetPropertyInfo(const LWOOBJID id) = 0; + // Get the property info for the given property id. virtual std::optional GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) = 0; diff --git a/dDatabase/GameDatabase/ITables/IPropertyContents.h b/dDatabase/GameDatabase/ITables/IPropertyContents.h index b2bb1a97..9a16d5d7 100644 --- a/dDatabase/GameDatabase/ITables/IPropertyContents.h +++ b/dDatabase/GameDatabase/ITables/IPropertyContents.h @@ -45,6 +45,6 @@ public: virtual void RemoveModel(const LWOOBJID& modelId) = 0; // Gets a model by ID - virtual Model GetModel(const LWOOBJID modelID) = 0; + virtual std::optional GetModel(const LWOOBJID modelID) = 0; }; #endif //!__IPROPERTIESCONTENTS__H__ diff --git a/dDatabase/GameDatabase/ITables/IUgc.h b/dDatabase/GameDatabase/ITables/IUgc.h index cbc770b8..9f6d2ef1 100644 --- a/dDatabase/GameDatabase/ITables/IUgc.h +++ b/dDatabase/GameDatabase/ITables/IUgc.h @@ -29,5 +29,7 @@ public: // Inserts a new UGC model into the database. virtual void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) = 0; + + virtual std::optional GetUgcModel(const LWOOBJID ugcId) = 0; }; #endif //!__IUGC__H__ diff --git a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h index e355ce62..456ab5fa 100644 --- a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h +++ b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h @@ -98,9 +98,9 @@ public: void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override; void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override; void SetMasterInfo(const IServers::MasterInfo& info) override; - std::optional GetCurrentPersistentId() override; + std::optional GetCurrentPersistentId() override; + IObjectIdTracker::Range GetPersistentIdRange() override; void InsertDefaultPersistentId() override; - void UpdatePersistentId(const uint32_t id) override; std::optional GetDonationTotal(const uint32_t activityId) override; std::optional IsPlaykeyActive(const int32_t playkeyId) override; std::vector GetUgcModels(const LWOOBJID& propertyId) override; @@ -127,7 +127,9 @@ public: void DeleteUgcBuild(const LWOOBJID bigId) override; uint32_t GetAccountCount() override; bool IsNameInUse(const std::string_view name) override; - IPropertyContents::Model GetModel(const LWOOBJID modelID) override; + std::optional GetModel(const LWOOBJID modelID) override; + std::optional GetUgcModel(const LWOOBJID ugcId) override; + std::optional GetPropertyInfo(const LWOOBJID id) override; sql::PreparedStatement* CreatePreppedStmt(const std::string& query); private: diff --git a/dDatabase/GameDatabase/MySQL/Tables/ObjectIdTracker.cpp b/dDatabase/GameDatabase/MySQL/Tables/ObjectIdTracker.cpp index f22cd855..462a0edf 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/ObjectIdTracker.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/ObjectIdTracker.cpp @@ -1,17 +1,42 @@ #include "MySQLDatabase.h" -std::optional MySQLDatabase::GetCurrentPersistentId() { +std::optional MySQLDatabase::GetCurrentPersistentId() { auto result = ExecuteSelect("SELECT last_object_id FROM object_id_tracker"); if (!result->next()) { return std::nullopt; } - return result->getUInt("last_object_id"); + return result->getUInt64("last_object_id"); } void MySQLDatabase::InsertDefaultPersistentId() { ExecuteInsert("INSERT INTO object_id_tracker VALUES (1);"); } -void MySQLDatabase::UpdatePersistentId(const uint32_t newId) { - ExecuteUpdate("UPDATE object_id_tracker SET last_object_id = ?;", newId); +IObjectIdTracker::Range MySQLDatabase::GetPersistentIdRange() { + IObjectIdTracker::Range range; + auto prevCommit = GetAutoCommit(); + SetAutoCommit(false); + + // THIS MUST ABSOLUTELY NOT FAIL. These IDs are expected to be unique. As such a transactional select is used to safely get a range + // of IDs that will never be used again. A separate feature could track unused IDs and recycle them, but that is not implemented. + ExecuteCustomQuery("START TRANSACTION;"); + // 200 seems like a good range to reserve at once. Only way this would be noticable is if a player + // added hundreds of items at once. + // Doing the update first ensures that all other connections are blocked from accessing this table until we commit. + auto result = ExecuteUpdate("UPDATE object_id_tracker SET last_object_id = last_object_id + 200;"); + // If no rows were updated, it means the table is empty, so we need to insert the default row first. + if (result == 0) { + InsertDefaultPersistentId(); + result = ExecuteUpdate("UPDATE object_id_tracker SET last_object_id = last_object_id + 200;"); + } + + // At this point all connections are waiting on us to finish the transaction, so we can safely select the ID we just set. + auto selectRes = ExecuteSelect("SELECT last_object_id FROM object_id_tracker;"); + selectRes->next(); + range.maxID = selectRes->getUInt64("last_object_id"); + range.minID = range.maxID - 199; + + ExecuteCustomQuery("COMMIT;"); + SetAutoCommit(prevCommit); + return range; } diff --git a/dDatabase/GameDatabase/MySQL/Tables/Property.cpp b/dDatabase/GameDatabase/MySQL/Tables/Property.cpp index 662070df..18916e24 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/Property.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/Property.cpp @@ -1,6 +1,23 @@ #include "MySQLDatabase.h" #include "ePropertySortType.h" +IProperty::Info ReadPropertyInfo(UniqueResultSet& result) { + IProperty::Info info; + info.id = result->getUInt64("id"); + info.ownerId = result->getInt64("owner_id"); + info.cloneId = result->getUInt64("clone_id"); + info.name = result->getString("name").c_str(); + info.description = result->getString("description").c_str(); + info.privacyOption = result->getInt("privacy_option"); + info.rejectionReason = result->getString("rejection_reason").c_str(); + info.lastUpdatedTime = result->getUInt("last_updated"); + info.claimedTime = result->getUInt("time_claimed"); + info.reputation = result->getUInt("reputation"); + info.modApproved = result->getUInt("mod_approved"); + info.performanceCost = result->getFloat("performance_cost"); + return info; +} + std::optional MySQLDatabase::GetProperties(const IProperty::PropertyLookup& params) { std::optional result; std::string query; @@ -117,19 +134,7 @@ std::optional MySQLDatabase::GetProperties(co while (properties->next()) { if (!result) result = IProperty::PropertyEntranceResult(); - auto& entry = result->entries.emplace_back(); - entry.id = properties->getUInt64("id"); - entry.ownerId = properties->getInt64("owner_id"); - entry.cloneId = properties->getUInt64("clone_id"); - entry.name = properties->getString("name").c_str(); - entry.description = properties->getString("description").c_str(); - entry.privacyOption = properties->getInt("privacy_option"); - entry.rejectionReason = properties->getString("rejection_reason").c_str(); - entry.lastUpdatedTime = properties->getUInt("last_updated"); - entry.claimedTime = properties->getUInt("time_claimed"); - entry.reputation = properties->getUInt("reputation"); - entry.modApproved = properties->getUInt("mod_approved"); - entry.performanceCost = properties->getFloat("performance_cost"); + result->entries.push_back(ReadPropertyInfo(properties)); } return result; @@ -144,21 +149,7 @@ std::optional MySQLDatabase::GetPropertyInfo(const LWOMAPID map return std::nullopt; } - IProperty::Info toReturn; - toReturn.id = propertyEntry->getUInt64("id"); - toReturn.ownerId = propertyEntry->getInt64("owner_id"); - toReturn.cloneId = propertyEntry->getUInt64("clone_id"); - toReturn.name = propertyEntry->getString("name").c_str(); - toReturn.description = propertyEntry->getString("description").c_str(); - toReturn.privacyOption = propertyEntry->getInt("privacy_option"); - toReturn.rejectionReason = propertyEntry->getString("rejection_reason").c_str(); - toReturn.lastUpdatedTime = propertyEntry->getUInt("last_updated"); - toReturn.claimedTime = propertyEntry->getUInt("time_claimed"); - toReturn.reputation = propertyEntry->getUInt("reputation"); - toReturn.modApproved = propertyEntry->getUInt("mod_approved"); - toReturn.performanceCost = propertyEntry->getFloat("performance_cost"); - - return toReturn; + return ReadPropertyInfo(propertyEntry); } void MySQLDatabase::UpdatePropertyModerationInfo(const IProperty::Info& info) { @@ -195,3 +186,15 @@ void MySQLDatabase::InsertNewProperty(const IProperty::Info& info, const uint32_ zoneId.GetMapID() ); } + +std::optional MySQLDatabase::GetPropertyInfo(const LWOOBJID id) { + auto propertyEntry = ExecuteSelect( + "SELECT id, owner_id, clone_id, name, description, privacy_option, rejection_reason, last_updated, time_claimed, reputation, mod_approved, performance_cost " + "FROM properties WHERE id = ?;", id); + + if (!propertyEntry->next()) { + return std::nullopt; + } + + return ReadPropertyInfo(propertyEntry); +} diff --git a/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp b/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp index 44394518..cecea68b 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp @@ -64,26 +64,27 @@ void MySQLDatabase::RemoveModel(const LWOOBJID& modelId) { ExecuteDelete("DELETE FROM properties_contents WHERE id = ?;", modelId); } -IPropertyContents::Model MySQLDatabase::GetModel(const LWOOBJID modelID) { +std::optional MySQLDatabase::GetModel(const LWOOBJID modelID) { auto result = ExecuteSelect("SELECT * FROM properties_contents WHERE id = ?", modelID); - IPropertyContents::Model model{}; + std::optional model = std::nullopt; while (result->next()) { - model.id = result->getUInt64("id"); - model.lot = static_cast(result->getUInt("lot")); - model.position.x = result->getFloat("x"); - model.position.y = result->getFloat("y"); - model.position.z = result->getFloat("z"); - model.rotation.w = result->getFloat("rw"); - model.rotation.x = result->getFloat("rx"); - model.rotation.y = result->getFloat("ry"); - model.rotation.z = result->getFloat("rz"); - model.ugcId = result->getUInt64("ugc_id"); - model.behaviors[0] = result->getUInt64("behavior_1"); - model.behaviors[1] = result->getUInt64("behavior_2"); - model.behaviors[2] = result->getUInt64("behavior_3"); - model.behaviors[3] = result->getUInt64("behavior_4"); - model.behaviors[4] = result->getUInt64("behavior_5"); + model = IPropertyContents::Model{}; + model->id = result->getUInt64("id"); + model->lot = static_cast(result->getUInt("lot")); + model->position.x = result->getFloat("x"); + model->position.y = result->getFloat("y"); + model->position.z = result->getFloat("z"); + model->rotation.w = result->getFloat("rw"); + model->rotation.x = result->getFloat("rx"); + model->rotation.y = result->getFloat("ry"); + model->rotation.z = result->getFloat("rz"); + model->ugcId = result->getUInt64("ugc_id"); + model->behaviors[0] = result->getUInt64("behavior_1"); + model->behaviors[1] = result->getUInt64("behavior_2"); + model->behaviors[2] = result->getUInt64("behavior_3"); + model->behaviors[3] = result->getUInt64("behavior_4"); + model->behaviors[4] = result->getUInt64("behavior_5"); } return model; diff --git a/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp b/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp index 031ae6c7..06865c5e 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/Ugc.cpp @@ -1,5 +1,17 @@ #include "MySQLDatabase.h" +IUgc::Model ReadModel(UniqueResultSet& result) { + IUgc::Model model; + + // blob is owned by the query, so we need to do a deep copy :/ + std::unique_ptr blob(result->getBlob("lxfml")); + model.lxfmlData << blob->rdbuf(); + model.id = result->getUInt64("ugcID"); + model.modelID = result->getUInt64("modelID"); + + return model; +} + std::vector MySQLDatabase::GetUgcModels(const LWOOBJID& propertyId) { auto result = ExecuteSelect( "SELECT lxfml, u.id as ugcID, pc.id as modelID FROM ugc AS u JOIN properties_contents AS pc ON u.id = pc.ugc_id WHERE lot = 14 AND property_id = ? AND pc.ugc_id IS NOT NULL;", @@ -8,14 +20,7 @@ std::vector MySQLDatabase::GetUgcModels(const LWOOBJID& propertyId) std::vector toReturn; while (result->next()) { - IUgc::Model model; - - // blob is owned by the query, so we need to do a deep copy :/ - std::unique_ptr blob(result->getBlob("lxfml")); - model.lxfmlData << blob->rdbuf(); - model.id = result->getUInt64("ugcID"); - model.modelID = result->getUInt64("modelID"); - toReturn.push_back(std::move(model)); + toReturn.push_back(ReadModel(result)); } return toReturn; @@ -27,14 +32,7 @@ std::vector MySQLDatabase::GetAllUgcModels() { std::vector models; models.reserve(result->rowsCount()); while (result->next()) { - IUgc::Model model; - model.id = result->getInt64("ugcID"); - model.modelID = result->getUInt64("modelID"); - - // blob is owned by the query, so we need to do a deep copy :/ - std::unique_ptr blob(result->getBlob("lxfml")); - model.lxfmlData << blob->rdbuf(); - models.push_back(std::move(model)); + models.push_back(ReadModel(result)); } return models; @@ -45,7 +43,7 @@ void MySQLDatabase::RemoveUnreferencedUgcModels() { } void MySQLDatabase::InsertNewUgcModel( - std:: stringstream& sd0Data, // cant be const sad + std::stringstream& sd0Data, // cant be const sad const uint64_t blueprintId, const uint32_t accountId, const LWOOBJID characterId) { @@ -71,3 +69,14 @@ void MySQLDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::stringstrea const std::istream stream(lxfml.rdbuf()); ExecuteUpdate("UPDATE ugc SET lxfml = ? WHERE id = ?;", &stream, modelId); } + +std::optional MySQLDatabase::GetUgcModel(const LWOOBJID ugcId) { + auto result = ExecuteSelect("SELECT u.id AS ugcID, lxfml, pc.id AS modelID FROM ugc AS u JOIN properties_contents AS pc ON pc.ugc_id = u.id WHERE u.id = ?", ugcId); + + std::optional toReturn = std::nullopt; + if (result->next()) { + toReturn = ReadModel(result); + } + + return toReturn; +} diff --git a/dDatabase/GameDatabase/SQLite/SQLiteDatabase.cpp b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.cpp index 00726be5..19a49e5f 100644 --- a/dDatabase/GameDatabase/SQLite/SQLiteDatabase.cpp +++ b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.cpp @@ -61,9 +61,9 @@ bool SQLiteDatabase::GetAutoCommit() { void SQLiteDatabase::SetAutoCommit(bool value) { if (value) { - if (GetAutoCommit()) con->compileStatement("BEGIN;").execDML(); - } else { if (!GetAutoCommit()) con->compileStatement("COMMIT;").execDML(); + } else { + if (GetAutoCommit()) con->compileStatement("BEGIN;").execDML(); } } diff --git a/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h index 54dc39d3..3b6dc643 100644 --- a/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h +++ b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h @@ -96,9 +96,9 @@ public: void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override; void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override; void SetMasterInfo(const IServers::MasterInfo& info) override; - std::optional GetCurrentPersistentId() override; + std::optional GetCurrentPersistentId() override; + IObjectIdTracker::Range GetPersistentIdRange() override; void InsertDefaultPersistentId() override; - void UpdatePersistentId(const uint32_t id) override; std::optional GetDonationTotal(const uint32_t activityId) override; std::optional IsPlaykeyActive(const int32_t playkeyId) override; std::vector GetUgcModels(const LWOOBJID& propertyId) override; @@ -125,7 +125,9 @@ public: void DeleteUgcBuild(const LWOOBJID bigId) override; uint32_t GetAccountCount() override; bool IsNameInUse(const std::string_view name) override; - IPropertyContents::Model GetModel(const LWOOBJID modelID) override; + std::optional GetModel(const LWOOBJID modelID) override; + std::optional GetUgcModel(const LWOOBJID ugcId) override; + std::optional GetPropertyInfo(const LWOOBJID id) override; private: CppSQLite3Statement CreatePreppedStmt(const std::string& query); diff --git a/dDatabase/GameDatabase/SQLite/Tables/ObjectIdTracker.cpp b/dDatabase/GameDatabase/SQLite/Tables/ObjectIdTracker.cpp index af8014dd..57439b33 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/ObjectIdTracker.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/ObjectIdTracker.cpp @@ -1,17 +1,40 @@ #include "SQLiteDatabase.h" -std::optional SQLiteDatabase::GetCurrentPersistentId() { +std::optional SQLiteDatabase::GetCurrentPersistentId() { auto [_, result] = ExecuteSelect("SELECT last_object_id FROM object_id_tracker"); if (result.eof()) { return std::nullopt; } - return result.getIntField("last_object_id"); + return result.getInt64Field("last_object_id"); } void SQLiteDatabase::InsertDefaultPersistentId() { ExecuteInsert("INSERT INTO object_id_tracker VALUES (1);"); } -void SQLiteDatabase::UpdatePersistentId(const uint32_t newId) { - ExecuteUpdate("UPDATE object_id_tracker SET last_object_id = ?;", newId); +IObjectIdTracker::Range SQLiteDatabase::GetPersistentIdRange() { + IObjectIdTracker::Range range; + auto prevCommit = GetAutoCommit(); + SetAutoCommit(false); // This begins the transaction for us if one is not already in progress + + // THIS MUST ABSOLUTELY NOT FAIL. These IDs are expected to be unique. As such a transactional select is used to safely get a range + // of IDs that will never be used again. A separate feature could track unused IDs and recycle them, but that is not implemented. + // 200 seems like a good range to reserve at once. Only way this would be noticable is if a player + // added hundreds of items at once. + // Doing the update first ensures that all other connections are blocked from accessing this table until we commit. + auto result = ExecuteUpdate("UPDATE object_id_tracker SET last_object_id = last_object_id + 200;"); + if (result == 0) { + InsertDefaultPersistentId(); + result = ExecuteUpdate("UPDATE object_id_tracker SET last_object_id = last_object_id + 200;"); + } + + // At this point all connections are waiting on us to finish the transaction, so we can safely select the ID we just set. + auto [_, selectResult] = ExecuteSelect("SELECT last_object_id FROM object_id_tracker;"); + range.maxID = selectResult.getInt64Field("last_object_id"); + range.minID = range.maxID - 199; + + // We must commit here manually, this will unlock the database for all other servers + ExecuteCustomQuery("COMMIT;"); + SetAutoCommit(prevCommit); + return range; } diff --git a/dDatabase/GameDatabase/SQLite/Tables/Property.cpp b/dDatabase/GameDatabase/SQLite/Tables/Property.cpp index 195fee2a..67fc57b3 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/Property.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/Property.cpp @@ -1,6 +1,23 @@ #include "SQLiteDatabase.h" #include "ePropertySortType.h" +IProperty::Info ReadPropertyInfo(CppSQLite3Query& propertyEntry) { + IProperty::Info toReturn; + toReturn.id = propertyEntry.getInt64Field("id"); + toReturn.ownerId = propertyEntry.getInt64Field("owner_id"); + toReturn.cloneId = propertyEntry.getInt64Field("clone_id"); + toReturn.name = propertyEntry.getStringField("name"); + toReturn.description = propertyEntry.getStringField("description"); + toReturn.privacyOption = propertyEntry.getIntField("privacy_option"); + toReturn.rejectionReason = propertyEntry.getStringField("rejection_reason"); + toReturn.lastUpdatedTime = propertyEntry.getIntField("last_updated"); + toReturn.claimedTime = propertyEntry.getIntField("time_claimed"); + toReturn.reputation = propertyEntry.getIntField("reputation"); + toReturn.modApproved = propertyEntry.getIntField("mod_approved"); + toReturn.performanceCost = propertyEntry.getFloatField("performance_cost"); + return toReturn; +} + std::optional SQLiteDatabase::GetProperties(const IProperty::PropertyLookup& params) { std::optional result; std::string query; @@ -118,19 +135,7 @@ std::optional SQLiteDatabase::GetProperties(c auto& [_, properties] = propertiesRes; if (!properties.eof() && !result.has_value()) result = IProperty::PropertyEntranceResult(); while (!properties.eof()) { - auto& entry = result->entries.emplace_back(); - entry.id = properties.getInt64Field("id"); - entry.ownerId = properties.getInt64Field("owner_id"); - entry.cloneId = properties.getInt64Field("clone_id"); - entry.name = properties.getStringField("name"); - entry.description = properties.getStringField("description"); - entry.privacyOption = properties.getIntField("privacy_option"); - entry.rejectionReason = properties.getStringField("rejection_reason"); - entry.lastUpdatedTime = properties.getIntField("last_updated"); - entry.claimedTime = properties.getIntField("time_claimed"); - entry.reputation = properties.getIntField("reputation"); - entry.modApproved = properties.getIntField("mod_approved"); - entry.performanceCost = properties.getFloatField("performance_cost"); + result->entries.push_back(ReadPropertyInfo(properties)); properties.nextRow(); } @@ -146,21 +151,7 @@ std::optional SQLiteDatabase::GetPropertyInfo(const LWOMAPID ma return std::nullopt; } - IProperty::Info toReturn; - toReturn.id = propertyEntry.getInt64Field("id"); - toReturn.ownerId = propertyEntry.getInt64Field("owner_id"); - toReturn.cloneId = propertyEntry.getInt64Field("clone_id"); - toReturn.name = propertyEntry.getStringField("name"); - toReturn.description = propertyEntry.getStringField("description"); - toReturn.privacyOption = propertyEntry.getIntField("privacy_option"); - toReturn.rejectionReason = propertyEntry.getStringField("rejection_reason"); - toReturn.lastUpdatedTime = propertyEntry.getIntField("last_updated"); - toReturn.claimedTime = propertyEntry.getIntField("time_claimed"); - toReturn.reputation = propertyEntry.getIntField("reputation"); - toReturn.modApproved = propertyEntry.getIntField("mod_approved"); - toReturn.performanceCost = propertyEntry.getFloatField("performance_cost"); - - return toReturn; + return ReadPropertyInfo(propertyEntry); } void SQLiteDatabase::UpdatePropertyModerationInfo(const IProperty::Info& info) { @@ -197,3 +188,15 @@ void SQLiteDatabase::InsertNewProperty(const IProperty::Info& info, const uint32 zoneId.GetMapID() ); } + +std::optional SQLiteDatabase::GetPropertyInfo(const LWOOBJID id) { + auto [_, propertyEntry] = ExecuteSelect( + "SELECT id, owner_id, clone_id, name, description, privacy_option, rejection_reason, last_updated, time_claimed, reputation, mod_approved, performance_cost " + "FROM properties WHERE id = ?;", id); + + if (propertyEntry.eof()) { + return std::nullopt; + } + + return ReadPropertyInfo(propertyEntry); +} diff --git a/dDatabase/GameDatabase/SQLite/Tables/PropertyContents.cpp b/dDatabase/GameDatabase/SQLite/Tables/PropertyContents.cpp index a04f3536..5251c534 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/PropertyContents.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/PropertyContents.cpp @@ -64,27 +64,28 @@ void SQLiteDatabase::RemoveModel(const LWOOBJID& modelId) { ExecuteDelete("DELETE FROM properties_contents WHERE id = ?;", modelId); } -IPropertyContents::Model SQLiteDatabase::GetModel(const LWOOBJID modelID) { +std::optional SQLiteDatabase::GetModel(const LWOOBJID modelID) { auto [_, result] = ExecuteSelect("SELECT * FROM properties_contents WHERE id = ?", modelID); - IPropertyContents::Model model{}; + std::optional model = std::nullopt; if (!result.eof()) { do { - model.id = result.getInt64Field("id"); - model.lot = static_cast(result.getIntField("lot")); - model.position.x = result.getFloatField("x"); - model.position.y = result.getFloatField("y"); - model.position.z = result.getFloatField("z"); - model.rotation.w = result.getFloatField("rw"); - model.rotation.x = result.getFloatField("rx"); - model.rotation.y = result.getFloatField("ry"); - model.rotation.z = result.getFloatField("rz"); - model.ugcId = result.getInt64Field("ugc_id"); - model.behaviors[0] = result.getInt64Field("behavior_1"); - model.behaviors[1] = result.getInt64Field("behavior_2"); - model.behaviors[2] = result.getInt64Field("behavior_3"); - model.behaviors[3] = result.getInt64Field("behavior_4"); - model.behaviors[4] = result.getInt64Field("behavior_5"); + model = IPropertyContents::Model{}; + model->id = result.getInt64Field("id"); + model->lot = static_cast(result.getIntField("lot")); + model->position.x = result.getFloatField("x"); + model->position.y = result.getFloatField("y"); + model->position.z = result.getFloatField("z"); + model->rotation.w = result.getFloatField("rw"); + model->rotation.x = result.getFloatField("rx"); + model->rotation.y = result.getFloatField("ry"); + model->rotation.z = result.getFloatField("rz"); + model->ugcId = result.getInt64Field("ugc_id"); + model->behaviors[0] = result.getInt64Field("behavior_1"); + model->behaviors[1] = result.getInt64Field("behavior_2"); + model->behaviors[2] = result.getInt64Field("behavior_3"); + model->behaviors[3] = result.getInt64Field("behavior_4"); + model->behaviors[4] = result.getInt64Field("behavior_5"); } while (result.nextRow()); } diff --git a/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp b/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp index 31a6a2e7..0649bb9f 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/Ugc.cpp @@ -1,5 +1,17 @@ #include "SQLiteDatabase.h" +IUgc::Model ReadModel(CppSQLite3Query& result) { + IUgc::Model model; + + int blobSize{}; + const auto* blob = result.getBlobField("lxfml", blobSize); + model.lxfmlData << std::string(reinterpret_cast(blob), blobSize); + model.id = result.getInt64Field("ugcID"); + model.modelID = result.getInt64Field("modelID"); + + return model; +} + std::vector SQLiteDatabase::GetUgcModels(const LWOOBJID& propertyId) { auto [_, result] = ExecuteSelect( "SELECT lxfml, u.id AS ugcID, pc.id AS modelID FROM ugc AS u JOIN properties_contents AS pc ON u.id = pc.ugc_id WHERE lot = 14 AND property_id = ? AND pc.ugc_id IS NOT NULL;", @@ -8,14 +20,7 @@ std::vector SQLiteDatabase::GetUgcModels(const LWOOBJID& propertyId std::vector toReturn; while (!result.eof()) { - IUgc::Model model; - - int blobSize{}; - const auto* blob = result.getBlobField("lxfml", blobSize); - model.lxfmlData << std::string(reinterpret_cast(blob), blobSize); - model.id = result.getInt64Field("ugcID"); - model.modelID = result.getInt64Field("modelID"); - toReturn.push_back(std::move(model)); + toReturn.push_back(ReadModel(result)); result.nextRow(); } @@ -27,14 +32,7 @@ std::vector SQLiteDatabase::GetAllUgcModels() { std::vector models; while (!result.eof()) { - IUgc::Model model; - model.id = result.getInt64Field("ugcID"); - model.modelID = result.getInt64Field("modelID"); - - int blobSize{}; - const auto* blob = result.getBlobField("lxfml", blobSize); - model.lxfmlData << std::string(reinterpret_cast(blob), blobSize); - models.push_back(std::move(model)); + models.push_back(ReadModel(result)); result.nextRow(); } @@ -72,3 +70,14 @@ void SQLiteDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::stringstre const std::istream stream(lxfml.rdbuf()); ExecuteUpdate("UPDATE ugc SET lxfml = ? WHERE id = ?;", &stream, modelId); } + +std::optional SQLiteDatabase::GetUgcModel(const LWOOBJID ugcId) { + auto [_, result] = ExecuteSelect("SELECT u.id AS ugcID, pc.id AS modelID, lxfml FROM ugc AS u JOIN properties_contents AS pc ON pc.id = u.id WHERE u.id = ?;", ugcId); + + std::optional toReturn = std::nullopt; + if (!result.eof()) { + toReturn = ReadModel(result); + } + + return toReturn; +} diff --git a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.cpp b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.cpp index f9c9fb09..1cd48ad9 100644 --- a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.cpp +++ b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.cpp @@ -244,7 +244,7 @@ void TestSQLDatabase::SetMasterInfo(const IServers::MasterInfo& info) { } -std::optional TestSQLDatabase::GetCurrentPersistentId() { +std::optional TestSQLDatabase::GetCurrentPersistentId() { return {}; } @@ -252,10 +252,6 @@ void TestSQLDatabase::InsertDefaultPersistentId() { } -void TestSQLDatabase::UpdatePersistentId(const uint32_t id) { - -} - std::optional TestSQLDatabase::GetDonationTotal(const uint32_t activityId) { return {}; } @@ -304,3 +300,6 @@ void TestSQLDatabase::UpdateAccountGmLevel(const uint32_t accountId, const eGame } +IObjectIdTracker::Range TestSQLDatabase::GetPersistentIdRange() { + return {}; +} diff --git a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h index 87e08e91..2c7890dd 100644 --- a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h +++ b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h @@ -75,9 +75,9 @@ class TestSQLDatabase : public GameDatabase { void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override; void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override; void SetMasterInfo(const IServers::MasterInfo& info) override; - std::optional GetCurrentPersistentId() override; + std::optional GetCurrentPersistentId() override; + IObjectIdTracker::Range GetPersistentIdRange() override; void InsertDefaultPersistentId() override; - void UpdatePersistentId(const uint32_t id) override; std::optional GetDonationTotal(const uint32_t activityId) override; std::optional IsPlaykeyActive(const int32_t playkeyId) override; std::vector GetUgcModels(const LWOOBJID& propertyId) override; @@ -105,7 +105,9 @@ class TestSQLDatabase : public GameDatabase { uint32_t GetAccountCount() override { return 0; }; bool IsNameInUse(const std::string_view name) override { return false; }; - IPropertyContents::Model GetModel(const LWOOBJID modelID) override { return {}; } + std::optional GetModel(const LWOOBJID modelID) override { return {}; } + std::optional GetPropertyInfo(const LWOOBJID id) override { return {}; } + std::optional GetUgcModel(const LWOOBJID ugcId) override { return {}; } }; #endif //!TESTSQLDATABASE_H diff --git a/dDatabase/ModelNormalizeMigration.cpp b/dDatabase/ModelNormalizeMigration.cpp index b415ef57..45fcdb82 100644 --- a/dDatabase/ModelNormalizeMigration.cpp +++ b/dDatabase/ModelNormalizeMigration.cpp @@ -10,7 +10,7 @@ void ModelNormalizeMigration::Run() { for (auto& [lxfmlData, id, modelID] : Database::Get()->GetAllUgcModels()) { const auto model = Database::Get()->GetModel(modelID); // only BBB models (lot 14) and models with a position of NiPoint3::ZERO need to have their position fixed. - if (model.position != NiPoint3Constant::ZERO || model.lot != 14) continue; + if (!model || model->position != NiPoint3Constant::ZERO || model->lot != 14) continue; Sd0 sd0(lxfmlData); const auto asStr = sd0.GetAsStringUncompressed(); @@ -23,7 +23,7 @@ void ModelNormalizeMigration::Run() { LOG("Updated model %llu to have a center of %f %f %f", modelID, newCenter.x, newCenter.y, newCenter.z); sd0.FromData(reinterpret_cast(newLxfml.data()), newLxfml.size()); auto asStream = sd0.GetAsStream(); - Database::Get()->UpdateModel(model.id, newCenter, model.rotation, model.behaviors); + Database::Get()->UpdateModel(model->id, newCenter, model->rotation, model->behaviors); Database::Get()->UpdateUgcModelData(id, asStream); } Database::Get()->SetAutoCommit(oldCommit); @@ -35,15 +35,15 @@ void ModelNormalizeMigration::RunAfterFirstPart() { for (auto& [lxfmlData, id, modelID] : Database::Get()->GetAllUgcModels()) { const auto model = Database::Get()->GetModel(modelID); // only BBB models (lot 14) need to have their position fixed from the above blunder - if (model.lot != 14) continue; + if (!model || model->lot != 14) continue; Sd0 sd0(lxfmlData); const auto asStr = sd0.GetAsStringUncompressed(); - const auto [newLxfml, newCenter] = Lxfml::NormalizePositionAfterFirstPart(asStr, model.position); + const auto [newLxfml, newCenter] = Lxfml::NormalizePositionAfterFirstPart(asStr, model->position); sd0.FromData(reinterpret_cast(newLxfml.data()), newLxfml.size()); auto asStream = sd0.GetAsStream(); - Database::Get()->UpdateModel(model.id, newCenter, model.rotation, model.behaviors); + Database::Get()->UpdateModel(model->id, newCenter, model->rotation, model->behaviors); Database::Get()->UpdateUgcModelData(id, asStream); } Database::Get()->SetAutoCommit(oldCommit); @@ -55,16 +55,16 @@ void ModelNormalizeMigration::RunBrickBuildGrid() { for (auto& [lxfmlData, id, modelID] : Database::Get()->GetAllUgcModels()) { const auto model = Database::Get()->GetModel(modelID); // only BBB models (lot 14) need to have their position fixed from the above blunder - if (model.lot != 14) continue; + if (!model || model->lot != 14) continue; Sd0 sd0(lxfmlData); const auto asStr = sd0.GetAsStringUncompressed(); - const auto [newLxfml, newCenter] = Lxfml::NormalizePosition(asStr, model.position); + const auto [newLxfml, newCenter] = Lxfml::NormalizePosition(asStr, model->position); sd0.FromData(reinterpret_cast(newLxfml.data()), newLxfml.size()); LOG("Updated model %llu to have a center of %f %f %f", modelID, newCenter.x, newCenter.y, newCenter.z); auto asStream = sd0.GetAsStream(); - Database::Get()->UpdateModel(model.id, newCenter, model.rotation, model.behaviors); + Database::Get()->UpdateModel(model->id, newCenter, model->rotation, model->behaviors); Database::Get()->UpdateUgcModelData(id, asStream); } Database::Get()->SetAutoCommit(oldCommit); diff --git a/dGame/UserManager.cpp b/dGame/UserManager.cpp index 82afb063..343ce28d 100644 --- a/dGame/UserManager.cpp +++ b/dGame/UserManager.cpp @@ -30,6 +30,7 @@ #include "BitStreamUtils.h" #include "CheatDetection.h" #include "CharacterComponent.h" +#include "eCharacterVersion.h" UserManager* UserManager::m_Address = nullptr; @@ -324,79 +325,77 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet) LOG("AccountID: %i is creating a character with name: %s (temporary: %s)", u->GetAccountID(), name.c_str(), predefinedName.c_str()); } - //Now that the name is ok, we can get an objectID from Master: - ObjectIDManager::RequestPersistentID([=, this](uint32_t persistentID) { - LWOOBJID objectID = persistentID; - GeneralUtils::SetBit(objectID, eObjectBits::CHARACTER); - if (Database::Get()->GetCharacterInfo(objectID)) { - LOG("Character object id unavailable, check object_id_tracker!"); - WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::OBJECT_ID_UNAVAILABLE); - return; - } + //Now that the name is ok, we can get a persistent ObjectID: + LWOOBJID objectID = ObjectIDManager::GetPersistentID(); + const uint32_t maxRetries = 100; + uint32_t tries = 0; + while (Database::Get()->GetCharacterInfo(objectID) && tries < maxRetries) { + tries++; + LOG("Found a duplicate character %llu, getting a new objectID", objectID); + objectID = ObjectIDManager::GetPersistentID(); + } - std::stringstream xml; - xml << ""; + if (tries >= maxRetries) { + LOG("Failed to get a unique objectID for new character after %i tries, aborting char creation for account %i", maxRetries, u->GetAccountID()); + WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::OBJECT_ID_UNAVAILABLE); + return; + } - xml << ""; + std::stringstream xml; + xml << ""; - xml << "GetAccountID() << "\" cc=\"0\" gm=\"0\" ft=\"0\" llog=\"" << time(NULL) << "\" "; - xml << "ls=\"0\" lzx=\"-626.5847\" lzy=\"613.3515\" lzz=\"-28.6374\" lzrx=\"0.0\" lzry=\"0.7015\" lzrz=\"0.0\" lzrw=\"0.7126\" "; - xml << "stt=\"0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;\">"; - xml << ""; + xml << ""; - xml << ""; + xml << "GetAccountID() << "\" cc=\"0\" gm=\"0\" ft=\"0\" llog=\"" << time(NULL) << "\" "; + xml << "ls=\"0\" lzx=\"-626.5847\" lzy=\"613.3515\" lzz=\"-28.6374\" lzrx=\"0.0\" lzry=\"0.7015\" lzrz=\"0.0\" lzrw=\"0.7126\" "; + xml << "stt=\"0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;\">"; + xml << ""; - xml << ""; + xml << ""; - xml << ""; + xml << ""; - LWOOBJID lwoidforshirt = ObjectIDManager::GenerateRandomObjectID(); - LWOOBJID lwoidforpants; + xml << ""; - do { - lwoidforpants = ObjectIDManager::GenerateRandomObjectID(); - } while (lwoidforpants == lwoidforshirt); //Make sure we don't have the same ID for both shirt and pants + LWOOBJID lwoidforshirt = ObjectIDManager::GetPersistentID(); + LWOOBJID lwoidforpants = ObjectIDManager::GetPersistentID(); - GeneralUtils::SetBit(lwoidforshirt, eObjectBits::CHARACTER); - GeneralUtils::SetBit(lwoidforpants, eObjectBits::CHARACTER); + xml << ""; + xml << ""; - xml << ""; - xml << ""; + xml << ""; - xml << ""; + //Check to see if our name was pre-approved: + bool nameOk = IsNamePreapproved(name); + if (!nameOk && u->GetMaxGMLevel() > eGameMasterLevel::FORUM_MODERATOR) nameOk = true; - //Check to see if our name was pre-approved: - bool nameOk = IsNamePreapproved(name); - if (!nameOk && u->GetMaxGMLevel() > eGameMasterLevel::FORUM_MODERATOR) nameOk = true; + // If predefined name is invalid, change it to be their object id + // that way more than one player can create characters if the predefined name files are not provided + auto assignedPredefinedName = predefinedName; + if (assignedPredefinedName == "INVALID") { + std::stringstream nameObjID; + nameObjID << "minifig" << objectID; + assignedPredefinedName = nameObjID.str(); + } - // If predefined name is invalid, change it to be their object id - // that way more than one player can create characters if the predefined name files are not provided - auto assignedPredefinedName = predefinedName; - if (assignedPredefinedName == "INVALID") { - std::stringstream nameObjID; - nameObjID << "minifig" << objectID; - assignedPredefinedName = nameObjID.str(); - } + std::string_view nameToAssign = !name.empty() && nameOk ? name : assignedPredefinedName; + std::string pendingName = !name.empty() && !nameOk ? name : ""; - std::string_view nameToAssign = !name.empty() && nameOk ? name : assignedPredefinedName; - std::string pendingName = !name.empty() && !nameOk ? name : ""; + ICharInfo::Info info; + info.name = nameToAssign; + info.pendingName = pendingName; + info.id = objectID; + info.accountId = u->GetAccountID(); - ICharInfo::Info info; - info.name = nameToAssign; - info.pendingName = pendingName; - info.id = objectID; - info.accountId = u->GetAccountID(); + Database::Get()->InsertNewCharacter(info); - Database::Get()->InsertNewCharacter(info); + //Now finally insert our character xml: + Database::Get()->InsertCharacterXml(objectID, xml.str()); - //Now finally insert our character xml: - Database::Get()->InsertCharacterXml(objectID, xml.str()); - - WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::SUCCESS); - UserManager::RequestCharacterList(sysAddr); - }); + WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::SUCCESS); + UserManager::RequestCharacterList(sysAddr); } void UserManager::DeleteCharacter(const SystemAddress& sysAddr, Packet* packet) { diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 4b50ece2..3138904b 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -626,7 +626,8 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument& document) { for (const auto& pair : this->m_Inventories) { auto* inventory = pair.second; - if (inventory->GetType() == VENDOR_BUYBACK || inventory->GetType() == eInventoryType::MODELS_IN_BBB) { + static const auto EXCLUDED_INVENTORIES = {VENDOR_BUYBACK, MODELS_IN_BBB, ITEM_SETS}; + if (std::ranges::find(EXCLUDED_INVENTORIES, inventory->GetType()) != EXCLUDED_INVENTORIES.end()) { continue; } @@ -1786,3 +1787,9 @@ void InventoryComponent::LoadGroupXml(const tinyxml2::XMLElement& groups) { groupElement = groupElement->NextSiblingElement("grp"); } } + +void InventoryComponent::RegenerateItemIDs() { + for (auto* const inventory : m_Inventories | std::views::values) { + inventory->RegenerateItemIDs(); + } +} diff --git a/dGame/dComponents/InventoryComponent.h b/dGame/dComponents/InventoryComponent.h index d67cf3c1..a1441655 100644 --- a/dGame/dComponents/InventoryComponent.h +++ b/dGame/dComponents/InventoryComponent.h @@ -407,6 +407,9 @@ public: void FixInvisibleItems(); + // Used to migrate a character version, no need to call outside of that context + void RegenerateItemIDs(); + ~InventoryComponent() override; private: diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index 5081db6c..df57aef7 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -479,9 +479,19 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) { return; } - LWOOBJID petSubKey = ObjectIDManager::GenerateRandomObjectID(); + LWOOBJID petSubKey = ObjectIDManager::GetPersistentID(); + const uint32_t maxTries = 100; + uint32_t tries = 0; + while (Database::Get()->GetPetNameInfo(petSubKey) && tries < maxTries) { + tries++; + LOG("Found a duplicate pet %llu, getting a new subKey", petSubKey); + petSubKey = ObjectIDManager::GetPersistentID(); + } - GeneralUtils::SetBit(petSubKey, eObjectBits::CHARACTER); + if (tries >= maxTries) { + LOG("Failed to get a unique pet subKey after %i tries, aborting pet creation for player %i", maxTries, tamer->GetCharacter() ? tamer->GetCharacter()->GetID() : -1); + return; + } m_DatabaseId = petSubKey; diff --git a/dGame/dComponents/PropertyEntranceComponent.cpp b/dGame/dComponents/PropertyEntranceComponent.cpp index 783de9a9..d2e595cf 100644 --- a/dGame/dComponents/PropertyEntranceComponent.cpp +++ b/dGame/dComponents/PropertyEntranceComponent.cpp @@ -135,7 +135,7 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl const auto owner = propertyEntry.ownerId; const auto otherCharacter = Database::Get()->GetCharacterInfo(owner); if (!otherCharacter.has_value()) { - LOG("Failed to find property owner name for %u!", owner); + LOG("Failed to find property owner name for %llu!", owner); continue; } auto& entry = entries.emplace_back(); diff --git a/dGame/dComponents/PropertyManagementComponent.cpp b/dGame/dComponents/PropertyManagementComponent.cpp index 06792897..c6edbf6d 100644 --- a/dGame/dComponents/PropertyManagementComponent.cpp +++ b/dGame/dComponents/PropertyManagementComponent.cpp @@ -170,7 +170,7 @@ void PropertyManagementComponent::UpdatePropertyDetails(std::string name, std::s info.name = propertyName; info.description = propertyDescription; info.lastUpdatedTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - + Database::Get()->UpdateLastSave(info); Database::Get()->UpdatePropertyDetails(info); @@ -203,14 +203,22 @@ bool PropertyManagementComponent::Claim(const LWOOBJID playerId) { auto prop_path = zone->GetPath(m_Parent->GetVarAsString(u"propertyName")); - if (prop_path){ + if (prop_path) { if (!prop_path->property.displayName.empty()) name = prop_path->property.displayName; description = prop_path->property.displayDesc; } SetOwnerId(playerId); - propertyId = ObjectIDManager::GenerateRandomObjectID(); + // Due to legacy IDs being random + propertyId = ObjectIDManager::GetPersistentID(); + const uint32_t maxTries = 100; + uint32_t tries = 0; + while (Database::Get()->GetPropertyInfo(propertyId) && tries < maxTries) { + tries++; + LOG("Found a duplicate property %llu, getting a new propertyId", propertyId); + propertyId = ObjectIDManager::GetPersistentID(); + } IProperty::Info info; info.id = propertyId; @@ -374,46 +382,45 @@ void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const N node->position = position; node->rotation = rotation; - ObjectIDManager::RequestPersistentID([this, node, modelLOT, entity, position, rotation, originalRotation](uint32_t persistentId) { - SpawnerInfo info{}; + SpawnerInfo info{}; - info.templateID = modelLOT; - info.nodes = { node }; - info.templateScale = 1.0f; - info.activeOnLoad = true; - info.amountMaintained = 1; - info.respawnTime = 10; + info.templateID = modelLOT; + info.nodes = { node }; + info.templateScale = 1.0f; + info.activeOnLoad = true; + info.amountMaintained = 1; + info.respawnTime = 10; - info.emulated = true; - info.emulator = Game::entityManager->GetZoneControlEntity()->GetObjectID(); + info.emulated = true; + info.emulator = Game::entityManager->GetZoneControlEntity()->GetObjectID(); - info.spawnerID = persistentId; - GeneralUtils::SetBit(info.spawnerID, eObjectBits::CLIENT); + info.spawnerID = ObjectIDManager::GetPersistentID(); + GeneralUtils::SetBit(info.spawnerID, eObjectBits::CLIENT); - const auto spawnerId = Game::zoneManager->MakeSpawner(info); + const auto spawnerId = Game::zoneManager->MakeSpawner(info); - auto* spawner = Game::zoneManager->GetSpawner(spawnerId); + 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.push_back(new LDFData(u"modelBehaviors", 0)); + info.nodes[0]->config.push_back(new LDFData(u"userModelID", info.spawnerID)); + info.nodes[0]->config.push_back(new LDFData(u"modelType", 2)); + info.nodes[0]->config.push_back(new LDFData(u"propertyObjectID", true)); + info.nodes[0]->config.push_back(new LDFData(u"componentWhitelist", 1)); - auto* model = spawner->Spawn(); - auto* modelComponent = model->GetComponent(); - if (modelComponent) modelComponent->Pause(); + auto* model = spawner->Spawn(); + auto* modelComponent = model->GetComponent(); + if (modelComponent) modelComponent->Pause(); - models.insert_or_assign(model->GetObjectID(), spawnerId); + models.insert_or_assign(model->GetObjectID(), spawnerId); - GameMessages::SendPlaceModelResponse(entity->GetObjectID(), entity->GetSystemAddress(), position, m_Parent->GetObjectID(), 14, originalRotation); + GameMessages::SendPlaceModelResponse(entity->GetObjectID(), entity->GetSystemAddress(), position, m_Parent->GetObjectID(), 14, originalRotation); - GameMessages::SendUGCEquipPreCreateBasedOnEditMode(entity->GetObjectID(), entity->GetSystemAddress(), 0, spawnerId); + GameMessages::SendUGCEquipPreCreateBasedOnEditMode(entity->GetObjectID(), entity->GetSystemAddress(), 0, spawnerId); - GameMessages::SendGetModelsOnProperty(entity->GetObjectID(), GetModels(), UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::SendGetModelsOnProperty(entity->GetObjectID(), GetModels(), UNASSIGNED_SYSTEM_ADDRESS); + + Game::entityManager->GetZoneControlEntity()->OnZonePropertyModelPlaced(entity); - Game::entityManager->GetZoneControlEntity()->OnZonePropertyModelPlaced(entity); - }); // Progress place model missions auto missionComponent = entity->GetComponent(); if (missionComponent != nullptr) missionComponent->Progress(eMissionTaskType::PLACE_MODEL, 0); @@ -693,7 +700,7 @@ void PropertyManagementComponent::Save() { // save the behaviors of the model for (const auto& [behaviorId, behaviorStr] : modelBehaviors) { if (behaviorStr.empty() || behaviorId == -1 || behaviorId == 0) continue; - IBehaviors::Info info { + IBehaviors::Info info{ .behaviorId = behaviorId, .characterId = character->GetID(), .behaviorInfo = behaviorStr @@ -821,7 +828,7 @@ void PropertyManagementComponent::OnChatMessageReceived(const std::string& sMess if (!model) continue; auto* const modelComponent = model->GetComponent(); if (!modelComponent) continue; - + modelComponent->OnChatMessageReceived(sMessage); } } diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 168469c9..c7108e50 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -2555,7 +2555,7 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent uint32_t sd0Size; inStream.Read(sd0Size); - std::shared_ptr sd0Data(new char[sd0Size]); + std::unique_ptr sd0Data(new char[sd0Size]); if (sd0Data == nullptr) return; @@ -2579,115 +2579,121 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent //Now, the cave of dragons: - //We runs this in async because the http library here is blocking, meaning it'll halt the thread. - //But we don't want the server to go unresponsive, because then the client would disconnect. - //We need to get a new ID for our model first: - ObjectIDManager::RequestPersistentID([=](uint32_t newID) { - if (!entity || !entity->GetCharacter() || !entity->GetCharacter()->GetParentUser()) return; - LWOOBJID newIDL = newID; - GeneralUtils::SetBit(newIDL, eObjectBits::CHARACTER); + if (!entity || !entity->GetCharacter() || !entity->GetCharacter()->GetParentUser()) return; + const uint32_t maxRetries = 100; + uint32_t retries = 0; + bool blueprintIDExists = true; + bool modelExists = true; - LWOOBJID blueprintID = ObjectIDManager::GenerateRandomObjectID(); - GeneralUtils::SetBit(blueprintID, eObjectBits::CHARACTER); + // Legacy logic to check for old random IDs (regenerating these is not really feasible) + // Probably good to have this anyway in case someone messes with the last_object_id or it gets reset somehow + LWOOBJID newIDL = LWOOBJID_EMPTY; + LWOOBJID blueprintID = LWOOBJID_EMPTY; + do { + if (newIDL != LWOOBJID_EMPTY) LOG("Generating blueprintID for UGC model, collision with existing model ID: %llu", blueprintID); + newIDL = ObjectIDManager::GetPersistentID(); + blueprintID = ObjectIDManager::GetPersistentID(); + ++retries; + blueprintIDExists = Database::Get()->GetUgcModel(blueprintID).has_value(); + modelExists = Database::Get()->GetModel(newIDL).has_value(); + } while ((blueprintIDExists || modelExists) && retries < maxRetries); - //We need to get the propertyID: (stolen from Wincent's propertyManagementComp) - const auto& worldId = Game::zoneManager->GetZone()->GetZoneID(); + //We need to get the propertyID: (stolen from Wincent's propertyManagementComp) + const auto& worldId = Game::zoneManager->GetZone()->GetZoneID(); - const auto zoneId = worldId.GetMapID(); - const auto cloneId = worldId.GetCloneID(); + const auto zoneId = worldId.GetMapID(); + const auto cloneId = worldId.GetCloneID(); - auto propertyInfo = Database::Get()->GetPropertyInfo(zoneId, cloneId); - LWOOBJID propertyId = LWOOBJID_EMPTY; - if (propertyInfo) propertyId = propertyInfo->id; + auto propertyInfo = Database::Get()->GetPropertyInfo(zoneId, cloneId); + LWOOBJID propertyId = LWOOBJID_EMPTY; + if (propertyInfo) propertyId = propertyInfo->id; - // Save the binary data to the Sd0 buffer - std::string str(sd0Data.get(), sd0Size); - std::istringstream sd0DataStream(str); - Sd0 sd0(sd0DataStream); + // Save the binary data to the Sd0 buffer + std::string str(sd0Data.get(), sd0Size); + std::istringstream sd0DataStream(str); + Sd0 sd0(sd0DataStream); - // Uncompress the data and normalize the position - const auto asStr = sd0.GetAsStringUncompressed(); - const auto [newLxfml, newCenter] = Lxfml::NormalizePosition(asStr); + // Uncompress the data and normalize the position + const auto asStr = sd0.GetAsStringUncompressed(); + const auto [newLxfml, newCenter] = Lxfml::NormalizePosition(asStr); - // Recompress the data and save to the database - sd0.FromData(reinterpret_cast(newLxfml.data()), newLxfml.size()); - auto sd0AsStream = sd0.GetAsStream(); - Database::Get()->InsertNewUgcModel(sd0AsStream, blueprintID, entity->GetCharacter()->GetParentUser()->GetAccountID(), entity->GetCharacter()->GetID()); + // Recompress the data and save to the database + sd0.FromData(reinterpret_cast(newLxfml.data()), newLxfml.size()); + auto sd0AsStream = sd0.GetAsStream(); + Database::Get()->InsertNewUgcModel(sd0AsStream, blueprintID, entity->GetCharacter()->GetParentUser()->GetAccountID(), entity->GetCharacter()->GetID()); - //Insert into the db as a BBB model: - IPropertyContents::Model model; - model.id = newIDL; - model.ugcId = blueprintID; - model.position = newCenter; - model.rotation = NiQuaternion(0.0f, 0.0f, 0.0f, 0.0f); - model.lot = 14; - Database::Get()->InsertNewPropertyModel(propertyId, model, "Objects_14_name"); + //Insert into the db as a BBB model: + IPropertyContents::Model model; + model.id = newIDL; + model.ugcId = blueprintID; + model.position = newCenter; + model.rotation = NiQuaternion(0.0f, 0.0f, 0.0f, 0.0f); + model.lot = 14; + Database::Get()->InsertNewPropertyModel(propertyId, model, "Objects_14_name"); - /* - Commented out until UGC server would be updated to use a sd0 file instead of lxfml stream. - (or you uncomment the lxfml decomp stuff above) - */ + /* + Commented out until UGC server would be updated to use a sd0 file instead of lxfml stream. + (or you uncomment the lxfml decomp stuff above) + */ - ////Send off to UGC for processing, if enabled: - //if (Game::config->GetValue("ugc_remote") == "1") { - // std::string ugcIP = Game::config->GetValue("ugc_ip"); - // int ugcPort = std::stoi(Game::config->GetValue("ugc_port")); + // //Send off to UGC for processing, if enabled: + // if (Game::config->GetValue("ugc_remote") == "1") { + // std::string ugcIP = Game::config->GetValue("ugc_ip"); + // int ugcPort = std::stoi(Game::config->GetValue("ugc_port")); - // httplib::Client cli(ugcIP, ugcPort); //connect to UGC HTTP server using our config above ^ + // httplib::Client cli(ugcIP, ugcPort); //connect to UGC HTTP server using our config above ^ - // //Send out a request: - // std::string request = "/3dservices/UGCC150/150" + std::to_string(blueprintID) + ".lxfml"; - // cli.Put(request.c_str(), lxfml.c_str(), "text/lxfml"); + // //Send out a request: + // std::string request = "/3dservices/UGCC150/150" + std::to_string(blueprintID) + ".lxfml"; + // cli.Put(request.c_str(), lxfml.c_str(), "text/lxfml"); - // //When the "put" above returns, it means that the UGC HTTP server is done processing our model & - // //the nif, hkx and checksum files are ready to be downloaded from cache. - //} + // //When the "put" above returns, it means that the UGC HTTP server is done processing our model & + // //the nif, hkx and checksum files are ready to be downloaded from cache. + // } - //Tell the client their model is saved: (this causes us to actually pop out of our current state): - const auto& newSd0 = sd0.GetAsVector(); - uint32_t sd0Size{}; - for (const auto& chunk : newSd0) sd0Size += chunk.size(); - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::BLUEPRINT_SAVE_RESPONSE); - bitStream.Write(localId); - bitStream.Write(eBlueprintSaveResponseType::EverythingWorked); - bitStream.Write(1); - bitStream.Write(blueprintID); + //Tell the client their model is saved: (this causes us to actually pop out of our current state): + const auto& newSd0 = sd0.GetAsVector(); + uint32_t newSd0Size{}; + for (const auto& chunk : newSd0) newSd0Size += chunk.size(); + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::BLUEPRINT_SAVE_RESPONSE); + bitStream.Write(localId); + bitStream.Write(eBlueprintSaveResponseType::EverythingWorked); + bitStream.Write(1); + bitStream.Write(blueprintID); - bitStream.Write(sd0Size); + bitStream.Write(newSd0Size); - for (const auto& chunk : newSd0) bitStream.WriteAlignedBytes(reinterpret_cast(chunk.data()), chunk.size()); + for (const auto& chunk : newSd0) bitStream.WriteAlignedBytes(reinterpret_cast(chunk.data()), chunk.size()); - SEND_PACKET; + SEND_PACKET; - //Now we have to construct this object: + //Now we have to construct this object: - EntityInfo info; - info.lot = 14; - info.pos = newCenter; - info.rot = {}; - info.spawner = nullptr; - info.spawnerID = entity->GetObjectID(); - info.spawnerNodeID = 0; + EntityInfo info; + info.lot = 14; + info.pos = newCenter; + info.rot = {}; + info.spawner = nullptr; + info.spawnerID = entity->GetObjectID(); + info.spawnerNodeID = 0; - info.settings.push_back(new LDFData(u"blueprintid", blueprintID)); - info.settings.push_back(new LDFData(u"componentWhitelist", 1)); - info.settings.push_back(new LDFData(u"modelType", 2)); - info.settings.push_back(new LDFData(u"propertyObjectID", true)); - info.settings.push_back(new LDFData(u"userModelID", newIDL)); + info.settings.push_back(new LDFData(u"blueprintid", blueprintID)); + info.settings.push_back(new LDFData(u"componentWhitelist", 1)); + info.settings.push_back(new LDFData(u"modelType", 2)); + info.settings.push_back(new LDFData(u"propertyObjectID", true)); + info.settings.push_back(new LDFData(u"userModelID", newIDL)); - Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr); - if (newEntity) { - Game::entityManager->ConstructEntity(newEntity); + Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr); + if (newEntity) { + Game::entityManager->ConstructEntity(newEntity); - //Make sure the propMgmt doesn't delete our model after the server dies - //Trying to do this after the entity is constructed. Shouldn't really change anything but - //there was an issue with builds not appearing since it was placed above ConstructEntity. - PropertyManagementComponent::Instance()->AddModel(newEntity->GetObjectID(), newIDL); - } - - }); + //Make sure the propMgmt doesn't delete our model after the server dies + //Trying to do this after the entity is constructed. Shouldn't really change anything but + //there was an issue with builds not appearing since it was placed above ConstructEntity. + PropertyManagementComponent::Instance()->AddModel(newEntity->GetObjectID(), newIDL); + } } void GameMessages::HandlePropertyEntranceSync(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { @@ -5526,55 +5532,52 @@ void GameMessages::HandleModularBuildFinish(RakNet::BitStream& inStream, Entity* } } - ObjectIDManager::RequestPersistentID([=](uint32_t newId) { - LOG("Build finished"); - GameMessages::SendFinishArrangingWithItem(character, entity->GetObjectID()); // kick them from modular build - GameMessages::SendModularBuildEnd(character); // i dont know if this does anything but DLUv2 did it + LOG("Build finished"); + GameMessages::SendFinishArrangingWithItem(character, entity->GetObjectID()); // kick them from modular build + GameMessages::SendModularBuildEnd(character); // i dont know if this does anything but DLUv2 did it - //inv->UnequipItem(inv->GetItemStackByLOT(6086, eInventoryType::ITEMS)); // take off the thinking cap - //Game::entityManager->SerializeEntity(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); + const auto moduleAssembly = new LDFData(u"assemblyPartLOTs", modules); - std::vector config; - config.push_back(moduleAssembly); + std::vector config; + config.push_back(moduleAssembly); - LWOOBJID newIdBig = newId; - GeneralUtils::SetBit(newIdBig, eObjectBits::CHARACTER); + LWOOBJID newID = ObjectIDManager::GetPersistentID(); - if (count == 3) { - inv->AddItem(6416, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newIdBig); - } else if (count == 7) { - inv->AddItem(8092, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newIdBig); + if (count == 3) { + inv->AddItem(6416, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newID); + } else if (count == 7) { + inv->AddItem(8092, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newID); + } + + auto* pCharacter = character->GetCharacter(); + Database::Get()->InsertUgcBuild(GeneralUtils::UTF16ToWTF8(modules), newID, pCharacter ? std::optional(character->GetCharacter()->GetID()) : std::nullopt); + + auto* missionComponent = character->GetComponent(); + + if (entity->GetLOT() != 9980 || Game::server->GetZoneID() != 1200) { + if (missionComponent != nullptr) { + missionComponent->Progress(eMissionTaskType::SCRIPT, entity->GetLOT(), entity->GetObjectID()); + if (count >= 7 && everyPieceSwapped) missionComponent->Progress(eMissionTaskType::RACING, LWOOBJID_EMPTY, static_cast(eRacingTaskParam::MODULAR_BUILDING)); } + } - auto* pCharacter = character->GetCharacter(); - Database::Get()->InsertUgcBuild(GeneralUtils::UTF16ToWTF8(modules), newIdBig, pCharacter ? std::optional(character->GetCharacter()->GetID()) : std::nullopt); + ScriptComponent* script = static_cast(entity->GetComponent(eReplicaComponentType::SCRIPT)); - auto* missionComponent = character->GetComponent(); + entity->GetScript()->OnModularBuildExit(entity, character, count >= 3, modList); - if (entity->GetLOT() != 9980 || Game::server->GetZoneID() != 1200) { - if (missionComponent != nullptr) { - missionComponent->Progress(eMissionTaskType::SCRIPT, entity->GetLOT(), entity->GetObjectID()); - if (count >= 7 && everyPieceSwapped) missionComponent->Progress(eMissionTaskType::RACING, LWOOBJID_EMPTY, static_cast(eRacingTaskParam::MODULAR_BUILDING)); - } - } + // Move remaining temp models back to models + std::vector items; - ScriptComponent* script = static_cast(entity->GetComponent(eReplicaComponentType::SCRIPT)); + for (const auto& pair : temp->GetItems()) { + items.push_back(pair.second); + } - entity->GetScript()->OnModularBuildExit(entity, character, count >= 3, modList); - - // Move remaining temp models back to models - std::vector items; - - for (const auto& pair : temp->GetItems()) { - items.push_back(pair.second); - } - - for (auto* item : items) { - inv->MoveItemToInventory(item, eInventoryType::MODELS, item->GetCount(), false); - } - }); + for (auto* item : items) { + inv->MoveItemToInventory(item, eInventoryType::MODELS, item->GetCount(), false); + } } } diff --git a/dGame/dInventory/Inventory.cpp b/dGame/dInventory/Inventory.cpp index 35222bea..98e34f8d 100644 --- a/dGame/dInventory/Inventory.cpp +++ b/dGame/dInventory/Inventory.cpp @@ -5,8 +5,11 @@ #include "InventoryComponent.h" #include "eItemType.h" #include "eReplicaComponentType.h" +#include "ObjectIDManager.h" +#include "eObjectBits.h" #include "CDComponentsRegistryTable.h" +#include std::vector Inventory::m_GameMasterRestrictedItems = { 1727, // GM Only - JetPack @@ -317,3 +320,16 @@ Inventory::~Inventory() { items.clear(); } + +void Inventory::RegenerateItemIDs() { + std::map newItems{}; + for (auto* const item : items | std::views::values) { + const auto oldID = item->GetId(); + const auto newID = item->GenerateID(); + LOG("Updating item ID from %llu to %llu", oldID, newID); + newItems.insert_or_assign(newID, item); + } + + // We don't want to delete the item pointers, we're just moving from map to map + items = newItems; +} diff --git a/dGame/dInventory/Inventory.h b/dGame/dInventory/Inventory.h index 5e0ac8d6..f07ec68e 100644 --- a/dGame/dInventory/Inventory.h +++ b/dGame/dInventory/Inventory.h @@ -158,6 +158,8 @@ public: */ void DeleteAllItems(); + void RegenerateItemIDs(); + ~Inventory(); private: diff --git a/dGame/dInventory/Item.cpp b/dGame/dInventory/Item.cpp index 1a12c25f..b39a1f73 100644 --- a/dGame/dInventory/Item.cpp +++ b/dGame/dInventory/Item.cpp @@ -98,21 +98,12 @@ Item::Item( this->preconditions = new PreconditionExpression(this->info->reqPrecondition); this->subKey = subKey; - LWOOBJID id = ObjectIDManager::GenerateRandomObjectID(); - - GeneralUtils::SetBit(id, eObjectBits::CHARACTER); - - const auto type = static_cast(info->itemType); - - if (type == eItemType::MOUNT) { - GeneralUtils::SetBit(id, eObjectBits::CLIENT); - } - - this->id = id; + auto* const inventoryComponent = inventory->GetComponent(); + GenerateID(); inventory->AddManagedItem(this); - auto* entity = inventory->GetComponent()->GetParent(); + auto* entity = inventoryComponent->GetParent(); GameMessages::SendAddItemToInventoryClientSync(entity, entity->GetSystemAddress(), this, id, showFlyingLoot, static_cast(this->count), subKey, lootSourceType); if (isModMoveAndEquip) { @@ -120,7 +111,7 @@ Item::Item( LOG("Move and equipped (%i) from (%i)", this->lot, this->inventory->GetType()); - Game::entityManager->SerializeEntity(inventory->GetComponent()->GetParent()); + Game::entityManager->SerializeEntity(inventoryComponent->GetParent()); } } @@ -573,3 +564,28 @@ void Item::LoadConfigXml(const tinyxml2::XMLElement& i) { config.push_back(LDFBaseData::DataFromString(value)); } } + +LWOOBJID Item::GenerateID() { + auto* const inventoryComponent = inventory->GetComponent(); + const bool isPlayer = inventoryComponent->GetParent()->IsPlayer(); + LWOOBJID id{}; + + // Only players and non-proxy items get persistent IDs (since they are the only ones that will persist between worlds) + if (isPlayer && parent == LWOOBJID_EMPTY) { + id = ObjectIDManager::GetPersistentID(); + } else { + id = ObjectIDManager::GenerateObjectID(); + GeneralUtils::SetBit(id, eObjectBits::SPAWNED); + GeneralUtils::SetBit(id, eObjectBits::CLIENT); + } + + LOG("Parent %llu lot %u Generated id %u:%llu", parent, GetLot(), static_cast(id), id); + const auto type = static_cast(info->itemType); + + if (type == eItemType::MOUNT) { + GeneralUtils::SetBit(id, eObjectBits::CLIENT); + } + + this->id = id; + return id; +} diff --git a/dGame/dInventory/Item.h b/dGame/dInventory/Item.h index 72ff264c..846a7aa7 100644 --- a/dGame/dInventory/Item.h +++ b/dGame/dInventory/Item.h @@ -228,6 +228,8 @@ public: void LoadConfigXml(const tinyxml2::XMLElement& i); + LWOOBJID GenerateID(); + private: /** * The object ID of this item diff --git a/dGame/dPropertyBehaviors/ControlBehaviors.cpp b/dGame/dPropertyBehaviors/ControlBehaviors.cpp index 1a142273..70a32981 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviors.cpp +++ b/dGame/dPropertyBehaviors/ControlBehaviors.cpp @@ -35,25 +35,21 @@ void ControlBehaviors::RequestUpdatedID(ControlBehaviorContext& context) { BehaviorMessageBase msgBase{ context.arguments }; const auto oldBehaviorID = msgBase.GetBehaviorId(); - ObjectIDManager::RequestPersistentID( - [context, oldBehaviorID](uint32_t persistentId) { - if (!context) { - LOG("Model to update behavior ID for is null. Cannot update ID."); - return; - } - LWOOBJID persistentIdBig = persistentId; - GeneralUtils::SetBit(persistentIdBig, eObjectBits::CHARACTER); - // This updates the behavior ID of the behavior should this be a new behavior - AMFArrayValue args; + if (!context) { + LOG("Model to update behavior ID for is null. Cannot update ID."); + return; + } + LWOOBJID persistentIdBig = ObjectIDManager::GetPersistentID(); + // This updates the behavior ID of the behavior should this be a new behavior + AMFArrayValue args; - args.Insert("behaviorID", std::to_string(persistentIdBig)); - args.Insert("objectID", std::to_string(context.modelComponent->GetParent()->GetObjectID())); + args.Insert("behaviorID", std::to_string(persistentIdBig)); + args.Insert("objectID", std::to_string(context.modelComponent->GetParent()->GetObjectID())); - GameMessages::SendUIMessageServerToSingleClient(context.modelOwner, context.modelOwner->GetSystemAddress(), "UpdateBehaviorID", args); - context.modelComponent->UpdatePendingBehaviorId(persistentIdBig, oldBehaviorID); + GameMessages::SendUIMessageServerToSingleClient(context.modelOwner, context.modelOwner->GetSystemAddress(), "UpdateBehaviorID", args); + context.modelComponent->UpdatePendingBehaviorId(persistentIdBig, oldBehaviorID); - ControlBehaviors::Instance().SendBehaviorListToClient(context); - }); + ControlBehaviors::Instance().SendBehaviorListToClient(context); } void ControlBehaviors::SendBehaviorListToClient(const ControlBehaviorContext& context) { diff --git a/dGame/dUtilities/ObjectIDManager.cpp b/dGame/dUtilities/ObjectIDManager.cpp index a30ede05..58f42444 100644 --- a/dGame/dUtilities/ObjectIDManager.cpp +++ b/dGame/dUtilities/ObjectIDManager.cpp @@ -5,47 +5,37 @@ #include "Database.h" #include "Logger.h" #include "Game.h" +#include "eObjectBits.h" - //! The persistent ID request -struct PersistentIDRequest { - PersistentIDRequest(const uint64_t& requestID, const std::function& callback) : requestID(requestID), callback(callback) {} - uint64_t requestID; - - std::function callback; -}; +// should the spawners from vanity also have the CLIENT flag? namespace { - std::vector Requests; //!< All outstanding persistent ID requests - uint64_t CurrentRequestID = 0; //!< The current request ID - uint32_t CurrentObjectID = uint32_t(1152921508165007067); //!< The current object ID - std::uniform_int_distribution Uni(10000000, INT32_MAX); + // Start the range in a way that it when first called it will fetch some new persistent IDs + std::optional CurrentRange = std::nullopt; + uint32_t CurrentObjectID = uint32_t(1152921508165007067); // The current object ID (this should really start at the highest current ID in the world, then increment from there) }; -//! Requests a persistent ID -void ObjectIDManager::RequestPersistentID(const std::function callback) { - const auto& request = Requests.emplace_back(++CurrentRequestID, callback); +uint64_t ObjectIDManager::GetPersistentID() { + if (!CurrentRange.has_value() || CurrentRange->minID > CurrentRange->maxID) { + CurrentRange = Database::Get()->GetPersistentIdRange(); + // We're getting close to being out of IDs in this range, log a warning + const auto WARNING_RANGE = 70368744100000ULL; + if (CurrentRange->minID >= 70368744100000ULL) { + LOG("WARNING: Your server is running low on persistent IDs, please consider an ID squash in the near future."); + } - MasterPackets::SendPersistentIDRequest(Game::server, request.requestID); + LOG("Reserved object ID range: %llu - %llu", CurrentRange->minID, CurrentRange->maxID); + } + + const auto usedID = CurrentRange->minID++; + auto toReturn = usedID; + // Any IDs gotten from persistent IDs use the CHARACTER bit + GeneralUtils::SetBit(toReturn, eObjectBits::CHARACTER); + LOG("Using ID: %llu:%llu", toReturn, usedID); + return toReturn; } -//! Handles a persistent ID response -void ObjectIDManager::HandleRequestPersistentIDResponse(const uint64_t requestID, const uint32_t persistentID) { - auto it = std::find_if(Requests.begin(), Requests.end(), [requestID](const PersistentIDRequest& request) { - return request.requestID == requestID; - }); - - if (it == Requests.end()) return; - - it->callback(persistentID); - Requests.erase(it); -} - -//! Handles cases where we have to get a unique object ID synchronously -uint32_t ObjectIDManager::GenerateRandomObjectID() { - return Uni(Game::randomEngine); -} - -//! Generates an object ID server-sided (used for regular entities like smashables) +// Generates an object ID server-sided (used for regular entities like smashables) uint32_t ObjectIDManager::GenerateObjectID() { return ++CurrentObjectID; } diff --git a/dGame/dUtilities/ObjectIDManager.h b/dGame/dUtilities/ObjectIDManager.h index 7650e4cc..4e2f245f 100644 --- a/dGame/dUtilities/ObjectIDManager.h +++ b/dGame/dUtilities/ObjectIDManager.h @@ -5,36 +5,25 @@ #include #include -/*! - \file ObjectIDManager.h - \brief A manager for handling object ID generation +/** + * There are 2 types of IDs: + * Persistent IDs - These are used for anything that needs to be persist between worlds. + * Ephemeral IDs - These are used for any objects that only need to be unique for this world session. */ -//! The Object ID Manager namespace ObjectIDManager { - //! Requests a persistent ID - /*! - \param callback The callback function + + /** + * @brief Returns a Persistent ID with the CHARACTER bit set. + * + * @return uint64_t A unique persistent ID with the CHARACTER bit set. */ - void RequestPersistentID(const std::function callback); + uint64_t GetPersistentID(); - - //! Handles a persistent ID response - /*! - \param requestID The request ID - \param persistentID The persistent ID - */ - void HandleRequestPersistentIDResponse(const uint64_t requestID, const uint32_t persistentID); - - //! Generates an object ID server-sided - /*! - \return A generated object ID + /** + * @brief Generates an ephemeral object ID for non-persistent objects. + * + * @return uint32_t */ uint32_t GenerateObjectID(); - - //! Generates a random object ID server-sided - /*! - \return A generated object ID - */ - uint32_t GenerateRandomObjectID(); }; diff --git a/dMasterServer/CMakeLists.txt b/dMasterServer/CMakeLists.txt index 2e2b4dd9..ec313ab0 100644 --- a/dMasterServer/CMakeLists.txt +++ b/dMasterServer/CMakeLists.txt @@ -1,6 +1,5 @@ set(DMASTERSERVER_SOURCES "InstanceManager.cpp" - "PersistentIDManager.cpp" "Start.cpp" ) diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 34d44ea8..32a2cb56 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -35,7 +35,6 @@ #include "Game.h" #include "InstanceManager.h" #include "MasterPackets.h" -#include "PersistentIDManager.h" #include "FdbToSqlite.h" #include "BitStreamUtils.h" #include "Start.h" @@ -360,7 +359,6 @@ int main(int argc, char** argv) { Database::Get()->SetMasterInfo(info); //Create additional objects here: - PersistentIDManager::Initialize(); Game::im = new InstanceManager(Game::server->GetIP()); //Get CDClient initial information @@ -534,17 +532,6 @@ void HandlePacket(Packet* packet) { if (static_cast(packet->data[1]) == ServiceType::MASTER) { switch (static_cast(packet->data[3])) { - case MessageType::Master::REQUEST_PERSISTENT_ID: { - LOG("A persistent ID req"); - RakNet::BitStream inStream(packet->data, packet->length, false); - uint64_t header = inStream.Read(header); - uint64_t requestID = 0; - inStream.Read(requestID); - - uint32_t objID = PersistentIDManager::GeneratePersistentID(); - MasterPackets::SendPersistentIDResponse(Game::server, packet->systemAddress, requestID, objID); - break; - } case MessageType::Master::REQUEST_ZONE_TRANSFER: { LOG("Received zone transfer req"); @@ -882,9 +869,6 @@ int ShutdownSequence(int32_t signal) { LOG("Triggered master shutdown"); } - PersistentIDManager::SaveToDatabase(); - LOG("Saved ObjectIDTracker to DB"); - // A server might not be finished spinning up yet, remove all of those here. for (const auto& instance : Game::im->GetInstances()) { if (!instance) continue; diff --git a/dMasterServer/PersistentIDManager.cpp b/dMasterServer/PersistentIDManager.cpp deleted file mode 100644 index 1b3a1c0b..00000000 --- a/dMasterServer/PersistentIDManager.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "PersistentIDManager.h" - -// Custom Classes -#include "Database.h" -#include "Logger.h" -#include "Game.h" - -namespace { - uint32_t CurrentPersistentID = 1; //!< The highest current persistent ID in use -}; - -//! Initializes the manager -void PersistentIDManager::Initialize() { - try { - auto lastObjectId = Database::Get()->GetCurrentPersistentId(); - - if (!lastObjectId) { - Database::Get()->InsertDefaultPersistentId(); - } else { - CurrentPersistentID = lastObjectId.value(); - } - - if (CurrentPersistentID <= 0) { - LOG("Invalid persistent object ID in database. Aborting to prevent bad id generation."); - throw std::runtime_error("Invalid persistent object ID in database. Aborting to prevent bad id generation."); - } - } catch (std::exception& e) { - LOG("Unable to fetch max persistent object ID in use. This will cause issues. Aborting to prevent collisions."); - LOG("Error: %s", e.what()); - throw e; - } -} - -//! Generates a new persistent ID -uint32_t PersistentIDManager::GeneratePersistentID() { - uint32_t toReturn = ++CurrentPersistentID; - - SaveToDatabase(); - - return toReturn; -} - -void PersistentIDManager::SaveToDatabase() { - Database::Get()->UpdatePersistentId(CurrentPersistentID); -} diff --git a/dMasterServer/PersistentIDManager.h b/dMasterServer/PersistentIDManager.h deleted file mode 100644 index 916ee33a..00000000 --- a/dMasterServer/PersistentIDManager.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -// C++ -#include - -/*! - \file PersistentIDManager.h - \brief A manager that handles requests for object IDs - */ - - //! The Object ID Manager -namespace PersistentIDManager { - //! Initializes the manager - void Initialize(); - - //! Generates a new persistent ID - /*! - \return The new persistent ID - */ - uint32_t GeneratePersistentID(); - - void SaveToDatabase(); -}; diff --git a/dNet/MasterPackets.cpp b/dNet/MasterPackets.cpp index 36a23f4f..aac49929 100644 --- a/dNet/MasterPackets.cpp +++ b/dNet/MasterPackets.cpp @@ -8,23 +8,6 @@ #include -void MasterPackets::SendPersistentIDRequest(dServer* server, uint64_t requestID) { - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::REQUEST_PERSISTENT_ID); - bitStream.Write(requestID); - server->SendToMaster(bitStream); -} - -void MasterPackets::SendPersistentIDResponse(dServer* server, const SystemAddress& sysAddr, uint64_t requestID, uint32_t objID) { - RakNet::BitStream bitStream; - BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::REQUEST_PERSISTENT_ID_RESPONSE); - - bitStream.Write(requestID); - bitStream.Write(objID); - - server->Send(bitStream, sysAddr, false); -} - void MasterPackets::SendZoneTransferRequest(dServer* server, uint64_t requestID, bool mythranShift, uint32_t zoneID, uint32_t cloneID) { RakNet::BitStream bitStream; BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::REQUEST_ZONE_TRANSFER); diff --git a/dNet/MasterPackets.h b/dNet/MasterPackets.h index 93fd158e..68f184eb 100644 --- a/dNet/MasterPackets.h +++ b/dNet/MasterPackets.h @@ -8,9 +8,6 @@ class dServer; namespace MasterPackets { - void SendPersistentIDRequest(dServer* server, uint64_t requestID); //Called from the World server - void SendPersistentIDResponse(dServer* server, const SystemAddress& sysAddr, uint64_t requestID, uint32_t objID); - void SendZoneTransferRequest(dServer* server, uint64_t requestID, bool mythranShift, uint32_t zoneID, uint32_t cloneID); void SendZoneTransferResponse(dServer* server, const SystemAddress& sysAddr, uint64_t requestID, bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, const std::string& serverIP, uint32_t serverPort); @@ -22,8 +19,6 @@ namespace MasterPackets { void SendZoneRequestPrivate(dServer* server, uint64_t requestID, bool mythranShift, const std::string& password); void SendWorldReady(dServer* server, LWOMAPID zoneId, LWOINSTANCEID instanceId); - - void HandleSetSessionKey(Packet* packet); } #endif // MASTERPACKETS_H diff --git a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp index d291be8b..b57acd58 100644 --- a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp +++ b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp @@ -8,7 +8,6 @@ #include "CharacterComponent.h" #include "SimplePhysicsComponent.h" #include "MovementAIComponent.h" -#include "ObjectIDManager.h" #include "MissionComponent.h" #include "Loot.h" #include "InventoryComponent.h" diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 3a22bbf0..964a4bb9 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -35,7 +35,6 @@ #include "CDClientManager.h" #include "CDClientDatabase.h" #include "GeneralUtils.h" -#include "ObjectIDManager.h" #include "ZoneInstanceManager.h" #include "dChatFilter.h" #include "ClientPackets.h" @@ -676,15 +675,6 @@ void HandleMasterPacket(Packet* packet) { if (packet->length < 2) return; if (static_cast(packet->data[1]) != ServiceType::MASTER || packet->length < 4) return; switch (static_cast(packet->data[3])) { - case MessageType::Master::REQUEST_PERSISTENT_ID_RESPONSE: { - CINSTREAM_SKIP_HEADER; - uint64_t requestID; - inStream.Read(requestID); - uint32_t objectID; - inStream.Read(objectID); - ObjectIDManager::HandleRequestPersistentIDResponse(requestID, objectID); - break; - } case MessageType::Master::SESSION_KEY_RESPONSE: { //Read our session key and to which user it belongs: @@ -1047,82 +1037,90 @@ void HandlePacket(Packet* packet) { auto version = levelComponent->GetCharacterVersion(); LOG("Updating character from version %s", StringifiedEnum::ToString(version).data()); - switch (version) { - case eCharacterVersion::RELEASE: - // TODO: Implement, super low priority - [[fallthrough]]; - case eCharacterVersion::LIVE: - LOG("Updating Character Flags"); - c->SetRetroactiveFlags(); - levelComponent->SetCharacterVersion(eCharacterVersion::PLAYER_FACTION_FLAGS); - [[fallthrough]]; - case eCharacterVersion::PLAYER_FACTION_FLAGS: - LOG("Updating Vault Size"); - player->RetroactiveVaultSize(); - levelComponent->SetCharacterVersion(eCharacterVersion::VAULT_SIZE); - [[fallthrough]]; - case eCharacterVersion::VAULT_SIZE: - LOG("Updaing Speedbase"); - levelComponent->SetRetroactiveBaseSpeed(); - levelComponent->SetCharacterVersion(eCharacterVersion::SPEED_BASE); - [[fallthrough]]; - case eCharacterVersion::SPEED_BASE: { - LOG("Removing lots from NJ Jay missions bugged at foss"); - // https://explorer.lu/missions/1789 - const auto* mission = missionComponent->GetMission(1789); - if (mission && mission->IsComplete()) { - inventoryComponent->RemoveItem(14474, 1, eInventoryType::ITEMS); - inventoryComponent->RemoveItem(14474, 1, eInventoryType::VAULT_ITEMS); - } - // https://explorer.lu/missions/1927 - mission = missionComponent->GetMission(1927); - if (mission && mission->IsComplete()) { - inventoryComponent->RemoveItem(14493, 1, eInventoryType::ITEMS); - inventoryComponent->RemoveItem(14493, 1, eInventoryType::VAULT_ITEMS); - } - levelComponent->SetCharacterVersion(eCharacterVersion::NJ_JAYMISSIONS); - [[fallthrough]]; - } - case eCharacterVersion::NJ_JAYMISSIONS: { - LOG("Fixing Nexus Force Explorer missions"); - auto missions = { 502 /* Pet Cove */, 593/* Nimbus Station */, 938/* Avant Gardens */, 284/* Gnarled Forest */, 754/* Forbidden Valley */ }; - bool complete = true; - for (auto missionID : missions) { - auto* mission = missionComponent->GetMission(missionID); - if (!mission || !mission->IsComplete()) { - complete = false; + if (version < eCharacterVersion::UP_TO_DATE) { + switch (version) { + case eCharacterVersion::RELEASE: + // TODO: Implement, super low priority + [[fallthrough]]; + case eCharacterVersion::LIVE: + LOG("Updating Character Flags"); + c->SetRetroactiveFlags(); + levelComponent->SetCharacterVersion(eCharacterVersion::PLAYER_FACTION_FLAGS); + [[fallthrough]]; + case eCharacterVersion::PLAYER_FACTION_FLAGS: + LOG("Updating Vault Size"); + player->RetroactiveVaultSize(); + levelComponent->SetCharacterVersion(eCharacterVersion::VAULT_SIZE); + [[fallthrough]]; + case eCharacterVersion::VAULT_SIZE: + LOG("Updaing Speedbase"); + levelComponent->SetRetroactiveBaseSpeed(); + levelComponent->SetCharacterVersion(eCharacterVersion::SPEED_BASE); + [[fallthrough]]; + case eCharacterVersion::SPEED_BASE: { + LOG("Removing lots from NJ Jay missions bugged at foss"); + // https://explorer.lu/missions/1789 + const auto* mission = missionComponent->GetMission(1789); + if (mission && mission->IsComplete()) { + inventoryComponent->RemoveItem(14474, 1, eInventoryType::ITEMS); + inventoryComponent->RemoveItem(14474, 1, eInventoryType::VAULT_ITEMS); } - } - - if (complete) missionComponent->CompleteMission(937 /* Nexus Force explorer */); - levelComponent->SetCharacterVersion(eCharacterVersion::NEXUS_FORCE_EXPLORER); - [[fallthrough]]; - } - case eCharacterVersion::NEXUS_FORCE_EXPLORER: { - LOG("Fixing pet IDs"); - - // First copy the original ids - const auto pets = inventoryComponent->GetPetsMut(); - - // Then clear the pets so we can re-add them with the updated IDs - auto& invPets = inventoryComponent->GetPetsMut(); - invPets.clear(); - for (auto& [id, databasePet] : pets) { - const auto originalID = id; - const auto newId = GeneralUtils::ClearBit(id, 32); // Persistent bit that didn't exist - LOG("New ID %llu", newId); - auto* item = inventoryComponent->FindItemBySubKey(originalID); - if (item) { - LOG("item subkey %llu", item->GetSubKey()); - item->SetSubKey(newId); - invPets[newId] = databasePet; + // https://explorer.lu/missions/1927 + mission = missionComponent->GetMission(1927); + if (mission && mission->IsComplete()) { + inventoryComponent->RemoveItem(14493, 1, eInventoryType::ITEMS); + inventoryComponent->RemoveItem(14493, 1, eInventoryType::VAULT_ITEMS); } + levelComponent->SetCharacterVersion(eCharacterVersion::NJ_JAYMISSIONS); + [[fallthrough]]; + } + case eCharacterVersion::NJ_JAYMISSIONS: { + LOG("Fixing Nexus Force Explorer missions"); + auto missions = { 502 /* Pet Cove */, 593/* Nimbus Station */, 938/* Avant Gardens */, 284/* Gnarled Forest */, 754/* Forbidden Valley */ }; + bool complete = true; + for (auto missionID : missions) { + auto* mission = missionComponent->GetMission(missionID); + if (!mission || !mission->IsComplete()) { + complete = false; + } + } + + if (complete) missionComponent->CompleteMission(937 /* Nexus Force explorer */); + levelComponent->SetCharacterVersion(eCharacterVersion::NEXUS_FORCE_EXPLORER); + [[fallthrough]]; + } + case eCharacterVersion::NEXUS_FORCE_EXPLORER: { + LOG("Fixing pet IDs"); + + // First copy the original ids + const auto pets = inventoryComponent->GetPetsMut(); + + // Then clear the pets so we can re-add them with the updated IDs + auto& invPets = inventoryComponent->GetPetsMut(); + invPets.clear(); + for (auto& [id, databasePet] : pets) { + const auto originalID = id; + const auto newId = GeneralUtils::ClearBit(id, 32); // Persistent bit that didn't exist + LOG("New ID %llu", newId); + auto* item = inventoryComponent->FindItemBySubKey(originalID); + if (item) { + LOG("item subkey %llu", item->GetSubKey()); + item->SetSubKey(newId); + invPets[newId] = databasePet; + } + } + levelComponent->SetCharacterVersion(eCharacterVersion::PET_IDS); + [[fallthrough]]; + } + case eCharacterVersion::PET_IDS: { + LOG("Regenerating item ids"); + inventoryComponent->RegenerateItemIDs(); + levelComponent->SetCharacterVersion(eCharacterVersion::UP_TO_DATE); + [[fallthrough]]; + } + case eCharacterVersion::UP_TO_DATE: + break; } - levelComponent->SetCharacterVersion(eCharacterVersion::UP_TO_DATE); - [[fallthrough]]; - } - case eCharacterVersion::UP_TO_DATE: - break; } // Update the characters xml to ensure the update above is not only saved, but so the client picks up on the changes. diff --git a/migrations/dlu/mysql/26_update_property_contents_ids.sql b/migrations/dlu/mysql/26_update_property_contents_ids.sql new file mode 100644 index 00000000..7e36924e --- /dev/null +++ b/migrations/dlu/mysql/26_update_property_contents_ids.sql @@ -0,0 +1 @@ +UPDATE `properties_contents` SET `id` = `id` | 0x1000000000000000; diff --git a/migrations/dlu/sqlite/9_update_property_contents_ids.sql b/migrations/dlu/sqlite/9_update_property_contents_ids.sql new file mode 100644 index 00000000..7e36924e --- /dev/null +++ b/migrations/dlu/sqlite/9_update_property_contents_ids.sql @@ -0,0 +1 @@ +UPDATE `properties_contents` SET `id` = `id` | 0x1000000000000000; From 670cb124c0c7e070c1cdc8810f0e8548cfa8ab19 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Tue, 30 Sep 2025 21:33:04 -0700 Subject: [PATCH 14/39] Initialize m_ActivityInfo with default constructor (#1889) --- dGame/dComponents/ActivityComponent.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dGame/dComponents/ActivityComponent.h b/dGame/dComponents/ActivityComponent.h index 925b3a8d..08ceb6da 100644 --- a/dGame/dComponents/ActivityComponent.h +++ b/dGame/dComponents/ActivityComponent.h @@ -353,7 +353,7 @@ private: /** * The database information for this activity */ - CDActivities m_ActivityInfo; + CDActivities m_ActivityInfo{}; /** * All the active instances of this activity From 205c190c614863e4d0306c549ede639abb3d3927 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Wed, 1 Oct 2025 05:55:51 -0700 Subject: [PATCH 15/39] fix: proxy items not equipping on login (#1892) tested that items now equip on login --- dWorldServer/WorldServer.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 964a4bb9..110b7573 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -1126,6 +1126,13 @@ void HandlePacket(Packet* packet) { // Update the characters xml to ensure the update above is not only saved, but so the client picks up on the changes. c->SaveXMLToDatabase(); + // Fix the destroyable component + auto* destroyableComponent = player->GetComponent(); + + if (destroyableComponent != nullptr) { + destroyableComponent->FixStats(); + } + WorldPackets::SendCreateCharacter(packet->systemAddress, characterComponent->GetReputation(), player->GetObjectID(), c->GetXMLData(), username, c->GetGMLevel(), c->GetPropertyCloneID()); WorldPackets::SendServerState(packet->systemAddress); @@ -1143,13 +1150,6 @@ void HandlePacket(Packet* packet) { player->GetCharacter()->SetTargetScene(""); - // Fix the destroyable component - auto* destroyableComponent = player->GetComponent(); - - if (destroyableComponent != nullptr) { - destroyableComponent->FixStats(); - } - //Tell the player to generate BBB models, if any: if (g_CloneID != 0) { const auto& worldId = Game::zoneManager->GetZone()->GetZoneID(); From 502c965d9735ec579239a85502fa040794d43924 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Wed, 1 Oct 2025 12:21:25 -0700 Subject: [PATCH 16/39] feat: script debug info (#1891) --- dGame/dComponents/ScriptComponent.cpp | 25 ++++++++++++++++++++----- dGame/dComponents/ScriptComponent.h | 7 +++++-- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/dGame/dComponents/ScriptComponent.cpp b/dGame/dComponents/ScriptComponent.cpp index d6bff5b5..b3be186e 100644 --- a/dGame/dComponents/ScriptComponent.cpp +++ b/dGame/dComponents/ScriptComponent.cpp @@ -5,16 +5,18 @@ #include "Entity.h" #include "ScriptComponent.h" +#include "GameMessages.h" +#include "Amf3.h" + +ScriptComponent::ScriptComponent(Entity* parent, const std::string& scriptName, bool serialized, bool client) : Component(parent) { + using namespace GameMessages; -ScriptComponent::ScriptComponent(Entity* parent, std::string scriptName, bool serialized, bool client) : Component(parent) { m_Serialized = serialized; m_Client = client; + m_ScriptName = scriptName; SetScript(scriptName); -} - -ScriptComponent::~ScriptComponent() { - + RegisterMsg(this, &ScriptComponent::OnGetObjectReportInfo); } void ScriptComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { @@ -50,3 +52,16 @@ void ScriptComponent::SetScript(const std::string& scriptName) { // and they may also be used by other script components so DON'T delete them. m_Script = CppScripts::GetScript(m_Parent, scriptName); } + +bool ScriptComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) { + auto& infoMsg = static_cast(msg); + + auto& scriptInfo = infoMsg.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()) { + networkSettings.PushDebug(GeneralUtils::UTF16ToWTF8(setting->GetKey())) = setting->GetValueAsString(); + } + + return true; +} diff --git a/dGame/dComponents/ScriptComponent.h b/dGame/dComponents/ScriptComponent.h index adc7cc8b..e73f7ef1 100644 --- a/dGame/dComponents/ScriptComponent.h +++ b/dGame/dComponents/ScriptComponent.h @@ -21,8 +21,7 @@ class ScriptComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SCRIPT; - ScriptComponent(Entity* parent, std::string scriptName, bool serialized, bool client = false); - ~ScriptComponent() override; + ScriptComponent(Entity* parent, const std::string& scriptName, bool serialized, bool client = false); void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; @@ -43,6 +42,8 @@ public: * @param scriptName the name of the script to find */ void SetScript(const std::string& scriptName); + + bool OnGetObjectReportInfo(GameMessages::GameMsg& msg); private: @@ -60,6 +61,8 @@ private: * Whether or not this script is a client script */ bool m_Client; + + std::string m_ScriptName; }; #endif // SCRIPTCOMPONENT_H From 25418fd8b20d76b7c33adff1dd4eba93dd8cedd4 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Wed, 1 Oct 2025 18:48:08 -0700 Subject: [PATCH 17/39] fix: exploding asset bugs (#1890) --- .../02_server/Map/General/ExplodingAsset.cpp | 69 +++++++++++-------- .../02_server/Map/General/ExplodingAsset.h | 7 +- 2 files changed, 44 insertions(+), 32 deletions(-) diff --git a/dScripts/02_server/Map/General/ExplodingAsset.cpp b/dScripts/02_server/Map/General/ExplodingAsset.cpp index ce96014b..788efa11 100644 --- a/dScripts/02_server/Map/General/ExplodingAsset.cpp +++ b/dScripts/02_server/Map/General/ExplodingAsset.cpp @@ -7,6 +7,8 @@ #include "CDClientManager.h" #include "CDObjectSkillsTable.h" #include "RenderComponent.h" +#include "TeamManager.h" +#include "ProximityMonitorComponent.h" //TODO: this has to be updated so that you only get killed if you're in a certain radius. //And so that all entities in a certain radius are killed, not just the attacker. @@ -17,22 +19,40 @@ void ExplodingAsset::OnStartup(Entity* self) { self->SetProximityRadius(10.0f, "crateHitters"); } +void ExplodingAsset::ProgressPlayerMissions(Entity& self, Entity& player) { + const auto missionID = self.GetVar(u"missionID"); + auto achievementIDs = self.GetVarAsString(u"achieveID"); + auto* const missionComponent = player.GetComponent(); + if (missionComponent) { + if (missionID != 0) { + missionComponent->ForceProgressValue(missionID, + static_cast(eMissionTaskType::SCRIPT), + self.GetLOT(), false); + } + + if (!achievementIDs.empty()) { + for (const auto& achievementID : GeneralUtils::SplitString(achievementIDs, u'_')) { + const auto achievementIDInt = GeneralUtils::TryParse(achievementID); + if (!achievementIDInt) continue; + missionComponent->ForceProgressValue(achievementIDInt.value(), + static_cast(eMissionTaskType::SCRIPT), + self.GetLOT()); + } + } + } +} + void ExplodingAsset::OnHit(Entity* self, Entity* attacker) { - std::vector entities; - entities.push_back(attacker); + const auto* const proximityComponent = self->GetComponent(); + if (!proximityComponent) return; if (!self->GetBoolean(u"bIsHit")) { - for (Entity* en : entities) { - if (en->GetObjectID() == attacker->GetObjectID()) { - if (Vector3::DistanceSquared(en->GetPosition(), self->GetPosition()) > 10 * 10) continue; + for (const auto objID : proximityComponent->GetProximityObjects("crateHitters")) { + auto* const entity = Game::entityManager->GetEntity(objID); + if (!entity) continue; - auto* destroyable = en->GetComponent(); - if (destroyable == nullptr) { - continue; - } - - destroyable->Smash(attacker->GetObjectID()); - } + auto* const destroyable = entity->GetComponent(); + if (destroyable) destroyable->Smash(attacker->GetObjectID()); } } @@ -48,26 +68,17 @@ void ExplodingAsset::OnHit(Entity* self, Entity* attacker) { // Technically supposed to get first skill in the skill component but only 1 object in the live game used this. skillComponent->CalculateBehavior(147, 4721, LWOOBJID_EMPTY, true); } - - const auto missionID = self->GetVar(u"missionID"); - auto achievementIDs = self->GetVar(u"achieveID"); - + const auto* const team = TeamManager::Instance()->GetTeam(attacker->GetObjectID()); // Progress all scripted missions related to this asset - auto* missionComponent = attacker->GetComponent(); - if (missionComponent != nullptr) { - if (missionID != 0) { - missionComponent->ForceProgressValue(missionID, - static_cast(eMissionTaskType::SCRIPT), - self->GetLOT(), false); - } - - if (!achievementIDs.empty()) { - for (const auto& achievementID : GeneralUtils::SplitString(achievementIDs, u'_')) { - missionComponent->ForceProgressValue(std::stoi(GeneralUtils::UTF16ToWTF8(achievementID)), - static_cast(eMissionTaskType::SCRIPT), - self->GetLOT()); + if (team) { + for (const auto& member : team->members) { + auto* const memberEntity = Game::entityManager->GetEntity(member); + if (memberEntity) { + ProgressPlayerMissions(*self, *memberEntity); } } + } else { + ProgressPlayerMissions(*self, *attacker); } } diff --git a/dScripts/02_server/Map/General/ExplodingAsset.h b/dScripts/02_server/Map/General/ExplodingAsset.h index 9b97ecc7..ce495672 100644 --- a/dScripts/02_server/Map/General/ExplodingAsset.h +++ b/dScripts/02_server/Map/General/ExplodingAsset.h @@ -4,7 +4,8 @@ class ExplodingAsset : public CppScripts::Script { public: - void OnStartup(Entity* self); - void OnHit(Entity* self, Entity* attacker); - void OnProximityUpdate(Entity* self, Entity* entering, std::string name, std::string status); + void OnStartup(Entity* self) override; + void OnHit(Entity* self, Entity* attacker) override; + void OnProximityUpdate(Entity* self, Entity* entering, std::string name, std::string status) override; + void ProgressPlayerMissions(Entity& self, Entity& player); }; From e8c0b3e6dad8ff90caee5d16e1820da824543931 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Fri, 3 Oct 2025 18:57:42 -0700 Subject: [PATCH 18/39] feat: Add component ID to root component object (#1893) --- dGame/Entity.cpp | 236 ++++++++++-------- .../AchievementVendorComponent.cpp | 2 +- .../dComponents/AchievementVendorComponent.h | 2 +- dGame/dComponents/ActivityComponent.cpp | 6 +- dGame/dComponents/BaseCombatAIComponent.cpp | 8 +- dGame/dComponents/BaseCombatAIComponent.h | 2 +- dGame/dComponents/BouncerComponent.cpp | 2 +- dGame/dComponents/BouncerComponent.h | 2 +- dGame/dComponents/BuffComponent.cpp | 2 +- dGame/dComponents/BuffComponent.h | 2 +- dGame/dComponents/BuildBorderComponent.cpp | 2 +- dGame/dComponents/BuildBorderComponent.h | 2 +- dGame/dComponents/CharacterComponent.cpp | 2 +- dGame/dComponents/CharacterComponent.h | 2 +- dGame/dComponents/CollectibleComponent.h | 2 +- dGame/dComponents/Component.h | 11 +- .../ControllablePhysicsComponent.cpp | 2 +- .../ControllablePhysicsComponent.h | 2 +- dGame/dComponents/DestroyableComponent.cpp | 2 +- dGame/dComponents/DestroyableComponent.h | 2 +- dGame/dComponents/DonationVendorComponent.cpp | 2 +- dGame/dComponents/DonationVendorComponent.h | 2 +- dGame/dComponents/GhostComponent.cpp | 2 +- dGame/dComponents/GhostComponent.h | 2 +- .../HavokVehiclePhysicsComponent.cpp | 2 +- .../HavokVehiclePhysicsComponent.h | 2 +- dGame/dComponents/InventoryComponent.cpp | 2 +- dGame/dComponents/InventoryComponent.h | 2 +- dGame/dComponents/ItemComponent.h | 2 +- dGame/dComponents/LUPExhibitComponent.h | 2 +- .../dComponents/LevelProgressionComponent.cpp | 2 +- dGame/dComponents/LevelProgressionComponent.h | 2 +- dGame/dComponents/MiniGameControlComponent.h | 2 +- dGame/dComponents/MinigameComponent.cpp | 5 - dGame/dComponents/MissionComponent.cpp | 2 +- dGame/dComponents/MissionComponent.h | 2 +- dGame/dComponents/MissionOfferComponent.cpp | 14 +- dGame/dComponents/MissionOfferComponent.h | 2 +- dGame/dComponents/ModelComponent.cpp | 2 +- dGame/dComponents/ModelComponent.h | 2 +- dGame/dComponents/ModuleAssemblyComponent.cpp | 2 +- dGame/dComponents/ModuleAssemblyComponent.h | 2 +- dGame/dComponents/MovementAIComponent.cpp | 2 +- dGame/dComponents/MovementAIComponent.h | 2 +- dGame/dComponents/MovingPlatformComponent.cpp | 2 +- dGame/dComponents/MovingPlatformComponent.h | 2 +- .../MultiZoneEntranceComponent.cpp | 2 +- .../dComponents/MultiZoneEntranceComponent.h | 2 +- dGame/dComponents/PetComponent.cpp | 7 +- dGame/dComponents/PetComponent.h | 7 +- dGame/dComponents/PhantomPhysicsComponent.cpp | 2 +- dGame/dComponents/PhantomPhysicsComponent.h | 2 +- dGame/dComponents/PhysicsComponent.cpp | 4 +- dGame/dComponents/PhysicsComponent.h | 2 +- .../PlayerForcedMovementComponent.cpp | 2 +- .../PlayerForcedMovementComponent.h | 2 +- dGame/dComponents/PossessableComponent.cpp | 4 +- dGame/dComponents/PossessableComponent.h | 7 +- dGame/dComponents/PossessorComponent.cpp | 2 +- dGame/dComponents/PossessorComponent.h | 2 +- dGame/dComponents/PropertyComponent.h | 2 +- .../dComponents/PropertyEntranceComponent.cpp | 2 +- dGame/dComponents/PropertyEntranceComponent.h | 2 +- .../PropertyManagementComponent.cpp | 2 +- .../dComponents/PropertyManagementComponent.h | 2 +- dGame/dComponents/PropertyVendorComponent.cpp | 2 +- dGame/dComponents/PropertyVendorComponent.h | 2 +- .../dComponents/ProximityMonitorComponent.cpp | 2 +- dGame/dComponents/ProximityMonitorComponent.h | 2 +- dGame/dComponents/QuickBuildComponent.cpp | 2 +- dGame/dComponents/QuickBuildComponent.h | 2 +- dGame/dComponents/RacingControlComponent.cpp | 4 +- dGame/dComponents/RacingControlComponent.h | 2 +- .../dComponents/RacingSoundTriggerComponent.h | 2 +- dGame/dComponents/RacingStatsComponent.h | 2 +- dGame/dComponents/RailActivatorComponent.cpp | 5 +- dGame/dComponents/RailActivatorComponent.h | 8 +- dGame/dComponents/RenderComponent.cpp | 6 +- dGame/dComponents/RenderComponent.h | 2 +- .../RigidbodyPhantomPhysicsComponent.cpp | 2 +- .../RigidbodyPhantomPhysicsComponent.h | 2 +- .../RocketLaunchpadControlComponent.cpp | 4 +- .../RocketLaunchpadControlComponent.h | 2 +- dGame/dComponents/ScriptComponent.cpp | 3 +- dGame/dComponents/ScriptComponent.h | 2 +- dGame/dComponents/ScriptedActivityComponent.h | 2 +- .../dComponents/ShootingGalleryComponent.cpp | 2 +- dGame/dComponents/ShootingGalleryComponent.h | 2 +- dGame/dComponents/SimplePhysicsComponent.cpp | 2 +- dGame/dComponents/SimplePhysicsComponent.h | 2 +- dGame/dComponents/SkillComponent.cpp | 2 +- dGame/dComponents/SkillComponent.h | 2 +- dGame/dComponents/SoundTriggerComponent.cpp | 2 +- dGame/dComponents/SoundTriggerComponent.h | 2 +- dGame/dComponents/SwitchComponent.cpp | 2 +- dGame/dComponents/SwitchComponent.h | 2 +- dGame/dComponents/TriggerComponent.cpp | 2 +- dGame/dComponents/TriggerComponent.h | 2 +- dGame/dComponents/VendorComponent.cpp | 2 +- dGame/dComponents/VendorComponent.h | 2 +- dScripts/02_server/Map/AM/WanderingVendor.cpp | 2 +- dScripts/NtFactionSpyServer.cpp | 2 +- dScripts/ai/AG/AgStromlingProperty.cpp | 2 +- .../ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp | 2 +- .../DestroyableComponentTests.cpp | 6 +- .../dComponentsTests/SavingTests.cpp | 4 +- 106 files changed, 263 insertions(+), 260 deletions(-) delete mode 100644 dGame/dComponents/MinigameComponent.cpp diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 543f324a..7a27c1b0 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -199,7 +199,7 @@ void Entity::Initialize() { const auto triggerInfo = GetVarAsString(u"trigger_id"); - if (!triggerInfo.empty()) AddComponent(triggerInfo); + if (!triggerInfo.empty()) AddComponent(-1, triggerInfo); /** * Setup groups @@ -234,11 +234,11 @@ void Entity::Initialize() { AddComponent(simplePhysicsComponentID); - AddComponent()->LoadBehaviors(); + AddComponent(-1)->LoadBehaviors(); - AddComponent(); + AddComponent(-1); - auto* destroyableComponent = AddComponent(); + auto* destroyableComponent = AddComponent(-1); destroyableComponent->SetHealth(1); destroyableComponent->SetMaxHealth(1.0f); destroyableComponent->SetFaction(-1, true); @@ -254,37 +254,42 @@ void Entity::Initialize() { */ if (m_Character && m_Character->GetParentUser()) { - AddComponent()->LoadFromXml(m_Character->GetXMLDoc()); + AddComponent(-1)->LoadFromXml(m_Character->GetXMLDoc()); } - const uint32_t petComponentId = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PET); - if (petComponentId > 0) { - AddComponent(petComponentId); + const auto petComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PET); + if (petComponentID > 0) { + AddComponent(petComponentID); } - if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MINI_GAME_CONTROL) > 0) { - AddComponent(); + const auto minigameControlID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MINI_GAME_CONTROL); + if (minigameControlID > 0) { + AddComponent(minigameControlID); } - const uint32_t possessableComponentId = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::POSSESSABLE); - if (possessableComponentId > 0) { - AddComponent(possessableComponentId); + const auto possessableComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::POSSESSABLE); + if (possessableComponentID > 0) { + AddComponent(possessableComponentID); } - if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MODULE_ASSEMBLY) > 0) { - AddComponent(); + const auto moduleAssemblyID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MODULE_ASSEMBLY); + if (moduleAssemblyID > 0) { + AddComponent(moduleAssemblyID); } - if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RACING_STATS) > 0) { - AddComponent(); + const auto racingStatsID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RACING_STATS); + if (racingStatsID > 0) { + AddComponent(racingStatsID); } - if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::LUP_EXHIBIT, -1) >= 0) { - AddComponent(); + const auto lupExhibitID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::LUP_EXHIBIT, -1); + if (lupExhibitID >= 0) { + AddComponent(lupExhibitID); } - if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RACING_CONTROL) > 0) { - AddComponent(); + const auto racingControlID =compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RACING_CONTROL); + if (racingControlID > 0) { + AddComponent(racingControlID); } const auto propertyEntranceComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PROPERTY_ENTRANCE); @@ -292,7 +297,7 @@ void Entity::Initialize() { AddComponent(propertyEntranceComponentID); } - const int32_t controllablePhysicsComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::CONTROLLABLE_PHYSICS); + const auto controllablePhysicsComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::CONTROLLABLE_PHYSICS); if (controllablePhysicsComponentID > 0) { auto* controllablePhysics = AddComponent(controllablePhysicsComponentID); @@ -337,46 +342,48 @@ void Entity::Initialize() { AddComponent(simplePhysicsComponentID); } - const int32_t rigidBodyPhantomPhysicsComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS); + const auto rigidBodyPhantomPhysicsComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS); if (rigidBodyPhantomPhysicsComponentID > 0) { AddComponent(rigidBodyPhantomPhysicsComponentID); } - const int32_t phantomPhysicsComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PHANTOM_PHYSICS); + const auto phantomPhysicsComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PHANTOM_PHYSICS); if (markedAsPhantom || phantomPhysicsComponentID > 0) { AddComponent(phantomPhysicsComponentID)->SetPhysicsEffectActive(false); } - const int32_t havokVehiclePhysicsComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::HAVOK_VEHICLE_PHYSICS); + const auto havokVehiclePhysicsComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::HAVOK_VEHICLE_PHYSICS); if (havokVehiclePhysicsComponentID > 0) { auto* havokVehiclePhysicsComponent = AddComponent(havokVehiclePhysicsComponentID); havokVehiclePhysicsComponent->SetPosition(m_DefaultPosition); havokVehiclePhysicsComponent->SetRotation(m_DefaultRotation); } - if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::SOUND_TRIGGER, -1) != -1) { - AddComponent(); - } else if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RACING_SOUND_TRIGGER, -1) != -1) { - AddComponent(); + const auto soundTriggerID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::SOUND_TRIGGER, -1); + const auto racingSoundTriggerID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RACING_SOUND_TRIGGER, -1); + if (soundTriggerID > -1) { + AddComponent(soundTriggerID); + } else if (racingSoundTriggerID > -1) { + AddComponent(racingSoundTriggerID); } - if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::BUFF) > 0) { - AddComponent(); + const auto buffComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::BUFF); + if (buffComponentID > 0) { + AddComponent(buffComponentID); } - const int collectibleComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::COLLECTIBLE); + const auto collectibleComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::COLLECTIBLE); if (collectibleComponentID > 0) { - AddComponent(GetVarAs(u"collectible_id")); + AddComponent(collectibleComponentID, GetVarAs(u"collectible_id")); } /** * Multiple components require the destructible component. */ - const int buffComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::BUFF); - const int quickBuildComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::QUICK_BUILD); + const auto quickBuildComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::QUICK_BUILD); - int componentID = -1; + int32_t componentID = -1; if (collectibleComponentID > 0) componentID = collectibleComponentID; if (quickBuildComponentID > 0) componentID = quickBuildComponentID; if (buffComponentID > 0) componentID = buffComponentID; @@ -384,7 +391,7 @@ void Entity::Initialize() { bool isSmashable = GetVarAs(u"is_smashable") != 0; if (buffComponentID > 0 || collectibleComponentID > 0 || isSmashable) { - DestroyableComponent* comp = AddComponent(); + DestroyableComponent* comp = AddComponent(componentID); auto* const destCompTable = CDClientManager::GetTable(); std::vector destCompData = destCompTable->Query([componentID](const CDDestructibleComponent& entry) { return (entry.id == componentID); }); @@ -473,27 +480,30 @@ void Entity::Initialize() { } } - if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::CHARACTER) > 0 || m_Character) { + const auto characterID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::CHARACTER); + if (characterID > 0 || m_Character) { // Character Component always has a possessor, level, and forced movement components - AddComponent(); + AddComponent(characterID); // load in the xml for the level - AddComponent()->LoadFromXml(m_Character->GetXMLDoc()); + AddComponent(characterID)->LoadFromXml(m_Character->GetXMLDoc()); - AddComponent(); + AddComponent(characterID); auto& systemAddress = m_Character->GetParentUser() ? m_Character->GetParentUser()->GetSystemAddress() : UNASSIGNED_SYSTEM_ADDRESS; - AddComponent(m_Character, systemAddress)->LoadFromXml(m_Character->GetXMLDoc()); + AddComponent(characterID, m_Character, systemAddress)->LoadFromXml(m_Character->GetXMLDoc()); - AddComponent(); + AddComponent(characterID); } - if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::INVENTORY) > 0 || m_Character) { - AddComponent(); + const auto inventoryID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::INVENTORY); + if (inventoryID > 0 || m_Character) { + AddComponent(inventoryID); } // if this component exists, then we initialize it. it's value is always 0 - if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MULTI_ZONE_ENTRANCE, -1) != -1) { - AddComponent(); + const auto multiZoneEntranceID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MULTI_ZONE_ENTRANCE, -1); + if (multiZoneEntranceID > -1) { + AddComponent(multiZoneEntranceID); } /** @@ -545,7 +555,7 @@ void Entity::Initialize() { } if (!scriptName.empty() || client || m_Character || scriptComponentID >= 0) { - AddComponent(scriptName, true, client && scriptName.empty()); + AddComponent(scriptComponentID, scriptName, true, client && scriptName.empty()); } // ZoneControl script @@ -554,26 +564,27 @@ void Entity::Initialize() { const CDZoneTable* const zoneData = CDZoneTableTable::Query(zoneID.GetMapID()); if (zoneData != nullptr) { - int zoneScriptID = zoneData->scriptID; + const int32_t zoneScriptID = zoneData->scriptID; CDScriptComponent zoneScriptData = scriptCompTable->GetByID(zoneScriptID); - AddComponent(zoneScriptData.script_name, true); + AddComponent(zoneScriptID, zoneScriptData.script_name, true, false); } } - if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::SKILL, -1) != -1 || m_Character) { - AddComponent(); + const auto skillID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::SKILL, -1); + if (skillID > -1 || m_Character) { + AddComponent(skillID); } - const auto combatAiId = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::BASE_COMBAT_AI); - if (combatAiId > 0) { - AddComponent(combatAiId); + const auto combatAiID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::BASE_COMBAT_AI); + if (combatAiID > 0) { + AddComponent(combatAiID); } - if (const int componentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::QUICK_BUILD) > 0) { - auto* const quickBuildComponent = AddComponent(); + if (quickBuildComponentID > 0) { + auto* const quickBuildComponent = AddComponent(quickBuildComponentID); CDRebuildComponentTable* const rebCompTable = CDClientManager::GetTable(); - const std::vector rebCompData = rebCompTable->Query([=](CDRebuildComponent entry) { return (entry.id == quickBuildComponentID); }); + const std::vector rebCompData = rebCompTable->Query([quickBuildComponentID](CDRebuildComponent entry) { return (entry.id == quickBuildComponentID); }); if (!rebCompData.empty()) { quickBuildComponent->SetResetTime(rebCompData[0].reset_time); @@ -618,53 +629,63 @@ void Entity::Initialize() { } } - if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::SWITCH, -1) != -1) { - AddComponent(); + const auto switchID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::SWITCH, -1); + if (switchID > -1) { + AddComponent(switchID); } - if ((compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::VENDOR) > 0)) { - AddComponent(); - } else if ((compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::DONATION_VENDOR, -1) != -1)) { - AddComponent(); - } else if ((compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::ACHIEVEMENT_VENDOR, -1) != -1)) { - AddComponent(); + const auto vendorID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::VENDOR); + const auto donationVendorID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::DONATION_VENDOR, -1); + const auto achievementVendorID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::ACHIEVEMENT_VENDOR, -1); + if (vendorID > 0) { + AddComponent(vendorID); + } else if (donationVendorID > -1) { + AddComponent(donationVendorID); + } else if (achievementVendorID > -1) { + AddComponent(achievementVendorID); } - if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PROPERTY_VENDOR, -1) != -1) { - AddComponent(); + const auto propertyVendorID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PROPERTY_VENDOR, -1); + if (propertyVendorID > -1) { + AddComponent(propertyVendorID); } - if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PROPERTY_MANAGEMENT, -1) != -1) { - AddComponent(); + const auto propertyManagementID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PROPERTY_MANAGEMENT, -1); + if (propertyManagementID > -1) { + AddComponent(propertyManagementID); } - if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::BOUNCER, -1) != -1) { // you have to determine it like this because all bouncers have a componentID of 0 - AddComponent(); + const auto bouncerID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::BOUNCER, -1); + if (bouncerID > -1) { // you have to determine it like this because all bouncers have a componentID of 0 + AddComponent(bouncerID); } - const int32_t renderComponentId = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RENDER); - if ((renderComponentId > 0 && m_TemplateID != 2365) || m_Character) { - AddComponent(renderComponentId); + const auto renderComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RENDER); + if ((renderComponentID > 0 && m_TemplateID != 2365) || m_Character) { + AddComponent(renderComponentID); } - if ((compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MISSION_OFFER) > 0) || m_Character) { - AddComponent(m_TemplateID); + const auto missionOfferComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MISSION_OFFER, -1); + if (missionOfferComponentID > -1 || m_Character) { + AddComponent(missionOfferComponentID); } - if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::BUILD_BORDER, -1) != -1) { - AddComponent(); + const auto buildBorderID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::BUILD_BORDER, -1); + if (buildBorderID > -1) { + AddComponent(buildBorderID); } // Scripted activity component - const int32_t scriptedActivityID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::SCRIPTED_ACTIVITY, -1); - if (scriptedActivityID != -1) { + const auto scriptedActivityID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::SCRIPTED_ACTIVITY, -1); + if (scriptedActivityID > -1) { AddComponent(scriptedActivityID); } - if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MODEL, -1) != -1 && !GetComponent()) { - AddComponent()->LoadBehaviors(); + const auto modelID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MODEL, -1); + if (modelID > -1 && !GetComponent()) { + AddComponent(modelID)->LoadBehaviors(); if (!HasComponent(eReplicaComponentType::DESTROYABLE)) { - auto* const destroyableComponent = AddComponent(); + auto* const destroyableComponent = AddComponent(-1); destroyableComponent->SetHealth(1); destroyableComponent->SetMaxHealth(1.0f); destroyableComponent->SetFaction(-1, true); @@ -672,9 +693,10 @@ void Entity::Initialize() { } } - PetComponent* petComponent; - if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::ITEM) > 0 && !TryGetComponent(eReplicaComponentType::PET, petComponent) && !HasComponent(eReplicaComponentType::MODEL)) { - AddComponent(); + PetComponent* petComponent{}; + const auto itemID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::ITEM); + if (itemID > 0 && !TryGetComponent(eReplicaComponentType::PET, petComponent) && !HasComponent(eReplicaComponentType::MODEL)) { + AddComponent(itemID); } // Shooting gallery component @@ -683,16 +705,17 @@ void Entity::Initialize() { AddComponent(shootingGalleryComponentID); } - if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PROPERTY, -1) != -1) { - AddComponent(); + const auto propertyID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PROPERTY, -1); + if (propertyID > -1) { + AddComponent(propertyID); } - const int rocketId = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::ROCKET_LAUNCH); - if ((rocketId > 0)) { - AddComponent(rocketId); + const auto rocketID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::ROCKET_LAUNCH); + if ((rocketID > 0)) { + AddComponent(rocketID); } - const int32_t railComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RAIL_ACTIVATOR); + const auto railComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RAIL_ACTIVATOR); if (railComponentID > 0) { AddComponent(railComponentID); } @@ -722,9 +745,9 @@ void Entity::Initialize() { } } - AddComponent(moveInfo); + AddComponent(movementAIID, moveInfo); } - } else if (petComponentId > 0 || combatAiId > 0 && GetComponent()->GetTetherSpeed() > 0) { + } else if (petComponentID > 0 || combatAiID > 0 && GetComponent()->GetTetherSpeed() > 0) { MovementAIInfo moveInfo{ .movementType = "", .wanderRadius = 16, @@ -734,7 +757,7 @@ void Entity::Initialize() { .wanderDelayMax = 5, }; - AddComponent(moveInfo); + AddComponent(-1, moveInfo); } const std::string pathName = GetVarAsString(u"attached_path"); @@ -744,10 +767,10 @@ void Entity::Initialize() { if (path) { // if we have a moving platform path, then we need a moving platform component if (path->pathType == PathType::MovingPlatform) { - AddComponent(pathName); + AddComponent(-1, pathName); } else if (path->pathType == PathType::Movement) { auto* const movementAIcomponent = GetComponent(); - if (movementAIcomponent && combatAiId == 0) { + if (movementAIcomponent && combatAiID == 0) { movementAIcomponent->SetPath(pathName); } else { MovementAIInfo moveInfo{ @@ -759,24 +782,24 @@ void Entity::Initialize() { .wanderDelayMax = 5, }; - AddComponent(moveInfo); + AddComponent(-1, moveInfo); } } } else { // else we still need to setup moving platform if it has a moving platform comp but no path - const int32_t movingPlatformComponentId = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MOVING_PLATFORM, -1); - if (movingPlatformComponentId >= 0) { - AddComponent(pathName); + const auto movingPlatformComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MOVING_PLATFORM, -1); + if (movingPlatformComponentID >= 0) { + AddComponent(movingPlatformComponentID, pathName); } } - const int proximityMonitorID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PROXIMITY_MONITOR); + const auto proximityMonitorID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PROXIMITY_MONITOR); if (proximityMonitorID > 0) { auto* const proxCompTable = CDClientManager::GetTable(); const auto proxCompData = proxCompTable->Query([proximityMonitorID](const CDProximityMonitorComponent& entry) { return (entry.id == proximityMonitorID); }); if (proxCompData.size() > 0) { std::vector proximityStr = GeneralUtils::SplitString(proxCompData[0].Proximities, ','); - AddComponent(std::stoi(proximityStr[0]), std::stoi(proximityStr[1])); + AddComponent(proximityMonitorID, std::stoi(proximityStr[0]), std::stoi(proximityStr[1])); } } @@ -882,12 +905,13 @@ void Entity::Unsubscribe(LWOOBJID scriptObjId, const std::string& notificationNa void Entity::SetProximityRadius(float proxRadius, std::string name) { auto* proxMon = GetComponent(); - if (!proxMon) proxMon = AddComponent(); + if (!proxMon) proxMon = AddComponent(-1); proxMon->SetProximityRadius(proxRadius, name); } void Entity::SetProximityRadius(dpEntity* entity, std::string name) { - ProximityMonitorComponent* proxMon = AddComponent(); + auto* proxMon = GetComponent(); + if (!proxMon) proxMon = AddComponent(-1); proxMon->SetProximityRadius(entity, name); } diff --git a/dGame/dComponents/AchievementVendorComponent.cpp b/dGame/dComponents/AchievementVendorComponent.cpp index 5ac03102..950d178d 100644 --- a/dGame/dComponents/AchievementVendorComponent.cpp +++ b/dGame/dComponents/AchievementVendorComponent.cpp @@ -9,7 +9,7 @@ #include "UserManager.h" #include "CDMissionsTable.h" -AchievementVendorComponent::AchievementVendorComponent(Entity* parent) : VendorComponent(parent) { +AchievementVendorComponent::AchievementVendorComponent(Entity* parent, const int32_t componentID) : VendorComponent(parent, componentID) { RefreshInventory(true); }; diff --git a/dGame/dComponents/AchievementVendorComponent.h b/dGame/dComponents/AchievementVendorComponent.h index ba6c7c2a..e8335714 100644 --- a/dGame/dComponents/AchievementVendorComponent.h +++ b/dGame/dComponents/AchievementVendorComponent.h @@ -11,7 +11,7 @@ class Entity; class AchievementVendorComponent final : public VendorComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::ACHIEVEMENT_VENDOR; - AchievementVendorComponent(Entity* parent); + AchievementVendorComponent(Entity* parent, const int32_t componentID); void RefreshInventory(bool isCreation = false) override; bool SellsItem(Entity* buyer, const LOT lot); diff --git a/dGame/dComponents/ActivityComponent.cpp b/dGame/dComponents/ActivityComponent.cpp index 8e0c62d3..6de50bed 100644 --- a/dGame/dComponents/ActivityComponent.cpp +++ b/dGame/dComponents/ActivityComponent.cpp @@ -30,7 +30,7 @@ #include "CharacterComponent.h" #include "Amf3.h" -ActivityComponent::ActivityComponent(Entity* parent, int32_t activityID) : Component(parent) { +ActivityComponent::ActivityComponent(Entity* parent, int32_t componentID) : Component(parent, componentID) { using namespace GameMessages; RegisterMsg(this, &ActivityComponent::OnGetObjectReportInfo); /* @@ -39,8 +39,8 @@ ActivityComponent::ActivityComponent(Entity* parent, int32_t activityID) : Compo * if activityID is specified and if that column exists in the activities table, update the activity info with that data. */ - m_ActivityID = activityID; - LoadActivityData(activityID); + m_ActivityID = componentID; + LoadActivityData(componentID); if (m_Parent->HasVar(u"activityID")) { m_ActivityID = parent->GetVar(u"activityID"); LoadActivityData(m_ActivityID); diff --git a/dGame/dComponents/BaseCombatAIComponent.cpp b/dGame/dComponents/BaseCombatAIComponent.cpp index 76228ec7..d264801f 100644 --- a/dGame/dComponents/BaseCombatAIComponent.cpp +++ b/dGame/dComponents/BaseCombatAIComponent.cpp @@ -28,7 +28,7 @@ #include "CDPhysicsComponentTable.h" #include "dNavMesh.h" -BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id) : Component(parent) { +BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { m_Target = LWOOBJID_EMPTY; m_DirtyStateOrTarget = true; m_State = AiState::spawn; @@ -43,7 +43,7 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id) //Grab the aggro information from BaseCombatAI: auto componentQuery = CDClientDatabase::CreatePreppedStmt( "SELECT aggroRadius, tetherSpeed, pursuitSpeed, softTetherRadius, hardTetherRadius FROM BaseCombatAIComponent WHERE id = ?;"); - componentQuery.bind(1, static_cast(id)); + componentQuery.bind(1, static_cast(componentID)); auto componentResult = componentQuery.execQuery(); @@ -111,12 +111,12 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id) int32_t collisionGroup = (COLLISION_GROUP_DYNAMIC | COLLISION_GROUP_ENEMY); CDComponentsRegistryTable* componentRegistryTable = CDClientManager::GetTable(); - auto componentID = componentRegistryTable->GetByIDAndType(parent->GetLOT(), eReplicaComponentType::CONTROLLABLE_PHYSICS); + const auto controllablePhysicsID = componentRegistryTable->GetByIDAndType(parent->GetLOT(), eReplicaComponentType::CONTROLLABLE_PHYSICS); CDPhysicsComponentTable* physicsComponentTable = CDClientManager::GetTable(); if (physicsComponentTable != nullptr) { - auto* info = physicsComponentTable->GetByID(componentID); + auto* info = physicsComponentTable->GetByID(controllablePhysicsID); if (info != nullptr) { collisionGroup = info->bStatic ? COLLISION_GROUP_NEUTRAL : info->collisionGroup; } diff --git a/dGame/dComponents/BaseCombatAIComponent.h b/dGame/dComponents/BaseCombatAIComponent.h index 52adb429..164b2ef5 100644 --- a/dGame/dComponents/BaseCombatAIComponent.h +++ b/dGame/dComponents/BaseCombatAIComponent.h @@ -49,7 +49,7 @@ class BaseCombatAIComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::BASE_COMBAT_AI; - BaseCombatAIComponent(Entity* parentEntity, uint32_t id); + BaseCombatAIComponent(Entity* parentEntity, int32_t componentID); ~BaseCombatAIComponent() override; void Update(float deltaTime) override; diff --git a/dGame/dComponents/BouncerComponent.cpp b/dGame/dComponents/BouncerComponent.cpp index d1c63bf4..a7c7f1a8 100644 --- a/dGame/dComponents/BouncerComponent.cpp +++ b/dGame/dComponents/BouncerComponent.cpp @@ -9,7 +9,7 @@ #include "BitStream.h" #include "eTriggerEventType.h" -BouncerComponent::BouncerComponent(Entity* parent) : Component(parent) { +BouncerComponent::BouncerComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { m_PetEnabled = false; m_PetBouncerEnabled = false; m_PetSwitchLoaded = false; diff --git a/dGame/dComponents/BouncerComponent.h b/dGame/dComponents/BouncerComponent.h index 441ba58e..53ba26fa 100644 --- a/dGame/dComponents/BouncerComponent.h +++ b/dGame/dComponents/BouncerComponent.h @@ -14,7 +14,7 @@ class BouncerComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::BOUNCER; - BouncerComponent(Entity* parentEntity); + BouncerComponent(Entity* parentEntity, const int32_t componentID); ~BouncerComponent() override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; diff --git a/dGame/dComponents/BuffComponent.cpp b/dGame/dComponents/BuffComponent.cpp index 2c940647..bf09d2ea 100644 --- a/dGame/dComponents/BuffComponent.cpp +++ b/dGame/dComponents/BuffComponent.cpp @@ -26,7 +26,7 @@ namespace { }; } -BuffComponent::BuffComponent(Entity* parent) : Component(parent) { +BuffComponent::BuffComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { } BuffComponent::~BuffComponent() { diff --git a/dGame/dComponents/BuffComponent.h b/dGame/dComponents/BuffComponent.h index 507e53a0..05dcaabb 100644 --- a/dGame/dComponents/BuffComponent.h +++ b/dGame/dComponents/BuffComponent.h @@ -51,7 +51,7 @@ class BuffComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::BUFF; - explicit BuffComponent(Entity* parent); + explicit BuffComponent(Entity* parent, const int32_t componentID); ~BuffComponent(); diff --git a/dGame/dComponents/BuildBorderComponent.cpp b/dGame/dComponents/BuildBorderComponent.cpp index 066a8c74..7209ec9a 100644 --- a/dGame/dComponents/BuildBorderComponent.cpp +++ b/dGame/dComponents/BuildBorderComponent.cpp @@ -9,7 +9,7 @@ #include "Item.h" #include "PropertyManagementComponent.h" -BuildBorderComponent::BuildBorderComponent(Entity* parent) : Component(parent) { +BuildBorderComponent::BuildBorderComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { } BuildBorderComponent::~BuildBorderComponent() { diff --git a/dGame/dComponents/BuildBorderComponent.h b/dGame/dComponents/BuildBorderComponent.h index a59ac363..f57313fb 100644 --- a/dGame/dComponents/BuildBorderComponent.h +++ b/dGame/dComponents/BuildBorderComponent.h @@ -18,7 +18,7 @@ class BuildBorderComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::BUILD_BORDER; - BuildBorderComponent(Entity* parent); + BuildBorderComponent(Entity* parent, const int32_t componentID); ~BuildBorderComponent() override; /** diff --git a/dGame/dComponents/CharacterComponent.cpp b/dGame/dComponents/CharacterComponent.cpp index 0559da7c..bdcf7446 100644 --- a/dGame/dComponents/CharacterComponent.cpp +++ b/dGame/dComponents/CharacterComponent.cpp @@ -25,7 +25,7 @@ #include "MessageType/Game.h" #include -CharacterComponent::CharacterComponent(Entity* parent, Character* character, const SystemAddress& systemAddress) : Component(parent) { +CharacterComponent::CharacterComponent(Entity* parent, const int32_t componentID, Character* character, const SystemAddress& systemAddress) : Component(parent, componentID) { m_Character = character; m_IsRacing = false; diff --git a/dGame/dComponents/CharacterComponent.h b/dGame/dComponents/CharacterComponent.h index 5e7059f3..c1f107b5 100644 --- a/dGame/dComponents/CharacterComponent.h +++ b/dGame/dComponents/CharacterComponent.h @@ -70,7 +70,7 @@ class CharacterComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::CHARACTER; - CharacterComponent(Entity* parent, Character* character, const SystemAddress& systemAddress); + CharacterComponent(Entity* parent, const int32_t componentID, Character* character, const SystemAddress& systemAddress); ~CharacterComponent() override; void LoadFromXml(const tinyxml2::XMLDocument& doc) override; diff --git a/dGame/dComponents/CollectibleComponent.h b/dGame/dComponents/CollectibleComponent.h index 5ecfb97e..d9356112 100644 --- a/dGame/dComponents/CollectibleComponent.h +++ b/dGame/dComponents/CollectibleComponent.h @@ -7,7 +7,7 @@ class CollectibleComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::COLLECTIBLE; - CollectibleComponent(Entity* parentEntity, int32_t collectibleId) : Component(parentEntity), m_CollectibleId(collectibleId) {} + CollectibleComponent(Entity* parentEntity, const int32_t componentID, const int32_t collectibleId) : Component(parentEntity, componentID), m_CollectibleId(collectibleId) {} int16_t GetCollectibleId() const { return m_CollectibleId; } void Serialize(RakNet::BitStream& outBitStream, bool isConstruction) override; diff --git a/dGame/dComponents/Component.h b/dGame/dComponents/Component.h index 9772ac49..b54a8fab 100644 --- a/dGame/dComponents/Component.h +++ b/dGame/dComponents/Component.h @@ -19,7 +19,7 @@ class Entity; */ class Component { public: - Component(Entity* parent) : m_Parent{ parent } {} + Component(Entity* parent, const int32_t componentID) : m_Parent{ parent }, m_ComponentID{componentID} {} virtual ~Component() = default; /** @@ -28,6 +28,8 @@ public: */ Entity* GetParent() const { return m_Parent; } + [[nodiscard]] int32_t GetComponentID() const noexcept { return m_ComponentID; } + /** * Updates the component in the game loop * @param deltaTime time passed since last update @@ -70,4 +72,11 @@ protected: * The entity that owns this component */ Entity* m_Parent; + + // The component ID, this should never be changed after initialization + // This is used in various different ways + // 1. To identify which entry this component is in its corresponding table + // 2. To mark that an Entity should have the component with no database entry (it will be 0 in this case) + // 3. The component exists implicitly due to design (CollectibleComponent always has a DestructibleComponent accompanying it). In this case the ID will be -1. + const int32_t m_ComponentID; }; diff --git a/dGame/dComponents/ControllablePhysicsComponent.cpp b/dGame/dComponents/ControllablePhysicsComponent.cpp index 09028fd8..b2a41358 100644 --- a/dGame/dComponents/ControllablePhysicsComponent.cpp +++ b/dGame/dComponents/ControllablePhysicsComponent.cpp @@ -17,7 +17,7 @@ #include "StringifiedEnum.h" #include "Amf3.h" -ControllablePhysicsComponent::ControllablePhysicsComponent(Entity* entity, int32_t componentId) : PhysicsComponent(entity, componentId) { +ControllablePhysicsComponent::ControllablePhysicsComponent(Entity* entity, const int32_t componentID) : PhysicsComponent(entity, componentID) { RegisterMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, this, &ControllablePhysicsComponent::OnGetObjectReportInfo); m_Velocity = {}; diff --git a/dGame/dComponents/ControllablePhysicsComponent.h b/dGame/dComponents/ControllablePhysicsComponent.h index 9a90628d..419e9250 100644 --- a/dGame/dComponents/ControllablePhysicsComponent.h +++ b/dGame/dComponents/ControllablePhysicsComponent.h @@ -25,7 +25,7 @@ class ControllablePhysicsComponent : public PhysicsComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::CONTROLLABLE_PHYSICS; - ControllablePhysicsComponent(Entity* entity, int32_t componentId); + ControllablePhysicsComponent(Entity* entity, const int32_t componentID); ~ControllablePhysicsComponent() override; void Update(float deltaTime) override; diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 512ac150..f86cd8e4 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -44,7 +44,7 @@ Implementation DestroyableComponent::IsEnemyImplentation; Implementation DestroyableComponent::IsFriendImplentation; -DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) { +DestroyableComponent::DestroyableComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { using namespace GameMessages; m_iArmor = 0; m_fMaxArmor = 0.0f; diff --git a/dGame/dComponents/DestroyableComponent.h b/dGame/dComponents/DestroyableComponent.h index 7ec9bc44..2984147a 100644 --- a/dGame/dComponents/DestroyableComponent.h +++ b/dGame/dComponents/DestroyableComponent.h @@ -26,7 +26,7 @@ class DestroyableComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::DESTROYABLE; - DestroyableComponent(Entity* parentEntity); + DestroyableComponent(Entity* parentEntity, const int32_t componentID); ~DestroyableComponent() override; void Update(float deltaTime) override; diff --git a/dGame/dComponents/DonationVendorComponent.cpp b/dGame/dComponents/DonationVendorComponent.cpp index 7fb06a90..9436631e 100644 --- a/dGame/dComponents/DonationVendorComponent.cpp +++ b/dGame/dComponents/DonationVendorComponent.cpp @@ -1,7 +1,7 @@ #include "DonationVendorComponent.h" #include "Database.h" -DonationVendorComponent::DonationVendorComponent(Entity* parent) : VendorComponent(parent) { +DonationVendorComponent::DonationVendorComponent(Entity* parent, const int32_t componentID) : VendorComponent(parent, componentID) { //LoadConfigData m_PercentComplete = 0.0; m_TotalDonated = 0; diff --git a/dGame/dComponents/DonationVendorComponent.h b/dGame/dComponents/DonationVendorComponent.h index af1eb829..5fef2e41 100644 --- a/dGame/dComponents/DonationVendorComponent.h +++ b/dGame/dComponents/DonationVendorComponent.h @@ -9,7 +9,7 @@ class Entity; class DonationVendorComponent final : public VendorComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::DONATION_VENDOR; - DonationVendorComponent(Entity* parent); + DonationVendorComponent(Entity* parent, const int32_t componentID); void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; uint32_t GetActivityID() {return m_ActivityId;}; void SubmitDonation(uint32_t count); diff --git a/dGame/dComponents/GhostComponent.cpp b/dGame/dComponents/GhostComponent.cpp index 3aea329a..4755a1f9 100644 --- a/dGame/dComponents/GhostComponent.cpp +++ b/dGame/dComponents/GhostComponent.cpp @@ -1,6 +1,6 @@ #include "GhostComponent.h" -GhostComponent::GhostComponent(Entity* parent) : Component(parent) { +GhostComponent::GhostComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { m_GhostReferencePoint = NiPoint3Constant::ZERO; m_GhostOverridePoint = NiPoint3Constant::ZERO; m_GhostOverride = false; diff --git a/dGame/dComponents/GhostComponent.h b/dGame/dComponents/GhostComponent.h index de0fb886..edf05c13 100644 --- a/dGame/dComponents/GhostComponent.h +++ b/dGame/dComponents/GhostComponent.h @@ -10,7 +10,7 @@ class NiPoint3; class GhostComponent final : public Component { public: static inline const eReplicaComponentType ComponentType = eReplicaComponentType::GHOST; - GhostComponent(Entity* parent); + GhostComponent(Entity* parent, const int32_t componentID); ~GhostComponent() override; void SetGhostOverride(bool value) { m_GhostOverride = value; }; diff --git a/dGame/dComponents/HavokVehiclePhysicsComponent.cpp b/dGame/dComponents/HavokVehiclePhysicsComponent.cpp index 50638880..afec4427 100644 --- a/dGame/dComponents/HavokVehiclePhysicsComponent.cpp +++ b/dGame/dComponents/HavokVehiclePhysicsComponent.cpp @@ -2,7 +2,7 @@ #include "EntityManager.h" #include "Amf3.h" -HavokVehiclePhysicsComponent::HavokVehiclePhysicsComponent(Entity* parent, int32_t componentId) : PhysicsComponent(parent, componentId) { +HavokVehiclePhysicsComponent::HavokVehiclePhysicsComponent(Entity* parent, const int32_t componentID) : PhysicsComponent(parent, componentID) { RegisterMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, this, &HavokVehiclePhysicsComponent::OnGetObjectReportInfo); m_Velocity = NiPoint3Constant::ZERO; diff --git a/dGame/dComponents/HavokVehiclePhysicsComponent.h b/dGame/dComponents/HavokVehiclePhysicsComponent.h index 5e4a1a65..3a84adca 100644 --- a/dGame/dComponents/HavokVehiclePhysicsComponent.h +++ b/dGame/dComponents/HavokVehiclePhysicsComponent.h @@ -13,7 +13,7 @@ class HavokVehiclePhysicsComponent : public PhysicsComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::HAVOK_VEHICLE_PHYSICS; - HavokVehiclePhysicsComponent(Entity* parentEntity, int32_t componentId); + HavokVehiclePhysicsComponent(Entity* parentEntity, const int32_t componentID); void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 3138904b..0b45a11e 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -42,7 +42,7 @@ #include -InventoryComponent::InventoryComponent(Entity* parent) : Component(parent) { +InventoryComponent::InventoryComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { this->m_Dirty = true; this->m_Equipped = {}; this->m_Pushed = {}; diff --git a/dGame/dComponents/InventoryComponent.h b/dGame/dComponents/InventoryComponent.h index a1441655..88d9da2c 100644 --- a/dGame/dComponents/InventoryComponent.h +++ b/dGame/dComponents/InventoryComponent.h @@ -67,7 +67,7 @@ public: static constexpr uint32_t MaximumGroupCount = 50; static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::INVENTORY; - InventoryComponent(Entity* parent); + InventoryComponent(Entity* parent, const int32_t componentID); void Update(float deltaTime) override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; diff --git a/dGame/dComponents/ItemComponent.h b/dGame/dComponents/ItemComponent.h index 1a02ad11..73e1ac83 100644 --- a/dGame/dComponents/ItemComponent.h +++ b/dGame/dComponents/ItemComponent.h @@ -8,7 +8,7 @@ class ItemComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::ITEM; - ItemComponent(Entity* entity) : Component(entity) {} + ItemComponent(Entity* entity, const int32_t componentID) : Component(entity, componentID) {} void Serialize(RakNet::BitStream& bitStream, bool isConstruction) override; }; diff --git a/dGame/dComponents/LUPExhibitComponent.h b/dGame/dComponents/LUPExhibitComponent.h index 8fd6d7ee..4e93dbf9 100644 --- a/dGame/dComponents/LUPExhibitComponent.h +++ b/dGame/dComponents/LUPExhibitComponent.h @@ -16,7 +16,7 @@ class LUPExhibitComponent final : public Component public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::LUP_EXHIBIT; - LUPExhibitComponent(Entity* parent) : Component(parent) {}; + LUPExhibitComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {}; void Update(float deltaTime) override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; void NextLUPExhibit(); diff --git a/dGame/dComponents/LevelProgressionComponent.cpp b/dGame/dComponents/LevelProgressionComponent.cpp index a6801a40..8dd1d5f0 100644 --- a/dGame/dComponents/LevelProgressionComponent.cpp +++ b/dGame/dComponents/LevelProgressionComponent.cpp @@ -6,7 +6,7 @@ #include "CDRewardsTable.h" -LevelProgressionComponent::LevelProgressionComponent(Entity* parent) : Component(parent) { +LevelProgressionComponent::LevelProgressionComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { m_Parent = parent; m_Level = 1; m_SpeedBase = 500.0f; diff --git a/dGame/dComponents/LevelProgressionComponent.h b/dGame/dComponents/LevelProgressionComponent.h index e9981ab6..b408bbed 100644 --- a/dGame/dComponents/LevelProgressionComponent.h +++ b/dGame/dComponents/LevelProgressionComponent.h @@ -19,7 +19,7 @@ public: * Constructor for this component * @param parent parent that contains this component */ - LevelProgressionComponent(Entity* parent); + LevelProgressionComponent(Entity* parent, const int32_t componentID); void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; diff --git a/dGame/dComponents/MiniGameControlComponent.h b/dGame/dComponents/MiniGameControlComponent.h index 2cd9ac6a..742142d3 100644 --- a/dGame/dComponents/MiniGameControlComponent.h +++ b/dGame/dComponents/MiniGameControlComponent.h @@ -8,7 +8,7 @@ class MiniGameControlComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::MINI_GAME_CONTROL; - MiniGameControlComponent(Entity* parent) : Component(parent) {} + MiniGameControlComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {} void Serialize(RakNet::BitStream& outBitStream, bool isConstruction); }; diff --git a/dGame/dComponents/MinigameComponent.cpp b/dGame/dComponents/MinigameComponent.cpp deleted file mode 100644 index 2174cd8a..00000000 --- a/dGame/dComponents/MinigameComponent.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include "MinigameComponent.h" - -void MinigameComponent::Serialize(RakNet::BitStream& outBitStream, bool isConstruction) { - outBitStream.Write(0x40000000); -} diff --git a/dGame/dComponents/MissionComponent.cpp b/dGame/dComponents/MissionComponent.cpp index b5a6572a..cf9aa767 100644 --- a/dGame/dComponents/MissionComponent.cpp +++ b/dGame/dComponents/MissionComponent.cpp @@ -26,7 +26,7 @@ std::unordered_map> MissionComponent::m_AchievementCache = {}; //! Initializer -MissionComponent::MissionComponent(Entity* parent) : Component(parent) { +MissionComponent::MissionComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { m_LastUsedMissionOrderUID = Game::zoneManager->GetUniqueMissionIdStartingValue(); } diff --git a/dGame/dComponents/MissionComponent.h b/dGame/dComponents/MissionComponent.h index a01794f0..28727ecf 100644 --- a/dGame/dComponents/MissionComponent.h +++ b/dGame/dComponents/MissionComponent.h @@ -28,7 +28,7 @@ class MissionComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::MISSION; - explicit MissionComponent(Entity* parent); + explicit MissionComponent(Entity* parent, const int32_t componentID); ~MissionComponent() override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate, unsigned int& flags); void LoadFromXml(const tinyxml2::XMLDocument& doc) override; diff --git a/dGame/dComponents/MissionOfferComponent.cpp b/dGame/dComponents/MissionOfferComponent.cpp index 7f26ed72..9bc6bc31 100644 --- a/dGame/dComponents/MissionOfferComponent.cpp +++ b/dGame/dComponents/MissionOfferComponent.cpp @@ -39,19 +39,13 @@ bool OfferedMission::GetAcceptsMission() const { //------------------------ MissionOfferComponent below ------------------------ -MissionOfferComponent::MissionOfferComponent(Entity* parent, const LOT parentLot) : Component(parent) { - auto* compRegistryTable = CDClientManager::GetTable(); - - auto value = compRegistryTable->GetByIDAndType(parentLot, eReplicaComponentType::MISSION_OFFER, -1); - - if (value != -1) { - const uint32_t componentId = value; - +MissionOfferComponent::MissionOfferComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { + if (componentID != -1) { // Now lookup the missions in the MissionNPCComponent table auto* missionNpcComponentTable = CDClientManager::GetTable(); - auto missions = missionNpcComponentTable->Query([=](const CDMissionNPCComponent& entry) { - return entry.id == static_cast(componentId); + auto missions = missionNpcComponentTable->Query([componentID](const CDMissionNPCComponent& entry) { + return entry.id == static_cast(componentID); }); for (auto& mission : missions) { diff --git a/dGame/dComponents/MissionOfferComponent.h b/dGame/dComponents/MissionOfferComponent.h index d842a92e..74510d83 100644 --- a/dGame/dComponents/MissionOfferComponent.h +++ b/dGame/dComponents/MissionOfferComponent.h @@ -63,7 +63,7 @@ class MissionOfferComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::MISSION_OFFER; - MissionOfferComponent(Entity* parent, LOT parentLot); + MissionOfferComponent(Entity* parent, const int32_t componentID); /** * Handles the OnUse event triggered by some entity, determines which missions to show based on what they may diff --git a/dGame/dComponents/ModelComponent.cpp b/dGame/dComponents/ModelComponent.cpp index 6066fa4b..f5f4e190 100644 --- a/dGame/dComponents/ModelComponent.cpp +++ b/dGame/dComponents/ModelComponent.cpp @@ -14,7 +14,7 @@ #include "Database.h" #include "DluAssert.h" -ModelComponent::ModelComponent(Entity* parent) : Component(parent) { +ModelComponent::ModelComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { using namespace GameMessages; m_OriginalPosition = m_Parent->GetDefaultPosition(); m_OriginalRotation = m_Parent->GetDefaultRotation(); diff --git a/dGame/dComponents/ModelComponent.h b/dGame/dComponents/ModelComponent.h index 3b354bbd..981abc8d 100644 --- a/dGame/dComponents/ModelComponent.h +++ b/dGame/dComponents/ModelComponent.h @@ -27,7 +27,7 @@ class ModelComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::MODEL; - ModelComponent(Entity* parent); + ModelComponent(Entity* parent, const int32_t componentID); void LoadBehaviors(); void Update(float deltaTime) override; diff --git a/dGame/dComponents/ModuleAssemblyComponent.cpp b/dGame/dComponents/ModuleAssemblyComponent.cpp index e217d9b7..7fcbc767 100644 --- a/dGame/dComponents/ModuleAssemblyComponent.cpp +++ b/dGame/dComponents/ModuleAssemblyComponent.cpp @@ -1,6 +1,6 @@ #include "ModuleAssemblyComponent.h" -ModuleAssemblyComponent::ModuleAssemblyComponent(Entity* parent) : Component(parent) { +ModuleAssemblyComponent::ModuleAssemblyComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { m_SubKey = LWOOBJID_EMPTY; m_UseOptionalParts = false; m_AssemblyPartsLOTs = u""; diff --git a/dGame/dComponents/ModuleAssemblyComponent.h b/dGame/dComponents/ModuleAssemblyComponent.h index 7e050ec7..573d70cd 100644 --- a/dGame/dComponents/ModuleAssemblyComponent.h +++ b/dGame/dComponents/ModuleAssemblyComponent.h @@ -14,7 +14,7 @@ class ModuleAssemblyComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::MODULE_ASSEMBLY; - ModuleAssemblyComponent(Entity* parent); + ModuleAssemblyComponent(Entity* parent, const int32_t componentID); ~ModuleAssemblyComponent() override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; diff --git a/dGame/dComponents/MovementAIComponent.cpp b/dGame/dComponents/MovementAIComponent.cpp index 30df333b..faedc848 100644 --- a/dGame/dComponents/MovementAIComponent.cpp +++ b/dGame/dComponents/MovementAIComponent.cpp @@ -26,7 +26,7 @@ namespace { std::map m_PhysicsSpeedCache; } -MovementAIComponent::MovementAIComponent(Entity* parent, MovementAIInfo info) : Component(parent) { +MovementAIComponent::MovementAIComponent(Entity* parent, const int32_t componentID, MovementAIInfo info) : Component(parent, componentID) { m_Info = info; m_AtFinalWaypoint = true; diff --git a/dGame/dComponents/MovementAIComponent.h b/dGame/dComponents/MovementAIComponent.h index 6c612b7e..72ff45e8 100644 --- a/dGame/dComponents/MovementAIComponent.h +++ b/dGame/dComponents/MovementAIComponent.h @@ -62,7 +62,7 @@ class MovementAIComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::MOVEMENT_AI; - MovementAIComponent(Entity* parentEntity, MovementAIInfo info); + MovementAIComponent(Entity* parentEntity, const int32_t componentID, MovementAIInfo info); void SetPath(const std::string pathName); diff --git a/dGame/dComponents/MovingPlatformComponent.cpp b/dGame/dComponents/MovingPlatformComponent.cpp index 77acbb8d..18911fc1 100644 --- a/dGame/dComponents/MovingPlatformComponent.cpp +++ b/dGame/dComponents/MovingPlatformComponent.cpp @@ -55,7 +55,7 @@ void MoverSubComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsIniti //------------- MovingPlatformComponent below -------------- -MovingPlatformComponent::MovingPlatformComponent(Entity* parent, const std::string& pathName) : Component(parent) { +MovingPlatformComponent::MovingPlatformComponent(Entity* parent, const int32_t componentID, const std::string& pathName) : Component(parent, componentID) { m_MoverSubComponentType = eMoverSubComponentType::mover; m_MoverSubComponent = new MoverSubComponent(m_Parent->GetDefaultPosition()); m_PathName = GeneralUtils::ASCIIToUTF16(pathName); diff --git a/dGame/dComponents/MovingPlatformComponent.h b/dGame/dComponents/MovingPlatformComponent.h index c4fbf308..065a2786 100644 --- a/dGame/dComponents/MovingPlatformComponent.h +++ b/dGame/dComponents/MovingPlatformComponent.h @@ -108,7 +108,7 @@ class MovingPlatformComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::MOVING_PLATFORM; - MovingPlatformComponent(Entity* parent, const std::string& pathName); + MovingPlatformComponent(Entity* parent, const int32_t componentID, const std::string& pathName); ~MovingPlatformComponent() override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; diff --git a/dGame/dComponents/MultiZoneEntranceComponent.cpp b/dGame/dComponents/MultiZoneEntranceComponent.cpp index 7c2e2b79..f8dabf18 100644 --- a/dGame/dComponents/MultiZoneEntranceComponent.cpp +++ b/dGame/dComponents/MultiZoneEntranceComponent.cpp @@ -3,7 +3,7 @@ #include "InventoryComponent.h" #include "CharacterComponent.h" -MultiZoneEntranceComponent::MultiZoneEntranceComponent(Entity* parent) : Component(parent) { +MultiZoneEntranceComponent::MultiZoneEntranceComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { m_Parent = parent; std::string zoneString = GeneralUtils::UTF16ToWTF8(m_Parent->GetVar(u"MultiZoneIDs")); std::stringstream ss(zoneString); diff --git a/dGame/dComponents/MultiZoneEntranceComponent.h b/dGame/dComponents/MultiZoneEntranceComponent.h index 8928be27..1ef18407 100644 --- a/dGame/dComponents/MultiZoneEntranceComponent.h +++ b/dGame/dComponents/MultiZoneEntranceComponent.h @@ -16,7 +16,7 @@ public: * Constructor for this component, builds the m_LUPWorlds vector * @param parent parent that contains this component */ - MultiZoneEntranceComponent(Entity* parent); + MultiZoneEntranceComponent(Entity* parent, const int32_t componentID); ~MultiZoneEntranceComponent() override; /** diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index df57aef7..06b7f21c 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -43,9 +43,8 @@ std::unordered_map PetComponent::activePets{}; * while the faction ones could be checked using their respective missions. */ -PetComponent::PetComponent(Entity* parentEntity, uint32_t componentId) : Component{ parentEntity } { - m_PetInfo = CDClientManager::GetTable()->GetByID(componentId); // TODO: Make reference when safe - m_ComponentId = componentId; +PetComponent::PetComponent(Entity* parentEntity, const int32_t componentID) : Component{ parentEntity, componentID } { + m_PetInfo = CDClientManager::GetTable()->GetByID(componentID); // TODO: Make reference when safe m_Interaction = LWOOBJID_EMPTY; m_Owner = LWOOBJID_EMPTY; @@ -537,7 +536,7 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) { // Triggers the catch a pet missions constexpr auto PET_FLAG_BASE = 800; - tamer->GetCharacter()->SetPlayerFlag(PET_FLAG_BASE + m_ComponentId, true); + tamer->GetCharacter()->SetPlayerFlag(PET_FLAG_BASE + m_ComponentID, true); auto* missionComponent = tamer->GetComponent(); diff --git a/dGame/dComponents/PetComponent.h b/dGame/dComponents/PetComponent.h index 5ab39021..2b1b4d6e 100644 --- a/dGame/dComponents/PetComponent.h +++ b/dGame/dComponents/PetComponent.h @@ -18,7 +18,7 @@ class PetComponent final : public Component public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::PET; - explicit PetComponent(Entity* parentEntity, uint32_t componentId); + explicit PetComponent(Entity* parentEntity, const int32_t componentID); ~PetComponent() override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; @@ -250,11 +250,6 @@ private: */ static std::unordered_map currentActivities; - /** - * The ID of the component in the pet component table - */ - uint32_t m_ComponentId; - /** * The ID of the model that was built to complete the taming minigame for this pet */ diff --git a/dGame/dComponents/PhantomPhysicsComponent.cpp b/dGame/dComponents/PhantomPhysicsComponent.cpp index 531a6e77..4ba95f5f 100644 --- a/dGame/dComponents/PhantomPhysicsComponent.cpp +++ b/dGame/dComponents/PhantomPhysicsComponent.cpp @@ -28,7 +28,7 @@ #include "dpShapeBox.h" #include "dpShapeSphere.h" -PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent, int32_t componentId) : PhysicsComponent(parent, componentId) { +PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent, const int32_t componentID) : PhysicsComponent(parent, componentID) { RegisterMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, this, &PhantomPhysicsComponent::OnGetObjectReportInfo); m_Position = m_Parent->GetDefaultPosition(); diff --git a/dGame/dComponents/PhantomPhysicsComponent.h b/dGame/dComponents/PhantomPhysicsComponent.h index 1d53ce2c..ac42dca3 100644 --- a/dGame/dComponents/PhantomPhysicsComponent.h +++ b/dGame/dComponents/PhantomPhysicsComponent.h @@ -28,7 +28,7 @@ class PhantomPhysicsComponent final : public PhysicsComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::PHANTOM_PHYSICS; - PhantomPhysicsComponent(Entity* parent, int32_t componentId); + PhantomPhysicsComponent(Entity* parent, const int32_t componentID); ~PhantomPhysicsComponent() override; void Update(float deltaTime) override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; diff --git a/dGame/dComponents/PhysicsComponent.cpp b/dGame/dComponents/PhysicsComponent.cpp index 97546c81..7d301476 100644 --- a/dGame/dComponents/PhysicsComponent.cpp +++ b/dGame/dComponents/PhysicsComponent.cpp @@ -15,7 +15,7 @@ #include "EntityInfo.h" #include "Amf3.h" -PhysicsComponent::PhysicsComponent(Entity* parent, int32_t componentId) : Component(parent) { +PhysicsComponent::PhysicsComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { m_Position = NiPoint3Constant::ZERO; m_Rotation = QuatUtils::IDENTITY; m_DirtyPosition = false; @@ -23,7 +23,7 @@ PhysicsComponent::PhysicsComponent(Entity* parent, int32_t componentId) : Compon CDPhysicsComponentTable* physicsComponentTable = CDClientManager::GetTable(); if (physicsComponentTable) { - auto* info = physicsComponentTable->GetByID(componentId); + auto* info = physicsComponentTable->GetByID(componentID); if (info) { m_CollisionGroup = info->collisionGroup; } diff --git a/dGame/dComponents/PhysicsComponent.h b/dGame/dComponents/PhysicsComponent.h index f100b4da..67a4a0a5 100644 --- a/dGame/dComponents/PhysicsComponent.h +++ b/dGame/dComponents/PhysicsComponent.h @@ -19,7 +19,7 @@ class dpEntity; class PhysicsComponent : public Component { public: - PhysicsComponent(Entity* parent, int32_t componentId); + PhysicsComponent(Entity* parent, const int32_t componentID); virtual ~PhysicsComponent() = default; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; diff --git a/dGame/dComponents/PlayerForcedMovementComponent.cpp b/dGame/dComponents/PlayerForcedMovementComponent.cpp index 0aea882c..4198a6dc 100644 --- a/dGame/dComponents/PlayerForcedMovementComponent.cpp +++ b/dGame/dComponents/PlayerForcedMovementComponent.cpp @@ -1,6 +1,6 @@ #include "PlayerForcedMovementComponent.h" -PlayerForcedMovementComponent::PlayerForcedMovementComponent(Entity* parent) : Component(parent) { +PlayerForcedMovementComponent::PlayerForcedMovementComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { m_Parent = parent; } diff --git a/dGame/dComponents/PlayerForcedMovementComponent.h b/dGame/dComponents/PlayerForcedMovementComponent.h index f184ede2..a39e2d39 100644 --- a/dGame/dComponents/PlayerForcedMovementComponent.h +++ b/dGame/dComponents/PlayerForcedMovementComponent.h @@ -16,7 +16,7 @@ public: * Constructor for this component * @param parent parent that contains this component */ - PlayerForcedMovementComponent(Entity* parent); + PlayerForcedMovementComponent(Entity* parent, const int32_t componentID); ~PlayerForcedMovementComponent() override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; diff --git a/dGame/dComponents/PossessableComponent.cpp b/dGame/dComponents/PossessableComponent.cpp index e0e8c8ad..39e732c6 100644 --- a/dGame/dComponents/PossessableComponent.cpp +++ b/dGame/dComponents/PossessableComponent.cpp @@ -4,7 +4,7 @@ #include "Inventory.h" #include "Item.h" -PossessableComponent::PossessableComponent(Entity* parent, uint32_t componentId) : Component(parent) { +PossessableComponent::PossessableComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { m_Possessor = LWOOBJID_EMPTY; CDItemComponent item = Inventory::FindItemComponent(m_Parent->GetLOT()); m_AnimationFlag = static_cast(item.animationFlag); @@ -12,7 +12,7 @@ PossessableComponent::PossessableComponent(Entity* parent, uint32_t componentId) // Get the possession Type from the CDClient auto query = CDClientDatabase::CreatePreppedStmt("SELECT possessionType, depossessOnHit FROM PossessableComponent WHERE id = ?;"); - query.bind(1, static_cast(componentId)); + query.bind(1, static_cast(componentID)); auto result = query.execQuery(); diff --git a/dGame/dComponents/PossessableComponent.h b/dGame/dComponents/PossessableComponent.h index 2102f7fe..64a9dd73 100644 --- a/dGame/dComponents/PossessableComponent.h +++ b/dGame/dComponents/PossessableComponent.h @@ -16,15 +16,10 @@ class PossessableComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::POSSESSABLE; - PossessableComponent(Entity* parentEntity, uint32_t componentId); + PossessableComponent(Entity* parentEntity, const int32_t componentID); void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; - /** - * @brief mounts the Entity - */ - void Mount(); - /** * @brief dismounts the Entity */ diff --git a/dGame/dComponents/PossessorComponent.cpp b/dGame/dComponents/PossessorComponent.cpp index 46ccbffb..88ac1c2a 100644 --- a/dGame/dComponents/PossessorComponent.cpp +++ b/dGame/dComponents/PossessorComponent.cpp @@ -7,7 +7,7 @@ #include "eControlScheme.h" #include "eStateChangeType.h" -PossessorComponent::PossessorComponent(Entity* parent) : Component(parent) { +PossessorComponent::PossessorComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { m_Possessable = LWOOBJID_EMPTY; } diff --git a/dGame/dComponents/PossessorComponent.h b/dGame/dComponents/PossessorComponent.h index 3fa6153d..8fb6ee3f 100644 --- a/dGame/dComponents/PossessorComponent.h +++ b/dGame/dComponents/PossessorComponent.h @@ -20,7 +20,7 @@ class PossessorComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::POSSESSOR; - PossessorComponent(Entity* parent); + PossessorComponent(Entity* parent, const int32_t componentID); ~PossessorComponent() override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; diff --git a/dGame/dComponents/PropertyComponent.h b/dGame/dComponents/PropertyComponent.h index 1085aac4..906847bc 100644 --- a/dGame/dComponents/PropertyComponent.h +++ b/dGame/dComponents/PropertyComponent.h @@ -16,7 +16,7 @@ class PropertyComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::PROPERTY; - explicit PropertyComponent(Entity* const parentEntity) noexcept : Component{ parentEntity } {} + explicit PropertyComponent(Entity* const parentEntity, const int32_t componentID) noexcept : Component{ parentEntity, componentID } {} }; #endif // !PROPERTYCOMPONENT_H diff --git a/dGame/dComponents/PropertyEntranceComponent.cpp b/dGame/dComponents/PropertyEntranceComponent.cpp index d2e595cf..1f3b28a2 100644 --- a/dGame/dComponents/PropertyEntranceComponent.cpp +++ b/dGame/dComponents/PropertyEntranceComponent.cpp @@ -17,7 +17,7 @@ #include "ePropertySortType.h" #include "User.h" -PropertyEntranceComponent::PropertyEntranceComponent(Entity* parent, uint32_t componentID) : Component(parent) { +PropertyEntranceComponent::PropertyEntranceComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { this->propertyQueries = {}; auto table = CDClientManager::GetTable(); diff --git a/dGame/dComponents/PropertyEntranceComponent.h b/dGame/dComponents/PropertyEntranceComponent.h index c0e76dcb..1d51d97e 100644 --- a/dGame/dComponents/PropertyEntranceComponent.h +++ b/dGame/dComponents/PropertyEntranceComponent.h @@ -13,7 +13,7 @@ */ class PropertyEntranceComponent final : public Component { public: - explicit PropertyEntranceComponent(Entity* parent, uint32_t componentID); + explicit PropertyEntranceComponent(Entity* parent, const int32_t componentID); static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::PROPERTY_ENTRANCE; /** diff --git a/dGame/dComponents/PropertyManagementComponent.cpp b/dGame/dComponents/PropertyManagementComponent.cpp index c6edbf6d..59b918e6 100644 --- a/dGame/dComponents/PropertyManagementComponent.cpp +++ b/dGame/dComponents/PropertyManagementComponent.cpp @@ -30,7 +30,7 @@ PropertyManagementComponent* PropertyManagementComponent::instance = nullptr; -PropertyManagementComponent::PropertyManagementComponent(Entity* parent) : Component(parent) { +PropertyManagementComponent::PropertyManagementComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { this->owner = LWOOBJID_EMPTY; this->templateId = 0; this->propertyId = LWOOBJID_EMPTY; diff --git a/dGame/dComponents/PropertyManagementComponent.h b/dGame/dComponents/PropertyManagementComponent.h index 708f0f14..c13fe991 100644 --- a/dGame/dComponents/PropertyManagementComponent.h +++ b/dGame/dComponents/PropertyManagementComponent.h @@ -31,7 +31,7 @@ enum class PropertyPrivacyOption { class PropertyManagementComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::PROPERTY_MANAGEMENT; - PropertyManagementComponent(Entity* parent); + PropertyManagementComponent(Entity* parent, const int32_t componentID); static PropertyManagementComponent* Instance(); /** diff --git a/dGame/dComponents/PropertyVendorComponent.cpp b/dGame/dComponents/PropertyVendorComponent.cpp index 502888d5..8b9e79f2 100644 --- a/dGame/dComponents/PropertyVendorComponent.cpp +++ b/dGame/dComponents/PropertyVendorComponent.cpp @@ -10,7 +10,7 @@ #include "PropertyManagementComponent.h" #include "UserManager.h" -PropertyVendorComponent::PropertyVendorComponent(Entity* parent) : Component(parent) { +PropertyVendorComponent::PropertyVendorComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { } void PropertyVendorComponent::OnUse(Entity* originator) { diff --git a/dGame/dComponents/PropertyVendorComponent.h b/dGame/dComponents/PropertyVendorComponent.h index fee8af9c..4865c988 100644 --- a/dGame/dComponents/PropertyVendorComponent.h +++ b/dGame/dComponents/PropertyVendorComponent.h @@ -10,7 +10,7 @@ class PropertyVendorComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::PROPERTY_VENDOR; - explicit PropertyVendorComponent(Entity* parent); + explicit PropertyVendorComponent(Entity* parent, const int32_t componentID); /** * Handles a use event from some entity, if the property is cleared this allows the entity to claim it diff --git a/dGame/dComponents/ProximityMonitorComponent.cpp b/dGame/dComponents/ProximityMonitorComponent.cpp index bd2b65f6..fd3f1b5d 100644 --- a/dGame/dComponents/ProximityMonitorComponent.cpp +++ b/dGame/dComponents/ProximityMonitorComponent.cpp @@ -7,7 +7,7 @@ const std::unordered_set ProximityMonitorComponent::m_EmptyObjectSet = {}; -ProximityMonitorComponent::ProximityMonitorComponent(Entity* parent, int radiusSmall, int radiusLarge) : Component(parent) { +ProximityMonitorComponent::ProximityMonitorComponent(Entity* parent, const int32_t componentID, int radiusSmall, int radiusLarge) : Component(parent, componentID) { if (radiusSmall != -1 && radiusLarge != -1) { SetProximityRadius(radiusSmall, "rocketSmall"); SetProximityRadius(radiusLarge, "rocketLarge"); diff --git a/dGame/dComponents/ProximityMonitorComponent.h b/dGame/dComponents/ProximityMonitorComponent.h index 2de9fca6..b83c0df0 100644 --- a/dGame/dComponents/ProximityMonitorComponent.h +++ b/dGame/dComponents/ProximityMonitorComponent.h @@ -23,7 +23,7 @@ class ProximityMonitorComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::PROXIMITY_MONITOR; - ProximityMonitorComponent(Entity* parentEntity, int smallRadius = -1, int largeRadius = -1); + ProximityMonitorComponent(Entity* parentEntity, const int32_t componentID, int smallRadius = -1, int largeRadius = -1); ~ProximityMonitorComponent() override; void Update(float deltaTime) override; diff --git a/dGame/dComponents/QuickBuildComponent.cpp b/dGame/dComponents/QuickBuildComponent.cpp index f1a54394..4f167c17 100644 --- a/dGame/dComponents/QuickBuildComponent.cpp +++ b/dGame/dComponents/QuickBuildComponent.cpp @@ -23,7 +23,7 @@ #include "CppScripts.h" -QuickBuildComponent::QuickBuildComponent(Entity* const entity) : Component{ entity } { +QuickBuildComponent::QuickBuildComponent(Entity* const entity, const int32_t componentID) : Component{ entity, componentID } { std::u16string checkPreconditions = entity->GetVar(u"CheckPrecondition"); if (!checkPreconditions.empty()) { diff --git a/dGame/dComponents/QuickBuildComponent.h b/dGame/dComponents/QuickBuildComponent.h index b220fecd..8f5f1773 100644 --- a/dGame/dComponents/QuickBuildComponent.h +++ b/dGame/dComponents/QuickBuildComponent.h @@ -24,7 +24,7 @@ class QuickBuildComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::QUICK_BUILD; - QuickBuildComponent(Entity* const entity); + QuickBuildComponent(Entity* const entity, const int32_t componentID); ~QuickBuildComponent() override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; diff --git a/dGame/dComponents/RacingControlComponent.cpp b/dGame/dComponents/RacingControlComponent.cpp index 5f2373f9..8749d64e 100644 --- a/dGame/dComponents/RacingControlComponent.cpp +++ b/dGame/dComponents/RacingControlComponent.cpp @@ -32,8 +32,8 @@ #define M_PI 3.14159265358979323846264338327950288 #endif -RacingControlComponent::RacingControlComponent(Entity* parent) - : Component(parent) { +RacingControlComponent::RacingControlComponent(Entity* parent, const int32_t componentID) + : Component(parent, componentID) { m_PathName = u"MainPath"; m_NumberOfLaps = 3; m_RemainingLaps = m_NumberOfLaps; diff --git a/dGame/dComponents/RacingControlComponent.h b/dGame/dComponents/RacingControlComponent.h index b9f21498..a1e99e29 100644 --- a/dGame/dComponents/RacingControlComponent.h +++ b/dGame/dComponents/RacingControlComponent.h @@ -108,7 +108,7 @@ class RacingControlComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::RACING_CONTROL; - RacingControlComponent(Entity* parentEntity); + RacingControlComponent(Entity* parentEntity, const int32_t componentID); ~RacingControlComponent(); void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; diff --git a/dGame/dComponents/RacingSoundTriggerComponent.h b/dGame/dComponents/RacingSoundTriggerComponent.h index 140bbe20..16380f85 100644 --- a/dGame/dComponents/RacingSoundTriggerComponent.h +++ b/dGame/dComponents/RacingSoundTriggerComponent.h @@ -9,7 +9,7 @@ class Entity; class RacingSoundTriggerComponent : public SoundTriggerComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::RACING_SOUND_TRIGGER; - RacingSoundTriggerComponent(Entity* parent) : SoundTriggerComponent(parent){}; + RacingSoundTriggerComponent(Entity* parent, const int32_t componentID) : SoundTriggerComponent(parent, componentID){}; }; #endif //!__RACINGSOUNDTRIGGERCOMPONENT__H__ diff --git a/dGame/dComponents/RacingStatsComponent.h b/dGame/dComponents/RacingStatsComponent.h index ad1b35bf..c73a9dfa 100644 --- a/dGame/dComponents/RacingStatsComponent.h +++ b/dGame/dComponents/RacingStatsComponent.h @@ -8,7 +8,7 @@ class RacingStatsComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::RACING_STATS; - RacingStatsComponent(Entity* parent) : Component(parent) {} + RacingStatsComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {} }; #endif //!__RACINGSTATSCOMPONENT__H__ diff --git a/dGame/dComponents/RailActivatorComponent.cpp b/dGame/dComponents/RailActivatorComponent.cpp index f269da49..8ef05075 100644 --- a/dGame/dComponents/RailActivatorComponent.cpp +++ b/dGame/dComponents/RailActivatorComponent.cpp @@ -11,9 +11,8 @@ #include "EntityManager.h" #include "eStateChangeType.h" -RailActivatorComponent::RailActivatorComponent(Entity* parent, int32_t componentID) : Component(parent) { - m_ComponentID = componentID; - const auto tableData = CDClientManager::GetTable()->GetEntryByID(componentID);; +RailActivatorComponent::RailActivatorComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { + const auto tableData = CDClientManager::GetTable()->GetEntryByID(componentID); m_Path = parent->GetVar(u"rail_path"); m_PathDirection = parent->GetVar(u"rail_path_direction"); diff --git a/dGame/dComponents/RailActivatorComponent.h b/dGame/dComponents/RailActivatorComponent.h index 015f36b7..c3e5d392 100644 --- a/dGame/dComponents/RailActivatorComponent.h +++ b/dGame/dComponents/RailActivatorComponent.h @@ -12,7 +12,7 @@ */ class RailActivatorComponent final : public Component { public: - explicit RailActivatorComponent(Entity* parent, int32_t componentID); + explicit RailActivatorComponent(Entity* parent, const int32_t componentID); ~RailActivatorComponent() override; static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::RAIL_ACTIVATOR; @@ -37,12 +37,6 @@ public: */ void OnCancelRailMovement(Entity* originator); private: - - /** - * The ID of this component in the components database - */ - int32_t m_ComponentID; - /** * The entities that are currently traversing the rail */ diff --git a/dGame/dComponents/RenderComponent.cpp b/dGame/dComponents/RenderComponent.cpp index cc5974a1..f30648cc 100644 --- a/dGame/dComponents/RenderComponent.cpp +++ b/dGame/dComponents/RenderComponent.cpp @@ -16,12 +16,12 @@ std::unordered_map RenderComponent::m_DurationCache{}; -RenderComponent::RenderComponent(Entity* const parentEntity, const int32_t componentId) : Component{ parentEntity } { +RenderComponent::RenderComponent(Entity* const parentEntity, const int32_t componentID) : Component{ parentEntity, componentID } { m_LastAnimationName = ""; - if (componentId == -1) return; + if (componentID == -1) return; auto query = CDClientDatabase::CreatePreppedStmt("SELECT * FROM RenderComponent WHERE id = ?;"); - query.bind(1, componentId); + query.bind(1, componentID); auto result = query.execQuery(); if (!result.eof()) { diff --git a/dGame/dComponents/RenderComponent.h b/dGame/dComponents/RenderComponent.h index e2bbcff5..c7dedcc6 100644 --- a/dGame/dComponents/RenderComponent.h +++ b/dGame/dComponents/RenderComponent.h @@ -63,7 +63,7 @@ class RenderComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::RENDER; - RenderComponent(Entity* const parentEntity, const int32_t componentId = -1); + RenderComponent(Entity* const parentEntity, const int32_t componentID); void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; void Update(float deltaTime) override; diff --git a/dGame/dComponents/RigidbodyPhantomPhysicsComponent.cpp b/dGame/dComponents/RigidbodyPhantomPhysicsComponent.cpp index 60b816f8..8f8df0ad 100644 --- a/dGame/dComponents/RigidbodyPhantomPhysicsComponent.cpp +++ b/dGame/dComponents/RigidbodyPhantomPhysicsComponent.cpp @@ -13,7 +13,7 @@ #include "EntityInfo.h" #include "Amf3.h" -RigidbodyPhantomPhysicsComponent::RigidbodyPhantomPhysicsComponent(Entity* parent, int32_t componentId) : PhysicsComponent(parent, componentId) { +RigidbodyPhantomPhysicsComponent::RigidbodyPhantomPhysicsComponent(Entity* parent, const int32_t componentID) : PhysicsComponent(parent, componentID) { RegisterMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, this, &RigidbodyPhantomPhysicsComponent::OnGetObjectReportInfo); m_Position = m_Parent->GetDefaultPosition(); diff --git a/dGame/dComponents/RigidbodyPhantomPhysicsComponent.h b/dGame/dComponents/RigidbodyPhantomPhysicsComponent.h index 1ad19846..dc18da49 100644 --- a/dGame/dComponents/RigidbodyPhantomPhysicsComponent.h +++ b/dGame/dComponents/RigidbodyPhantomPhysicsComponent.h @@ -21,7 +21,7 @@ class RigidbodyPhantomPhysicsComponent : public PhysicsComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::RIGID_BODY_PHANTOM_PHYSICS; - RigidbodyPhantomPhysicsComponent(Entity* parent, int32_t componentId); + RigidbodyPhantomPhysicsComponent(Entity* parent, const int32_t componentID); void Update(const float deltaTime) override; diff --git a/dGame/dComponents/RocketLaunchpadControlComponent.cpp b/dGame/dComponents/RocketLaunchpadControlComponent.cpp index 746a161a..ec6305b0 100644 --- a/dGame/dComponents/RocketLaunchpadControlComponent.cpp +++ b/dGame/dComponents/RocketLaunchpadControlComponent.cpp @@ -20,10 +20,10 @@ #include "ServiceType.h" #include "MessageType/Master.h" -RocketLaunchpadControlComponent::RocketLaunchpadControlComponent(Entity* parent, int rocketId) : Component(parent) { +RocketLaunchpadControlComponent::RocketLaunchpadControlComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { auto query = CDClientDatabase::CreatePreppedStmt( "SELECT targetZone, defaultZoneID, targetScene, altLandingPrecondition, altLandingSpawnPointName FROM RocketLaunchpadControlComponent WHERE id = ?;"); - query.bind(1, rocketId); + query.bind(1, componentID); auto result = query.execQuery(); diff --git a/dGame/dComponents/RocketLaunchpadControlComponent.h b/dGame/dComponents/RocketLaunchpadControlComponent.h index 03d2f141..def3bacd 100644 --- a/dGame/dComponents/RocketLaunchpadControlComponent.h +++ b/dGame/dComponents/RocketLaunchpadControlComponent.h @@ -20,7 +20,7 @@ class RocketLaunchpadControlComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::ROCKET_LAUNCH; - RocketLaunchpadControlComponent(Entity* parent, int rocketId); + RocketLaunchpadControlComponent(Entity* parent, const int32_t componentID); ~RocketLaunchpadControlComponent() override; /** diff --git a/dGame/dComponents/ScriptComponent.cpp b/dGame/dComponents/ScriptComponent.cpp index b3be186e..c9123377 100644 --- a/dGame/dComponents/ScriptComponent.cpp +++ b/dGame/dComponents/ScriptComponent.cpp @@ -8,9 +8,8 @@ #include "GameMessages.h" #include "Amf3.h" -ScriptComponent::ScriptComponent(Entity* parent, const std::string& scriptName, bool serialized, bool client) : Component(parent) { +ScriptComponent::ScriptComponent(Entity* parent, const int32_t componentID, const std::string& scriptName, bool serialized, bool client) : Component(parent, componentID) { using namespace GameMessages; - m_Serialized = serialized; m_Client = client; m_ScriptName = scriptName; diff --git a/dGame/dComponents/ScriptComponent.h b/dGame/dComponents/ScriptComponent.h index e73f7ef1..cf4b2537 100644 --- a/dGame/dComponents/ScriptComponent.h +++ b/dGame/dComponents/ScriptComponent.h @@ -21,7 +21,7 @@ class ScriptComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SCRIPT; - ScriptComponent(Entity* parent, const std::string& scriptName, bool serialized, bool client = false); + ScriptComponent(Entity* parent, const int32_t componentID, const std::string& scriptName, bool serialized, bool client = false); void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; diff --git a/dGame/dComponents/ScriptedActivityComponent.h b/dGame/dComponents/ScriptedActivityComponent.h index 308a0a86..5cd7d956 100644 --- a/dGame/dComponents/ScriptedActivityComponent.h +++ b/dGame/dComponents/ScriptedActivityComponent.h @@ -9,7 +9,7 @@ class Entity; class ScriptedActivityComponent final : public ActivityComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SCRIPTED_ACTIVITY; - ScriptedActivityComponent(Entity* parent, int activityID) : ActivityComponent(parent, activityID){}; + ScriptedActivityComponent(Entity* parent, const int32_t componentID) : ActivityComponent(parent, componentID){}; }; #endif //!__SCRIPTEDACTIVITYCOMPONENT__H__ diff --git a/dGame/dComponents/ShootingGalleryComponent.cpp b/dGame/dComponents/ShootingGalleryComponent.cpp index 34f3693b..974867d4 100644 --- a/dGame/dComponents/ShootingGalleryComponent.cpp +++ b/dGame/dComponents/ShootingGalleryComponent.cpp @@ -2,7 +2,7 @@ #include "EntityManager.h" #include "ScriptedActivityComponent.h" -ShootingGalleryComponent::ShootingGalleryComponent(Entity* parent, int32_t activityID) : ActivityComponent(parent, activityID) { +ShootingGalleryComponent::ShootingGalleryComponent(Entity* parent, const int32_t componentID) : ActivityComponent(parent, componentID) { } void ShootingGalleryComponent::SetStaticParams(const StaticShootingGalleryParams& params) { diff --git a/dGame/dComponents/ShootingGalleryComponent.h b/dGame/dComponents/ShootingGalleryComponent.h index b6c5a9ba..622189a6 100644 --- a/dGame/dComponents/ShootingGalleryComponent.h +++ b/dGame/dComponents/ShootingGalleryComponent.h @@ -76,7 +76,7 @@ class ShootingGalleryComponent final : public ActivityComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SHOOTING_GALLERY; - explicit ShootingGalleryComponent(Entity* parent, int32_t activityID); + explicit ShootingGalleryComponent(Entity* parent, const int32_t componentID); void Serialize(RakNet::BitStream& outBitStream, bool isInitialUpdate) override; /** diff --git a/dGame/dComponents/SimplePhysicsComponent.cpp b/dGame/dComponents/SimplePhysicsComponent.cpp index 3651c10f..c0efa544 100644 --- a/dGame/dComponents/SimplePhysicsComponent.cpp +++ b/dGame/dComponents/SimplePhysicsComponent.cpp @@ -15,7 +15,7 @@ #include "StringifiedEnum.h" #include "Amf3.h" -SimplePhysicsComponent::SimplePhysicsComponent(Entity* parent, int32_t componentID) : PhysicsComponent(parent, componentID) { +SimplePhysicsComponent::SimplePhysicsComponent(Entity* parent, const int32_t componentID) : PhysicsComponent(parent, componentID) { RegisterMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, this, &SimplePhysicsComponent::OnGetObjectReportInfo); m_Position = m_Parent->GetDefaultPosition(); diff --git a/dGame/dComponents/SimplePhysicsComponent.h b/dGame/dComponents/SimplePhysicsComponent.h index f8db9197..d928e489 100644 --- a/dGame/dComponents/SimplePhysicsComponent.h +++ b/dGame/dComponents/SimplePhysicsComponent.h @@ -30,7 +30,7 @@ class SimplePhysicsComponent : public PhysicsComponent { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SIMPLE_PHYSICS; - SimplePhysicsComponent(Entity* parent, int32_t componentID); + SimplePhysicsComponent(Entity* parent, const int32_t componentID); ~SimplePhysicsComponent() override; void Update(const float deltaTime) override; diff --git a/dGame/dComponents/SkillComponent.cpp b/dGame/dComponents/SkillComponent.cpp index 4909b43f..5ac27bb6 100644 --- a/dGame/dComponents/SkillComponent.cpp +++ b/dGame/dComponents/SkillComponent.cpp @@ -489,7 +489,7 @@ void SkillComponent::HandleUnCast(const uint32_t behaviorId, const LWOOBJID targ behavior->UnCast(&context, { target }); } -SkillComponent::SkillComponent(Entity* parent) : Component(parent) { +SkillComponent::SkillComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { this->m_skillUid = 0; } diff --git a/dGame/dComponents/SkillComponent.h b/dGame/dComponents/SkillComponent.h index a5483ee5..84c3b277 100644 --- a/dGame/dComponents/SkillComponent.h +++ b/dGame/dComponents/SkillComponent.h @@ -63,7 +63,7 @@ class SkillComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SKILL; - explicit SkillComponent(Entity* parent); + explicit SkillComponent(Entity* parent, const int32_t componentID); ~SkillComponent() override; void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; diff --git a/dGame/dComponents/SoundTriggerComponent.cpp b/dGame/dComponents/SoundTriggerComponent.cpp index 878ce848..384aeae2 100644 --- a/dGame/dComponents/SoundTriggerComponent.cpp +++ b/dGame/dComponents/SoundTriggerComponent.cpp @@ -25,7 +25,7 @@ void MixerProgram::Serialize(RakNet::BitStream& outBitStream){ outBitStream.Write(name.c_str(), name.size()); outBitStream.Write(result); } -SoundTriggerComponent::SoundTriggerComponent(Entity* parent) : Component(parent) { +SoundTriggerComponent::SoundTriggerComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { const auto musicCueName = parent->GetVar(u"NDAudioMusicCue_Name"); if (!musicCueName.empty()) { diff --git a/dGame/dComponents/SoundTriggerComponent.h b/dGame/dComponents/SoundTriggerComponent.h index 2851aff1..3089784b 100644 --- a/dGame/dComponents/SoundTriggerComponent.h +++ b/dGame/dComponents/SoundTriggerComponent.h @@ -60,7 +60,7 @@ struct MixerProgram { class SoundTriggerComponent : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SOUND_TRIGGER; - explicit SoundTriggerComponent(Entity* parent); + explicit SoundTriggerComponent(Entity* parent, const int32_t componentID); void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; void ActivateMusicCue(const std::string& name, float bordemTime = -1.0); void DeactivateMusicCue(const std::string& name); diff --git a/dGame/dComponents/SwitchComponent.cpp b/dGame/dComponents/SwitchComponent.cpp index 4f48fb46..6406690a 100644 --- a/dGame/dComponents/SwitchComponent.cpp +++ b/dGame/dComponents/SwitchComponent.cpp @@ -6,7 +6,7 @@ std::vector SwitchComponent::petSwitches; -SwitchComponent::SwitchComponent(Entity* parent) : Component(parent) { +SwitchComponent::SwitchComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { m_Active = false; m_ResetTime = m_Parent->GetVarAs(u"switch_reset_time"); diff --git a/dGame/dComponents/SwitchComponent.h b/dGame/dComponents/SwitchComponent.h index 49819481..ecbdeb73 100644 --- a/dGame/dComponents/SwitchComponent.h +++ b/dGame/dComponents/SwitchComponent.h @@ -18,7 +18,7 @@ class SwitchComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SWITCH; - SwitchComponent(Entity* parent); + SwitchComponent(Entity* parent, const int32_t componentID); ~SwitchComponent() override; void Update(float deltaTime) override; diff --git a/dGame/dComponents/TriggerComponent.cpp b/dGame/dComponents/TriggerComponent.cpp index f2d47716..e0bd1c4c 100644 --- a/dGame/dComponents/TriggerComponent.cpp +++ b/dGame/dComponents/TriggerComponent.cpp @@ -19,7 +19,7 @@ #include -TriggerComponent::TriggerComponent(Entity* parent, const std::string triggerInfo) : Component(parent) { +TriggerComponent::TriggerComponent(Entity* parent, const int32_t componentID, const std::string triggerInfo) : Component(parent, componentID) { m_Parent = parent; m_Trigger = nullptr; diff --git a/dGame/dComponents/TriggerComponent.h b/dGame/dComponents/TriggerComponent.h index 94a7682e..6a45168d 100644 --- a/dGame/dComponents/TriggerComponent.h +++ b/dGame/dComponents/TriggerComponent.h @@ -9,7 +9,7 @@ class TriggerComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::TRIGGER; - explicit TriggerComponent(Entity* parent, const std::string triggerInfo); + explicit TriggerComponent(Entity* parent, const int32_t componentID, const std::string triggerInfo); void TriggerEvent(eTriggerEventType event, Entity* optionalTarget = nullptr); LUTriggers::Trigger* GetTrigger() const { return m_Trigger; } diff --git a/dGame/dComponents/VendorComponent.cpp b/dGame/dComponents/VendorComponent.cpp index b6c89a50..55808e73 100644 --- a/dGame/dComponents/VendorComponent.cpp +++ b/dGame/dComponents/VendorComponent.cpp @@ -14,7 +14,7 @@ #include "UserManager.h" #include "CheatDetection.h" -VendorComponent::VendorComponent(Entity* parent) : Component(parent) { +VendorComponent::VendorComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { m_HasStandardCostItems = false; m_HasMultiCostItems = false; SetupConstants(); diff --git a/dGame/dComponents/VendorComponent.h b/dGame/dComponents/VendorComponent.h index 432f1801..733ab3f0 100644 --- a/dGame/dComponents/VendorComponent.h +++ b/dGame/dComponents/VendorComponent.h @@ -21,7 +21,7 @@ struct SoldItem { class VendorComponent : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::VENDOR; - VendorComponent(Entity* parent); + VendorComponent(Entity* parent, const int32_t componentID); void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; diff --git a/dScripts/02_server/Map/AM/WanderingVendor.cpp b/dScripts/02_server/Map/AM/WanderingVendor.cpp index d6bb3247..4a224d25 100644 --- a/dScripts/02_server/Map/AM/WanderingVendor.cpp +++ b/dScripts/02_server/Map/AM/WanderingVendor.cpp @@ -17,7 +17,7 @@ void WanderingVendor::OnProximityUpdate(Entity* self, Entity* entering, std::str self->CancelTimer("startWalking"); } else if (status == "LEAVE") { auto* proximityMonitorComponent = self->GetComponent(); - if (!proximityMonitorComponent) self->AddComponent(); + if (!proximityMonitorComponent) self->AddComponent(-1); const auto proxObjs = proximityMonitorComponent->GetProximityObjects("playermonitor"); bool foundPlayer = false; diff --git a/dScripts/NtFactionSpyServer.cpp b/dScripts/NtFactionSpyServer.cpp index 2d88e4ea..532c128a 100644 --- a/dScripts/NtFactionSpyServer.cpp +++ b/dScripts/NtFactionSpyServer.cpp @@ -15,7 +15,7 @@ void NtFactionSpyServer::OnStartup(Entity* self) { // Set the proximity to sense later auto* proximityMonitor = self->GetComponent(); if (proximityMonitor == nullptr) { - proximityMonitor = self->AddComponent(-1, -1); + proximityMonitor = self->AddComponent(-1, -1, -1); } proximityMonitor->SetProximityRadius(self->GetVar(m_SpyProximityVariable), m_ProximityName); diff --git a/dScripts/ai/AG/AgStromlingProperty.cpp b/dScripts/ai/AG/AgStromlingProperty.cpp index 83f5ab7a..255cec19 100644 --- a/dScripts/ai/AG/AgStromlingProperty.cpp +++ b/dScripts/ai/AG/AgStromlingProperty.cpp @@ -12,5 +12,5 @@ void AgStromlingProperty::OnStartup(Entity* self) { 4 }; - self->AddComponent(movementInfo); + self->AddComponent(-1, movementInfo); } diff --git a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp index b57acd58..ecdacc42 100644 --- a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp +++ b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp @@ -273,7 +273,7 @@ void SGCannon::DoSpawnTimerFunc(Entity* self, const std::string& name) { auto* enemy = Game::entityManager->CreateEntity(info, nullptr, self); - auto* movementAI = enemy->AddComponent(MovementAIInfo{}); + auto* movementAI = enemy->AddComponent(-1, MovementAIInfo{}); auto* simplePhysicsComponent = enemy->GetComponent(); if (simplePhysicsComponent) { simplePhysicsComponent->SetPhysicsMotionState(4); diff --git a/tests/dGameTests/dComponentsTests/DestroyableComponentTests.cpp b/tests/dGameTests/dComponentsTests/DestroyableComponentTests.cpp index d14004ee..cddf06c2 100644 --- a/tests/dGameTests/dComponentsTests/DestroyableComponentTests.cpp +++ b/tests/dGameTests/dComponentsTests/DestroyableComponentTests.cpp @@ -16,7 +16,7 @@ protected: void SetUp() override { SetUpDependencies(); baseEntity = new Entity(15, GameDependenciesTest::info); - destroyableComponent = baseEntity->AddComponent(); + destroyableComponent = baseEntity->AddComponent(-1); // Initialize some values to be not default destroyableComponent->SetMaxHealth(12345.0f); destroyableComponent->SetHealth(23); @@ -39,7 +39,7 @@ protected: TEST_F(DestroyableTest, PlacementNewAddComponentTest) { ASSERT_NE(destroyableComponent, nullptr); ASSERT_EQ(destroyableComponent->GetArmor(), 7); - baseEntity->AddComponent(); + baseEntity->AddComponent(-1); ASSERT_NE(baseEntity->GetComponent(), nullptr); ASSERT_EQ(destroyableComponent->GetArmor(), 0); } @@ -325,7 +325,7 @@ TEST_F(DestroyableTest, DestroyableComponentFactionTest) { TEST_F(DestroyableTest, DestroyableComponentValiditiyTest) { auto* enemyEntity = new Entity(19, info); - enemyEntity->AddComponent()->AddFactionNoLookup(16); + enemyEntity->AddComponent(-1)->AddFactionNoLookup(16); destroyableComponent->AddEnemyFaction(16); EXPECT_TRUE(destroyableComponent->IsEnemy(enemyEntity)); EXPECT_FALSE(destroyableComponent->IsFriend(enemyEntity)); diff --git a/tests/dGameTests/dComponentsTests/SavingTests.cpp b/tests/dGameTests/dComponentsTests/SavingTests.cpp index 7123b698..d9c72193 100644 --- a/tests/dGameTests/dComponentsTests/SavingTests.cpp +++ b/tests/dGameTests/dComponentsTests/SavingTests.cpp @@ -32,7 +32,7 @@ protected: character->_doQuickXMLDataParse(); character->LoadXmlRespawnCheckpoints(); - entity->AddComponent(character.get(), UNASSIGNED_SYSTEM_ADDRESS)->LoadFromXml(entity->GetCharacter()->GetXMLDoc()); + entity->AddComponent(-1, character.get(), UNASSIGNED_SYSTEM_ADDRESS)->LoadFromXml(entity->GetCharacter()->GetXMLDoc()); } void TearDown() override { @@ -61,7 +61,7 @@ TEST_F(SavingTest, CharacterComponentTest) { // Reload the component and character from the now updated xml data const auto prevTotalTime = characterComponent->GetTotalTimePlayed(); character->_doQuickXMLDataParse(); - entity->AddComponent(character.get(), UNASSIGNED_SYSTEM_ADDRESS); + entity->AddComponent(-1, character.get(), UNASSIGNED_SYSTEM_ADDRESS); characterComponent->LoadFromXml(entity->GetCharacter()->GetXMLDoc()); // Check that the buff component is the same as before which means resaving data and loading it back in didn't change anything From b2609ff6cbdc550f6636dd09f16ffea92523d72b Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Fri, 3 Oct 2025 23:07:52 -0700 Subject: [PATCH 19/39] fix: live accurate player flag missions and flag debugging (#1894) * feat: Add component ID to root component object * fix: live accurate player flag missions and flag debugging Tested that the client reflects the correct server progression after a test map and manually setting a flag off. tested that session flags correctly pick up on progression updates * banana --- dGame/Character.h | 3 +++ dGame/dComponents/CharacterComponent.cpp | 26 +++++++++++++++++++++++- dGame/dMission/Mission.cpp | 9 +++++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/dGame/Character.h b/dGame/Character.h index 2c88fd0d..03086de2 100644 --- a/dGame/Character.h +++ b/dGame/Character.h @@ -682,6 +682,9 @@ private: * NOTE: quick as there's no DB lookups */ void DoQuickXMLDataParse(); +public: + const decltype(m_PlayerFlags)& GetPlayerFlags() const { return m_PlayerFlags; } + const decltype(m_SessionFlags)& GetSessionFlags() const { return m_SessionFlags; } }; #endif // CHARACTER_H diff --git a/dGame/dComponents/CharacterComponent.cpp b/dGame/dComponents/CharacterComponent.cpp index bdcf7446..8eaa4103 100644 --- a/dGame/dComponents/CharacterComponent.cpp +++ b/dGame/dComponents/CharacterComponent.cpp @@ -84,6 +84,30 @@ bool CharacterComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) { cmptType.PushDebug("Current Activity Type") = GeneralUtils::ToUnderlying(m_CurrentActivity); cmptType.PushDebug("Property Clone ID") = m_Character->GetPropertyCloneID(); + auto& flagCmptType = reportInfo.info->PushDebug("Player Flag"); + auto& allFlags = flagCmptType.PushDebug("All flags"); + + for (const auto& [id, flagChunk] : m_Character->GetPlayerFlags()) { + const auto base = id * 64; + auto flagChunkCopy = flagChunk; + for (int i = 0; i < 64; i++) { + if (static_cast(flagChunkCopy & 1)) { + const int32_t flagId = base + i; + std::stringstream stream; + stream << "Flag: " << flagId; + allFlags.PushDebug(stream.str()) = ""; + } + flagChunkCopy >>= 1; + } + } + + auto& sessionFlags = flagCmptType.PushDebug("Session Only Flags"); + for (const auto flagId : m_Character->GetSessionFlags()) { + std::stringstream stream; + stream << "Flag: " << flagId; + sessionFlags.PushDebug(stream.str()); + } + return true; } @@ -859,7 +883,7 @@ void CharacterComponent::SendToZone(LWOMAPID zoneId, LWOCLONEID cloneId) const { character->SetZoneID(zoneID); character->SetZoneInstance(zoneInstance); character->SetZoneClone(zoneClone); - + characterComponent->SetLastRocketConfig(u""); characterComponent->AddVisitedLevel(LWOZONEID(zoneID, LWOINSTANCEID_INVALID, zoneClone)); diff --git a/dGame/dMission/Mission.cpp b/dGame/dMission/Mission.cpp index 72641d80..93bf397d 100644 --- a/dGame/dMission/Mission.cpp +++ b/dGame/dMission/Mission.cpp @@ -32,7 +32,7 @@ #include "StringifiedEnum.h" namespace { - std::set g_TestedMissions = {773, 774, 775, 776, 777}; // TODO Figure out why these missions are broken sometimes + std::set g_TestedMissions = { 773, 774, 775, 776, 777 }; // TODO Figure out why these missions are broken sometimes } Mission::Mission(MissionComponent* missionComponent, const uint32_t missionId) { @@ -87,6 +87,7 @@ void Mission::LoadFromXmlDone(const tinyxml2::XMLElement& element) { } void Mission::LoadFromXmlCur(const tinyxml2::XMLElement& element) { + const auto* const character = GetCharacter(); // Start custom XML if (element.Attribute("state") != nullptr) { m_State = static_cast(std::stoul(element.Attribute("state"))); @@ -126,6 +127,12 @@ void Mission::LoadFromXmlCur(const tinyxml2::XMLElement& element) { } curTask->SetUnique(uniques); + } else if (type == eMissionTaskType::PLAYER_FLAG) { + int32_t progress = 0; // Update the progress to not include session flags which are unset between logins + for (const auto flag : curTask->GetAllTargets()) { + if (character->GetPlayerFlag(flag)) progress++; + } + curTask->SetProgress(progress, false); } index++; From 69b1a694a6b1fe153c8a036358df113ef561aa85 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sat, 4 Oct 2025 11:57:16 -0700 Subject: [PATCH 20/39] fix: ignore foreign key checks more (#1895) fixes an issue if you delete users in an earlier build of dlu. --- migrations/dlu/mysql/24_remove_persistent_bit.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/migrations/dlu/mysql/24_remove_persistent_bit.sql b/migrations/dlu/mysql/24_remove_persistent_bit.sql index 2ed84947..5d565a58 100644 --- a/migrations/dlu/mysql/24_remove_persistent_bit.sql +++ b/migrations/dlu/mysql/24_remove_persistent_bit.sql @@ -28,10 +28,12 @@ CREATE TABLE `properties_contents_2` ( `behavior_4` BIGINT DEFAULT 0, `behavior_5` BIGINT DEFAULT 0 ); +SET foreign_key_checks = 0; INSERT INTO `ugc_2` SELECT `id`|0x1000000000000000,`account_id`,`character_id`,`is_optimized`,`lxfml`,`bake_ao`,`filename` FROM `ugc`; INSERT INTO `properties_contents_2` SELECT `id`,`property_id`,`ugc_id`|0x1000000000000000,`lot`,`x`,`y`,`z`,`rx`,`ry`,`rz`,`rw`,`model_name`,`model_description`,`behavior_1`,`behavior_2`,`behavior_3`,`behavior_4`,`behavior_5` FROM `properties_contents`; DROP TABLE `properties_contents`; DROP TABLE `ugc`; RENAME TABLE `properties_contents_2` TO `properties_contents`; RENAME TABLE `ugc_2` TO `ugc`; +SET foreign_key_checks = 1; COMMIT; From 06958cb9cdba6baa1c44afb827aaa2294217b7fd Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sat, 4 Oct 2025 17:25:23 -0700 Subject: [PATCH 21/39] feat: hardcore limit % coins dropped on death (#1898) * feat: hardcore limit % coins dropped on death Update EntityManager.cpp * fix log msg --- dGame/EntityManager.cpp | 2 ++ dGame/EntityManager.h | 2 ++ dGame/dComponents/DestroyableComponent.cpp | 18 ++++++++++++++---- resources/worldconfig.ini | 3 +++ 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/dGame/EntityManager.cpp b/dGame/EntityManager.cpp index 026fc02a..12be2e06 100644 --- a/dGame/EntityManager.cpp +++ b/dGame/EntityManager.cpp @@ -87,6 +87,8 @@ void EntityManager::ReloadConfig() { auto hcXpReduction = Game::config->GetValue("hardcore_uscore_reduction"); m_HardcoreUscoreReduction = hcXpReduction.empty() ? 1.0f : GeneralUtils::TryParse(hcXpReduction).value_or(1.0f); m_HardcoreMode = GetHardcoreDisabledWorlds().contains(Game::zoneManager->GetZoneID().GetMapID()) ? false : m_HardcoreMode; + auto hcCoinKeep = Game::config->GetValue("hardcore_coin_keep"); + m_HardcoreCoinKeep = hcCoinKeep.empty() ? false : GeneralUtils::TryParse(hcCoinKeep).value_or(0.0f); } void EntityManager::Initialize() { diff --git a/dGame/EntityManager.h b/dGame/EntityManager.h index 549c922e..26daf750 100644 --- a/dGame/EntityManager.h +++ b/dGame/EntityManager.h @@ -81,6 +81,7 @@ public: const std::set& GetHardcoreUscoreReducedLots() const { return m_HardcoreUscoreReducedLots; }; const std::set& GetHardcoreUscoreExcludedEnemies() const { return m_HardcoreUscoreExcludedEnemies; }; const std::set& GetHardcoreDisabledWorlds() const { return m_HardcoreDisabledWorlds; }; + float GetHardcoreCoinKeep() const { return m_HardcoreCoinKeep; } // Messaging bool SendMessage(GameMessages::GameMsg& msg) const; @@ -125,6 +126,7 @@ private: std::set m_HardcoreUscoreReducedLots{}; std::set m_HardcoreUscoreExcludedEnemies{}; std::set m_HardcoreDisabledWorlds{}; + float m_HardcoreCoinKeep{}; }; #endif // ENTITYMANAGER_H diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index f86cd8e4..e03efd43 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -786,7 +786,7 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType } } else { //Check if this zone allows coin drops - if (Game::zoneManager->GetPlayerLoseCoinOnDeath()) { + if (Game::zoneManager->GetPlayerLoseCoinOnDeath() && !Game::entityManager->GetHardcoreMode()) { auto* character = m_Parent->GetCharacter(); uint64_t coinsTotal = character->GetCoins(); const uint64_t minCoinsToLose = Game::zoneManager->GetWorldConfig().coinsLostOnDeathMin; @@ -1012,13 +1012,23 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) { //get character: auto* chars = m_Parent->GetCharacter(); if (chars) { - auto coins = chars->GetCoins(); + auto oldCoins = chars->GetCoins(); + // Floor this so there arent coins generated from rounding + auto coins = static_cast(oldCoins * Game::entityManager->GetHardcoreCoinKeep()); + auto coinsToDrop = oldCoins - coins; + LOG("Player had %llu coins, will lose %i coins to have %i", oldCoins, coinsToDrop, coins); //lose all coins: - chars->SetCoins(0, eLootSourceType::NONE); + chars->SetCoins(coins, eLootSourceType::NONE); //drop all coins: - GameMessages::SendDropClientLoot(m_Parent, source, LOT_NULL, coins, m_Parent->GetPosition()); + constexpr auto MAX_TO_DROP_PER_GM = 100'000; + while (coinsToDrop > MAX_TO_DROP_PER_GM) { + LOG("Dropping 100,000, %llu left", coinsToDrop); + GameMessages::SendDropClientLoot(m_Parent, source, LOT_NULL, MAX_TO_DROP_PER_GM, m_Parent->GetPosition()); + coinsToDrop -= MAX_TO_DROP_PER_GM; + } + GameMessages::SendDropClientLoot(m_Parent, source, LOT_NULL, coinsToDrop, m_Parent->GetPosition()); } return; } diff --git a/resources/worldconfig.ini b/resources/worldconfig.ini index 9631183a..38350086 100644 --- a/resources/worldconfig.ini +++ b/resources/worldconfig.ini @@ -99,3 +99,6 @@ hardcore_uscore_excluded_enemies= # Disables hardcore mode for specific worlds, if hardcore is enabled hardcore_disabled_worlds= + +# Keeps this percentage of a players' coins on death in hardcore +hardcore_coin_keep= From 7dbbef81ace80fe6751cf78cebd840c777f2c060 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sat, 4 Oct 2025 18:42:34 -0700 Subject: [PATCH 22/39] fix: regenerated proxy items dont need new ids and fix equip item ids (#1897) * fix: changed item ids not reflected in equipped items * dont do it for proxy items --- dGame/dInventory/Inventory.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dGame/dInventory/Inventory.cpp b/dGame/dInventory/Inventory.cpp index 98e34f8d..db83ac58 100644 --- a/dGame/dInventory/Inventory.cpp +++ b/dGame/dInventory/Inventory.cpp @@ -324,10 +324,14 @@ Inventory::~Inventory() { void Inventory::RegenerateItemIDs() { std::map newItems{}; for (auto* const item : items | std::views::values) { + if (item->GetParent() != LWOOBJID_EMPTY) continue; // temp items dont need new ids + const bool equipped = item->IsEquipped(); + if (equipped) item->UnEquip(); const auto oldID = item->GetId(); const auto newID = item->GenerateID(); LOG("Updating item ID from %llu to %llu", oldID, newID); newItems.insert_or_assign(newID, item); + if (equipped) item->Equip(); } // We don't want to delete the item pointers, we're just moving from map to map From 17d0c45382cc34ff958bdc1cbd0e942aefde13d0 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sat, 4 Oct 2025 20:45:42 -0700 Subject: [PATCH 23/39] fix: why oh why is the aggro radius apart of the enemy (#1899) --- dScripts/02_server/Map/General/ExplodingAsset.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dScripts/02_server/Map/General/ExplodingAsset.cpp b/dScripts/02_server/Map/General/ExplodingAsset.cpp index 788efa11..54ae4207 100644 --- a/dScripts/02_server/Map/General/ExplodingAsset.cpp +++ b/dScripts/02_server/Map/General/ExplodingAsset.cpp @@ -49,7 +49,7 @@ void ExplodingAsset::OnHit(Entity* self, Entity* attacker) { if (!self->GetBoolean(u"bIsHit")) { for (const auto objID : proximityComponent->GetProximityObjects("crateHitters")) { auto* const entity = Game::entityManager->GetEntity(objID); - if (!entity) continue; + if (!entity || !entity->IsPlayer()) continue; auto* const destroyable = entity->GetComponent(); if (destroyable) destroyable->Smash(attacker->GetObjectID()); From 5791c55a9e0b92d143c9d100fb515fb5195eb632 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 5 Oct 2025 00:19:46 -0700 Subject: [PATCH 24/39] =?UTF-8?q?fix:=20the=20exploding=20script=20is=20th?= =?UTF-8?q?e=20most=20amazing=20piece=20of=20code=20i=20have=20ev=E2=80=A6?= =?UTF-8?q?=20(#1900)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: the exploding script is the most amazing piece of code i have ever had the pleasure of working with and has been amazing to work on and translate from lua hahahahahahahahahahwwwwwwwwwwwwwwww草 * Enhance hit detection with proximity object checks Refactor hit handling to include proximity checks for destroyable entities. --- dScripts/02_server/Map/General/ExplodingAsset.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dScripts/02_server/Map/General/ExplodingAsset.cpp b/dScripts/02_server/Map/General/ExplodingAsset.cpp index 54ae4207..bba56f0c 100644 --- a/dScripts/02_server/Map/General/ExplodingAsset.cpp +++ b/dScripts/02_server/Map/General/ExplodingAsset.cpp @@ -49,7 +49,7 @@ void ExplodingAsset::OnHit(Entity* self, Entity* attacker) { if (!self->GetBoolean(u"bIsHit")) { for (const auto objID : proximityComponent->GetProximityObjects("crateHitters")) { auto* const entity = Game::entityManager->GetEntity(objID); - if (!entity || !entity->IsPlayer()) continue; + if (!entity || entity->GetObjectID() != attacker->GetObjectID()) continue; auto* const destroyable = entity->GetComponent(); if (destroyable) destroyable->Smash(attacker->GetObjectID()); From 5d5bce53d0173ea625ce62757b26ec825a91ab36 Mon Sep 17 00:00:00 2001 From: HailStorm32 Date: Sun, 5 Oct 2025 20:09:43 -0700 Subject: [PATCH 25/39] feat: Add configurable restrictions for muted accounts (#1887) * Add configurable restrictions for muted accounts * switched to and updated GetRandomElement * Update config option check * implement cached config values for mute settings and update handlers * Address review * Update dGame/dComponents/PetComponent.cpp Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> * Update dGame/dComponents/PetComponent.cpp Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> * reduce if argument chain --------- Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> --- dCommon/GeneralUtils.h | 5 +-- dGame/UserManager.cpp | 49 ++++++++++++++++++++++++++-- dGame/UserManager.h | 10 ++++++ dGame/dComponents/PetComponent.cpp | 44 +++++++++++++++++++------ dGame/dGameMessages/GameMessages.cpp | 7 ++-- dGame/dUtilities/Mail.cpp | 13 +++++--- resources/sharedconfig.ini | 6 ++++ 7 files changed, 112 insertions(+), 22 deletions(-) diff --git a/dCommon/GeneralUtils.h b/dCommon/GeneralUtils.h index b5915739..828fcee3 100644 --- a/dCommon/GeneralUtils.h +++ b/dCommon/GeneralUtils.h @@ -3,7 +3,7 @@ // C++ #include #include -#include +#include #include #include #include @@ -19,6 +19,7 @@ #include "dPlatforms.h" #include "Game.h" #include "Logger.h" +#include "DluAssert.h" #include @@ -305,7 +306,7 @@ namespace GeneralUtils { template inline Container::value_type GetRandomElement(const Container& container) { DluAssert(!container.empty()); - return container[GenerateRandomNumber(0, container.size() - 1)]; + return container[GenerateRandomNumber(0, container.size() - 1)]; } /** diff --git a/dGame/UserManager.cpp b/dGame/UserManager.cpp index 343ce28d..15fbc2d6 100644 --- a/dGame/UserManager.cpp +++ b/dGame/UserManager.cpp @@ -30,6 +30,7 @@ #include "BitStreamUtils.h" #include "CheatDetection.h" #include "CharacterComponent.h" +#include "dConfig.h" #include "eCharacterVersion.h" UserManager* UserManager::m_Address = nullptr; @@ -92,6 +93,23 @@ void UserManager::Initialize() { StripCR(line); m_PreapprovedNames.push_back(line); } + + // Initialize cached config values and register a handler to update them on config reload + // This avoids repeated lookups into dConfig at runtime. + if (Game::config) { + m_MuteAutoRejectNames = (Game::config->GetValue("mute_auto_reject_names") == "1"); + m_MuteRestrictTrade = (Game::config->GetValue("mute_restrict_trade") == "1"); + m_MuteRestrictMail = (Game::config->GetValue("mute_restrict_mail") == "1"); + + Game::config->AddConfigHandler([this]() { + this->m_MuteAutoRejectNames = (Game::config->GetValue("mute_auto_reject_names") == "1"); + this->m_MuteRestrictTrade = (Game::config->GetValue("mute_restrict_trade") == "1"); + this->m_MuteRestrictMail = (Game::config->GetValue("mute_restrict_mail") == "1"); + }); + } + else { + LOG("Warning: dConfig not initialized before UserManager. Cached config values will not be available."); + } } UserManager::~UserManager() { @@ -301,7 +319,9 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet) inStream.Read(eyes); inStream.Read(mouth); - const auto name = LUWStringName.GetAsString(); + const bool autoRejectNames = this->GetMuteAutoRejectNames() && u->GetIsMuted(); + + const auto name = autoRejectNames ? "" : LUWStringName.GetAsString(); std::string predefinedName = GetPredefinedName(firstNameIndex, middleNameIndex, lastNameIndex); LOT shirtLOT = FindCharShirtID(shirtColor, shirtStyle); @@ -319,6 +339,10 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet) return; } + if (autoRejectNames) { + LOG("AccountID: %i is muted, forcing use of predefined name", u->GetAccountID()); + } + if (name.empty()) { LOG("AccountID: %i is creating a character with predefined name: %s", u->GetAccountID(), predefinedName.c_str()); } else { @@ -369,6 +393,7 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet) //Check to see if our name was pre-approved: bool nameOk = IsNamePreapproved(name); + if (!nameOk && u->GetMaxGMLevel() > eGameMasterLevel::FORUM_MODERATOR) nameOk = true; // If predefined name is invalid, change it to be their object id @@ -448,9 +473,10 @@ void UserManager::RenameCharacter(const SystemAddress& sysAddr, Packet* packet) LUWString LUWStringName; inStream.Read(LUWStringName); - const auto newName = LUWStringName.GetAsString(); + auto newName = LUWStringName.GetAsString(); Character* character = nullptr; + const bool autoRejectNames = this->GetMuteAutoRejectNames() && u->GetIsMuted(); //Check if this user has this character: bool ownsCharacter = CheatDetection::VerifyLwoobjidIsSender( @@ -471,13 +497,30 @@ void UserManager::RenameCharacter(const SystemAddress& sysAddr, Packet* packet) if (!ownsCharacter || !character) { WorldPackets::SendCharacterRenameResponse(sysAddr, eRenameResponse::UNKNOWN_ERROR); } else if (ownsCharacter && character) { + if (autoRejectNames) { + // Create a random preapproved name (fallback to default if none available) + if (!m_FirstNames.empty() && !m_MiddleNames.empty() && !m_LastNames.empty()) { + std::string firstName = GeneralUtils::GetRandomElement(m_FirstNames); + std::string middleName = GeneralUtils::GetRandomElement(m_MiddleNames); + std::string lastName = GeneralUtils::GetRandomElement(m_LastNames); + newName = firstName + middleName + lastName; + } else { + newName = "character" + std::to_string(objectID); + } + } + if (newName == character->GetName()) { WorldPackets::SendCharacterRenameResponse(sysAddr, eRenameResponse::NAME_UNAVAILABLE); return; } if (!Database::Get()->GetCharacterInfo(newName)) { - if (IsNamePreapproved(newName)) { + if (autoRejectNames) { + Database::Get()->SetCharacterName(objectID, newName); + LOG("Character %s auto-renamed to preapproved name %s due to mute", character->GetName().c_str(), newName.c_str()); + WorldPackets::SendCharacterRenameResponse(sysAddr, eRenameResponse::SUCCESS); + UserManager::RequestCharacterList(sysAddr); + } else if (IsNamePreapproved(newName)) { Database::Get()->SetCharacterName(objectID, newName); LOG("Character %s now known as %s", character->GetName().c_str(), newName.c_str()); WorldPackets::SendCharacterRenameResponse(sysAddr, eRenameResponse::SUCCESS); diff --git a/dGame/UserManager.h b/dGame/UserManager.h index 9b4af26e..0244807e 100644 --- a/dGame/UserManager.h +++ b/dGame/UserManager.h @@ -41,6 +41,11 @@ public: size_t GetUserCount() const { return m_Users.size(); } + // Access cached config values + bool GetMuteAutoRejectNames() const { return m_MuteAutoRejectNames; } + bool GetMuteRestrictTrade() const { return m_MuteRestrictTrade; } + bool GetMuteRestrictMail() const { return m_MuteRestrictMail; } + private: static UserManager* m_Address; //Singleton std::map m_Users; @@ -50,6 +55,11 @@ private: std::vector m_MiddleNames; std::vector m_LastNames; std::vector m_PreapprovedNames; + + // Cached config values that can change on config reload + bool m_MuteAutoRejectNames = false; + bool m_MuteRestrictTrade = false; + bool m_MuteRestrictMail = false; }; #endif // USERMANAGER_H diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index 06b7f21c..6fb5eedf 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -10,9 +10,11 @@ #include "InventoryComponent.h" #include "Item.h" #include "MissionComponent.h" +#include "User.h" #include "SwitchComponent.h" #include "DestroyableComponent.h" #include "dpWorld.h" +#include "UserManager.h" #include "PetDigServer.h" #include "ObjectIDManager.h" #include "eUnequippableActiveType.h" @@ -21,6 +23,7 @@ #include "eUseItemResponse.h" #include "ePlayerFlag.h" +#include "GeneralUtils.h" #include "Game.h" #include "dConfig.h" #include "dChatFilter.h" @@ -553,18 +556,29 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) { } void PetComponent::RequestSetPetName(std::u16string name) { + const bool autoRejectNames = UserManager::Instance()->GetMuteAutoRejectNames(); + if (m_Tamer == LWOOBJID_EMPTY) { if (m_Owner != LWOOBJID_EMPTY) { auto* owner = GetOwner(); - m_ModerationStatus = 1; // Pending - m_Name = ""; + // If auto reject names is on, and the user is muted, force use of predefined name + if (autoRejectNames && owner && owner->GetCharacter() && owner->GetCharacter()->GetParentUser()->GetIsMuted()) { + m_ModerationStatus = 2; // Approved + std::string forcedName = "Pet"; + Database::Get()->SetPetNameModerationStatus(m_DatabaseId, IPetNames::Info{ forcedName, static_cast(m_ModerationStatus) }); + GameMessages::SendSetPetName(m_Owner, GeneralUtils::UTF8ToUTF16(m_Name), m_DatabaseId, owner->GetSystemAddress()); + GameMessages::SendSetPetNameModerated(m_Owner, m_DatabaseId, m_ModerationStatus, owner->GetSystemAddress()); + } else { + m_ModerationStatus = 1; // Pending + m_Name = ""; - //Save our pet's new name to the db: - SetPetNameForModeration(GeneralUtils::UTF16ToWTF8(name)); + //Save our pet's new name to the db: + SetPetNameForModeration(GeneralUtils::UTF16ToWTF8(name)); - GameMessages::SendSetPetName(m_Owner, GeneralUtils::UTF8ToUTF16(m_Name), m_DatabaseId, owner->GetSystemAddress()); - GameMessages::SendSetPetNameModerated(m_Owner, m_DatabaseId, m_ModerationStatus, owner->GetSystemAddress()); + GameMessages::SendSetPetName(m_Owner, GeneralUtils::UTF8ToUTF16(m_Name), m_DatabaseId, owner->GetSystemAddress()); + GameMessages::SendSetPetNameModerated(m_Owner, m_DatabaseId, m_ModerationStatus, owner->GetSystemAddress()); + } } return; @@ -586,11 +600,21 @@ void PetComponent::RequestSetPetName(std::u16string name) { return; } - m_ModerationStatus = 1; // Pending - m_Name = ""; + // If auto reject names is on, and the user is muted, force use of predefined name ELSE proceed with normal name check + if (autoRejectNames && tamer->GetCharacter() && tamer->GetCharacter()->GetParentUser()->GetIsMuted()) { + m_ModerationStatus = 2; // Approved + m_Name = ""; + std::string forcedName = "Pet"; - //Save our pet's new name to the db: - SetPetNameForModeration(GeneralUtils::UTF16ToWTF8(name)); + Database::Get()->SetPetNameModerationStatus(m_DatabaseId, IPetNames::Info{ forcedName, static_cast(m_ModerationStatus) }); + LOG("AccountID: %i is muted, forcing use of predefined pet name", tamer->GetCharacter()->GetParentUser()->GetAccountID()); + } else { + m_ModerationStatus = 1; // Pending + m_Name = ""; + + //Save our pet's new name to the db: + SetPetNameForModeration(GeneralUtils::UTF16ToWTF8(name)); + } Game::entityManager->SerializeEntity(m_Parent); diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index c7108e50..47262629 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -3243,12 +3243,13 @@ void GameMessages::SendServerTradeUpdate(LWOOBJID objectId, uint64_t coins, cons void GameMessages::HandleClientTradeRequest(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { // Check if the player has restricted trade access auto* character = entity->GetCharacter(); + const bool restrictTradeOnMute = UserManager::Instance()->GetMuteRestrictTrade(); - if (character->HasPermission(ePermissionMap::RestrictedTradeAccess)) { + if (character->HasPermission(ePermissionMap::RestrictedTradeAccess) || (restrictTradeOnMute && character->GetParentUser()->GetIsMuted())) { // Send a message to the player ChatPackets::SendSystemMessage( sysAddr, - u"This character has restricted trade access." + u"Your character has restricted trade access." ); return; @@ -3264,7 +3265,7 @@ void GameMessages::HandleClientTradeRequest(RakNet::BitStream& inStream, Entity* if (invitee != nullptr && invitee->IsPlayer()) { character = invitee->GetCharacter(); - if (character->HasPermission(ePermissionMap::RestrictedTradeAccess)) { + if (character->HasPermission(ePermissionMap::RestrictedTradeAccess) || (restrictTradeOnMute && character->GetParentUser()->GetIsMuted())) { // Send a message to the player ChatPackets::SendSystemMessage( sysAddr, diff --git a/dGame/dUtilities/Mail.cpp b/dGame/dUtilities/Mail.cpp index 5ab82f61..5f1377e7 100644 --- a/dGame/dUtilities/Mail.cpp +++ b/dGame/dUtilities/Mail.cpp @@ -9,6 +9,7 @@ #include "GeneralUtils.h" #include "Database.h" #include "Game.h" +#include "dConfig.h" #include "dServer.h" #include "Entity.h" #include "Character.h" @@ -28,6 +29,7 @@ #include "ServiceType.h" #include "User.h" #include "StringifiedEnum.h" +#include "UserManager.h" namespace { const std::string DefaultSender = "%[MAIL_SYSTEM_NOTIFICATION]"; @@ -72,7 +74,10 @@ namespace Mail { void SendRequest::Handle() { SendResponse response; auto* character = player->GetCharacter(); - if (character && !(character->HasPermission(ePermissionMap::RestrictedMailAccess) || character->GetParentUser()->GetIsMuted())) { + const bool restrictMailOnMute = UserManager::Instance()->GetMuteRestrictMail() && character->GetParentUser()->GetIsMuted(); + const bool restrictedMailAccess = character->HasPermission(ePermissionMap::RestrictedMailAccess); + + if (character && !(restrictedMailAccess || restrictMailOnMute)) { mailInfo.recipient = std::regex_replace(mailInfo.recipient, std::regex("[^0-9a-zA-Z]+"), ""); auto receiverID = Database::Get()->GetCharacterInfo(mailInfo.recipient); @@ -83,7 +88,7 @@ namespace Mail { } else { uint32_t mailCost = Game::zoneManager->GetWorldConfig().mailBaseFee; uint32_t stackSize = 0; - + auto inventoryComponent = player->GetComponent(); Item* item = nullptr; @@ -123,7 +128,7 @@ namespace Mail { Database::Get()->InsertNewMail(mailInfo); response.status = eSendResponse::Success; - character->SaveXMLToDatabase(); + character->SaveXMLToDatabase(); } else { response.status = eSendResponse::AttachmentNotFound; } @@ -165,7 +170,7 @@ namespace Mail { void DataResponse::Serialize(RakNet::BitStream& bitStream) const { MailLUBitStream::Serialize(bitStream); bitStream.Write(this->throttled); - + bitStream.Write(this->playerMail.size()); bitStream.Write(0); // packing for (const auto& mail : this->playerMail) { diff --git a/resources/sharedconfig.ini b/resources/sharedconfig.ini index 2e004eae..ace0ef55 100644 --- a/resources/sharedconfig.ini +++ b/resources/sharedconfig.ini @@ -74,3 +74,9 @@ database_type=sqlite # Skips the account creation check in master. Used for non-interactive setups. skip_account_creation=0 +# 0 or 1, restrict mail while account is muted +mute_restrict_mail=1 +# 0 or 1, restrict trade while account is muted +mute_restrict_trade=1 +# 0 or 1, automatically reject character and pet names submitted while muted +mute_auto_reject_names=1 From 62ac65c52024460e5aecb54464f552636472f6f5 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 5 Oct 2025 20:13:27 -0700 Subject: [PATCH 26/39] feat: Mission Component debug (#1901) * feat: Mission Component debug * Add player argument to inspect command * Add completion details * Remove unlocalized server string done on client instead --- dCommon/dEnums/eMissionState.h | 5 +- dGame/Entity.cpp | 5 +- dGame/dComponents/CharacterComponent.cpp | 4 +- dGame/dComponents/MissionComponent.cpp | 111 +++++++++++++++ dGame/dComponents/MissionComponent.h | 1 + dGame/dGameMessages/GameMessages.cpp | 1 + dGame/dMission/Mission.cpp | 6 + dGame/dMission/Mission.h | 2 + .../SlashCommands/DEVGMCommands.cpp | 127 +++++++++--------- docs/Commands.md | 2 +- 10 files changed, 197 insertions(+), 67 deletions(-) diff --git a/dCommon/dEnums/eMissionState.h b/dCommon/dEnums/eMissionState.h index e080f455..8c52c439 100644 --- a/dCommon/dEnums/eMissionState.h +++ b/dCommon/dEnums/eMissionState.h @@ -50,7 +50,10 @@ enum class eMissionState : int { /** * The mission has been completed before and has now been completed again. Used for daily missions. */ - COMPLETE_READY_TO_COMPLETE = 12 + COMPLETE_READY_TO_COMPLETE = 12, + + // The mission is failed (don't know where this is used) + FAILED = 16, }; #endif //!__MISSIONSTATE__H__ diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 7a27c1b0..45136774 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -2247,6 +2247,7 @@ bool Entity::MsgRequestServerObjectInfo(GameMessages::GameMsg& msg) { response.Insert("objectID", std::to_string(m_ObjectID)); response.Insert("serverInfo", true); GameMessages::GetObjectReportInfo info{}; + info.bVerbose = requestInfo.bVerbose; info.info = response.InsertArray("data"); auto& objectInfo = info.info->PushDebug("Object Details"); auto* table = CDClientManager::GetTable(); @@ -2260,14 +2261,14 @@ bool Entity::MsgRequestServerObjectInfo(GameMessages::GameMsg& msg) { auto& componentDetails = objectInfo.PushDebug("Component Information"); for (const auto [id, component] : m_Components) { - componentDetails.PushDebug(StringifiedEnum::ToString(id)) = ""; + componentDetails.PushDebug(StringifiedEnum::ToString(id)); } auto& configData = objectInfo.PushDebug("Config Data"); for (const auto config : m_Settings) { configData.PushDebug(GeneralUtils::UTF16ToWTF8(config->GetKey())) = config->GetValueAsString(); - } + HandleMsg(info); auto* client = Game::entityManager->GetEntity(requestInfo.clientId); diff --git a/dGame/dComponents/CharacterComponent.cpp b/dGame/dComponents/CharacterComponent.cpp index 8eaa4103..a0c44961 100644 --- a/dGame/dComponents/CharacterComponent.cpp +++ b/dGame/dComponents/CharacterComponent.cpp @@ -70,7 +70,7 @@ bool CharacterComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) { for (const auto zoneID : m_VisitedLevels) { std::stringstream sstream; sstream << "MapID: " << zoneID.GetMapID() << " CloneID: " << zoneID.GetCloneID(); - vl.PushDebug(sstream.str()) = ""; + vl.PushDebug(sstream.str()); } // visited locations @@ -95,7 +95,7 @@ bool CharacterComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) { const int32_t flagId = base + i; std::stringstream stream; stream << "Flag: " << flagId; - allFlags.PushDebug(stream.str()) = ""; + allFlags.PushDebug(stream.str()); } flagChunkCopy >>= 1; } diff --git a/dGame/dComponents/MissionComponent.cpp b/dGame/dComponents/MissionComponent.cpp index cf9aa767..6c85f908 100644 --- a/dGame/dComponents/MissionComponent.cpp +++ b/dGame/dComponents/MissionComponent.cpp @@ -27,7 +27,10 @@ std::unordered_map> MissionComponent: //! Initializer MissionComponent::MissionComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { + using namespace GameMessages; m_LastUsedMissionOrderUID = Game::zoneManager->GetUniqueMissionIdStartingValue(); + + RegisterMsg(this, &MissionComponent::OnGetObjectReportInfo); } //! Destructor @@ -622,3 +625,111 @@ void MissionComponent::ResetMission(const int32_t missionId) { m_Missions.erase(missionId); GameMessages::SendResetMissions(m_Parent, m_Parent->GetSystemAddress(), missionId); } + +void PushMissions(const std::map& missions, AMFArrayValue& V, bool verbose) { + for (const auto& [id, mission] : missions) { + std::stringstream ss; + if (!mission) { + ss << "Mission ID: " << id; + V.PushDebug(ss.str()); + } else if (!verbose) { + ss << "%[Missions_" << id << "_name]" << ", Mission ID"; + V.PushDebug(ss.str()) = id; + } else { + ss << "%[Missions_" << id << "_name]" << ", Mission ID: " << id; + auto& missionV = V.PushDebug(ss.str()); + auto& missionInformation = missionV.PushDebug("Mission Information"); + + if (mission->IsComplete()) { + missionInformation.PushDebug("Time mission last completed") = std::to_string(mission->GetTimestamp()); + missionInformation.PushDebug("Number of times completed") = mission->GetCompletions(); + } + // Expensive to network this especially when its read from the client anyways + // missionInformation.PushDebug("Description").PushDebug("None"); + // missionInformation.PushDebug("Text").PushDebug("None"); + + auto& statusInfo = missionInformation.PushDebug("Mission statuses for local player"); + if (mission->IsAvalible()) statusInfo.PushDebug("Available"); + if (mission->IsActive()) statusInfo.PushDebug("Active"); + if (mission->IsReadyToComplete()) statusInfo.PushDebug("Ready To Complete"); + if (mission->IsComplete()) statusInfo.PushDebug("Completed"); + if (mission->IsFailed()) statusInfo.PushDebug("Failed"); + const auto& clientInfo = mission->GetClientInfo(); + + statusInfo.PushDebug("Is an achievement mission") = mission->IsAchievement(); + statusInfo.PushDebug("Is an timed mission") = clientInfo.time_limit > 0; + auto& taskInfo = statusInfo.PushDebug("Task Info"); + taskInfo.PushDebug("Number of tasks in this mission") = mission->GetTasks().size(); + int32_t i = 0; + for (const auto* task : mission->GetTasks()) { + auto& thisTask = taskInfo.PushDebug("Task " + std::to_string(i)); + // Expensive to network this especially when its read from the client anyways + // thisTask.PushDebug("Description").PushDebug("%[MissionTasks_" + taskUidStr + "_description]"); + thisTask.PushDebug("Number done") = std::min(task->GetProgress(), static_cast(task->GetClientInfo().targetValue)); + thisTask.PushDebug("Number total needed") = task->GetClientInfo().targetValue; + thisTask.PushDebug("Task Type") = task->GetClientInfo().taskType; + i++; + } + + + // auto& chatText = missionInformation.PushDebug("Chat Text for Mission States"); + // Expensive to network this especially when its read from the client anyways + // chatText.PushDebug("Available Text").PushDebug("%[MissionText_" + idStr + "_chat_state_1]"); + // chatText.PushDebug("Active Text").PushDebug("%[MissionText_" + idStr + "_chat_state_2]"); + // chatText.PushDebug("Ready-to-Complete Text").PushDebug("%[MissionText_" + idStr + "_chat_state_3]"); + // chatText.PushDebug("Complete Text").PushDebug("%[MissionText_" + idStr + "_chat_state_4]"); + + if (clientInfo.time_limit > 0) { + missionInformation.PushDebug("Time Limit") = clientInfo.time_limit; + missionInformation.PushDebug("Time Remaining") = 0; + } + + if (clientInfo.offer_objectID != -1) { + missionInformation.PushDebug("Offer Object LOT") = clientInfo.offer_objectID; + } + + if (clientInfo.target_objectID != -1) { + missionInformation.PushDebug("Complete Object LOT") = clientInfo.target_objectID; + } + + if (!clientInfo.prereqMissionID.empty()) { + missionInformation.PushDebug("Requirement Mission IDs") = clientInfo.prereqMissionID; + } + + missionInformation.PushDebug("Is Repeatable") = clientInfo.repeatable; + const bool hasNoOfferer = clientInfo.offer_objectID == -1 || clientInfo.offer_objectID == 0; + const bool hasNoCompleter = clientInfo.target_objectID == -1 || clientInfo.target_objectID == 0; + missionInformation.PushDebug("Is Achievement") = hasNoOfferer && hasNoCompleter; + } + } +} + +bool MissionComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) { + auto& reportMsg = static_cast(msg); + auto& missionInfo = reportMsg.info->PushDebug("Mission (Laggy)"); + missionInfo.PushDebug("Component ID") = GetComponentID(); + // Sort the missions so they are easier to parse and present to the end user + std::map achievements; + std::map missions; + std::map doneMissions; + for (const auto [id, mission] : m_Missions) { + if (!mission) continue; + else if (mission->IsComplete()) doneMissions[id] = mission; + else if (mission->IsAchievement()) achievements[id] = mission; + else if (mission->IsMission()) missions[id] = mission; + } + + // None of these should be empty, but if they are dont print the field + if (!achievements.empty() || !missions.empty()) { + auto& incompleteMissions = missionInfo.PushDebug("Incomplete Missions"); + PushMissions(achievements, incompleteMissions, reportMsg.bVerbose); + PushMissions(missions, incompleteMissions, reportMsg.bVerbose); + } + + if (!doneMissions.empty()) { + auto& completeMissions = missionInfo.PushDebug("Completed Missions"); + PushMissions(doneMissions, completeMissions, reportMsg.bVerbose); + } + + return true; +} diff --git a/dGame/dComponents/MissionComponent.h b/dGame/dComponents/MissionComponent.h index 28727ecf..53f09d41 100644 --- a/dGame/dComponents/MissionComponent.h +++ b/dGame/dComponents/MissionComponent.h @@ -171,6 +171,7 @@ public: void ResetMission(const int32_t missionId); private: + bool OnGetObjectReportInfo(GameMessages::GameMsg& msg); /** * All the missions owned by this entity, mapped by mission ID */ diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 47262629..08261a23 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -6415,6 +6415,7 @@ namespace GameMessages { void RequestServerObjectInfo::Handle(Entity& entity, const SystemAddress& sysAddr) { auto* handlingEntity = Game::entityManager->GetEntity(targetForReport); if (handlingEntity) handlingEntity->HandleMsg(*this); + else LOG("Failed to find target %llu", targetForReport); } bool RequestUse::Deserialize(RakNet::BitStream& stream) { diff --git a/dGame/dMission/Mission.cpp b/dGame/dMission/Mission.cpp index 93bf397d..3d46cde8 100644 --- a/dGame/dMission/Mission.cpp +++ b/dGame/dMission/Mission.cpp @@ -270,6 +270,12 @@ bool Mission::IsReadyToComplete() const { return m_State == eMissionState::READY_TO_COMPLETE || m_State == eMissionState::COMPLETE_READY_TO_COMPLETE; } +bool Mission::IsFailed() const { + const auto underlying = GeneralUtils::ToUnderlying(m_State); + const auto target = GeneralUtils::ToUnderlying(eMissionState::FAILED); + return (underlying & target) != 0; +} + void Mission::MakeReadyToComplete() { SetMissionState(m_Completions == 0 ? eMissionState::READY_TO_COMPLETE : eMissionState::COMPLETE_READY_TO_COMPLETE); } diff --git a/dGame/dMission/Mission.h b/dGame/dMission/Mission.h index 9526c40a..edd21d09 100644 --- a/dGame/dMission/Mission.h +++ b/dGame/dMission/Mission.h @@ -244,6 +244,8 @@ public: const std::set& GetTestedMissions() const; + bool IsFailed() const; + private: /** * Progresses all the newly accepted tasks for this mission after it has been accepted to reflect the state of the diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp index 447d5155..0e4cbb5b 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp @@ -1475,50 +1475,55 @@ namespace DEVGMCommands { if (splitArgs.empty()) return; Entity* closest = nullptr; + float closestDistance = 0.0f; std::u16string ldf; bool isLDF = false; - auto component = GeneralUtils::TryParse(splitArgs[0]); - if (!component) { - component = eReplicaComponentType::INVALID; + closest = PlayerManager::GetPlayer(splitArgs[0]); + if (!closest) { + auto component = GeneralUtils::TryParse(splitArgs[0]); + if (!component) { + component = eReplicaComponentType::INVALID; - ldf = GeneralUtils::UTF8ToUTF16(splitArgs[0]); + ldf = GeneralUtils::UTF8ToUTF16(splitArgs[0]); - isLDF = true; - } - - auto reference = entity->GetPosition(); - - auto closestDistance = 0.0f; - - const auto candidates = Game::entityManager->GetEntitiesByComponent(component.value()); - - for (auto* candidate : candidates) { - if (candidate->GetLOT() == 1 || candidate->GetLOT() == 8092) { - continue; + isLDF = true; } - if (isLDF && !candidate->HasVar(ldf)) { - continue; - } - - if (!closest) { - closest = candidate; - - closestDistance = NiPoint3::Distance(candidate->GetPosition(), reference); - - continue; - } - - const auto distance = NiPoint3::Distance(candidate->GetPosition(), reference); - - if (distance < closestDistance) { - closest = candidate; - - closestDistance = distance; + auto reference = entity->GetPosition(); + + + const auto candidates = Game::entityManager->GetEntitiesByComponent(component.value()); + + for (auto* candidate : candidates) { + if (candidate->GetLOT() == 1 || candidate->GetLOT() == 8092) { + continue; + } + + if (isLDF && !candidate->HasVar(ldf)) { + continue; + } + + if (!closest) { + closest = candidate; + + closestDistance = NiPoint3::Distance(candidate->GetPosition(), reference); + + continue; + } + + const auto distance = NiPoint3::Distance(candidate->GetPosition(), reference); + + if (distance < closestDistance) { + closest = candidate; + + closestDistance = distance; + } } + } else { + closestDistance = NiPoint3::Distance(entity->GetPosition(), closest->GetPosition()); } if (!closest) return; @@ -1684,7 +1689,7 @@ namespace DEVGMCommands { } const auto splitArgs = GeneralUtils::SplitString(args, ' '); - + // Prevent execute command recursion by checking if this is already an execute command for (const auto& arg : splitArgs) { if (arg == "execute" || arg == "exec") { @@ -1692,51 +1697,51 @@ namespace DEVGMCommands { return; } } - + // Context variables for execution Entity* execEntity = entity; // Entity to execute as NiPoint3 execPosition = entity->GetPosition(); // Position to execute from bool positionOverridden = false; std::string finalCommand; - + // Parse subcommands size_t i = 0; while (i < splitArgs.size()) { const std::string& subcommand = splitArgs[i]; - + if (subcommand == "as") { if (i + 1 >= splitArgs.size()) { ChatPackets::SendSystemMessage(sysAddr, u"Error: 'as' requires a player name"); return; } - + const std::string& targetName = splitArgs[i + 1]; auto* targetPlayer = PlayerManager::GetPlayer(targetName); if (!targetPlayer) { ChatPackets::SendSystemMessage(sysAddr, u"Error: Player '" + GeneralUtils::ASCIIToUTF16(targetName) + u"' not found"); return; } - + execEntity = targetPlayer; i += 2; - + } else if (subcommand == "at") { if (i + 1 >= splitArgs.size()) { ChatPackets::SendSystemMessage(sysAddr, u"Error: 'at' requires a player name"); return; } - + const std::string& targetName = splitArgs[i + 1]; auto* targetPlayer = PlayerManager::GetPlayer(targetName); if (!targetPlayer) { ChatPackets::SendSystemMessage(sysAddr, u"Error: Player '" + GeneralUtils::ASCIIToUTF16(targetName) + u"' not found"); return; } - + execPosition = targetPlayer->GetPosition(); positionOverridden = true; i += 2; - + } else if (subcommand == "positioned") { if (i + 3 >= splitArgs.size()) { ChatPackets::SendSystemMessage(sysAddr, u"Error: 'positioned' requires x, y, z coordinates"); @@ -1754,69 +1759,69 @@ namespace DEVGMCommands { execPosition = NiPoint3(xOpt.value(), yOpt.value(), zOpt.value()); positionOverridden = true; - + i += 4; - + } else if (subcommand == "run") { // Everything after "run" is the command to execute if (i + 1 >= splitArgs.size()) { ChatPackets::SendSystemMessage(sysAddr, u"Error: 'run' requires a command"); return; } - + // Reconstruct the command from remaining args for (size_t j = i + 1; j < splitArgs.size(); ++j) { if (!finalCommand.empty()) finalCommand += " "; finalCommand += splitArgs[j]; } break; - + } else { ChatPackets::SendSystemMessage(sysAddr, u"Error: Unknown subcommand '" + GeneralUtils::ASCIIToUTF16(subcommand) + u"'"); ChatPackets::SendSystemMessage(sysAddr, u"Valid subcommands: as, at, positioned, run"); return; } } - + if (finalCommand.empty()) { ChatPackets::SendSystemMessage(sysAddr, u"Error: No command specified to run. Use 'run ' at the end."); return; } - + // Validate that the command starts with a valid character if (finalCommand.empty() || finalCommand[0] == '/') { ChatPackets::SendSystemMessage(sysAddr, u"Error: Command should not start with '/'. Just specify the command name."); return; } - + // Store original position if we need to restore it NiPoint3 originalPosition; bool needToRestore = false; - + if (positionOverridden && execEntity == entity) { // If we're executing as ourselves but from a different position, // temporarily move the entity originalPosition = entity->GetPosition(); needToRestore = true; - + // Set the position temporarily for the command execution auto* controllable = entity->GetComponent(); if (controllable) { controllable->SetPosition(execPosition); } } - + // Provide feedback about what we're executing std::string execAsName = execEntity->GetCharacter() ? execEntity->GetCharacter()->GetName() : "Unknown"; - ChatPackets::SendSystemMessage(sysAddr, u"[Execute] Running as '" + GeneralUtils::ASCIIToUTF16(execAsName) + - u"' from <" + GeneralUtils::to_u16string(execPosition.x) + u", " + - GeneralUtils::to_u16string(execPosition.y) + u", " + - GeneralUtils::to_u16string(execPosition.z) + u">: /" + - GeneralUtils::ASCIIToUTF16(finalCommand)); - + ChatPackets::SendSystemMessage(sysAddr, u"[Execute] Running as '" + GeneralUtils::ASCIIToUTF16(execAsName) + + u"' from <" + GeneralUtils::to_u16string(execPosition.x) + u", " + + GeneralUtils::to_u16string(execPosition.y) + u", " + + GeneralUtils::to_u16string(execPosition.z) + u">: /" + + GeneralUtils::ASCIIToUTF16(finalCommand)); + // Execute the command through the slash command handler SlashCommandHandler::HandleChatCommand(GeneralUtils::ASCIIToUTF16("/" + finalCommand), execEntity, sysAddr); - + // Restore original position if needed if (needToRestore) { auto* controllable = entity->GetComponent(); diff --git a/docs/Commands.md b/docs/Commands.md index 54d5e531..f9a00452 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -80,7 +80,7 @@ These commands are primarily for development and testing. The usage of many of t |getnavmeshheight|`/getnavmeshheight`|Displays the navmesh height at your current position.|8| |giveuscore|`/giveuscore `|Gives uscore.|8| |gmadditem|`/gmadditem (count)`|Adds the given item to your inventory by id.|8| -|inspect|`/inspect (-m \| -a \| -s \| -p \| -f (faction) \| -t)`|Finds the closest entity with the given component or LDF variable (ignoring players and racing cars), printing its ID, distance from the player, and whether it is sleeping, as well as the the IDs of all components the entity has. See [Detailed `/inspect` Usage](#detailed-inspect-usage) below.|8| +|inspect|`/inspect (-m \| -a \| -s \| -p \| -f (faction) \| -t)`|Finds the closest entity with the given component or LDF variable (ignoring players and racing cars), printing its ID, distance from the player, and whether it is sleeping, as well as the the IDs of all components the entity has. See [Detailed `/inspect` Usage](#detailed-inspect-usage) below.|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.|8| |locrow|`/locrow`|Prints the your current position and rotation information to the console.|8| |lookup|`/lookup `|Searches through the Objects table in the client SQLite database for items whose display name, name, or description contains the query. Query can be multiple words delimited by spaces.|8| From cbdd5d9bc68a76f583184634aa1bf010113a78e1 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 9 Oct 2025 23:15:21 -0700 Subject: [PATCH 27/39] fix: dying while dead (#1905) --- dGame/dComponents/DestroyableComponent.cpp | 3 +++ dGame/dComponents/DestroyableComponent.h | 2 ++ dGame/dGameMessages/GameMessages.cpp | 1 + 3 files changed, 6 insertions(+) diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index e03efd43..49b88cf4 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -694,6 +694,8 @@ void DestroyableComponent::NotifySubscribers(Entity* attacker, uint32_t damage) } void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType, const std::u16string& deathType, uint32_t skillID) { + if (m_IsDead) return; + //check if hardcore mode is enabled if (Game::entityManager->GetHardcoreMode()) { DoHardcoreModeDrops(source); @@ -706,6 +708,7 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType Game::entityManager->SerializeEntity(m_Parent); } + m_IsDead = true; m_KillerID = source; auto* owner = Game::entityManager->GetEntity(source); diff --git a/dGame/dComponents/DestroyableComponent.h b/dGame/dComponents/DestroyableComponent.h index 2984147a..c6bb0a98 100644 --- a/dGame/dComponents/DestroyableComponent.h +++ b/dGame/dComponents/DestroyableComponent.h @@ -471,6 +471,8 @@ public: bool OnGetObjectReportInfo(GameMessages::GameMsg& msg); bool OnSetFaction(GameMessages::GameMsg& msg); + void SetIsDead(const bool value) { m_IsDead = value; } + static Implementation IsEnemyImplentation; static Implementation IsFriendImplentation; diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 08261a23..fffa52b9 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -978,6 +978,7 @@ void GameMessages::SendResurrect(Entity* entity) { auto* destroyableComponent = entity->GetComponent(); if (destroyableComponent != nullptr && entity->GetLOT() == 1) { + destroyableComponent->SetIsDead(false); auto* levelComponent = entity->GetComponent(); if (levelComponent) { int32_t healthToRestore = levelComponent->GetLevel() >= 45 ? 8 : 4; From ce28834dce34ad9e692780de9a93cacadc212f2c Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Fri, 10 Oct 2025 23:07:16 -0500 Subject: [PATCH 28/39] feat: lxfml splitting for bbb (#1877) * LXFML SPLITTING Included test file * move base to global namespace * wip need to test * update last fixes * update world sending bbb to be more efficient * Address feedback form Emo in doscord * Make LXFML class for robust and add more tests to edge cases and malformed data * get rid of the string copy and make the deep clone have a recursive limit * cleanup tests * fix test file locations * fix file path * KISS * add cmakelists * fix typos * NL @ EOF * tabs and split out to func * naming standard --- dCommon/Lxfml.cpp | 354 +++++++++++++++++- dCommon/Lxfml.h | 2 + dGame/dGameMessages/GameMessages.cpp | 179 ++++----- dWorldServer/WorldServer.cpp | 34 +- tests/dCommonTests/CMakeLists.txt | 3 + .../LxfmlTestFiles/CMakeLists.txt | 18 + .../LxfmlTestFiles/deeply_nested.lxfml | 50 +++ .../LxfmlTestFiles/empty_transform.lxfml | 11 + .../LxfmlTestFiles/invalid_transform.lxfml | 20 + .../mixed_invalid_transform.lxfml | 11 + .../LxfmlTestFiles/mixed_valid_invalid.lxfml | 44 +++ .../LxfmlTestFiles/no_bricks.lxfml | 4 + .../non_numeric_transform.lxfml | 11 + tests/dCommonTests/LxfmlTestFiles/test.lxfml | 336 +++++++++++++++++ .../LxfmlTestFiles/too_few_values.lxfml | 11 + tests/dCommonTests/LxfmlTests.cpp | 297 +++++++++++++++ 16 files changed, 1267 insertions(+), 118 deletions(-) create mode 100644 tests/dCommonTests/LxfmlTestFiles/CMakeLists.txt create mode 100644 tests/dCommonTests/LxfmlTestFiles/deeply_nested.lxfml create mode 100644 tests/dCommonTests/LxfmlTestFiles/empty_transform.lxfml create mode 100644 tests/dCommonTests/LxfmlTestFiles/invalid_transform.lxfml create mode 100644 tests/dCommonTests/LxfmlTestFiles/mixed_invalid_transform.lxfml create mode 100644 tests/dCommonTests/LxfmlTestFiles/mixed_valid_invalid.lxfml create mode 100644 tests/dCommonTests/LxfmlTestFiles/no_bricks.lxfml create mode 100644 tests/dCommonTests/LxfmlTestFiles/non_numeric_transform.lxfml create mode 100644 tests/dCommonTests/LxfmlTestFiles/test.lxfml create mode 100644 tests/dCommonTests/LxfmlTestFiles/too_few_values.lxfml create mode 100644 tests/dCommonTests/LxfmlTests.cpp diff --git a/dCommon/Lxfml.cpp b/dCommon/Lxfml.cpp index e71b2d8e..fd71be4c 100644 --- a/dCommon/Lxfml.cpp +++ b/dCommon/Lxfml.cpp @@ -5,13 +5,43 @@ #include "TinyXmlUtils.h" #include +#include +#include +#include +#include + +namespace { + // The base LXFML xml file to use when creating new models. + std::string g_base = R"( + + + + + + + + + + + + + + +)"; +} Lxfml::Result Lxfml::NormalizePosition(const std::string_view data, const NiPoint3& curPosition) { Result toReturn; + + // Handle empty or invalid input + if (data.empty()) { + return toReturn; + } + tinyxml2::XMLDocument doc; - const auto err = doc.Parse(data.data()); + // Use length-based parsing to avoid expensive string copy + const auto err = doc.Parse(data.data(), data.size()); if (err != tinyxml2::XML_SUCCESS) { - LOG("Failed to parse xml %s.", StringifiedEnum::ToString(err).data()); return toReturn; } @@ -20,7 +50,6 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data, const NiPoin auto lxfml = reader["LXFML"]; if (!lxfml) { - LOG("Failed to find LXFML element."); return toReturn; } @@ -49,16 +78,19 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data, const NiPoin // Calculate the lowest and highest points on the entire model for (const auto& transformation : transformations | std::views::values) { auto split = GeneralUtils::SplitString(transformation, ','); - if (split.size() < 12) { - LOG("Not enough in the split?"); - continue; - } - - auto x = GeneralUtils::TryParse(split[9]).value(); - auto y = GeneralUtils::TryParse(split[10]).value(); - auto z = GeneralUtils::TryParse(split[11]).value(); - if (x < lowest.x) lowest.x = x; - if (y < lowest.y) lowest.y = y; + if (split.size() < 12) continue; + + auto xOpt = GeneralUtils::TryParse(split[9]); + auto yOpt = GeneralUtils::TryParse(split[10]); + auto zOpt = GeneralUtils::TryParse(split[11]); + + if (!xOpt.has_value() || !yOpt.has_value() || !zOpt.has_value()) continue; + + auto x = xOpt.value(); + auto y = yOpt.value(); + auto z = zOpt.value(); + if (x < lowest.x) lowest.x = x; + if (y < lowest.y) lowest.y = y; if (z < lowest.z) lowest.z = z; if (highest.x < x) highest.x = x; @@ -87,13 +119,19 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data, const NiPoin for (auto& transformation : transformations | std::views::values) { auto split = GeneralUtils::SplitString(transformation, ','); if (split.size() < 12) { - LOG("Not enough in the split?"); continue; } - auto x = GeneralUtils::TryParse(split[9]).value() - newRootPos.x + curPosition.x; - auto y = GeneralUtils::TryParse(split[10]).value() - newRootPos.y + curPosition.y; - auto z = GeneralUtils::TryParse(split[11]).value() - newRootPos.z + curPosition.z; + auto xOpt = GeneralUtils::TryParse(split[9]); + auto yOpt = GeneralUtils::TryParse(split[10]); + auto zOpt = GeneralUtils::TryParse(split[11]); + + if (!xOpt.has_value() || !yOpt.has_value() || !zOpt.has_value()) { + continue; + } + auto x = xOpt.value() - newRootPos.x + curPosition.x; + auto y = yOpt.value() - newRootPos.y + curPosition.y; + auto z = zOpt.value() - newRootPos.z + curPosition.z; std::stringstream stream; for (int i = 0; i < 9; i++) { stream << split[i]; @@ -128,3 +166,285 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data, const NiPoin toReturn.center = newRootPos; return toReturn; } + +// Deep-clone an XMLElement (attributes, text, and child elements) into a target document +// with maximum depth protection to prevent infinite loops +static tinyxml2::XMLElement* CloneElementDeep(const tinyxml2::XMLElement* src, tinyxml2::XMLDocument& dstDoc, int maxDepth = 100) { + if (!src || maxDepth <= 0) return nullptr; + auto* dst = dstDoc.NewElement(src->Name()); + + // copy attributes + for (const tinyxml2::XMLAttribute* attr = src->FirstAttribute(); attr; attr = attr->Next()) { + dst->SetAttribute(attr->Name(), attr->Value()); + } + + // copy children (elements and text) + for (const tinyxml2::XMLNode* child = src->FirstChild(); child; child = child->NextSibling()) { + if (const tinyxml2::XMLElement* childElem = child->ToElement()) { + // Recursively clone child elements with decremented depth + auto* clonedChild = CloneElementDeep(childElem, dstDoc, maxDepth - 1); + if (clonedChild) dst->InsertEndChild(clonedChild); + } else if (const tinyxml2::XMLText* txt = child->ToText()) { + auto* n = dstDoc.NewText(txt->Value()); + dst->InsertEndChild(n); + } else if (const tinyxml2::XMLComment* c = child->ToComment()) { + auto* n = dstDoc.NewComment(c->Value()); + dst->InsertEndChild(n); + } + } + + return dst; +} + +std::vector Lxfml::Split(const std::string_view data, const NiPoint3& curPosition) { + std::vector results; + + // Handle empty or invalid input + if (data.empty()) { + return results; + } + + // Prevent processing extremely large inputs that could cause hangs + if (data.size() > 10000000) { // 10MB limit + return results; + } + + tinyxml2::XMLDocument doc; + // Use length-based parsing to avoid expensive string copy + const auto err = doc.Parse(data.data(), data.size()); + if (err != tinyxml2::XML_SUCCESS) { + return results; + } + + auto* lxfml = doc.FirstChildElement("LXFML"); + if (!lxfml) { + return results; + } + + // Build maps: partRef -> Part element, partRef -> Brick element, boneRef -> partRef, brickRef -> Brick element + std::unordered_map partRefToPart; + std::unordered_map partRefToBrick; + std::unordered_map boneRefToPartRef; + std::unordered_map brickByRef; + + auto* bricksParent = lxfml->FirstChildElement("Bricks"); + if (bricksParent) { + for (auto* brick = bricksParent->FirstChildElement("Brick"); brick; brick = brick->NextSiblingElement("Brick")) { + const char* brickRef = brick->Attribute("refID"); + if (brickRef) brickByRef.emplace(std::string(brickRef), brick); + for (auto* part = brick->FirstChildElement("Part"); part; part = part->NextSiblingElement("Part")) { + const char* partRef = part->Attribute("refID"); + if (partRef) { + partRefToPart.emplace(std::string(partRef), part); + partRefToBrick.emplace(std::string(partRef), brick); + } + auto* bone = part->FirstChildElement("Bone"); + if (bone) { + const char* boneRef = bone->Attribute("refID"); + if (boneRef) boneRefToPartRef.emplace(std::string(boneRef), partRef ? std::string(partRef) : std::string()); + } + } + } + } + + // Collect RigidSystem elements + std::vector rigidSystems; + auto* rigidSystemsParent = lxfml->FirstChildElement("RigidSystems"); + if (rigidSystemsParent) { + for (auto* rs = rigidSystemsParent->FirstChildElement("RigidSystem"); rs; rs = rs->NextSiblingElement("RigidSystem")) { + rigidSystems.push_back(rs); + } + } + + // Collect top-level groups (immediate children of GroupSystem) + std::vector groupRoots; + auto* groupSystemsParent = lxfml->FirstChildElement("GroupSystems"); + if (groupSystemsParent) { + for (auto* gs = groupSystemsParent->FirstChildElement("GroupSystem"); gs; gs = gs->NextSiblingElement("GroupSystem")) { + for (auto* group = gs->FirstChildElement("Group"); group; group = group->NextSiblingElement("Group")) { + groupRoots.push_back(group); + } + } + } + + // Track used bricks and rigidsystems + std::unordered_set usedBrickRefs; + std::unordered_set usedRigidSystems; + + // Helper to create output document from sets of brick refs and rigidsystem pointers + auto makeOutput = [&](const std::unordered_set& bricksToInclude, const std::vector& rigidSystemsToInclude, const std::vector& groupsToInclude = {}) { + tinyxml2::XMLDocument outDoc; + outDoc.Parse(g_base.c_str()); + auto* outRoot = outDoc.FirstChildElement("LXFML"); + auto* outBricks = outRoot->FirstChildElement("Bricks"); + auto* outRigidSystems = outRoot->FirstChildElement("RigidSystems"); + auto* outGroupSystems = outRoot->FirstChildElement("GroupSystems"); + + // clone and insert bricks + for (const auto& bref : bricksToInclude) { + auto it = brickByRef.find(bref); + if (it == brickByRef.end()) continue; + tinyxml2::XMLElement* cloned = CloneElementDeep(it->second, outDoc); + if (cloned) outBricks->InsertEndChild(cloned); + } + + // clone and insert rigidsystems + for (auto* rsPtr : rigidSystemsToInclude) { + tinyxml2::XMLElement* cloned = CloneElementDeep(rsPtr, outDoc); + if (cloned) outRigidSystems->InsertEndChild(cloned); + } + + // clone and insert group(s) if requested + if (outGroupSystems && !groupsToInclude.empty()) { + // clear default children + while (outGroupSystems->FirstChild()) outGroupSystems->DeleteChild(outGroupSystems->FirstChild()); + // create a GroupSystem element and append requested groups + auto* newGS = outDoc.NewElement("GroupSystem"); + for (auto* gptr : groupsToInclude) { + tinyxml2::XMLElement* clonedG = CloneElementDeep(gptr, outDoc); + if (clonedG) newGS->InsertEndChild(clonedG); + } + outGroupSystems->InsertEndChild(newGS); + } + + // Print to string + tinyxml2::XMLPrinter printer; + outDoc.Print(&printer); + // Normalize position and compute center using existing helper + std::string xmlString = printer.CStr(); + if (xmlString.size() > 5000000) { // 5MB limit for normalization + Result emptyResult; + emptyResult.lxfml = xmlString; + return emptyResult; + } + auto normalized = NormalizePosition(xmlString, curPosition); + return normalized; + }; + + // 1) Process groups (each top-level Group becomes one output; nested groups are included) + for (auto* groupRoot : groupRoots) { + // collect all partRefs in this group's subtree + std::unordered_set partRefs; + std::function collectParts = [&](const tinyxml2::XMLElement* g) { + if (!g) return; + const char* partAttr = g->Attribute("partRefs"); + if (partAttr) { + for (auto& tok : GeneralUtils::SplitString(partAttr, ',')) partRefs.insert(tok); + } + for (auto* child = g->FirstChildElement("Group"); child; child = child->NextSiblingElement("Group")) collectParts(child); + }; + collectParts(groupRoot); + + // Build initial sets of bricks and boneRefs + std::unordered_set bricksIncluded; + std::unordered_set boneRefsIncluded; + for (const auto& pref : partRefs) { + auto pit = partRefToBrick.find(pref); + if (pit != partRefToBrick.end()) { + const char* bref = pit->second->Attribute("refID"); + if (bref) bricksIncluded.insert(std::string(bref)); + } + auto partIt = partRefToPart.find(pref); + if (partIt != partRefToPart.end()) { + auto* bone = partIt->second->FirstChildElement("Bone"); + if (bone) { + const char* bref = bone->Attribute("refID"); + if (bref) boneRefsIncluded.insert(std::string(bref)); + } + } + } + + // Iteratively include any RigidSystems that reference any boneRefsIncluded + bool changed = true; + std::vector rigidSystemsToInclude; + int maxIterations = 1000; // Safety limit to prevent infinite loops + int iteration = 0; + while (changed && iteration < maxIterations) { + changed = false; + iteration++; + for (auto* rs : rigidSystems) { + if (usedRigidSystems.find(rs) != usedRigidSystems.end()) continue; + // parse boneRefs of this rigid system (from its children) + bool intersects = false; + std::vector rsBoneRefs; + for (auto* rigid = rs->FirstChildElement("Rigid"); rigid; rigid = rigid->NextSiblingElement("Rigid")) { + const char* battr = rigid->Attribute("boneRefs"); + if (!battr) continue; + for (auto& tok : GeneralUtils::SplitString(battr, ',')) { + rsBoneRefs.push_back(tok); + if (boneRefsIncluded.find(tok) != boneRefsIncluded.end()) intersects = true; + } + } + if (!intersects) continue; + // include this rigid system and all boneRefs it references + usedRigidSystems.insert(rs); + rigidSystemsToInclude.push_back(rs); + for (const auto& br : rsBoneRefs) { + boneRefsIncluded.insert(br); + auto bpIt = boneRefToPartRef.find(br); + if (bpIt != boneRefToPartRef.end()) { + auto partRef = bpIt->second; + auto pbIt = partRefToBrick.find(partRef); + if (pbIt != partRefToBrick.end()) { + const char* bref = pbIt->second->Attribute("refID"); + if (bref && bricksIncluded.insert(std::string(bref)).second) changed = true; + } + } + } + } + } + + if (iteration >= maxIterations) { + // Iteration limit reached, stop processing to prevent infinite loops + // The file is likely malformed, so just skip further processing + return results; + } + // include bricks from bricksIncluded into used set + for (const auto& b : bricksIncluded) usedBrickRefs.insert(b); + + // make output doc and push result (include this group's XML) + std::vector groupsVec{ groupRoot }; + auto normalized = makeOutput(bricksIncluded, rigidSystemsToInclude, groupsVec); + results.push_back(normalized); + } + + // 2) Process remaining RigidSystems (each becomes its own file) + for (auto* rs : rigidSystems) { + if (usedRigidSystems.find(rs) != usedRigidSystems.end()) continue; + std::unordered_set bricksIncluded; + // collect boneRefs referenced by this rigid system + for (auto* rigid = rs->FirstChildElement("Rigid"); rigid; rigid = rigid->NextSiblingElement("Rigid")) { + const char* battr = rigid->Attribute("boneRefs"); + if (!battr) continue; + for (auto& tok : GeneralUtils::SplitString(battr, ',')) { + auto bpIt = boneRefToPartRef.find(tok); + if (bpIt != boneRefToPartRef.end()) { + auto partRef = bpIt->second; + auto pbIt = partRefToBrick.find(partRef); + if (pbIt != partRefToBrick.end()) { + const char* bref = pbIt->second->Attribute("refID"); + if (bref) bricksIncluded.insert(std::string(bref)); + } + } + } + } + // mark used + for (const auto& b : bricksIncluded) usedBrickRefs.insert(b); + usedRigidSystems.insert(rs); + + std::vector rsVec{ rs }; + auto normalized = makeOutput(bricksIncluded, rsVec); + results.push_back(normalized); + } + + // 3) Any remaining bricks not included become their own files + for (const auto& [bref, brickPtr] : brickByRef) { + if (usedBrickRefs.find(bref) != usedBrickRefs.end()) continue; + std::unordered_set bricksIncluded{ bref }; + auto normalized = makeOutput(bricksIncluded, {}); + results.push_back(normalized); + usedBrickRefs.insert(bref); + } + + return results; +} diff --git a/dCommon/Lxfml.h b/dCommon/Lxfml.h index 80710713..1baaeeed 100644 --- a/dCommon/Lxfml.h +++ b/dCommon/Lxfml.h @@ -6,6 +6,7 @@ #include #include +#include #include "NiPoint3.h" @@ -18,6 +19,7 @@ namespace Lxfml { // Normalizes a LXFML model to be positioned relative to its local 0, 0, 0 rather than a game worlds 0, 0, 0. // Returns a struct of its new center and the updated LXFML containing these edits. [[nodiscard]] Result NormalizePosition(const std::string_view data, const NiPoint3& curPosition = NiPoint3Constant::ZERO); + [[nodiscard]] std::vector Split(const std::string_view data, const NiPoint3& curPosition = NiPoint3Constant::ZERO); // these are only for the migrations due to a bug in one of the implementations. [[nodiscard]] Result NormalizePositionOnlyFirstPart(const std::string_view data); diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index fffa52b9..c23b4988 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -2566,9 +2566,6 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent inStream.Read(timeTaken); /* - Disabled this, as it's kinda silly to do this roundabout way of storing plaintext lxfml, then recompressing - it to send it back to the client. - On DLU we had agreed that bricks wouldn't be taken anyway, but if your server decides otherwise, feel free to comment this back out and add the needed code to get the bricks used from lxfml and take them from the inventory. @@ -2582,23 +2579,6 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent //We need to get a new ID for our model first: if (!entity || !entity->GetCharacter() || !entity->GetCharacter()->GetParentUser()) return; - const uint32_t maxRetries = 100; - uint32_t retries = 0; - bool blueprintIDExists = true; - bool modelExists = true; - - // Legacy logic to check for old random IDs (regenerating these is not really feasible) - // Probably good to have this anyway in case someone messes with the last_object_id or it gets reset somehow - LWOOBJID newIDL = LWOOBJID_EMPTY; - LWOOBJID blueprintID = LWOOBJID_EMPTY; - do { - if (newIDL != LWOOBJID_EMPTY) LOG("Generating blueprintID for UGC model, collision with existing model ID: %llu", blueprintID); - newIDL = ObjectIDManager::GetPersistentID(); - blueprintID = ObjectIDManager::GetPersistentID(); - ++retries; - blueprintIDExists = Database::Get()->GetUgcModel(blueprintID).has_value(); - modelExists = Database::Get()->GetModel(newIDL).has_value(); - } while ((blueprintIDExists || modelExists) && retries < maxRetries); //We need to get the propertyID: (stolen from Wincent's propertyManagementComp) const auto& worldId = Game::zoneManager->GetZone()->GetZoneID(); @@ -2615,85 +2595,112 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent std::istringstream sd0DataStream(str); Sd0 sd0(sd0DataStream); - // Uncompress the data and normalize the position + // Uncompress the data, split, and nornmalize the model const auto asStr = sd0.GetAsStringUncompressed(); - const auto [newLxfml, newCenter] = Lxfml::NormalizePosition(asStr); + auto splitLxfmls = Lxfml::Split(asStr); + LOG_DEBUG("Split into %zu models", splitLxfmls.size()); - // Recompress the data and save to the database - sd0.FromData(reinterpret_cast(newLxfml.data()), newLxfml.size()); - auto sd0AsStream = sd0.GetAsStream(); - Database::Get()->InsertNewUgcModel(sd0AsStream, blueprintID, entity->GetCharacter()->GetParentUser()->GetAccountID(), entity->GetCharacter()->GetID()); - - //Insert into the db as a BBB model: - IPropertyContents::Model model; - model.id = newIDL; - model.ugcId = blueprintID; - model.position = newCenter; - model.rotation = NiQuaternion(0.0f, 0.0f, 0.0f, 0.0f); - model.lot = 14; - Database::Get()->InsertNewPropertyModel(propertyId, model, "Objects_14_name"); - - /* - Commented out until UGC server would be updated to use a sd0 file instead of lxfml stream. - (or you uncomment the lxfml decomp stuff above) - */ - - // //Send off to UGC for processing, if enabled: - // if (Game::config->GetValue("ugc_remote") == "1") { - // std::string ugcIP = Game::config->GetValue("ugc_ip"); - // int ugcPort = std::stoi(Game::config->GetValue("ugc_port")); - - // httplib::Client cli(ugcIP, ugcPort); //connect to UGC HTTP server using our config above ^ - - // //Send out a request: - // std::string request = "/3dservices/UGCC150/150" + std::to_string(blueprintID) + ".lxfml"; - // cli.Put(request.c_str(), lxfml.c_str(), "text/lxfml"); - - // //When the "put" above returns, it means that the UGC HTTP server is done processing our model & - // //the nif, hkx and checksum files are ready to be downloaded from cache. - // } - - //Tell the client their model is saved: (this causes us to actually pop out of our current state): - const auto& newSd0 = sd0.GetAsVector(); - uint32_t newSd0Size{}; - for (const auto& chunk : newSd0) newSd0Size += chunk.size(); CBITSTREAM; BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::BLUEPRINT_SAVE_RESPONSE); bitStream.Write(localId); bitStream.Write(eBlueprintSaveResponseType::EverythingWorked); - bitStream.Write(1); - bitStream.Write(blueprintID); + bitStream.Write(splitLxfmls.size()); - bitStream.Write(newSd0Size); + std::vector blueprintIDs; + std::vector modelIDs; - for (const auto& chunk : newSd0) bitStream.WriteAlignedBytes(reinterpret_cast(chunk.data()), chunk.size()); + for (size_t i = 0; i < splitLxfmls.size(); ++i) { + // Legacy logic to check for old random IDs (regenerating these is not really feasible) + // Probably good to have this anyway in case someone messes with the last_object_id or it gets reset somehow + const uint32_t maxRetries = 100; + uint32_t retries = 0; + bool blueprintIDExists = true; + bool modelExists = true; + + LWOOBJID newID = LWOOBJID_EMPTY; + LWOOBJID blueprintID = LWOOBJID_EMPTY; + do { + if (newID != LWOOBJID_EMPTY) LOG("Generating blueprintID for UGC model, collision with existing model ID: %llu", blueprintID); + newID = ObjectIDManager::GetPersistentID(); + blueprintID = ObjectIDManager::GetPersistentID(); + ++retries; + blueprintIDExists = Database::Get()->GetUgcModel(blueprintID).has_value(); + modelExists = Database::Get()->GetModel(newID).has_value(); + } while ((blueprintIDExists || modelExists) && retries < maxRetries); + + blueprintIDs.push_back(blueprintID); + modelIDs.push_back(newID); + + // Save each model to the database + sd0.FromData(reinterpret_cast(splitLxfmls[i].lxfml.data()), splitLxfmls[i].lxfml.size()); + auto sd0AsStream = sd0.GetAsStream(); + Database::Get()->InsertNewUgcModel(sd0AsStream, blueprintID, entity->GetCharacter()->GetParentUser()->GetAccountID(), entity->GetCharacter()->GetID()); + + // Insert the new property model + IPropertyContents::Model model; + model.id = newID; + model.ugcId = blueprintID; + model.position = splitLxfmls[i].center; + model.rotation = QuatUtils::IDENTITY; + model.lot = 14; + Database::Get()->InsertNewPropertyModel(propertyId, model, "Objects_14_name"); + + /* + Commented out until UGC server would be updated to use a sd0 file instead of lxfml stream. + (or you uncomment the lxfml decomp stuff above) + */ + + // Send off to UGC for processing, if enabled: + // if (Game::config->GetValue("ugc_remote") == "1") { + // std::string ugcIP = Game::config->GetValue("ugc_ip"); + // int ugcPort = std::stoi(Game::config->GetValue("ugc_port")); + + // httplib::Client cli(ugcIP, ugcPort); //connect to UGC HTTP server using our config above ^ + + // //Send out a request: + // std::string request = "/3dservices/UGCC150/150" + std::to_string(blueprintID) + ".lxfml"; + // cli.Put(request.c_str(), lxfml.c_str(), "text/lxfml"); + + // //When the "put" above returns, it means that the UGC HTTP server is done processing our model & + // //the nif, hkx and checksum files are ready to be downloaded from cache. + // } + + // Write the ID and data to the response packet + bitStream.Write(blueprintID); + + const auto& newSd0 = sd0.GetAsVector(); + uint32_t newSd0Size{}; + for (const auto& chunk : newSd0) newSd0Size += chunk.size(); + bitStream.Write(newSd0Size); + for (const auto& chunk : newSd0) bitStream.WriteAlignedBytes(reinterpret_cast(chunk.data()), chunk.size()); + } SEND_PACKET; - //Now we have to construct this object: + // Create entities for each model + for (size_t i = 0; i < splitLxfmls.size(); ++i) { + EntityInfo info; + info.lot = 14; + info.pos = splitLxfmls[i].center; + info.rot = QuatUtils::IDENTITY; + info.spawner = nullptr; + info.spawnerID = entity->GetObjectID(); + info.spawnerNodeID = 0; - EntityInfo info; - info.lot = 14; - info.pos = newCenter; - info.rot = {}; - info.spawner = nullptr; - 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])); + Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr); + if (newEntity) { + Game::entityManager->ConstructEntity(newEntity); - info.settings.push_back(new LDFData(u"blueprintid", blueprintID)); - info.settings.push_back(new LDFData(u"componentWhitelist", 1)); - info.settings.push_back(new LDFData(u"modelType", 2)); - info.settings.push_back(new LDFData(u"propertyObjectID", true)); - info.settings.push_back(new LDFData(u"userModelID", newIDL)); - - Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr); - if (newEntity) { - Game::entityManager->ConstructEntity(newEntity); - - //Make sure the propMgmt doesn't delete our model after the server dies - //Trying to do this after the entity is constructed. Shouldn't really change anything but - //there was an issue with builds not appearing since it was placed above ConstructEntity. - PropertyManagementComponent::Instance()->AddModel(newEntity->GetObjectID(), newIDL); + //Make sure the propMgmt doesn't delete our model after the server dies + //Trying to do this after the entity is constructed. Shouldn't really change anything but + //there was an issue with builds not appearing since it was placed above ConstructEntity. + PropertyManagementComponent::Instance()->AddModel(newEntity->GetObjectID(), modelIDs[i]); + } } } diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 110b7573..ad570534 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -1166,32 +1166,36 @@ void HandlePacket(Packet* packet) { LOG("Couldn't find property ID for zone %i, clone %i", zoneId, cloneId); goto noBBB; } - for (auto& bbbModel : Database::Get()->GetUgcModels(propertyId)) { + + // Workaround for not having a UGC server to get model LXFML onto the client so it + // can generate the physics and nif for the object. + + auto bbbModels = Database::Get()->GetUgcModels(propertyId); + if (bbbModels.empty()) { + LOG("No BBB models found for property %llu", propertyId); + goto noBBB; + } + + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::BLUEPRINT_SAVE_RESPONSE); + bitStream.Write(LWOOBJID_EMPTY); //always zero so that a check on the client passes + bitStream.Write(eBlueprintSaveResponseType::EverythingWorked); + bitStream.Write(bbbModels.size()); + for (auto& bbbModel : bbbModels) { LOG("Getting lxfml ugcID: %llu", bbbModel.id); bbbModel.lxfmlData.seekg(0, std::ios::end); size_t lxfmlSize = bbbModel.lxfmlData.tellg(); bbbModel.lxfmlData.seekg(0); - //Send message: + // write data LWOOBJID blueprintID = bbbModel.id; - - // Workaround for not having a UGC server to get model LXFML onto the client so it - // can generate the physics and nif for the object. - CBITSTREAM; - BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::BLUEPRINT_SAVE_RESPONSE); - bitStream.Write(LWOOBJID_EMPTY); //always zero so that a check on the client passes - bitStream.Write(eBlueprintSaveResponseType::EverythingWorked); - bitStream.Write(1); bitStream.Write(blueprintID); - bitStream.Write(lxfmlSize); - bitStream.WriteAlignedBytes(reinterpret_cast(bbbModel.lxfmlData.str().c_str()), lxfmlSize); - - SystemAddress sysAddr = packet->systemAddress; - SEND_PACKET; } + SystemAddress sysAddr = packet->systemAddress; + SEND_PACKET; } noBBB: diff --git a/tests/dCommonTests/CMakeLists.txt b/tests/dCommonTests/CMakeLists.txt index 17b31ced..74039be1 100644 --- a/tests/dCommonTests/CMakeLists.txt +++ b/tests/dCommonTests/CMakeLists.txt @@ -10,6 +10,7 @@ set(DCOMMONTEST_SOURCES "TestLUString.cpp" "TestLUWString.cpp" "dCommonDependencies.cpp" + "LxfmlTests.cpp" ) add_subdirectory(dEnumsTests) @@ -32,6 +33,8 @@ target_link_libraries(dCommonTests ${COMMON_LIBRARIES} GTest::gtest_main) # Copy test files to testing directory add_subdirectory(TestBitStreams) file(COPY ${TESTBITSTREAMS} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) +add_subdirectory(LxfmlTestFiles) +file(COPY ${LXFMLTESTFILES} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) # Discover the tests gtest_discover_tests(dCommonTests) diff --git a/tests/dCommonTests/LxfmlTestFiles/CMakeLists.txt b/tests/dCommonTests/LxfmlTestFiles/CMakeLists.txt new file mode 100644 index 00000000..e07cbd62 --- /dev/null +++ b/tests/dCommonTests/LxfmlTestFiles/CMakeLists.txt @@ -0,0 +1,18 @@ +set(LXFMLTESTFILES + "deeply_nested.lxfml" + "empty_transform.lxfml" + "invalid_transform.lxfml" + "mixed_invalid_transform.lxfml" + "mixed_valid_invalid.lxfml" + "non_numeric_transform.lxfml" + "no_bricks.lxfml" + "test.lxfml" + "too_few_values.lxfml" +) + +# Get the folder name and prepend it to the files above +get_filename_component(thisFolderName ${CMAKE_CURRENT_SOURCE_DIR} NAME) +list(TRANSFORM LXFMLTESTFILES PREPEND "${thisFolderName}/") + +# Export our list of files +set(LXFMLTESTFILES ${LXFMLTESTFILES} PARENT_SCOPE) diff --git a/tests/dCommonTests/LxfmlTestFiles/deeply_nested.lxfml b/tests/dCommonTests/LxfmlTestFiles/deeply_nested.lxfml new file mode 100644 index 00000000..ea473ab1 --- /dev/null +++ b/tests/dCommonTests/LxfmlTestFiles/deeply_nested.lxfml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/dCommonTests/LxfmlTestFiles/empty_transform.lxfml b/tests/dCommonTests/LxfmlTestFiles/empty_transform.lxfml new file mode 100644 index 00000000..5d5cc90d --- /dev/null +++ b/tests/dCommonTests/LxfmlTestFiles/empty_transform.lxfml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/tests/dCommonTests/LxfmlTestFiles/invalid_transform.lxfml b/tests/dCommonTests/LxfmlTestFiles/invalid_transform.lxfml new file mode 100644 index 00000000..19899850 --- /dev/null +++ b/tests/dCommonTests/LxfmlTestFiles/invalid_transform.lxfml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/tests/dCommonTests/LxfmlTestFiles/mixed_invalid_transform.lxfml b/tests/dCommonTests/LxfmlTestFiles/mixed_invalid_transform.lxfml new file mode 100644 index 00000000..6b0730d1 --- /dev/null +++ b/tests/dCommonTests/LxfmlTestFiles/mixed_invalid_transform.lxfml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/tests/dCommonTests/LxfmlTestFiles/mixed_valid_invalid.lxfml b/tests/dCommonTests/LxfmlTestFiles/mixed_valid_invalid.lxfml new file mode 100644 index 00000000..30a0b278 --- /dev/null +++ b/tests/dCommonTests/LxfmlTestFiles/mixed_valid_invalid.lxfml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/dCommonTests/LxfmlTestFiles/no_bricks.lxfml b/tests/dCommonTests/LxfmlTestFiles/no_bricks.lxfml new file mode 100644 index 00000000..da7d56ae --- /dev/null +++ b/tests/dCommonTests/LxfmlTestFiles/no_bricks.lxfml @@ -0,0 +1,4 @@ + + + + diff --git a/tests/dCommonTests/LxfmlTestFiles/non_numeric_transform.lxfml b/tests/dCommonTests/LxfmlTestFiles/non_numeric_transform.lxfml new file mode 100644 index 00000000..3245cb10 --- /dev/null +++ b/tests/dCommonTests/LxfmlTestFiles/non_numeric_transform.lxfml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/tests/dCommonTests/LxfmlTestFiles/test.lxfml b/tests/dCommonTests/LxfmlTestFiles/test.lxfml new file mode 100644 index 00000000..f9ce0d35 --- /dev/null +++ b/tests/dCommonTests/LxfmlTestFiles/test.lxfml @@ -0,0 +1,336 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/dCommonTests/LxfmlTestFiles/too_few_values.lxfml b/tests/dCommonTests/LxfmlTestFiles/too_few_values.lxfml new file mode 100644 index 00000000..ae37857b --- /dev/null +++ b/tests/dCommonTests/LxfmlTestFiles/too_few_values.lxfml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/tests/dCommonTests/LxfmlTests.cpp b/tests/dCommonTests/LxfmlTests.cpp new file mode 100644 index 00000000..7e1ca4b7 --- /dev/null +++ b/tests/dCommonTests/LxfmlTests.cpp @@ -0,0 +1,297 @@ +#include "gtest/gtest.h" + +#include "Lxfml.h" +#include "TinyXmlUtils.h" +#include "dCommonDependencies.h" + +#include +#include +#include +#include + +using namespace TinyXmlUtils; + +static std::string ReadFile(const std::string& filename) { + std::ifstream in(filename, std::ios::in | std::ios::binary); + if (!in.is_open()) { + return ""; + } + std::ostringstream ss; + ss << in.rdbuf(); + return ss.str(); +} + +std::string SerializeElement(tinyxml2::XMLElement* elem) { + tinyxml2::XMLPrinter p; + elem->Accept(&p); + return std::string(p.CStr()); +}; + +TEST(LxfmlTests, SplitUsesAllBricksAndNoDuplicates) { + // Read the test.lxfml file copied to build directory by CMake + std::string data = ReadFile("test.lxfml"); + ASSERT_FALSE(data.empty()) << "Failed to read test.lxfml from build directory"; + + auto results = Lxfml::Split(data); + ASSERT_GT(results.size(), 0); + + // parse original to count bricks + tinyxml2::XMLDocument doc; + ASSERT_EQ(doc.Parse(data.c_str()), tinyxml2::XML_SUCCESS); + DocumentReader reader(doc); + auto lxfml = reader["LXFML"]; + ASSERT_TRUE(lxfml); + + std::unordered_set originalRigidSet; + if (auto* rsParent = doc.FirstChildElement("LXFML")->FirstChildElement("RigidSystems")) { + for (auto* rs = rsParent->FirstChildElement("RigidSystem"); rs; rs = rs->NextSiblingElement("RigidSystem")) { + originalRigidSet.insert(SerializeElement(rs)); + } + } + + std::unordered_set originalGroupSet; + if (auto* gsParent = doc.FirstChildElement("LXFML")->FirstChildElement("GroupSystems")) { + for (auto* gs = gsParent->FirstChildElement("GroupSystem"); gs; gs = gs->NextSiblingElement("GroupSystem")) { + for (auto* g = gs->FirstChildElement("Group"); g; g = g->NextSiblingElement("Group")) { + // collect this group and nested groups + std::function collectGroups = [&](tinyxml2::XMLElement* grp) { + originalGroupSet.insert(SerializeElement(grp)); + for (auto* child = grp->FirstChildElement("Group"); child; child = child->NextSiblingElement("Group")) collectGroups(child); + }; + collectGroups(g); + } + } + } + + std::unordered_set originalBricks; + for (const auto& brick : lxfml["Bricks"]) { + const auto* ref = brick.Attribute("refID"); + if (ref) originalBricks.insert(ref); + } + ASSERT_GT(originalBricks.size(), 0); + + // Collect bricks across all results and ensure no duplicates and all used + std::unordered_set usedBricks; + // Track used rigid systems and groups (serialized strings) + std::unordered_set usedRigidSet; + std::unordered_set usedGroupSet; + for (const auto& res : results) { + tinyxml2::XMLDocument outDoc; + ASSERT_EQ(outDoc.Parse(res.lxfml.c_str()), tinyxml2::XML_SUCCESS); + DocumentReader outReader(outDoc); + auto outLxfml = outReader["LXFML"]; + ASSERT_TRUE(outLxfml); + // collect rigid systems in this output + if (auto* rsParent = outDoc.FirstChildElement("LXFML")->FirstChildElement("RigidSystems")) { + for (auto* rs = rsParent->FirstChildElement("RigidSystem"); rs; rs = rs->NextSiblingElement("RigidSystem")) { + auto s = SerializeElement(rs); + // no duplicate allowed across outputs + ASSERT_EQ(usedRigidSet.find(s), usedRigidSet.end()) << "Duplicate RigidSystem across splits"; + usedRigidSet.insert(s); + } + } + // collect groups in this output + if (auto* gsParent = outDoc.FirstChildElement("LXFML")->FirstChildElement("GroupSystems")) { + for (auto* gs = gsParent->FirstChildElement("GroupSystem"); gs; gs = gs->NextSiblingElement("GroupSystem")) { + for (auto* g = gs->FirstChildElement("Group"); g; g = g->NextSiblingElement("Group")) { + std::function collectGroupsOut = [&](tinyxml2::XMLElement* grp) { + auto s = SerializeElement(grp); + ASSERT_EQ(usedGroupSet.find(s), usedGroupSet.end()) << "Duplicate Group across splits"; + usedGroupSet.insert(s); + for (auto* child = grp->FirstChildElement("Group"); child; child = child->NextSiblingElement("Group")) collectGroupsOut(child); + }; + collectGroupsOut(g); + } + } + } + for (const auto& brick : outLxfml["Bricks"]) { + const auto* ref = brick.Attribute("refID"); + if (ref) { + // no duplicate allowed + ASSERT_EQ(usedBricks.find(ref), usedBricks.end()) << "Duplicate brick ref across splits: " << ref; + usedBricks.insert(ref); + } + } + } + + // Every original brick must be used in one of the outputs + for (const auto& bref : originalBricks) { + ASSERT_NE(usedBricks.find(bref), usedBricks.end()) << "Brick not used in splits: " << bref; + } + + // And usedBricks should not contain anything outside original + for (const auto& ub : usedBricks) { + ASSERT_NE(originalBricks.find(ub), originalBricks.end()) << "Split produced unknown brick: " << ub; + } + + // Ensure all original rigid systems and groups were used exactly once + ASSERT_EQ(originalRigidSet.size(), usedRigidSet.size()) << "RigidSystem count mismatch"; + for (const auto& s : originalRigidSet) ASSERT_NE(usedRigidSet.find(s), usedRigidSet.end()) << "RigidSystem missing in splits"; + + ASSERT_EQ(originalGroupSet.size(), usedGroupSet.size()) << "Group count mismatch"; + for (const auto& s : originalGroupSet) ASSERT_NE(usedGroupSet.find(s), usedGroupSet.end()) << "Group missing in splits"; +} + +// Tests for invalid input handling - now working with the improved Split function + +TEST(LxfmlTests, InvalidLxfmlHandling) { + // Test LXFML with invalid transformation matrices + std::string invalidTransformData = ReadFile("invalid_transform.lxfml"); + ASSERT_FALSE(invalidTransformData.empty()) << "Failed to read invalid_transform.lxfml from build directory"; + + // The Split function should handle invalid transformation matrices gracefully + std::vector results; + EXPECT_NO_FATAL_FAILURE({ + results = Lxfml::Split(invalidTransformData); + }) << "Split should not crash on invalid transformation matrices"; + + // Function should handle invalid transforms gracefully, possibly returning empty or partial results + // The exact behavior depends on how the function handles invalid numeric parsing +} + +TEST(LxfmlTests, EmptyLxfmlHandling) { + // Test with completely empty input + std::string emptyData = ""; + std::vector results; + + EXPECT_NO_FATAL_FAILURE({ + results = Lxfml::Split(emptyData); + }) << "Split should not crash on empty input"; + + EXPECT_EQ(results.size(), 0) << "Empty input should return empty results"; +} + +TEST(LxfmlTests, EmptyTransformHandling) { + // Test LXFML with empty transformation matrix + std::string testData = ReadFile("empty_transform.lxfml"); + ASSERT_FALSE(testData.empty()) << "Failed to read empty_transform.lxfml from build directory"; + + std::vector results; + EXPECT_NO_FATAL_FAILURE({ + results = Lxfml::Split(testData); + }) << "Split should not crash on empty transformation matrix"; + + // The function should handle empty transforms gracefully + // May return empty results or skip invalid bricks +} + +TEST(LxfmlTests, TooFewValuesTransformHandling) { + // Test LXFML with too few transformation values (needs 12, has fewer) + std::string testData = ReadFile("too_few_values.lxfml"); + ASSERT_FALSE(testData.empty()) << "Failed to read too_few_values.lxfml from build directory"; + + std::vector results; + EXPECT_NO_FATAL_FAILURE({ + results = Lxfml::Split(testData); + }) << "Split should not crash on transformation matrix with too few values"; + + // The function should handle incomplete transforms gracefully + // May return empty results or skip invalid bricks +} + +TEST(LxfmlTests, NonNumericTransformHandling) { + // Test LXFML with non-numeric transformation values + std::string testData = ReadFile("non_numeric_transform.lxfml"); + ASSERT_FALSE(testData.empty()) << "Failed to read non_numeric_transform.lxfml from build directory"; + + std::vector results; + EXPECT_NO_FATAL_FAILURE({ + results = Lxfml::Split(testData); + }) << "Split should not crash on non-numeric transformation values"; + + // The function should handle non-numeric transforms gracefully + // May return empty results or skip invalid bricks +} + +TEST(LxfmlTests, MixedInvalidTransformHandling) { + // Test LXFML with mixed valid/invalid transformation values within a matrix + std::string testData = ReadFile("mixed_invalid_transform.lxfml"); + ASSERT_FALSE(testData.empty()) << "Failed to read mixed_invalid_transform.lxfml from build directory"; + + std::vector results; + EXPECT_NO_FATAL_FAILURE({ + results = Lxfml::Split(testData); + }) << "Split should not crash on mixed valid/invalid transformation values"; + + // The function should handle mixed valid/invalid transforms gracefully + // May return empty results or skip invalid bricks +} + +TEST(LxfmlTests, NoBricksHandling) { + // Test LXFML with no Bricks section (should return empty gracefully) + std::string testData = ReadFile("no_bricks.lxfml"); + ASSERT_FALSE(testData.empty()) << "Failed to read no_bricks.lxfml from build directory"; + + std::vector results; + EXPECT_NO_FATAL_FAILURE({ + results = Lxfml::Split(testData); + }) << "Split should not crash on LXFML with no Bricks section"; + + // Should return empty results gracefully when no bricks are present + EXPECT_EQ(results.size(), 0) << "LXFML with no bricks should return empty results"; +} + +TEST(LxfmlTests, MixedValidInvalidTransformsHandling) { + // Test LXFML with mix of valid and invalid transformation data + std::string mixedValidData = ReadFile("mixed_valid_invalid.lxfml"); + ASSERT_FALSE(mixedValidData.empty()) << "Failed to read mixed_valid_invalid.lxfml from build directory"; + + // The Split function should handle mixed valid/invalid transforms gracefully + std::vector results; + EXPECT_NO_FATAL_FAILURE({ + results = Lxfml::Split(mixedValidData); + }) << "Split should not crash on mixed valid/invalid transforms"; + + // Should process valid bricks and handle invalid ones gracefully + if (results.size() > 0) { + EXPECT_NO_FATAL_FAILURE({ + for (size_t i = 0; i < results.size(); ++i) { + // Each result should have valid LXFML structure + tinyxml2::XMLDocument doc; + auto parseResult = doc.Parse(results[i].lxfml.c_str()); + EXPECT_EQ(parseResult, tinyxml2::XML_SUCCESS) + << "Result " << i << " should produce valid XML"; + + if (parseResult == tinyxml2::XML_SUCCESS) { + auto* lxfml = doc.FirstChildElement("LXFML"); + EXPECT_NE(lxfml, nullptr) << "Result " << i << " should have LXFML root element"; + } + } + }) << "Mixed valid/invalid transform processing should not cause fatal errors"; + } +} + +TEST(LxfmlTests, DeepCloneDepthProtection) { + // Test that deep cloning has protection against excessive nesting + std::string deeplyNestedLxfml = ReadFile("deeply_nested.lxfml"); + ASSERT_FALSE(deeplyNestedLxfml.empty()) << "Failed to read deeply_nested.lxfml from build directory"; + + // The Split function should handle deeply nested structures without hanging + std::vector results; + EXPECT_NO_FATAL_FAILURE({ + results = Lxfml::Split(deeplyNestedLxfml); + }) << "Split should not hang or crash on deeply nested XML structures"; + + // Should still produce valid output despite depth limitations + EXPECT_GT(results.size(), 0) << "Should produce at least one result even with deep nesting"; + + if (results.size() > 0) { + // Verify the result is still valid XML + tinyxml2::XMLDocument doc; + auto parseResult = doc.Parse(results[0].lxfml.c_str()); + EXPECT_EQ(parseResult, tinyxml2::XML_SUCCESS) << "Result should still be valid XML"; + + if (parseResult == tinyxml2::XML_SUCCESS) { + auto* lxfml = doc.FirstChildElement("LXFML"); + EXPECT_NE(lxfml, nullptr) << "Result should have LXFML root element"; + + // Verify that bricks are still included despite group nesting issues + auto* bricks = lxfml->FirstChildElement("Bricks"); + EXPECT_NE(bricks, nullptr) << "Bricks element should be present"; + if (bricks) { + auto* brick = bricks->FirstChildElement("Brick"); + EXPECT_NE(brick, nullptr) << "At least one brick should be present"; + } + } + } +} From e051229fb6edeef5a25d57256601b71194c1ae8b Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Fri, 10 Oct 2025 22:58:52 -0700 Subject: [PATCH 29/39] feat: InventoryComponent debug info (#1902) --- dGame/dComponents/InventoryComponent.cpp | 101 ++++++++++++++++++++++- dGame/dComponents/InventoryComponent.h | 2 + 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 0b45a11e..5361eb6b 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -39,10 +39,13 @@ #include "CDObjectSkillsTable.h" #include "CDSkillBehaviorTable.h" #include "StringifiedEnum.h" +#include "Amf3.h" #include InventoryComponent::InventoryComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { + using namespace GameMessages; + RegisterMsg(this, &InventoryComponent::OnGetObjectReportInfo); this->m_Dirty = true; this->m_Equipped = {}; this->m_Pushed = {}; @@ -626,7 +629,7 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument& document) { for (const auto& pair : this->m_Inventories) { auto* inventory = pair.second; - static const auto EXCLUDED_INVENTORIES = {VENDOR_BUYBACK, MODELS_IN_BBB, ITEM_SETS}; + static const auto EXCLUDED_INVENTORIES = { VENDOR_BUYBACK, MODELS_IN_BBB, ITEM_SETS }; if (std::ranges::find(EXCLUDED_INVENTORIES, inventory->GetType()) != EXCLUDED_INVENTORIES.end()) { continue; } @@ -1793,3 +1796,99 @@ void InventoryComponent::RegenerateItemIDs() { inventory->RegenerateItemIDs(); } } + +std::string DebugInvToString(const eInventoryType inv, bool verbose) { + switch (inv) { + case ITEMS: + return "Backpack"; + case VAULT_ITEMS: + return "Bank"; + case BRICKS: + return verbose ? "Bricks" : "Bricks (contents only shown in high-detail report)"; + case MODELS_IN_BBB: + return "Models in BBB"; + case TEMP_ITEMS: + return "Temp Equip"; + case MODELS: + return verbose ? "Model" : "Model (contents only shown in high-detail report)"; + case TEMP_MODELS: + return "Module"; + case BEHAVIORS: + return "B3 Behavior"; + case PROPERTY_DEEDS: + return "Property"; + case BRICKS_IN_BBB: + return "Brick In BBB"; + case VENDOR: + return "Vendor"; + case VENDOR_BUYBACK: + return "BuyBack"; + case QUEST: + return "Quest"; + case DONATION: + return "Donation"; + case VAULT_MODELS: + return "Bank Model"; + case ITEM_SETS: + return "Bank Behavior"; + case INVALID: + return "Invalid"; + case ALL: + return "All"; + } + + return ""; +} + +bool InventoryComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) { + auto& report = static_cast(msg); + auto& cmpt = report.info->PushDebug("Inventory"); + cmpt.PushDebug("Component ID") = GetComponentID(); + uint32_t numItems = 0; + for (auto* inventory : m_Inventories | std::views::values) numItems += inventory->GetItems().size(); + cmpt.PushDebug("Inventory Item Count") = numItems; + + auto& itemsInBags = cmpt.PushDebug("Items in bags"); + for (const auto& [id, inventoryMut] : m_Inventories) { + if (!inventoryMut) continue; + const auto* const inventory = inventoryMut; + auto& curInv = itemsInBags.PushDebug(DebugInvToString(id, report.bVerbose) + " - " + std::to_string(id)); + for (uint32_t i = 0; i < inventory->GetSize(); i++) { + const auto* const item = inventory->FindItemBySlot(i); + if (!item) continue; + + std::stringstream ss; + ss << "%[Objects_" << item->GetLot() << "_name] Slot " << item->GetSlot(); + auto& slot = curInv.PushDebug(ss.str()); + slot.PushDebug("Object ID") = std::to_string(item->GetId()); + slot.PushDebug("LOT") = item->GetLot(); + if (item->GetSubKey() != LWOOBJID_EMPTY) slot.PushDebug("Subkey") = std::to_string(item->GetSubKey()); + slot.PushDebug("Count") = item->GetCount(); + slot.PushDebug("Slot") = item->GetSlot(); + slot.PushDebug("Bind on pickup") = item->GetInfo().isBOP; + 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()) { + if (setting) extra.PushDebug(GeneralUtils::UTF16ToWTF8(setting->GetKey())) = setting->GetValueAsString(); + } + } + } + + auto& equipped = cmpt.PushDebug("Equipped Items"); + for (const auto& [location, info] : GetEquippedItems()) { + std::stringstream ss; + ss << "%[Objects_" << info.lot << "_name]"; + auto& equipSlot = equipped.PushDebug(ss.str()); + equipSlot.PushDebug("Location") = location; + equipSlot.PushDebug("Object ID") = std::to_string(info.id); + equipSlot.PushDebug("Slot") = info.slot; + equipSlot.PushDebug("Count") = info.count; + auto& extra = equipSlot.PushDebug("Extra Info"); + for (const auto* const setting : info.config) { + if (setting) extra.PushDebug(GeneralUtils::UTF16ToWTF8(setting->GetKey())) = setting->GetValueAsString(); + } + } + + return true; +} diff --git a/dGame/dComponents/InventoryComponent.h b/dGame/dComponents/InventoryComponent.h index 88d9da2c..6e0fdba7 100644 --- a/dGame/dComponents/InventoryComponent.h +++ b/dGame/dComponents/InventoryComponent.h @@ -410,6 +410,8 @@ public: // Used to migrate a character version, no need to call outside of that context void RegenerateItemIDs(); + bool OnGetObjectReportInfo(GameMessages::GameMsg& msg); + ~InventoryComponent() override; private: From ff645a6662e0418a9535afa07eaa7258bf8229ab Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sat, 11 Oct 2025 15:33:28 -0700 Subject: [PATCH 30/39] feat: model debug (#1907) --- dGame/dComponents/ModelComponent.cpp | 17 +++++++++++++++++ dGame/dComponents/ModelComponent.h | 1 + 2 files changed, 18 insertions(+) diff --git a/dGame/dComponents/ModelComponent.cpp b/dGame/dComponents/ModelComponent.cpp index f5f4e190..85d206e2 100644 --- a/dGame/dComponents/ModelComponent.cpp +++ b/dGame/dComponents/ModelComponent.cpp @@ -24,6 +24,7 @@ ModelComponent::ModelComponent(Entity* parent, const int32_t componentID) : Comp m_userModelID = m_Parent->GetVarAs(u"userModelID"); RegisterMsg(this, &ModelComponent::OnRequestUse); RegisterMsg(this, &ModelComponent::OnResetModelToDefaults); + RegisterMsg(this, &ModelComponent::OnGetObjectReportInfo); } bool ModelComponent::OnResetModelToDefaults(GameMessages::GameMsg& msg) { @@ -338,3 +339,19 @@ void ModelComponent::RemoveAttack() { set.Send(); } } + +bool ModelComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) { + auto& reportMsg = static_cast(msg); + if (!reportMsg.info) return false; + auto& cmptInfo = reportMsg.info->PushDebug("Model Behaviors (Mutable)"); + cmptInfo.PushDebug("Component ID") = GetComponentID(); + + cmptInfo.PushDebug("Name") = "Objects_" + std::to_string(m_Parent->GetLOT()) + "_name"; + cmptInfo.PushDebug("Has Unique Name") = false; + cmptInfo.PushDebug("UGID (from item)") = std::to_string(m_userModelID); + cmptInfo.PushDebug("UGID") = std::to_string(m_userModelID); + cmptInfo.PushDebug("Description") = ""; + cmptInfo.PushDebug("Behavior Count") = m_Behaviors.size(); + + return true; +} diff --git a/dGame/dComponents/ModelComponent.h b/dGame/dComponents/ModelComponent.h index 981abc8d..97b165cb 100644 --- a/dGame/dComponents/ModelComponent.h +++ b/dGame/dComponents/ModelComponent.h @@ -34,6 +34,7 @@ public: bool OnRequestUse(GameMessages::GameMsg& msg); bool OnResetModelToDefaults(GameMessages::GameMsg& msg); + bool OnGetObjectReportInfo(GameMessages::GameMsg& msg); void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; From fd6029ae102195f594f735c8ea08fcac56ebd24b Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sat, 11 Oct 2025 15:33:38 -0700 Subject: [PATCH 31/39] feat: read from server macros folder as well (#1906) --- dCommon/dClient/AssetManager.h | 3 ++ .../SlashCommands/DEVGMCommands.cpp | 36 +++++++++++-------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/dCommon/dClient/AssetManager.h b/dCommon/dClient/AssetManager.h index a63ffcf6..7ddd196f 100644 --- a/dCommon/dClient/AssetManager.h +++ b/dCommon/dClient/AssetManager.h @@ -81,6 +81,9 @@ public: [[nodiscard]] AssetStream GetFile(const char* name) const; + [[nodiscard]] + AssetStream GetFile(const std::string& name) const { return GetFile(name.c_str()); }; + private: void LoadPackIndex(); diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp index 0e4cbb5b..0c5b7585 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp @@ -52,7 +52,7 @@ #include "eInventoryType.h" #include "ePlayerFlag.h" #include "StringifiedEnum.h" - +#include "BinaryPathFinder.h" namespace DEVGMCommands { void SetGMLevel(Entity* entity, const SystemAddress& sysAddr, const std::string args) { @@ -368,6 +368,20 @@ namespace DEVGMCommands { } } + void HandleMacro(Entity& entity, const SystemAddress& sysAddr, std::istream& inStream) { + if (inStream.good()) { + std::string line; + while (std::getline(inStream, line)) { + // Do this in two separate calls to catch both \n and \r\n + line.erase(std::remove(line.begin(), line.end(), '\n'), line.end()); + line.erase(std::remove(line.begin(), line.end(), '\r'), line.end()); + SlashCommandHandler::HandleChatCommand(GeneralUtils::ASCIIToUTF16(line), &entity, sysAddr); + } + } else { + ChatPackets::SendSystemMessage(sysAddr, u"Unknown macro! Is the filename right?"); + } + } + void RunMacro(Entity* entity, const SystemAddress& sysAddr, const std::string args) { const auto splitArgs = GeneralUtils::SplitString(args, ' '); if (splitArgs.empty()) return; @@ -376,24 +390,16 @@ namespace DEVGMCommands { if (splitArgs[0].find("/") != std::string::npos) return; if (splitArgs[0].find("\\") != std::string::npos) return; - auto infile = Game::assetManager->GetFile(("macros/" + splitArgs[0] + ".scm").c_str()); - - if (!infile) { + const auto resServerPath = BinaryPathFinder::GetBinaryDir() / "resServer"; + auto infile = Game::assetManager->GetFile("macros/" + splitArgs[0] + ".scm"); + auto resServerInFile = std::ifstream(resServerPath / "macros" / (splitArgs[0] + ".scm")); + if (!infile.good() && !resServerInFile.good()) { ChatPackets::SendSystemMessage(sysAddr, u"Unknown macro! Is the filename right?"); return; } - if (infile.good()) { - std::string line; - while (std::getline(infile, line)) { - // Do this in two separate calls to catch both \n and \r\n - line.erase(std::remove(line.begin(), line.end(), '\n'), line.end()); - line.erase(std::remove(line.begin(), line.end(), '\r'), line.end()); - SlashCommandHandler::HandleChatCommand(GeneralUtils::ASCIIToUTF16(line), entity, sysAddr); - } - } else { - ChatPackets::SendSystemMessage(sysAddr, u"Unknown macro! Is the filename right?"); - } + HandleMacro(*entity, sysAddr, infile); + HandleMacro(*entity, sysAddr, resServerInFile); } void AddMission(Entity* entity, const SystemAddress& sysAddr, const std::string args) { From 74630b56c8f9935d0785c237816802723396b717 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Tue, 14 Oct 2025 22:53:39 -0700 Subject: [PATCH 32/39] feat: Loot rework (#1909) * feat: Loot rework * Allow dupe powerup pickups * change default team loot to shared --- dGame/Entity.cpp | 81 ++- dGame/Entity.h | 12 +- dGame/TeamManager.cpp | 10 + dGame/TeamManager.h | 2 + dGame/dComponents/ActivityComponent.cpp | 31 - dGame/dComponents/ActivityComponent.h | 11 - dGame/dComponents/DestroyableComponent.cpp | 35 +- dGame/dComponents/InventoryComponent.cpp | 2 +- dGame/dComponents/InventoryComponent.h | 3 +- dGame/dComponents/MissionComponent.cpp | 14 + dGame/dComponents/MissionComponent.h | 2 + dGame/dGameMessages/GameMessageHandler.cpp | 6 +- dGame/dGameMessages/GameMessages.cpp | 117 ++-- dGame/dGameMessages/GameMessages.h | 66 ++- dGame/dInventory/Item.cpp | 5 +- dGame/dUtilities/Loot.cpp | 547 +++++++++++++----- dGame/dUtilities/Loot.h | 15 +- .../SlashCommands/DEVGMCommands.cpp | 9 +- dScripts/ScriptedPowerupSpawner.cpp | 18 +- dScripts/ai/AG/AgPicnicBlanket.cpp | 17 +- .../ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp | 4 +- resources/sharedconfig.ini | 2 +- 22 files changed, 685 insertions(+), 324 deletions(-) diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 45136774..fee53aef 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -84,6 +84,8 @@ #include "GhostComponent.h" #include "AchievementVendorComponent.h" #include "VanityUtilities.h" +#include "ObjectIDManager.h" +#include "ePlayerFlag.h" // Table includes #include "CDComponentsRegistryTable.h" @@ -192,7 +194,10 @@ Entity::~Entity() { } void Entity::Initialize() { - RegisterMsg(MessageType::Game::REQUEST_SERVER_OBJECT_INFO, this, &Entity::MsgRequestServerObjectInfo); + RegisterMsg(this, &Entity::MsgRequestServerObjectInfo); + RegisterMsg(this, &Entity::MsgDropClientLoot); + RegisterMsg(this, &Entity::MsgGetFactionTokenType); + RegisterMsg(this, &Entity::MsgPickupItem); /** * Setup trigger */ @@ -287,7 +292,7 @@ void Entity::Initialize() { AddComponent(lupExhibitID); } - const auto racingControlID =compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RACING_CONTROL); + const auto racingControlID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::RACING_CONTROL); if (racingControlID > 0) { AddComponent(racingControlID); } @@ -1663,7 +1668,7 @@ void Entity::AddLootItem(const Loot::Info& info) const { auto* const characterComponent = GetComponent(); if (!characterComponent) return; - + LOG("Player %llu has been allowed to pickup %i with id %llu", m_ObjectID, info.lot, info.id); auto& droppedLoot = characterComponent->GetDroppedLoot(); droppedLoot[info.id] = info; } @@ -2275,3 +2280,73 @@ bool Entity::MsgRequestServerObjectInfo(GameMessages::GameMsg& msg) { if (client) GameMessages::SendUIMessageServerToSingleClient("ToggleObjectDebugger", response, client->GetSystemAddress()); return true; } + +bool Entity::MsgDropClientLoot(GameMessages::GameMsg& msg) { + auto& dropLootMsg = static_cast(msg); + + if (dropLootMsg.item != LOT_NULL && dropLootMsg.item != 0) { + Loot::Info info{ + .id = dropLootMsg.lootID, + .lot = dropLootMsg.item, + .count = dropLootMsg.count, + }; + AddLootItem(info); + } + + if (dropLootMsg.item == LOT_NULL && dropLootMsg.currency != 0) { + RegisterCoinDrop(dropLootMsg.currency); + } + + return true; +} + +bool Entity::MsgGetFlag(GameMessages::GameMsg& msg) { + auto& flagMsg = static_cast(msg); + if (m_Character) flagMsg.flag = m_Character->GetPlayerFlag(flagMsg.flagID); + return true; +} +bool Entity::MsgGetFactionTokenType(GameMessages::GameMsg& msg) { + auto& tokenMsg = static_cast(msg); + GameMessages::GetFlag getFlagMsg{}; + + getFlagMsg.flagID = ePlayerFlag::ASSEMBLY_FACTION; + MsgGetFlag(getFlagMsg); + if (getFlagMsg.flag) tokenMsg.tokenType = 8318; + + getFlagMsg.flagID = ePlayerFlag::SENTINEL_FACTION; + MsgGetFlag(getFlagMsg); + if (getFlagMsg.flag) tokenMsg.tokenType = 8319; + + getFlagMsg.flagID = ePlayerFlag::PARADOX_FACTION; + MsgGetFlag(getFlagMsg); + if (getFlagMsg.flag) tokenMsg.tokenType = 8320; + + getFlagMsg.flagID = ePlayerFlag::VENTURE_FACTION; + MsgGetFlag(getFlagMsg); + if (getFlagMsg.flag) tokenMsg.tokenType = 8321; + + LOG("Returning token type %i", tokenMsg.tokenType); + return tokenMsg.tokenType != LOT_NULL; +} + +bool Entity::MsgPickupItem(GameMessages::GameMsg& msg) { + auto& pickupItemMsg = static_cast(msg); + if (GetObjectID() == pickupItemMsg.lootOwnerID) { + PickupItem(pickupItemMsg.lootID); + } else { + auto* const characterComponent = GetComponent(); + if (!characterComponent) return false; + auto& droppedLoot = characterComponent->GetDroppedLoot(); + const auto it = droppedLoot.find(pickupItemMsg.lootID); + if (it != droppedLoot.end()) { + CDObjectsTable* objectsTable = CDClientManager::GetTable(); + const CDObjects& object = objectsTable->GetByID(it->second.lot); + if (object.id != 0 && object.type == "Powerup") { + return false; // Let powerups be duplicated + } + } + droppedLoot.erase(pickupItemMsg.lootID); + } + + return true; +} diff --git a/dGame/Entity.h b/dGame/Entity.h index f9498854..6d50efa7 100644 --- a/dGame/Entity.h +++ b/dGame/Entity.h @@ -176,6 +176,10 @@ public: void AddComponent(eReplicaComponentType componentId, Component* component); bool MsgRequestServerObjectInfo(GameMessages::GameMsg& msg); + bool MsgDropClientLoot(GameMessages::GameMsg& msg); + bool MsgGetFlag(GameMessages::GameMsg& msg); + bool MsgGetFactionTokenType(GameMessages::GameMsg& msg); + bool MsgPickupItem(GameMessages::GameMsg& msg); // This is expceted to never return nullptr, an assert checks this. CppScripts::Script* const GetScript() const; @@ -342,6 +346,12 @@ public: RegisterMsg(msgId, std::bind(handler, self, std::placeholders::_1)); } + template + inline void RegisterMsg(auto* self, const auto handler) { + T msg; + RegisterMsg(msg.msgId, self, handler); + } + /** * @brief The observable for player entity position updates. */ @@ -600,5 +610,5 @@ auto Entity::GetComponents() const { template auto Entity::GetComponentsMut() const { - return std::tuple{GetComponent()...}; + return std::tuple{ GetComponent()... }; } diff --git a/dGame/TeamManager.cpp b/dGame/TeamManager.cpp index abb20c48..89eff408 100644 --- a/dGame/TeamManager.cpp +++ b/dGame/TeamManager.cpp @@ -9,6 +9,16 @@ Team::Team() { lootOption = Game::config->GetValue("default_team_loot") == "0" ? 0 : 1; } +LWOOBJID Team::GetNextLootOwner() { + lootRound++; + + if (lootRound >= members.size()) { + lootRound = 0; + } + + return members[lootRound]; +} + TeamManager::TeamManager() { } diff --git a/dGame/TeamManager.h b/dGame/TeamManager.h index 5d4716f8..1c99a9d2 100644 --- a/dGame/TeamManager.h +++ b/dGame/TeamManager.h @@ -4,6 +4,8 @@ struct Team { Team(); + + LWOOBJID GetNextLootOwner(); LWOOBJID teamID = LWOOBJID_EMPTY; char lootOption = 0; std::vector members{}; diff --git a/dGame/dComponents/ActivityComponent.cpp b/dGame/dComponents/ActivityComponent.cpp index 6de50bed..7567e20b 100644 --- a/dGame/dComponents/ActivityComponent.cpp +++ b/dGame/dComponents/ActivityComponent.cpp @@ -45,33 +45,6 @@ ActivityComponent::ActivityComponent(Entity* parent, int32_t componentID) : Comp m_ActivityID = parent->GetVar(u"activityID"); LoadActivityData(m_ActivityID); } - - auto* destroyableComponent = m_Parent->GetComponent(); - - if (destroyableComponent) { - // First lookup the loot matrix id for this component id. - CDActivityRewardsTable* activityRewardsTable = CDClientManager::GetTable(); - std::vector activityRewards = activityRewardsTable->Query([=](CDActivityRewards entry) {return (entry.LootMatrixIndex == destroyableComponent->GetLootMatrixID()); }); - - uint32_t startingLMI = 0; - - // If we have one, set the starting loot matrix id to that. - if (activityRewards.size() > 0) { - startingLMI = activityRewards[0].LootMatrixIndex; - } - - if (startingLMI > 0) { - // We may have more than 1 loot matrix index to use depending ont the size of the team that is looting the activity. - // So this logic will get the rest of the loot matrix indices for this activity. - - std::vector objectTemplateActivities = activityRewardsTable->Query([=](CDActivityRewards entry) {return (activityRewards[0].objectTemplate == entry.objectTemplate); }); - for (const auto& item : objectTemplateActivities) { - if (item.activityRating > 0 && item.activityRating < 5) { - m_ActivityLootMatrices.insert({ item.activityRating, item.LootMatrixIndex }); - } - } - } - } } void ActivityComponent::LoadActivityData(const int32_t activityId) { CDActivitiesTable* activitiesTable = CDClientManager::GetTable(); @@ -698,10 +671,6 @@ bool ActivityComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) { } } - auto& lootMatrices = activityInfo.PushDebug("Loot Matrices"); - for (const auto& [activityRating, lootMatrixID] : m_ActivityLootMatrices) { - lootMatrices.PushDebug("Loot Matrix " + std::to_string(activityRating)) = lootMatrixID; - } activityInfo.PushDebug("ActivityID") = m_ActivityID; return true; } diff --git a/dGame/dComponents/ActivityComponent.h b/dGame/dComponents/ActivityComponent.h index 08ceb6da..dc249068 100644 --- a/dGame/dComponents/ActivityComponent.h +++ b/dGame/dComponents/ActivityComponent.h @@ -341,12 +341,6 @@ public: */ void SetInstanceMapID(uint32_t mapID) { m_ActivityInfo.instanceMapID = mapID; }; - /** - * Returns the LMI that this activity points to for a team size - * @param teamSize the team size to get the LMI for - * @return the LMI that this activity points to for a team size - */ - uint32_t GetLootMatrixForTeamSize(uint32_t teamSize) { return m_ActivityLootMatrices[teamSize]; } private: bool OnGetObjectReportInfo(GameMessages::GameMsg& msg); @@ -370,11 +364,6 @@ private: */ std::vector m_ActivityPlayers; - /** - * LMIs for team sizes - */ - std::unordered_map m_ActivityLootMatrices; - /** * The activity id */ diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 49b88cf4..8b654f9f 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -756,36 +756,7 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType //NANI?! if (!isPlayer) { if (owner != nullptr) { - auto* team = TeamManager::Instance()->GetTeam(owner->GetObjectID()); - - if (team != nullptr && m_Parent->GetComponent() != nullptr) { - LWOOBJID specificOwner = LWOOBJID_EMPTY; - auto* scriptedActivityComponent = m_Parent->GetComponent(); - uint32_t teamSize = team->members.size(); - uint32_t lootMatrixId = GetLootMatrixID(); - - if (scriptedActivityComponent) { - lootMatrixId = scriptedActivityComponent->GetLootMatrixForTeamSize(teamSize); - } - - if (team->lootOption == 0) { // Round robin - specificOwner = TeamManager::Instance()->GetNextLootOwner(team); - - auto* member = Game::entityManager->GetEntity(specificOwner); - - if (member) Loot::DropLoot(member, m_Parent->GetObjectID(), lootMatrixId, GetMinCoins(), GetMaxCoins()); - } else { - for (const auto memberId : team->members) { // Free for all - auto* member = Game::entityManager->GetEntity(memberId); - - if (member == nullptr) continue; - - Loot::DropLoot(member, m_Parent->GetObjectID(), lootMatrixId, GetMinCoins(), GetMaxCoins()); - } - } - } else { // drop loot for non team user - Loot::DropLoot(owner, m_Parent->GetObjectID(), GetLootMatrixID(), GetMinCoins(), GetMaxCoins()); - } + Loot::DropLoot(owner, m_Parent->GetObjectID(), GetLootMatrixID(), GetMinCoins(), GetMaxCoins()); } } else { //Check if this zone allows coin drops @@ -1046,8 +1017,8 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) { auto maxHealth = GetMaxHealth(); const auto uscoreMultiplier = Game::entityManager->GetHardcoreUscoreEnemiesMultiplier(); const bool isUscoreReducedLot = - Game::entityManager->GetHardcoreUscoreReducedLots().contains(lot) || - Game::entityManager->GetHardcoreUscoreReduced(); + Game::entityManager->GetHardcoreUscoreReducedLots().contains(lot) || + Game::entityManager->GetHardcoreUscoreReduced(); const auto uscoreReduction = isUscoreReducedLot ? Game::entityManager->GetHardcoreUscoreReduction() : 1.0f; int uscore = maxHealth * Game::entityManager->GetHardcoreUscoreEnemiesMultiplier() * uscoreReduction; diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 5361eb6b..6bd690f8 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -443,7 +443,7 @@ Item* InventoryComponent::FindItemBySubKey(LWOOBJID id, eInventoryType inventory } } -bool InventoryComponent::HasSpaceForLoot(const std::unordered_map& loot) { +bool InventoryComponent::HasSpaceForLoot(const Loot::Return& loot) { std::unordered_map spaceOffset{}; uint32_t slotsNeeded = 0; diff --git a/dGame/dComponents/InventoryComponent.h b/dGame/dComponents/InventoryComponent.h index 6e0fdba7..3728c428 100644 --- a/dGame/dComponents/InventoryComponent.h +++ b/dGame/dComponents/InventoryComponent.h @@ -22,6 +22,7 @@ #include "eInventoryType.h" #include "eReplicaComponentType.h" #include "eLootSourceType.h" +#include "Loot.h" class Entity; class ItemSet; @@ -200,7 +201,7 @@ public: * @param loot a map of items to add and how many to add * @return whether the entity has enough space for all the items */ - bool HasSpaceForLoot(const std::unordered_map& loot); + bool HasSpaceForLoot(const Loot::Return& loot); /** * Equips an item in the specified slot diff --git a/dGame/dComponents/MissionComponent.cpp b/dGame/dComponents/MissionComponent.cpp index 6c85f908..b3a503d5 100644 --- a/dGame/dComponents/MissionComponent.cpp +++ b/dGame/dComponents/MissionComponent.cpp @@ -31,6 +31,8 @@ MissionComponent::MissionComponent(Entity* parent, const int32_t componentID) : m_LastUsedMissionOrderUID = Game::zoneManager->GetUniqueMissionIdStartingValue(); RegisterMsg(this, &MissionComponent::OnGetObjectReportInfo); + RegisterMsg(this, &MissionComponent::OnGetMissionState); + RegisterMsg(this, &MissionComponent::OnMissionNeedsLot); } //! Destructor @@ -733,3 +735,15 @@ bool MissionComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) { return true; } + +bool MissionComponent::OnGetMissionState(GameMessages::GameMsg& msg) { + auto misState = static_cast(msg); + misState.missionState = GetMissionState(misState.missionID); + + return true; +} + +bool MissionComponent::OnMissionNeedsLot(GameMessages::GameMsg& msg) { + const auto& needMsg = static_cast(msg); + return RequiresItem(needMsg.item); +} diff --git a/dGame/dComponents/MissionComponent.h b/dGame/dComponents/MissionComponent.h index 53f09d41..a5cd058a 100644 --- a/dGame/dComponents/MissionComponent.h +++ b/dGame/dComponents/MissionComponent.h @@ -172,6 +172,8 @@ public: void ResetMission(const int32_t missionId); private: bool OnGetObjectReportInfo(GameMessages::GameMsg& msg); + bool OnGetMissionState(GameMessages::GameMsg& msg); + bool OnMissionNeedsLot(GameMessages::GameMsg& msg); /** * All the missions owned by this entity, mapped by mission ID */ diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index eae01e56..1e89e2a9 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -48,6 +48,7 @@ namespace { { REQUEST_USE, []() { return std::make_unique(); }}, { REQUEST_SERVER_OBJECT_INFO, []() { return std::make_unique(); } }, { SHOOTING_GALLERY_FIRE, []() { return std::make_unique(); } }, + { PICKUP_ITEM, []() { return std::make_unique(); } }, }; }; @@ -281,11 +282,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System break; } - case MessageType::Game::PICKUP_ITEM: { - GameMessages::HandlePickupItem(inStream, entity); - break; - } - case MessageType::Game::RESURRECT: { GameMessages::HandleResurrect(inStream, entity); break; diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index c23b4988..6855778d 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -1103,52 +1103,6 @@ void GameMessages::SendDropClientLoot(Entity* entity, const LWOOBJID& sourceID, finalPosition = NiPoint3(static_cast(spawnPos.GetX() + sin_v), spawnPos.GetY(), static_cast(spawnPos.GetZ() + cos_v)); } - - //Write data to packet & send: - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(entity->GetObjectID()); - bitStream.Write(MessageType::Game::DROP_CLIENT_LOOT); - - bitStream.Write(bUsePosition); - - bitStream.Write(finalPosition != NiPoint3Constant::ZERO); - if (finalPosition != NiPoint3Constant::ZERO) bitStream.Write(finalPosition); - - bitStream.Write(currency); - bitStream.Write(item); - bitStream.Write(lootID); - bitStream.Write(owner); - bitStream.Write(sourceID); - - bitStream.Write(spawnPos != NiPoint3Constant::ZERO); - if (spawnPos != NiPoint3Constant::ZERO) bitStream.Write(spawnPos); - - auto* team = TeamManager::Instance()->GetTeam(owner); - - // Currency and powerups should not sync - if (team != nullptr && currency == 0) { - CDObjectsTable* objectsTable = CDClientManager::GetTable(); - - const CDObjects& object = objectsTable->GetByID(item); - - if (object.type != "Powerup") { - for (const auto memberId : team->members) { - auto* member = Game::entityManager->GetEntity(memberId); - - if (member == nullptr) continue; - - SystemAddress sysAddr = member->GetSystemAddress(); - SEND_PACKET; - } - - return; - } - } - - SystemAddress sysAddr = entity->GetSystemAddress(); - SEND_PACKET; } void GameMessages::SendSetPlayerControlScheme(Entity* entity, eControlScheme controlScheme) { @@ -5725,27 +5679,6 @@ void GameMessages::HandleModularBuildMoveAndEquip(RakNet::BitStream& inStream, E inv->MoveItemToInventory(item, eInventoryType::MODELS, 1, false, true); } -void GameMessages::HandlePickupItem(RakNet::BitStream& inStream, Entity* entity) { - LWOOBJID lootObjectID; - LWOOBJID playerID; - inStream.Read(lootObjectID); - inStream.Read(playerID); - - entity->PickupItem(lootObjectID); - - auto* team = TeamManager::Instance()->GetTeam(entity->GetObjectID()); - - if (team != nullptr) { - for (const auto memberId : team->members) { - auto* member = Game::entityManager->GetEntity(memberId); - - if (member == nullptr || memberId == playerID) continue; - - SendTeamPickupItem(lootObjectID, lootObjectID, playerID, member->GetSystemAddress()); - } - } -} - void GameMessages::HandleResurrect(RakNet::BitStream& inStream, Entity* entity) { bool immediate = inStream.ReadBit(); @@ -6329,6 +6262,11 @@ namespace GameMessages { return Game::entityManager->SendMessage(*this); } + bool GameMsg::Send(const LWOOBJID _target) { + target = _target; + return Send(); + } + void GameMsg::Send(const SystemAddress& sysAddr) const { CBITSTREAM; CMSGHEADER; @@ -6496,4 +6434,49 @@ namespace GameMessages { stream.Write(emoteID); stream.Write(targetID); } + + void DropClientLoot::Serialize(RakNet::BitStream& stream) const { + stream.Write(bUsePosition); + + stream.Write(finalPosition != NiPoint3Constant::ZERO); + if (finalPosition != NiPoint3Constant::ZERO) stream.Write(finalPosition); + + stream.Write(currency); + stream.Write(item); + stream.Write(lootID); + stream.Write(ownerID); + stream.Write(sourceID); + + stream.Write(spawnPos != NiPoint3Constant::ZERO); + if (spawnPos != NiPoint3Constant::ZERO) stream.Write(spawnPos); + } + + bool PickupItem::Deserialize(RakNet::BitStream& stream) { + if (!stream.Read(lootID)) return false; + if (!stream.Read(lootOwnerID)) return false; + return true; + } + + void PickupItem::Handle(Entity& entity, const SystemAddress& sysAddr) { + auto* team = TeamManager::Instance()->GetTeam(entity.GetObjectID()); + LOG("Has team %i picking up %llu:%llu", team != nullptr, lootID, lootOwnerID); + if (team) { + for (const auto memberId : team->members) { + this->Send(memberId); + TeamPickupItem teamPickupMsg{}; + teamPickupMsg.target = lootID; + teamPickupMsg.lootID = lootID; + teamPickupMsg.lootOwnerID = lootOwnerID; + const auto* const memberEntity = Game::entityManager->GetEntity(memberId); + if (memberEntity) teamPickupMsg.Send(memberEntity->GetSystemAddress()); + } + } else { + entity.PickupItem(lootID); + } + } + + void TeamPickupItem::Serialize(RakNet::BitStream& stream) const { + stream.Write(lootID); + stream.Write(lootOwnerID); + } } diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 08ca677f..4f9ce78b 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -43,6 +43,7 @@ enum class eQuickBuildState : uint32_t; enum class BehaviorSlot : int32_t; enum class eVendorTransactionResult : uint32_t; enum class eReponseMoveItemBetweenInventoryTypeCode : int32_t; +enum class eMissionState : int; enum class eCameraTargetCyclingMode : int32_t { ALLOW_CYCLE_TEAMMATES, @@ -57,6 +58,7 @@ namespace GameMessages { // Sends a message to the entity manager to route to the target bool Send(); + bool Send(const LWOOBJID _target); // Sends the message to the specified client or // all clients if UNASSIGNED_SYSTEM_ADDRESS is specified @@ -850,9 +852,9 @@ namespace GameMessages { struct EmotePlayed : public GameMsg { EmotePlayed() : GameMsg(MessageType::Game::EMOTE_PLAYED), emoteID(0), targetID(0) {} - + void Serialize(RakNet::BitStream& stream) const override; - + int32_t emoteID; LWOOBJID targetID; }; @@ -870,5 +872,65 @@ namespace GameMessages { bool bIgnoreChecks{ false }; }; + + struct DropClientLoot : public GameMsg { + DropClientLoot() : GameMsg(MessageType::Game::DROP_CLIENT_LOOT) {} + + void Serialize(RakNet::BitStream& stream) const override; + LWOOBJID sourceID{ LWOOBJID_EMPTY }; + LOT item{ LOT_NULL }; + int32_t currency{}; + NiPoint3 spawnPos{}; + NiPoint3 finalPosition{}; + int32_t count{}; + bool bUsePosition{}; + LWOOBJID lootID{ LWOOBJID_EMPTY }; + LWOOBJID ownerID{ LWOOBJID_EMPTY }; + }; + + struct GetMissionState : public GameMsg { + GetMissionState() : GameMsg(MessageType::Game::GET_MISSION_STATE) {} + + int32_t missionID{}; + eMissionState missionState{}; + bool cooldownInfoRequested{}; + bool cooldownFinished{}; + }; + + struct GetFlag : public GameMsg { + GetFlag() : GameMsg(MessageType::Game::GET_FLAG) {} + + uint32_t flagID{}; + bool flag{}; + }; + + struct GetFactionTokenType : public GameMsg { + GetFactionTokenType() : GameMsg(MessageType::Game::GET_FACTION_TOKEN_TYPE) {} + + LOT tokenType{ LOT_NULL }; + }; + + struct MissionNeedsLot : public GameMsg { + MissionNeedsLot() : GameMsg(MessageType::Game::MISSION_NEEDS_LOT) {} + + LOT item{}; + }; + + struct PickupItem : public GameMsg { + PickupItem() : GameMsg(MessageType::Game::PICKUP_ITEM) {} + + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + bool Deserialize(RakNet::BitStream& stream) override; + LWOOBJID lootID{}; + LWOOBJID lootOwnerID{}; + }; + + struct TeamPickupItem : public GameMsg { + TeamPickupItem() : GameMsg(MessageType::Game::TEAM_PICKUP_ITEM) {} + + void Serialize(RakNet::BitStream& stream) const override; + LWOOBJID lootID{}; + LWOOBJID lootOwnerID{}; + }; }; #endif // GAMEMESSAGES_H diff --git a/dGame/dInventory/Item.cpp b/dGame/dInventory/Item.cpp index b39a1f73..7ee27f01 100644 --- a/dGame/dInventory/Item.cpp +++ b/dGame/dInventory/Item.cpp @@ -343,9 +343,9 @@ void Item::UseNonEquip(Item* item) { if (this->GetPreconditionExpression()->Check(playerInventoryComponent->GetParent())) { auto* entityParent = playerInventoryComponent->GetParent(); // Roll the loot for all the packages then see if it all fits. If it fits, give it to the player, otherwise don't. - std::unordered_map rolledLoot{}; + Loot::Return rolledLoot{}; for (auto& pack : packages) { - auto thisPackage = Loot::RollLootMatrix(entityParent, pack.LootMatrixIndex); + const auto thisPackage = Loot::RollLootMatrix(entityParent, pack.LootMatrixIndex); for (auto& loot : thisPackage) { // If we already rolled this lot, add it to the existing one, otherwise create a new entry. auto existingLoot = rolledLoot.find(loot.first); @@ -356,6 +356,7 @@ void Item::UseNonEquip(Item* item) { } } } + if (playerInventoryComponent->HasSpaceForLoot(rolledLoot)) { Loot::GiveLoot(playerInventoryComponent->GetParent(), rolledLoot, eLootSourceType::CONSUMPTION); item->SetCount(item->GetCount() - 1); diff --git a/dGame/dUtilities/Loot.cpp b/dGame/dUtilities/Loot.cpp index 5cbdf2d4..656ff61f 100644 --- a/dGame/dUtilities/Loot.cpp +++ b/dGame/dUtilities/Loot.cpp @@ -18,131 +18,27 @@ #include "MissionComponent.h" #include "eMissionState.h" #include "eReplicaComponentType.h" +#include "TeamManager.h" +#include "CDObjectsTable.h" +#include "ObjectIDManager.h" namespace { std::unordered_set CachedMatrices; + constexpr float g_MAX_DROP_RADIUS = 700.0f; } -void Loot::CacheMatrix(uint32_t matrixIndex) { - if (CachedMatrices.contains(matrixIndex)) return; +struct LootDropInfo { + CDLootTable table{}; + uint32_t count{ 0 }; +}; - CachedMatrices.insert(matrixIndex); +std::map RollLootMatrix(uint32_t matrixIndex) { CDComponentsRegistryTable* componentsRegistryTable = CDClientManager::GetTable(); CDItemComponentTable* itemComponentTable = CDClientManager::GetTable(); CDLootMatrixTable* lootMatrixTable = CDClientManager::GetTable(); CDLootTableTable* lootTableTable = CDClientManager::GetTable(); CDRarityTableTable* rarityTableTable = CDClientManager::GetTable(); - - const auto& matrix = lootMatrixTable->GetMatrix(matrixIndex); - - for (const auto& entry : matrix) { - const auto& lootTable = lootTableTable->GetTable(entry.LootTableIndex); - const auto& rarityTable = rarityTableTable->GetRarityTable(entry.RarityTableIndex); - for (const auto& loot : lootTable) { - uint32_t itemComponentId = componentsRegistryTable->GetByIDAndType(loot.itemid, eReplicaComponentType::ITEM); - uint32_t rarity = itemComponentTable->GetItemComponentByID(itemComponentId).rarity; - } - } -} - -std::unordered_map Loot::RollLootMatrix(Entity* player, uint32_t matrixIndex) { - CDComponentsRegistryTable* componentsRegistryTable = CDClientManager::GetTable(); - CDItemComponentTable* itemComponentTable = CDClientManager::GetTable(); - CDLootMatrixTable* lootMatrixTable = CDClientManager::GetTable(); - CDLootTableTable* lootTableTable = CDClientManager::GetTable(); - CDRarityTableTable* rarityTableTable = CDClientManager::GetTable(); - auto* missionComponent = player->GetComponent(); - - std::unordered_map drops; - - if (missionComponent == nullptr) return drops; - - const auto& matrix = lootMatrixTable->GetMatrix(matrixIndex); - - for (const auto& entry : matrix) { - if (GeneralUtils::GenerateRandomNumber(0, 1) < entry.percent) { // GetTable - const auto& lootTable = lootTableTable->GetTable(entry.LootTableIndex); - const auto& rarityTable = rarityTableTable->GetRarityTable(entry.RarityTableIndex); - - uint32_t dropCount = GeneralUtils::GenerateRandomNumber(entry.minToDrop, entry.maxToDrop); - for (uint32_t i = 0; i < dropCount; ++i) { - uint32_t maxRarity = 1; - - float rarityRoll = GeneralUtils::GenerateRandomNumber(0, 1); - - for (const auto& rarity : rarityTable) { - if (rarity.randmax >= rarityRoll) { - maxRarity = rarity.rarity; - } else { - break; - } - } - - bool rarityFound = false; - std::vector possibleDrops; - - for (const auto& loot : lootTable) { - uint32_t itemComponentId = componentsRegistryTable->GetByIDAndType(loot.itemid, eReplicaComponentType::ITEM); - uint32_t rarity = itemComponentTable->GetItemComponentByID(itemComponentId).rarity; - - if (rarity == maxRarity) { - possibleDrops.push_back(loot); - rarityFound = true; - } else if (rarity < maxRarity && !rarityFound) { - possibleDrops.push_back(loot); - maxRarity = rarity; - } - } - - if (possibleDrops.size() > 0) { - const auto& drop = possibleDrops[GeneralUtils::GenerateRandomNumber(0, possibleDrops.size() - 1)]; - - // filter out uneeded mission items - if (drop.MissionDrop && !missionComponent->RequiresItem(drop.itemid)) - continue; - - LOT itemID = drop.itemid; - // convert faction token proxy - if (itemID == 13763) { - if (missionComponent->GetMissionState(545) == eMissionState::COMPLETE) - itemID = 8318; // "Assembly Token" - else if (missionComponent->GetMissionState(556) == eMissionState::COMPLETE) - itemID = 8321; // "Venture League Token" - else if (missionComponent->GetMissionState(567) == eMissionState::COMPLETE) - itemID = 8319; // "Sentinels Token" - else if (missionComponent->GetMissionState(578) == eMissionState::COMPLETE) - itemID = 8320; // "Paradox Token" - } - - if (itemID == 13763) { - continue; - } // check if we aren't in faction - - // drops[itemID]++; this should work? - if (drops.find(itemID) == drops.end()) { - drops.insert({ itemID, 1 }); - } else { - ++drops[itemID]; - } - } - } - } - } - - for (const auto& drop : drops) { - LOG("Player %llu has rolled %i of item %i from loot matrix %i", player->GetObjectID(), drop.second, drop.first, matrixIndex); - } - - return drops; -} - -std::unordered_map Loot::RollLootMatrix(uint32_t matrixIndex) { - CDComponentsRegistryTable* componentsRegistryTable = CDClientManager::GetTable(); - CDItemComponentTable* itemComponentTable = CDClientManager::GetTable(); - CDLootMatrixTable* lootMatrixTable = CDClientManager::GetTable(); - CDLootTableTable* lootTableTable = CDClientManager::GetTable(); - CDRarityTableTable* rarityTableTable = CDClientManager::GetTable(); - std::unordered_map drops; + std::map drops; const auto& matrix = lootMatrixTable->GetMatrix(matrixIndex); @@ -181,14 +77,12 @@ std::unordered_map Loot::RollLootMatrix(uint32_t matrixIndex) { } } - if (possibleDrops.size() > 0) { + if (!possibleDrops.empty()) { const auto& drop = possibleDrops[GeneralUtils::GenerateRandomNumber(0, possibleDrops.size() - 1)]; - if (drops.find(drop.itemid) == drops.end()) { - drops.insert({ drop.itemid, 1 }); - } else { - ++drops[drop.itemid]; - } + auto& info = drops[drop.itemid]; + if (info.count == 0) info.table = drop; + info.count++; } } } @@ -197,15 +91,395 @@ std::unordered_map Loot::RollLootMatrix(uint32_t matrixIndex) { return drops; } +// Generates a 'random' final position for the loot drop based on its input spawn position. +void CalcFinalDropPos(GameMessages::DropClientLoot& lootMsg) { + lootMsg.bUsePosition = true; + + //Calculate where the loot will go: + uint16_t degree = GeneralUtils::GenerateRandomNumber(0, 360); + + double rad = degree * 3.14 / 180; + double sin_v = sin(rad) * 4.2; + double cos_v = cos(rad) * 4.2; + + const auto [x, y, z] = lootMsg.spawnPos; + lootMsg.finalPosition = NiPoint3(static_cast(x + sin_v), y, static_cast(z + cos_v)); +} + +// Visually drop the loot to all team members, though only the lootMsg.ownerID can pick it up +void DistrbuteMsgToTeam(const GameMessages::DropClientLoot& lootMsg, const Team& team) { + for (const auto memberClient : team.members) { + const auto* const memberEntity = Game::entityManager->GetEntity(memberClient); + if (memberEntity) lootMsg.Send(memberEntity->GetSystemAddress()); + } +} + +// The following 8 functions are all ever so slightly different such that combining them +// would make the logic harder to follow. Please read the comments! + +// Given a faction token proxy LOT to drop, drop 1 token for each player on a team, or the provided player. +// token drops are always given to every player on the team. +void DropFactionLoot(Entity& player, GameMessages::DropClientLoot& lootMsg) { + const auto playerID = player.GetObjectID(); + + GameMessages::GetFactionTokenType factionTokenType{}; + factionTokenType.target = playerID; + // If we're not in a faction, this message will return false + if (factionTokenType.Send()) { + lootMsg.item = factionTokenType.tokenType; + lootMsg.target = playerID; + lootMsg.ownerID = playerID; + lootMsg.lootID = ObjectIDManager::GenerateObjectID(); + CalcFinalDropPos(lootMsg); + // Register the drop on the player + lootMsg.Send(); + // Visually drop it for the player + lootMsg.Send(player.GetSystemAddress()); + } +} + +// Drops 1 token for each player on a team +// token drops are always given to every player on the team. +void DropFactionLoot(const Team& team, GameMessages::DropClientLoot& lootMsg) { + for (const auto member : team.members) { + GameMessages::GetPosition memberPosMsg{}; + memberPosMsg.target = member; + memberPosMsg.Send(); + if (NiPoint3::Distance(memberPosMsg.pos, lootMsg.spawnPos) > g_MAX_DROP_RADIUS) continue; + + GameMessages::GetFactionTokenType factionTokenType{}; + factionTokenType.target = member; + // If we're not in a faction, this message will return false + if (factionTokenType.Send()) { + lootMsg.item = factionTokenType.tokenType; + lootMsg.target = member; + lootMsg.ownerID = member; + lootMsg.lootID = ObjectIDManager::GenerateObjectID(); + CalcFinalDropPos(lootMsg); + // Register the drop on this team member + lootMsg.Send(); + // Show the rewards on all connected members of the team. Only the loot owner will be able to pick the tokens up. + DistrbuteMsgToTeam(lootMsg, team); + } + } +} + +// Drop the power up with no owner +// Power ups can be picked up by anyone on a team, however unlike actual loot items, +// if multiple clients say they picked one up, we let them pick it up. +void DropPowerupLoot(Entity& player, GameMessages::DropClientLoot& lootMsg) { + const auto playerID = player.GetObjectID(); + CalcFinalDropPos(lootMsg); + + lootMsg.lootID = ObjectIDManager::GenerateObjectID(); + lootMsg.ownerID = playerID; + lootMsg.target = playerID; + + // Register the drop on the player + lootMsg.Send(); + // Visually drop it for the player + lootMsg.Send(player.GetSystemAddress()); +} + +// Drop the power up with no owner +// Power ups can be picked up by anyone on a team, however unlike actual loot items, +// if multiple clients say they picked one up, we let them pick it up. +void DropPowerupLoot(const Team& team, GameMessages::DropClientLoot& lootMsg) { + lootMsg.lootID = ObjectIDManager::GenerateObjectID(); + lootMsg.ownerID = LWOOBJID_EMPTY; // By setting ownerID to empty, any client that gets this DropClientLoot message can pick up the item. + CalcFinalDropPos(lootMsg); + + // We want to drop the powerups as the same ID and the same position to all members of the team + for (const auto member : team.members) { + GameMessages::GetPosition memberPosMsg{}; + memberPosMsg.target = member; + memberPosMsg.Send(); + if (NiPoint3::Distance(memberPosMsg.pos, lootMsg.spawnPos) > g_MAX_DROP_RADIUS) continue; + + lootMsg.target = member; + // By sending this message with the same ID to all players on the team, all players on the team are allowed to pick it up. + lootMsg.Send(); + // No need to send to all members in a loop since that will happen by using the outer loop above and also since there is no owner + // sending to all will do nothing. + const auto* const memberEntity = Game::entityManager->GetEntity(member); + if (memberEntity) lootMsg.Send(memberEntity->GetSystemAddress()); + } +} + +// Drops a mission item for a player +// If the player does not need this item, it will not be dropped. +void DropMissionLoot(Entity& player, GameMessages::DropClientLoot& lootMsg) { + GameMessages::MissionNeedsLot needMsg{}; + needMsg.item = lootMsg.item; + const auto playerID = player.GetObjectID(); + needMsg.target = playerID; + // Will return false if the item is not required + if (needMsg.Send()) { + lootMsg.target = playerID; + lootMsg.ownerID = playerID; + lootMsg.lootID = ObjectIDManager::GenerateObjectID(); + CalcFinalDropPos(lootMsg); + // Register the drop with the player + lootMsg.Send(); + // Visually drop the loot to be picked up + lootMsg.Send(player.GetSystemAddress()); + } +} + +// Check if the item needs to be dropped for anyone on the team +// Only players who need the item will have it dropped +void DropMissionLoot(const Team& team, GameMessages::DropClientLoot& lootMsg) { + GameMessages::MissionNeedsLot needMsg{}; + needMsg.item = lootMsg.item; + for (const auto member : team.members) { + GameMessages::GetPosition memberPosMsg{}; + memberPosMsg.target = member; + memberPosMsg.Send(); + if (NiPoint3::Distance(memberPosMsg.pos, lootMsg.spawnPos) > g_MAX_DROP_RADIUS) continue; + + needMsg.target = member; + // Will return false if the item is not required + if (needMsg.Send()) { + lootMsg.target = member; + lootMsg.ownerID = member; + lootMsg.lootID = ObjectIDManager::GenerateObjectID(); + CalcFinalDropPos(lootMsg); + // Register the drop with the player + lootMsg.Send(); + DistrbuteMsgToTeam(lootMsg, team); + } + } +} + +// Drop a regular piece of loot. +// Most items will go through this. +// A player will always get a drop that goes through this function +void DropRegularLoot(Entity& player, GameMessages::DropClientLoot& lootMsg) { + const auto playerID = player.GetObjectID(); + + CalcFinalDropPos(lootMsg); + + lootMsg.lootID = ObjectIDManager::GenerateObjectID(); + lootMsg.target = playerID; + lootMsg.ownerID = playerID; + // Register the drop with the player + lootMsg.Send(); + // Visually drop the loot to be picked up + lootMsg.Send(player.GetSystemAddress()); + +} + +// Drop a regular piece of loot. +// Most items will go through this. +// Finds the next loot owner on the team the is in range of the kill and gives them this reward. +void DropRegularLoot(Team& team, GameMessages::DropClientLoot& lootMsg) { + auto earningPlayer = LWOOBJID_EMPTY; + lootMsg.lootID = ObjectIDManager::GenerateObjectID(); + CalcFinalDropPos(lootMsg); + GameMessages::GetPosition memberPosMsg{}; + // Find the next loot owner. Eventually this will run into the `player` passed into this function, since those will + // have the same ID, this loop will only ever run at most 4 times. + do { + earningPlayer = team.GetNextLootOwner(); + memberPosMsg.target = earningPlayer; + memberPosMsg.Send(); + } while (NiPoint3::Distance(memberPosMsg.pos, lootMsg.spawnPos) > g_MAX_DROP_RADIUS); + + if (team.lootOption == 0 /* Shared loot */) { + lootMsg.target = earningPlayer; + lootMsg.ownerID = earningPlayer; + lootMsg.Send(); + } else /* Free for all loot */ { + lootMsg.ownerID = LWOOBJID_EMPTY; + // By sending the loot with NO owner and to ALL members of the team, + // its a first come, first serve with who picks the item up. + for (const auto ffaMember : team.members) { + lootMsg.target = ffaMember; + lootMsg.Send(); + } + } + + DistrbuteMsgToTeam(lootMsg, team); +} + +void DropLoot(Entity* player, const LWOOBJID source, const std::map& rolledItems, uint32_t minCoins, uint32_t maxCoins) { + player = player->GetOwner(); // if the owner is overwritten, we collect that here + const auto playerID = player->GetObjectID(); + if (!player || !player->IsPlayer()) { + LOG("Trying to drop loot for non-player %llu:%i", playerID, player->GetLOT()); + return; + } + + // TODO should be scene based instead of radius based + // drop loot to either single player or team + // powerups never have an owner when dropped + // for every player on the team in a radius of 700 (arbitrary value, not lore) + // if shared loot, drop everything but tokens to the next team member that gets loot, + // then tokens to everyone (1 token drop in a 3 person team means everyone gets a token) + // if Free for all, drop everything with NO owner, except tokens which follow the same logic as above + auto* team = TeamManager::Instance()->GetTeam(playerID); + + GameMessages::GetPosition posMsg; + posMsg.target = source; + posMsg.Send(); + + const auto spawnPosition = posMsg.pos; + auto* const objectsTable = CDClientManager::GetTable(); + + constexpr LOT TOKEN_PROXY = 13763; + // Go through the drops 1 at a time to drop them + for (auto it = rolledItems.begin(); it != rolledItems.end(); it++) { + auto& [lootLot, info] = *it; + for (int i = 0; i < info.count; i++) { + GameMessages::DropClientLoot lootMsg{}; + lootMsg.spawnPos = spawnPosition; + lootMsg.sourceID = source; + lootMsg.item = lootLot; + lootMsg.count = 1; + lootMsg.currency = 0; + const CDObjects& object = objectsTable->GetByID(lootLot); + + if (lootLot == TOKEN_PROXY) { + team ? DropFactionLoot(*team, lootMsg) : DropFactionLoot(*player, lootMsg); + } else if (info.table.MissionDrop) { + team ? DropMissionLoot(*team, lootMsg) : DropMissionLoot(*player, lootMsg); + } else if (object.type == "Powerup") { + team ? DropPowerupLoot(*team, lootMsg) : DropPowerupLoot(*player, lootMsg); + } else { + team ? DropRegularLoot(*team, lootMsg) : DropRegularLoot(*player, lootMsg); + } + } + } + + // Coin roll is divided up between the members, rounded up, then dropped for each player + const uint32_t coinRoll = static_cast(minCoins + GeneralUtils::GenerateRandomNumber(0, 1) * (maxCoins - minCoins)); + const auto droppedCoins = team ? std::ceil(coinRoll / team->members.size()) : coinRoll; + if (team) { + for (auto member : team->members) { + GameMessages::DropClientLoot lootMsg{}; + lootMsg.target = member; + lootMsg.ownerID = member; + lootMsg.currency = droppedCoins; + lootMsg.spawnPos = spawnPosition; + lootMsg.sourceID = source; + lootMsg.item = LOT_NULL; + lootMsg.Send(); + const auto* const memberEntity = Game::entityManager->GetEntity(member); + if (memberEntity) lootMsg.Send(memberEntity->GetSystemAddress()); + } + } else { + GameMessages::DropClientLoot lootMsg{}; + lootMsg.target = playerID; + lootMsg.ownerID = playerID; + lootMsg.currency = droppedCoins; + lootMsg.spawnPos = spawnPosition; + lootMsg.sourceID = source; + lootMsg.item = LOT_NULL; + lootMsg.Send(); + lootMsg.Send(player->GetSystemAddress()); + } +} + +void Loot::DropItem(Entity& player, GameMessages::DropClientLoot& lootMsg, bool useTeam, bool forceFfa) { + auto* const team = useTeam ? TeamManager::Instance()->GetTeam(player.GetObjectID()) : nullptr; + char oldTeamLoot{}; + if (team && forceFfa) { + oldTeamLoot = team->lootOption; + team->lootOption = 1; + } + + auto* const objectsTable = CDClientManager::GetTable(); + const CDObjects& object = objectsTable->GetByID(lootMsg.item); + + constexpr LOT TOKEN_PROXY = 13763; + if (lootMsg.item == TOKEN_PROXY) { + team ? DropFactionLoot(*team, lootMsg) : DropFactionLoot(player, lootMsg); + } else if (object.type == "Powerup") { + team ? DropPowerupLoot(*team, lootMsg) : DropPowerupLoot(player, lootMsg); + } else { + team ? DropRegularLoot(*team, lootMsg) : DropRegularLoot(player, lootMsg); + } + + if (team) team->lootOption = oldTeamLoot; +} + +void Loot::CacheMatrix(uint32_t matrixIndex) { + if (CachedMatrices.contains(matrixIndex)) return; + + CachedMatrices.insert(matrixIndex); + CDComponentsRegistryTable* componentsRegistryTable = CDClientManager::GetTable(); + CDItemComponentTable* itemComponentTable = CDClientManager::GetTable(); + CDLootMatrixTable* lootMatrixTable = CDClientManager::GetTable(); + CDLootTableTable* lootTableTable = CDClientManager::GetTable(); + CDRarityTableTable* rarityTableTable = CDClientManager::GetTable(); + + const auto& matrix = lootMatrixTable->GetMatrix(matrixIndex); + + for (const auto& entry : matrix) { + const auto& lootTable = lootTableTable->GetTable(entry.LootTableIndex); + const auto& rarityTable = rarityTableTable->GetRarityTable(entry.RarityTableIndex); + for (const auto& loot : lootTable) { + uint32_t itemComponentId = componentsRegistryTable->GetByIDAndType(loot.itemid, eReplicaComponentType::ITEM); + uint32_t rarity = itemComponentTable->GetItemComponentByID(itemComponentId).rarity; + } + } +} + +Loot::Return Loot::RollLootMatrix(Entity* player, uint32_t matrixIndex) { + auto* const missionComponent = player ? player->GetComponent() : nullptr; + + Loot::Return toReturn; + const auto drops = ::RollLootMatrix(matrixIndex); + // if no mission component, just convert the map and skip checking if its a mission drop + if (!missionComponent) { + for (const auto& [lot, info] : drops) toReturn[lot] = info.count; + } else { + for (const auto& [lot, info] : drops) { + const auto& itemInfo = info.table; + + // filter out uneeded mission items + if (itemInfo.MissionDrop && !missionComponent->RequiresItem(itemInfo.itemid)) + continue; + + LOT itemLot = lot; + // convert faction token proxy + if (itemLot == 13763) { + if (missionComponent->GetMissionState(545) == eMissionState::COMPLETE) + itemLot = 8318; // "Assembly Token" + else if (missionComponent->GetMissionState(556) == eMissionState::COMPLETE) + itemLot = 8321; // "Venture League Token" + else if (missionComponent->GetMissionState(567) == eMissionState::COMPLETE) + itemLot = 8319; // "Sentinels Token" + else if (missionComponent->GetMissionState(578) == eMissionState::COMPLETE) + itemLot = 8320; // "Paradox Token" + } + + if (itemLot == 13763) { + continue; + } // check if we aren't in faction + + toReturn[itemLot] = info.count; + } + } + + if (player) { + for (const auto& [lot, count] : toReturn) { + LOG("Player %llu has rolled %i of item %i from loot matrix %i", player->GetObjectID(), count, lot, matrixIndex); + } + } + + return toReturn; +} + void Loot::GiveLoot(Entity* player, uint32_t matrixIndex, eLootSourceType lootSourceType) { player = player->GetOwner(); // If the owner is overwritten, we collect that here - std::unordered_map result = RollLootMatrix(player, matrixIndex); + const auto result = RollLootMatrix(player, matrixIndex); GiveLoot(player, result, lootSourceType); } -void Loot::GiveLoot(Entity* player, std::unordered_map& result, eLootSourceType lootSourceType) { +void Loot::GiveLoot(Entity* player, const Loot::Return& result, eLootSourceType lootSourceType) { player = player->GetOwner(); // if the owner is overwritten, we collect that here auto* inventoryComponent = player->GetComponent(); @@ -260,34 +534,9 @@ void Loot::DropLoot(Entity* player, const LWOOBJID source, uint32_t matrixIndex, if (!inventoryComponent) return; - std::unordered_map result = RollLootMatrix(player, matrixIndex); + const auto result = ::RollLootMatrix(matrixIndex); - DropLoot(player, source, result, minCoins, maxCoins); -} - -void Loot::DropLoot(Entity* player, const LWOOBJID source, std::unordered_map& result, uint32_t minCoins, uint32_t maxCoins) { - player = player->GetOwner(); // if the owner is overwritten, we collect that here - - auto* inventoryComponent = player->GetComponent(); - - if (!inventoryComponent) - return; - - GameMessages::GetPosition posMsg; - posMsg.target = source; - posMsg.Send(); - - const auto spawnPosition = posMsg.pos; - - for (const auto& pair : result) { - for (int i = 0; i < pair.second; ++i) { - GameMessages::SendDropClientLoot(player, source, pair.first, 0, spawnPosition, 1); - } - } - - uint32_t coins = static_cast(minCoins + GeneralUtils::GenerateRandomNumber(0, 1) * (maxCoins - minCoins)); - - GameMessages::SendDropClientLoot(player, source, LOT_NULL, coins, spawnPosition); + ::DropLoot(player, source, result, minCoins, maxCoins); } void Loot::DropActivityLoot(Entity* player, const LWOOBJID source, uint32_t activityID, int32_t rating) { diff --git a/dGame/dUtilities/Loot.h b/dGame/dUtilities/Loot.h index c8247b5b..266e35c6 100644 --- a/dGame/dUtilities/Loot.h +++ b/dGame/dUtilities/Loot.h @@ -6,20 +6,25 @@ class Entity; +namespace GameMessages { + struct DropClientLoot; +}; + namespace Loot { struct Info { LWOOBJID id = 0; LOT lot = 0; - uint32_t count = 0; + int32_t count = 0; }; - std::unordered_map RollLootMatrix(Entity* player, uint32_t matrixIndex); - std::unordered_map RollLootMatrix(uint32_t matrixIndex); + using Return = std::map; + + Loot::Return RollLootMatrix(Entity* player, uint32_t matrixIndex); void CacheMatrix(const uint32_t matrixIndex); void GiveLoot(Entity* player, uint32_t matrixIndex, eLootSourceType lootSourceType = eLootSourceType::NONE); - void GiveLoot(Entity* player, std::unordered_map& result, eLootSourceType lootSourceType = eLootSourceType::NONE); + void GiveLoot(Entity* player, const Loot::Return& result, eLootSourceType lootSourceType = eLootSourceType::NONE); void GiveActivityLoot(Entity* player, const LWOOBJID source, uint32_t activityID, int32_t rating = 0); void DropLoot(Entity* player, const LWOOBJID source, uint32_t matrixIndex, uint32_t minCoins, uint32_t maxCoins); - void DropLoot(Entity* player, const LWOOBJID source, std::unordered_map& result, uint32_t minCoins, uint32_t maxCoins); + void DropItem(Entity& player, GameMessages::DropClientLoot& lootMsg, bool useTeam = false, bool forceFfa = false); void DropActivityLoot(Entity* player, const LWOOBJID source, uint32_t activityID, int32_t rating = 0); }; diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp index 0c5b7585..0fd86512 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp @@ -1314,7 +1314,7 @@ namespace DEVGMCommands { for (uint32_t i = 0; i < loops; i++) { while (true) { - auto lootRoll = Loot::RollLootMatrix(lootMatrixIndex.value()); + const auto lootRoll = Loot::RollLootMatrix(nullptr, lootMatrixIndex.value()); totalRuns += 1; bool doBreak = false; for (const auto& kv : lootRoll) { @@ -1479,15 +1479,20 @@ namespace DEVGMCommands { void Inspect(Entity* entity, const SystemAddress& sysAddr, const std::string args) { const auto splitArgs = GeneralUtils::SplitString(args, ' '); if (splitArgs.empty()) return; + const auto idParsed = GeneralUtils::TryParse(splitArgs[0]); + // First try to get the object by its ID if provided. + // Second try to get the object by player name. + // Lastly assume we were passed a component or LDF and try to find the closest entity with that component or LDF. Entity* closest = nullptr; + if (idParsed) closest = Game::entityManager->GetEntity(idParsed.value()); float closestDistance = 0.0f; std::u16string ldf; bool isLDF = false; - closest = PlayerManager::GetPlayer(splitArgs[0]); + if (!closest) closest = PlayerManager::GetPlayer(splitArgs[0]); if (!closest) { auto component = GeneralUtils::TryParse(splitArgs[0]); if (!component) { diff --git a/dScripts/ScriptedPowerupSpawner.cpp b/dScripts/ScriptedPowerupSpawner.cpp index 9abb8fd6..8539b363 100644 --- a/dScripts/ScriptedPowerupSpawner.cpp +++ b/dScripts/ScriptedPowerupSpawner.cpp @@ -15,11 +15,6 @@ void ScriptedPowerupSpawner::OnTimerDone(Entity* self, std::string message) { const auto itemLOT = self->GetVar(u"lootLOT"); - // Build drop table - std::unordered_map drops; - - drops.emplace(itemLOT, 1); - // Spawn the required number of powerups auto* owner = Game::entityManager->GetEntity(self->GetSpawnerID()); if (owner != nullptr) { @@ -28,8 +23,19 @@ void ScriptedPowerupSpawner::OnTimerDone(Entity* self, std::string message) { if (renderComponent != nullptr) { renderComponent->PlayEffect(0, u"cast", "N_cast"); } + GameMessages::GetPosition posMsg{}; + posMsg.target = self->GetObjectID(); + posMsg.Send(); - Loot::DropLoot(owner, self->GetObjectID(), drops, 0, 0); + GameMessages::DropClientLoot lootMsg{}; + lootMsg.target = owner->GetObjectID(); + lootMsg.ownerID = owner->GetObjectID(); + lootMsg.sourceID = self->GetObjectID(); + lootMsg.spawnPos = posMsg.pos; + lootMsg.item = itemLOT; + lootMsg.count = 1; + lootMsg.currency = 0; + Loot::DropItem(*owner, lootMsg, true, true); } // Increment the current cycle diff --git a/dScripts/ai/AG/AgPicnicBlanket.cpp b/dScripts/ai/AG/AgPicnicBlanket.cpp index 4e0bb44a..fd3ced79 100644 --- a/dScripts/ai/AG/AgPicnicBlanket.cpp +++ b/dScripts/ai/AG/AgPicnicBlanket.cpp @@ -10,8 +10,21 @@ void AgPicnicBlanket::OnUse(Entity* self, Entity* user) { return; self->SetVar(u"active", true); - auto lootTable = std::unordered_map{ {935, 3} }; - Loot::DropLoot(user, self->GetObjectID(), lootTable, 0, 0); + GameMessages::GetPosition posMsg{}; + posMsg.target = self->GetObjectID(); + posMsg.Send(); + + for (int32_t i = 0; i < 3; i++) { + GameMessages::DropClientLoot lootMsg{}; + lootMsg.target = user->GetObjectID(); + lootMsg.ownerID = user->GetObjectID(); + lootMsg.sourceID = self->GetObjectID(); + lootMsg.item = 935; + lootMsg.count = 1; + lootMsg.spawnPos = posMsg.pos; + lootMsg.currency = 0; + Loot::DropItem(*user, lootMsg, true); + } self->AddCallbackTimer(5.0f, [self]() { self->SetVar(u"active", false); diff --git a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp index ecdacc42..e97316e5 100644 --- a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp +++ b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp @@ -415,9 +415,7 @@ void SGCannon::SpawnNewModel(Entity* self) { } if (lootMatrix != 0) { - std::unordered_map toDrop = {}; - toDrop = Loot::RollLootMatrix(player, lootMatrix); - + const auto toDrop = Loot::RollLootMatrix(player, lootMatrix); for (const auto [lot, count] : toDrop) { GameMessages::SetModelToBuild modelToBuild{}; modelToBuild.modelLot = lot; diff --git a/resources/sharedconfig.ini b/resources/sharedconfig.ini index ace0ef55..333f2249 100644 --- a/resources/sharedconfig.ini +++ b/resources/sharedconfig.ini @@ -47,7 +47,7 @@ client_net_version=171022 # Turn to 0 to default teams to use the live accurate Shared Loot (0) by default as opposed to Free for All (1) # This is used in both Chat and World servers. -default_team_loot=1 +default_team_loot=0 # event gating for login response and luz gating event_1=Talk_Like_A_Pirate From c2dba31f70c2699c9ce7c6896721df8af1fc52a6 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Wed, 15 Oct 2025 18:45:09 -0500 Subject: [PATCH 33/39] fix: bbb splitting dupe issue (#1908) * fix bbb group splitting issues * address feedback --- .github/copilot-instructions.md | 29 ++++ dCommon/Lxfml.cpp | 80 ++++++++-- dGame/dGameMessages/GameMessages.cpp | 8 + resources/worldconfig.ini | 3 + .../LxfmlTestFiles/CMakeLists.txt | 2 + .../LxfmlTestFiles/complex_grouping.lxfml | 132 ++++++++++++++++ .../LxfmlTestFiles/group_issue.lxfml | 48 ++++++ tests/dCommonTests/LxfmlTests.cpp | 142 ++++++++++++++++-- 8 files changed, 421 insertions(+), 23 deletions(-) create mode 100644 .github/copilot-instructions.md create mode 100644 tests/dCommonTests/LxfmlTestFiles/complex_grouping.lxfml create mode 100644 tests/dCommonTests/LxfmlTestFiles/group_issue.lxfml diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..ec4fa8c9 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,29 @@ +# 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 diff --git a/dCommon/Lxfml.cpp b/dCommon/Lxfml.cpp index fd71be4c..b950d037 100644 --- a/dCommon/Lxfml.cpp +++ b/dCommon/Lxfml.cpp @@ -271,6 +271,9 @@ std::vector Lxfml::Split(const std::string_view data, const NiPoi std::unordered_set usedBrickRefs; std::unordered_set usedRigidSystems; + // Track used groups to avoid processing them twice + std::unordered_set usedGroups; + // Helper to create output document from sets of brick refs and rigidsystem pointers auto makeOutput = [&](const std::unordered_set& bricksToInclude, const std::vector& rigidSystemsToInclude, const std::vector& groupsToInclude = {}) { tinyxml2::XMLDocument outDoc; @@ -323,19 +326,27 @@ std::vector Lxfml::Split(const std::string_view data, const NiPoi // 1) Process groups (each top-level Group becomes one output; nested groups are included) for (auto* groupRoot : groupRoots) { - // collect all partRefs in this group's subtree - std::unordered_set partRefs; - std::function collectParts = [&](const tinyxml2::XMLElement* g) { + // Skip if this group was already processed as part of another group + if (usedGroups.find(groupRoot) != usedGroups.end()) continue; + + // Helper to collect all partRefs in a group's subtree + std::function&)> collectParts = [&](const tinyxml2::XMLElement* g, std::unordered_set& partRefs) { if (!g) return; const char* partAttr = g->Attribute("partRefs"); if (partAttr) { for (auto& tok : GeneralUtils::SplitString(partAttr, ',')) partRefs.insert(tok); } - for (auto* child = g->FirstChildElement("Group"); child; child = child->NextSiblingElement("Group")) collectParts(child); + for (auto* child = g->FirstChildElement("Group"); child; child = child->NextSiblingElement("Group")) collectParts(child, partRefs); }; - collectParts(groupRoot); - // Build initial sets of bricks and boneRefs + // Collect all groups that need to be merged into this output + std::vector groupsToInclude{ groupRoot }; + usedGroups.insert(groupRoot); + + // Build initial sets of bricks and boneRefs from the starting group + std::unordered_set partRefs; + collectParts(groupRoot, partRefs); + std::unordered_set bricksIncluded; std::unordered_set boneRefsIncluded; for (const auto& pref : partRefs) { @@ -355,6 +366,7 @@ std::vector Lxfml::Split(const std::string_view data, const NiPoi } // Iteratively include any RigidSystems that reference any boneRefsIncluded + // and check if those rigid systems' bricks span other groups bool changed = true; std::vector rigidSystemsToInclude; int maxIterations = 1000; // Safety limit to prevent infinite loops @@ -362,6 +374,8 @@ std::vector Lxfml::Split(const std::string_view data, const NiPoi while (changed && iteration < maxIterations) { changed = false; iteration++; + + // First, expand rigid systems based on current boneRefsIncluded for (auto* rs : rigidSystems) { if (usedRigidSystems.find(rs) != usedRigidSystems.end()) continue; // parse boneRefs of this rigid system (from its children) @@ -392,6 +406,53 @@ std::vector Lxfml::Split(const std::string_view data, const NiPoi } } } + + // Second, check if the newly included bricks span any other groups + // If so, merge those groups into the current output + for (auto* otherGroup : groupRoots) { + if (usedGroups.find(otherGroup) != usedGroups.end()) continue; + + // Collect partRefs from this other group + std::unordered_set otherPartRefs; + collectParts(otherGroup, otherPartRefs); + + // Check if any of these partRefs correspond to bricks we've already included + bool spansOtherGroup = false; + for (const auto& pref : otherPartRefs) { + auto pit = partRefToBrick.find(pref); + if (pit != partRefToBrick.end()) { + const char* bref = pit->second->Attribute("refID"); + if (bref && bricksIncluded.find(std::string(bref)) != bricksIncluded.end()) { + spansOtherGroup = true; + break; + } + } + } + + if (spansOtherGroup) { + // Merge this group into the current output + usedGroups.insert(otherGroup); + groupsToInclude.push_back(otherGroup); + changed = true; + + // Add all partRefs, boneRefs, and bricks from this group + for (const auto& pref : otherPartRefs) { + auto pit = partRefToBrick.find(pref); + if (pit != partRefToBrick.end()) { + const char* bref = pit->second->Attribute("refID"); + if (bref) bricksIncluded.insert(std::string(bref)); + } + auto partIt = partRefToPart.find(pref); + if (partIt != partRefToPart.end()) { + auto* bone = partIt->second->FirstChildElement("Bone"); + if (bone) { + const char* bref = bone->Attribute("refID"); + if (bref) boneRefsIncluded.insert(std::string(bref)); + } + } + } + } + } } if (iteration >= maxIterations) { @@ -402,10 +463,9 @@ std::vector Lxfml::Split(const std::string_view data, const NiPoi // include bricks from bricksIncluded into used set for (const auto& b : bricksIncluded) usedBrickRefs.insert(b); - // make output doc and push result (include this group's XML) - std::vector groupsVec{ groupRoot }; - auto normalized = makeOutput(bricksIncluded, rigidSystemsToInclude, groupsVec); - results.push_back(normalized); + // make output doc and push result (include all merged groups' XML) + auto normalized = makeOutput(bricksIncluded, rigidSystemsToInclude, groupsToInclude); + results.push_back(normalized); } // 2) Process remaining RigidSystems (each becomes its own file) diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 6855778d..42fd2a58 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -2551,6 +2551,14 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent // Uncompress the data, split, and nornmalize the model const auto asStr = sd0.GetAsStringUncompressed(); + + if (Game::config->GetValue("save_lxfmls") == "1") { + // save using localId to avoid conflicts + std::ofstream outFile("debug_lxfml_uncompressed_" + std::to_string(localId) + ".lxfml"); + outFile << asStr; + outFile.close(); + } + auto splitLxfmls = Lxfml::Split(asStr); LOG_DEBUG("Split into %zu models", splitLxfmls.size()); diff --git a/resources/worldconfig.ini b/resources/worldconfig.ini index 38350086..5d23a32f 100644 --- a/resources/worldconfig.ini +++ b/resources/worldconfig.ini @@ -102,3 +102,6 @@ hardcore_disabled_worlds= # Keeps this percentage of a players' coins on death in hardcore hardcore_coin_keep= + +# save pre-split lxfmls to disk for debugging +save_lxfmls=0 diff --git a/tests/dCommonTests/LxfmlTestFiles/CMakeLists.txt b/tests/dCommonTests/LxfmlTestFiles/CMakeLists.txt index e07cbd62..52e709c2 100644 --- a/tests/dCommonTests/LxfmlTestFiles/CMakeLists.txt +++ b/tests/dCommonTests/LxfmlTestFiles/CMakeLists.txt @@ -8,6 +8,8 @@ set(LXFMLTESTFILES "no_bricks.lxfml" "test.lxfml" "too_few_values.lxfml" + "group_issue.lxfml" + "complex_grouping.lxfml" ) # Get the folder name and prepend it to the files above diff --git a/tests/dCommonTests/LxfmlTestFiles/complex_grouping.lxfml b/tests/dCommonTests/LxfmlTestFiles/complex_grouping.lxfml new file mode 100644 index 00000000..b17fe16b --- /dev/null +++ b/tests/dCommonTests/LxfmlTestFiles/complex_grouping.lxfml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/dCommonTests/LxfmlTestFiles/group_issue.lxfml b/tests/dCommonTests/LxfmlTestFiles/group_issue.lxfml new file mode 100644 index 00000000..7536fd1a --- /dev/null +++ b/tests/dCommonTests/LxfmlTestFiles/group_issue.lxfml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/dCommonTests/LxfmlTests.cpp b/tests/dCommonTests/LxfmlTests.cpp index 7e1ca4b7..67db1149 100644 --- a/tests/dCommonTests/LxfmlTests.cpp +++ b/tests/dCommonTests/LxfmlTests.cpp @@ -27,20 +27,25 @@ std::string SerializeElement(tinyxml2::XMLElement* elem) { return std::string(p.CStr()); }; -TEST(LxfmlTests, SplitUsesAllBricksAndNoDuplicates) { - // Read the test.lxfml file copied to build directory by CMake - std::string data = ReadFile("test.lxfml"); - ASSERT_FALSE(data.empty()) << "Failed to read test.lxfml from build directory"; +// Helper function to test splitting functionality +static void TestSplitUsesAllBricksAndNoDuplicatesHelper(const std::string& filename) { + // Read the LXFML file + std::string data = ReadFile(filename); + ASSERT_FALSE(data.empty()) << "Failed to read " << filename << " from build directory"; + std::cout << "\n=== Testing LXFML splitting for: " << filename << " ===" << std::endl; + auto results = Lxfml::Split(data); - ASSERT_GT(results.size(), 0); + ASSERT_GT(results.size(), 0) << "Split results should not be empty for " << filename; + + std::cout << "Split produced " << results.size() << " output(s)" << std::endl; // parse original to count bricks tinyxml2::XMLDocument doc; - ASSERT_EQ(doc.Parse(data.c_str()), tinyxml2::XML_SUCCESS); + ASSERT_EQ(doc.Parse(data.c_str()), tinyxml2::XML_SUCCESS) << "Failed to parse " << filename; DocumentReader reader(doc); auto lxfml = reader["LXFML"]; - ASSERT_TRUE(lxfml); + ASSERT_TRUE(lxfml) << "No LXFML element found in " << filename; std::unordered_set originalRigidSet; if (auto* rsParent = doc.FirstChildElement("LXFML")->FirstChildElement("RigidSystems")) { @@ -75,7 +80,20 @@ TEST(LxfmlTests, SplitUsesAllBricksAndNoDuplicates) { // Track used rigid systems and groups (serialized strings) std::unordered_set usedRigidSet; std::unordered_set usedGroupSet; + + std::cout << "Original file contains " << originalBricks.size() << " bricks: "; + for (const auto& brick : originalBricks) { + std::cout << brick << " "; + } + std::cout << std::endl; + + int splitIndex = 0; + std::filesystem::path baseFilename = std::filesystem::path(filename).stem(); + for (const auto& res : results) { + splitIndex++; + std::cout << "\n--- Split " << splitIndex << " ---" << std::endl; + tinyxml2::XMLDocument outDoc; ASSERT_EQ(outDoc.Parse(res.lxfml.c_str()), tinyxml2::XML_SUCCESS); DocumentReader outReader(outDoc); @@ -104,32 +122,130 @@ TEST(LxfmlTests, SplitUsesAllBricksAndNoDuplicates) { } } } + + // Collect and display bricks in this split + std::vector splitBricks; for (const auto& brick : outLxfml["Bricks"]) { const auto* ref = brick.Attribute("refID"); if (ref) { // no duplicate allowed ASSERT_EQ(usedBricks.find(ref), usedBricks.end()) << "Duplicate brick ref across splits: " << ref; usedBricks.insert(ref); + splitBricks.push_back(ref); } } + + std::cout << "Contains " << splitBricks.size() << " bricks: "; + for (const auto& brick : splitBricks) { + std::cout << brick << " "; + } + std::cout << std::endl; + + // Count rigid systems and groups + int rigidCount = 0; + if (auto* rsParent = outDoc.FirstChildElement("LXFML")->FirstChildElement("RigidSystems")) { + for (auto* rs = rsParent->FirstChildElement("RigidSystem"); rs; rs = rs->NextSiblingElement("RigidSystem")) { + rigidCount++; + } + } + + int groupCount = 0; + if (auto* gsParent = outDoc.FirstChildElement("LXFML")->FirstChildElement("GroupSystems")) { + for (auto* gs = gsParent->FirstChildElement("GroupSystem"); gs; gs = gs->NextSiblingElement("GroupSystem")) { + for (auto* g = gs->FirstChildElement("Group"); g; g = g->NextSiblingElement("Group")) { + groupCount++; + } + } + } + + std::cout << "Contains " << rigidCount << " rigid systems and " << groupCount << " groups" << std::endl; } // Every original brick must be used in one of the outputs for (const auto& bref : originalBricks) { - ASSERT_NE(usedBricks.find(bref), usedBricks.end()) << "Brick not used in splits: " << bref; + ASSERT_NE(usedBricks.find(bref), usedBricks.end()) << "Brick not used in splits: " << bref << " in " << filename; } // And usedBricks should not contain anything outside original for (const auto& ub : usedBricks) { - ASSERT_NE(originalBricks.find(ub), originalBricks.end()) << "Split produced unknown brick: " << ub; + ASSERT_NE(originalBricks.find(ub), originalBricks.end()) << "Split produced unknown brick: " << ub << " in " << filename; } // Ensure all original rigid systems and groups were used exactly once - ASSERT_EQ(originalRigidSet.size(), usedRigidSet.size()) << "RigidSystem count mismatch"; - for (const auto& s : originalRigidSet) ASSERT_NE(usedRigidSet.find(s), usedRigidSet.end()) << "RigidSystem missing in splits"; + ASSERT_EQ(originalRigidSet.size(), usedRigidSet.size()) << "RigidSystem count mismatch in " << filename; + for (const auto& s : originalRigidSet) ASSERT_NE(usedRigidSet.find(s), usedRigidSet.end()) << "RigidSystem missing in splits in " << filename; - ASSERT_EQ(originalGroupSet.size(), usedGroupSet.size()) << "Group count mismatch"; - for (const auto& s : originalGroupSet) ASSERT_NE(usedGroupSet.find(s), usedGroupSet.end()) << "Group missing in splits"; + ASSERT_EQ(originalGroupSet.size(), usedGroupSet.size()) << "Group count mismatch in " << filename; + for (const auto& s : originalGroupSet) ASSERT_NE(usedGroupSet.find(s), usedGroupSet.end()) << "Group missing in splits in " << filename; +} + +TEST(LxfmlTests, SplitGroupIssueFile) { + // Specific test for the group issue file + TestSplitUsesAllBricksAndNoDuplicatesHelper("group_issue.lxfml"); +} + +TEST(LxfmlTests, SplitTestFile) { + // Specific test for the larger test file + TestSplitUsesAllBricksAndNoDuplicatesHelper("test.lxfml"); +} + +TEST(LxfmlTests, SplitComplexGroupingFile) { + // Test for the complex grouping file - should produce only one split + // because all groups are connected via rigid systems + std::string data = ReadFile("complex_grouping.lxfml"); + ASSERT_FALSE(data.empty()) << "Failed to read complex_grouping.lxfml from build directory"; + + std::cout << "\n=== Testing complex grouping file ===" << std::endl; + + auto results = Lxfml::Split(data); + ASSERT_GT(results.size(), 0) << "Split results should not be empty"; + + // The complex grouping file should produce exactly ONE split + // because all groups share bricks through rigid systems + if (results.size() != 1) { + FAIL() << "Complex grouping file produced " << results.size() + << " splits instead of 1 (all groups should be merged)"; + } + + std::cout << "✓ Correctly produced 1 merged split" << std::endl; + + // Verify the split contains all the expected elements + tinyxml2::XMLDocument doc; + ASSERT_EQ(doc.Parse(results[0].lxfml.c_str()), tinyxml2::XML_SUCCESS); + + auto* lxfml = doc.FirstChildElement("LXFML"); + ASSERT_NE(lxfml, nullptr); + + // Count bricks + int brickCount = 0; + if (auto* bricks = lxfml->FirstChildElement("Bricks")) { + for (auto* brick = bricks->FirstChildElement("Brick"); brick; brick = brick->NextSiblingElement("Brick")) { + brickCount++; + } + } + std::cout << "Contains " << brickCount << " bricks" << std::endl; + + // Count rigid systems + int rigidCount = 0; + if (auto* rigidSystems = lxfml->FirstChildElement("RigidSystems")) { + for (auto* rs = rigidSystems->FirstChildElement("RigidSystem"); rs; rs = rs->NextSiblingElement("RigidSystem")) { + rigidCount++; + } + } + std::cout << "Contains " << rigidCount << " rigid systems" << std::endl; + EXPECT_GT(rigidCount, 0) << "Should contain rigid systems"; + + // Count groups + int groupCount = 0; + if (auto* groupSystems = lxfml->FirstChildElement("GroupSystems")) { + for (auto* gs = groupSystems->FirstChildElement("GroupSystem"); gs; gs = gs->NextSiblingElement("GroupSystem")) { + for (auto* g = gs->FirstChildElement("Group"); g; g = g->NextSiblingElement("Group")) { + groupCount++; + } + } + } + std::cout << "Contains " << groupCount << " groups" << std::endl; + EXPECT_GT(groupCount, 1) << "Should contain multiple groups (all merged into one split)"; } // Tests for invalid input handling - now working with the improved Split function From ec6253c80cc03ee81b91f11c3318e28ddd937342 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Wed, 15 Oct 2025 20:36:45 -0700 Subject: [PATCH 34/39] fix: coin dupe on same team (#1911) * feat: Loot rework * Allow dupe powerup pickups * change default team loot to shared * fix: coin dupe on team --- dGame/dComponents/DestroyableComponent.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 8b654f9f..f037f860 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -773,7 +773,15 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType coinsTotal -= coinsToLose; - Loot::DropLoot(m_Parent, m_Parent->GetObjectID(), -1, coinsToLose, coinsToLose); + GameMessages::DropClientLoot lootMsg{}; + lootMsg.target = m_Parent->GetObjectID(); + lootMsg.ownerID = m_Parent->GetObjectID(); + lootMsg.currency = coinsToLose; + lootMsg.spawnPos = m_Parent->GetPosition(); + lootMsg.sourceID = source; + lootMsg.item = LOT_NULL; + lootMsg.Send(); + lootMsg.Send(m_Parent->GetSystemAddress()); character->SetCoins(coinsTotal, eLootSourceType::PICKUP); } } From 4c9c773ec5ec14def9605673dc6254ca9f9b6e37 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 16 Oct 2025 12:13:38 -0700 Subject: [PATCH 35/39] fix: powerup drops and hardcore loot drops (#1914) tested the following are now functional ag buff station tiki torch ve rocket part boxes ns statue property behavior extra items from full inventory hardcore drops (items and coins) --- dGame/dComponents/DestroyableComponent.cpp | 27 +++++++++++-- dGame/dComponents/InventoryComponent.cpp | 9 ++++- dGame/dGameMessages/GameMessages.cpp | 38 ------------------- dGame/dGameMessages/GameMessages.h | 1 - dGame/dPropertyBehaviors/Strip.cpp | 10 ++++- dGame/dUtilities/Loot.cpp | 18 +++++---- dScripts/02_server/Map/GF/GfTikiTorch.cpp | 10 ++++- .../Objects/AgSurvivalBuffStation.cpp | 10 ++++- dScripts/ai/AG/AgImagSmashable.cpp | 10 ++++- dScripts/ai/NS/NsQbImaginationStatue.cpp | 11 +++++- 10 files changed, 86 insertions(+), 58 deletions(-) diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index f037f860..fabcd3ef 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -982,7 +982,14 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) { for (const auto item : itemMap | std::views::values) { // Don't drop excluded items or null ones if (!item || Game::entityManager->GetHardcoreExcludedItemDrops().contains(item->GetLot())) continue; - GameMessages::SendDropClientLoot(m_Parent, source, item->GetLot(), 0, m_Parent->GetPosition(), item->GetCount()); + GameMessages::DropClientLoot lootMsg{}; + lootMsg.target = m_Parent->GetObjectID(); + lootMsg.ownerID = m_Parent->GetObjectID(); + lootMsg.sourceID = m_Parent->GetObjectID(); + lootMsg.item = item->GetLot(); + lootMsg.count = 1; + lootMsg.spawnPos = m_Parent->GetPosition(); + for (int i = 0; i < item->GetCount(); i++) Loot::DropItem(*m_Parent, lootMsg); item->SetCount(0, false, false); } Game::entityManager->SerializeEntity(m_Parent); @@ -1005,12 +1012,24 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) { //drop all coins: constexpr auto MAX_TO_DROP_PER_GM = 100'000; + GameMessages::DropClientLoot lootMsg{}; + lootMsg.target = m_Parent->GetObjectID(); + lootMsg.ownerID = m_Parent->GetObjectID(); + lootMsg.spawnPos = m_Parent->GetPosition(); + lootMsg.sourceID = source; + lootMsg.item = LOT_NULL; + lootMsg.Send(); + lootMsg.Send(m_Parent->GetSystemAddress()); while (coinsToDrop > MAX_TO_DROP_PER_GM) { LOG("Dropping 100,000, %llu left", coinsToDrop); - GameMessages::SendDropClientLoot(m_Parent, source, LOT_NULL, MAX_TO_DROP_PER_GM, m_Parent->GetPosition()); - coinsToDrop -= MAX_TO_DROP_PER_GM; + lootMsg.currency = 100'000; + lootMsg.Send(); + lootMsg.Send(m_Parent->GetSystemAddress()); + coinsToDrop -= 100'000; } - GameMessages::SendDropClientLoot(m_Parent, source, LOT_NULL, coinsToDrop, m_Parent->GetPosition()); + lootMsg.currency = coinsToDrop; + lootMsg.Send(); + lootMsg.Send(m_Parent->GetSystemAddress()); } return; } diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 6bd690f8..212572ae 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -282,7 +282,14 @@ void InventoryComponent::AddItem( case 1: for (size_t i = 0; i < size; i++) { - GameMessages::SendDropClientLoot(this->m_Parent, this->m_Parent->GetObjectID(), lot, 0, this->m_Parent->GetPosition(), 1); + GameMessages::DropClientLoot lootMsg{}; + lootMsg.target = m_Parent->GetObjectID(); + lootMsg.ownerID = m_Parent->GetObjectID(); + lootMsg.sourceID = m_Parent->GetObjectID(); + lootMsg.item = lot; + lootMsg.count = 1; + lootMsg.spawnPos = m_Parent->GetPosition(); + Loot::DropItem(*m_Parent, lootMsg); } break; diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 42fd2a58..522f4741 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -1067,44 +1067,6 @@ void GameMessages::SendSetNetworkScriptVar(Entity* entity, const SystemAddress& SEND_PACKET; } -void GameMessages::SendDropClientLoot(Entity* entity, const LWOOBJID& sourceID, LOT item, int currency, NiPoint3 spawnPos, int count) { - if (Game::config->GetValue("disable_drops") == "1" || !entity) { - return; - } - - bool bUsePosition = false; - NiPoint3 finalPosition; - LWOOBJID lootID = LWOOBJID_EMPTY; - LWOOBJID owner = entity->GetObjectID(); - - if (item != LOT_NULL && item != 0) { - lootID = ObjectIDManager::GenerateObjectID(); - - Loot::Info info; - info.id = lootID; - info.count = count; - info.lot = item; - entity->AddLootItem(info); - } - - if (item == LOT_NULL && currency != 0) { - entity->RegisterCoinDrop(currency); - } - - if (spawnPos != NiPoint3Constant::ZERO) { - bUsePosition = true; - - //Calculate where the loot will go: - uint16_t degree = GeneralUtils::GenerateRandomNumber(0, 360); - - double rad = degree * 3.14 / 180; - double sin_v = sin(rad) * 4.2; - double cos_v = cos(rad) * 4.2; - - finalPosition = NiPoint3(static_cast(spawnPos.GetX() + sin_v), spawnPos.GetY(), static_cast(spawnPos.GetZ() + cos_v)); - } -} - void GameMessages::SendSetPlayerControlScheme(Entity* entity, eControlScheme controlScheme) { CBITSTREAM; CMSGHEADER; diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 4f9ce78b..5c682075 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -153,7 +153,6 @@ namespace GameMessages { void SendStop2DAmbientSound(Entity* entity, bool force, std::string audioGUID, bool result = false); void SendPlay2DAmbientSound(Entity* entity, std::string audioGUID, bool result = false); void SendSetNetworkScriptVar(Entity* entity, const SystemAddress& sysAddr, std::string data); - void SendDropClientLoot(Entity* entity, const LWOOBJID& sourceID, LOT item, int currency, NiPoint3 spawnPos = NiPoint3Constant::ZERO, int count = 1); void SendSetPlayerControlScheme(Entity* entity, eControlScheme controlScheme); void SendPlayerReachedRespawnCheckpoint(Entity* entity, const NiPoint3& position, const NiQuaternion& rotation); diff --git a/dGame/dPropertyBehaviors/Strip.cpp b/dGame/dPropertyBehaviors/Strip.cpp index fcbad8c2..330284a9 100644 --- a/dGame/dPropertyBehaviors/Strip.cpp +++ b/dGame/dPropertyBehaviors/Strip.cpp @@ -13,6 +13,7 @@ #include "dChatFilter.h" #include "DluAssert.h" +#include "Loot.h" template <> void Strip::HandleMsg(AddStripMessage& msg) { @@ -148,7 +149,14 @@ void Strip::Spawn(LOT lot, Entity& entity) { // Spawns a specific drop for all void Strip::SpawnDrop(LOT dropLOT, Entity& entity) { for (auto* const player : PlayerManager::GetAllPlayers()) { - GameMessages::SendDropClientLoot(player, entity.GetObjectID(), dropLOT, 0, entity.GetPosition()); + GameMessages::DropClientLoot lootMsg{}; + lootMsg.target = player->GetObjectID(); + lootMsg.ownerID = player->GetObjectID(); + lootMsg.sourceID = entity.GetObjectID(); + lootMsg.item = dropLOT; + lootMsg.count = 1; + lootMsg.spawnPos = entity.GetPosition(); + Loot::DropItem(*player, lootMsg); } } diff --git a/dGame/dUtilities/Loot.cpp b/dGame/dUtilities/Loot.cpp index 656ff61f..0b88f5af 100644 --- a/dGame/dUtilities/Loot.cpp +++ b/dGame/dUtilities/Loot.cpp @@ -93,17 +93,19 @@ std::map RollLootMatrix(uint32_t matrixIndex) { // Generates a 'random' final position for the loot drop based on its input spawn position. void CalcFinalDropPos(GameMessages::DropClientLoot& lootMsg) { - lootMsg.bUsePosition = true; + if (lootMsg.spawnPos != NiPoint3Constant::ZERO) { + lootMsg.bUsePosition = true; - //Calculate where the loot will go: - uint16_t degree = GeneralUtils::GenerateRandomNumber(0, 360); + //Calculate where the loot will go: + uint16_t degree = GeneralUtils::GenerateRandomNumber(0, 360); - double rad = degree * 3.14 / 180; - double sin_v = sin(rad) * 4.2; - double cos_v = cos(rad) * 4.2; + double rad = degree * 3.14 / 180; + double sin_v = sin(rad) * 4.2; + double cos_v = cos(rad) * 4.2; - const auto [x, y, z] = lootMsg.spawnPos; - lootMsg.finalPosition = NiPoint3(static_cast(x + sin_v), y, static_cast(z + cos_v)); + const auto [x, y, z] = lootMsg.spawnPos; + lootMsg.finalPosition = NiPoint3(static_cast(x + sin_v), y, static_cast(z + cos_v)); + } } // Visually drop the loot to all team members, though only the lootMsg.ownerID can pick it up diff --git a/dScripts/02_server/Map/GF/GfTikiTorch.cpp b/dScripts/02_server/Map/GF/GfTikiTorch.cpp index e61abd76..da55a77f 100644 --- a/dScripts/02_server/Map/GF/GfTikiTorch.cpp +++ b/dScripts/02_server/Map/GF/GfTikiTorch.cpp @@ -7,6 +7,7 @@ #include "eReplicaComponentType.h" #include "RenderComponent.h" #include "eTerminateType.h" +#include "Loot.h" void GfTikiTorch::OnStartup(Entity* self) { LightTorch(self); @@ -22,7 +23,14 @@ void GfTikiTorch::OnUse(Entity* self, Entity* killer) { self->SetI64(u"userID", killer->GetObjectID()); for (int i = 0; i < m_numspawn; i++) { - GameMessages::SendDropClientLoot(killer, self->GetObjectID(), 935, 0, self->GetPosition()); + GameMessages::DropClientLoot lootMsg{}; + lootMsg.target = killer->GetObjectID(); + lootMsg.ownerID = killer->GetObjectID(); + lootMsg.sourceID = self->GetObjectID(); + lootMsg.item = 935; + lootMsg.count = 1; + lootMsg.spawnPos = self->GetPosition(); + Loot::DropItem(*killer, lootMsg); } self->AddTimer("InteractionCooldown", 4); diff --git a/dScripts/02_server/Objects/AgSurvivalBuffStation.cpp b/dScripts/02_server/Objects/AgSurvivalBuffStation.cpp index 1a7cac11..16395a4e 100644 --- a/dScripts/02_server/Objects/AgSurvivalBuffStation.cpp +++ b/dScripts/02_server/Objects/AgSurvivalBuffStation.cpp @@ -4,6 +4,7 @@ #include "GameMessages.h" #include "SkillComponent.h" #include "TeamManager.h" +#include "Loot.h" void AgSurvivalBuffStation::OnQuickBuildComplete(Entity* self, Entity* target) { auto destroyableComponent = self->GetComponent(); @@ -55,7 +56,14 @@ void AgSurvivalBuffStation::OnTimerDone(Entity* self, std::string timerName) { for (auto memberID : team) { auto member = Game::entityManager->GetEntity(memberID); if (member != nullptr && !member->GetIsDead()) { - GameMessages::SendDropClientLoot(member, self->GetObjectID(), powerupToDrop, 0, self->GetPosition()); + GameMessages::DropClientLoot lootMsg{}; + lootMsg.target = member->GetObjectID(); + lootMsg.ownerID = member->GetObjectID(); + lootMsg.sourceID = self->GetObjectID(); + lootMsg.item = powerupToDrop; + lootMsg.count = 1; + lootMsg.spawnPos = self->GetPosition(); + Loot::DropItem(*member, lootMsg, true, true); } } } diff --git a/dScripts/ai/AG/AgImagSmashable.cpp b/dScripts/ai/AG/AgImagSmashable.cpp index 2a35ed9a..7d67cc29 100644 --- a/dScripts/ai/AG/AgImagSmashable.cpp +++ b/dScripts/ai/AG/AgImagSmashable.cpp @@ -5,6 +5,7 @@ #include "EntityInfo.h" #include "DestroyableComponent.h" #include "eReplicaComponentType.h" +#include "Loot.h" void AgImagSmashable::OnDie(Entity* self, Entity* killer) { bool maxImagGreaterThanZero = false; @@ -18,7 +19,14 @@ void AgImagSmashable::OnDie(Entity* self, Entity* killer) { if (maxImagGreaterThanZero) { int amount = GeneralUtils::GenerateRandomNumber(0, 3); for (int i = 0; i < amount; ++i) { - GameMessages::SendDropClientLoot(killer, self->GetObjectID(), 935, 0, self->GetPosition()); + GameMessages::DropClientLoot lootMsg{}; + lootMsg.target = killer->GetObjectID(); + lootMsg.ownerID = killer->GetObjectID(); + lootMsg.sourceID = self->GetObjectID(); + lootMsg.item = 935; + lootMsg.count = 1; + lootMsg.spawnPos = self->GetPosition(); + Loot::DropItem(*killer, lootMsg); } } } diff --git a/dScripts/ai/NS/NsQbImaginationStatue.cpp b/dScripts/ai/NS/NsQbImaginationStatue.cpp index 0d080d54..2a0b2107 100644 --- a/dScripts/ai/NS/NsQbImaginationStatue.cpp +++ b/dScripts/ai/NS/NsQbImaginationStatue.cpp @@ -1,6 +1,7 @@ #include "NsQbImaginationStatue.h" #include "EntityManager.h" #include "GameMessages.h" +#include "Loot.h" void NsQbImaginationStatue::OnStartup(Entity* self) { @@ -35,6 +36,12 @@ void NsQbImaginationStatue::SpawnLoot(Entity* self) { if (player == nullptr) return; - GameMessages::SendDropClientLoot(player, self->GetObjectID(), 935, 0); - GameMessages::SendDropClientLoot(player, self->GetObjectID(), 935, 0); + GameMessages::DropClientLoot lootMsg{}; + lootMsg.target = player->GetObjectID(); + lootMsg.ownerID = player->GetObjectID(); + lootMsg.sourceID = self->GetObjectID(); + lootMsg.item = 935; + lootMsg.count = 1; + Loot::DropItem(*player, lootMsg); + Loot::DropItem(*player, lootMsg); } From f3a5f60d81cb5582cf30170a3cc5797bcec25c7a Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 16 Oct 2025 12:15:02 -0700 Subject: [PATCH 36/39] feat: more destroyable debug info (#1912) * feat: more destroyable info * Change type and remove duplicate value --- dGame/Entity.cpp | 2 + dGame/dComponents/DestroyableComponent.cpp | 110 +++++++++++++++++---- dGame/dComponents/DestroyableComponent.h | 5 + dGame/dGameMessages/GameMessages.h | 1 + 4 files changed, 99 insertions(+), 19 deletions(-) diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index fee53aef..7d8046e0 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -424,6 +424,7 @@ void Entity::Initialize() { comp->SetIsSmashable(destCompData[0].isSmashable); comp->SetLootMatrixID(destCompData[0].LootMatrixIndex); + comp->SetCurrencyIndex(destCompData[0].CurrencyIndex); Loot::CacheMatrix(destCompData[0].LootMatrixIndex); // Now get currency information @@ -2252,6 +2253,7 @@ bool Entity::MsgRequestServerObjectInfo(GameMessages::GameMsg& msg) { response.Insert("objectID", std::to_string(m_ObjectID)); response.Insert("serverInfo", true); GameMessages::GetObjectReportInfo info{}; + info.clientID = requestInfo.clientId; info.bVerbose = requestInfo.bVerbose; info.info = response.InsertArray("data"); auto& objectInfo = info.info->PushDebug("Object Details"); diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index fabcd3ef..0658757c 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -3,6 +3,9 @@ #include "Logger.h" #include "Game.h" #include "dConfig.h" +#include "CDLootMatrixTable.h" +#include "CDLootTableTable.h" +#include "CDRarityTableTable.h" #include "Amf3.h" #include "AmfSerialize.h" @@ -1060,38 +1063,89 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) { bool DestroyableComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) { auto& reportInfo = static_cast(msg); - auto& destroyableInfo = reportInfo.info->PushDebug("Destroyable"); - destroyableInfo.PushDebug("Health") = m_iHealth; - destroyableInfo.PushDebug("Max Health") = m_fMaxHealth; - destroyableInfo.PushDebug("Armor") = m_iArmor; - destroyableInfo.PushDebug("Max Armor") = m_fMaxArmor; - destroyableInfo.PushDebug("Imagination") = m_iImagination; - destroyableInfo.PushDebug("Max Imagination") = m_fMaxImagination; - destroyableInfo.PushDebug("Damage To Absorb") = m_DamageToAbsorb; + destroyableInfo.PushDebug("DestructibleComponent DB Table Template ID") = m_ComponentID; + + if (m_CurrencyIndex == -1) { + destroyableInfo.PushDebug("Has Loot Currency") = false; + } else { + destroyableInfo.PushDebug("Loot Currency ID") = m_CurrencyIndex; + auto& detailedCoinInfo = destroyableInfo.PushDebug("Coin Info"); + detailedCoinInfo.PushDebug("Min Coins") = m_MinCoins; + detailedCoinInfo.PushDebug("Max Coins") = m_MaxCoins; + } + + if (m_LootMatrixID == -1 || m_LootMatrixID == 0) { + destroyableInfo.PushDebug("Has Loot Matrix") = false; + } else { + auto& lootInfo = destroyableInfo.PushDebug("Loot Info"); + lootInfo.PushDebug("Loot Matrix ID") = m_LootMatrixID; + auto* const componentsRegistryTable = CDClientManager::GetTable(); + auto* const itemComponentTable = CDClientManager::GetTable(); + auto* const lootMatrixTable = CDClientManager::GetTable(); + auto* const lootTableTable = CDClientManager::GetTable(); + auto* const rarityTableTable = CDClientManager::GetTable(); + + const auto& matrix = lootMatrixTable->GetMatrix(m_LootMatrixID); + + for (const auto& entry : matrix) { + auto& thisEntry = lootInfo.PushDebug("Loot table Index - " + std::to_string(entry.LootTableIndex)); + thisEntry.PushDebug("Percent chance to drop") = entry.percent * 100.0f; + thisEntry.PushDebug("Minimum amount to drop") = entry.minToDrop; + thisEntry.PushDebug("Maximum amount to drop") = entry.maxToDrop; + const auto& lootTable = lootTableTable->GetTable(entry.LootTableIndex); + const auto& rarityTable = rarityTableTable->GetRarityTable(entry.RarityTableIndex); + + auto& thisRarity = thisEntry.PushDebug("Rarity"); + for (const auto& rarity : rarityTable) { + thisRarity.PushDebug("Rarity " + std::to_string(rarity.rarity)) = rarity.randmax; + } + + auto& thisItems = thisEntry.PushDebug("Drop(s) Info"); + for (const auto& loot : lootTable) { + uint32_t itemComponentId = componentsRegistryTable->GetByIDAndType(loot.itemid, eReplicaComponentType::ITEM); + uint32_t rarity = itemComponentTable->GetItemComponentByID(itemComponentId).rarity; + auto title = "%[Objects_" + std::to_string(loot.itemid) + "_name] " + std::to_string(loot.itemid); + if (loot.MissionDrop) title += " - Mission Drop"; + thisItems.PushDebug(title); + } + } + } + + auto* const entity = Game::entityManager->GetEntity(reportInfo.clientID); + destroyableInfo.PushDebug("Is on your team") = entity ? IsFriend(entity) : false; + auto& stats = destroyableInfo.PushDebug("Statistics"); + stats.PushDebug("Health") = m_iHealth; + stats.PushDebug("Maximum Health") = m_fMaxHealth; + stats.PushDebug("Armor") = m_iArmor; + stats.PushDebug("Maximum Armor") = m_fMaxArmor; + stats.PushDebug("Imagination") = m_iImagination; + stats.PushDebug("Maximum Imagination") = m_fMaxImagination; + stats.PushDebug("Damage Absorption Points") = m_DamageToAbsorb; destroyableInfo.PushDebug("Is GM Immune") = m_IsGMImmune; destroyableInfo.PushDebug("Is Shielded") = m_IsShielded; destroyableInfo.PushDebug("Attacks To Block") = m_AttacksToBlock; destroyableInfo.PushDebug("Damage Reduction") = m_DamageReduction; - auto& factions = destroyableInfo.PushDebug("Factions"); - size_t i = 0; + std::stringstream factionsStream; for (const auto factionID : m_FactionIDs) { - factions.PushDebug(std::to_string(i++) + " " + std::to_string(factionID)) = ""; + factionsStream << factionID << " "; } - auto& enemyFactions = destroyableInfo.PushDebug("Enemy Factions"); - i = 0; + + destroyableInfo.PushDebug("Factions") = factionsStream.str(); + + factionsStream.str(""); for (const auto enemyFactionID : m_EnemyFactionIDs) { - enemyFactions.PushDebug(std::to_string(i++) + " " + std::to_string(enemyFactionID)) = ""; + factionsStream << enemyFactionID << " "; } + + destroyableInfo.PushDebug("Enemy Factions") = factionsStream.str(); + destroyableInfo.PushDebug("Is Smashable") = m_IsSmashable; - destroyableInfo.PushDebug("Is Dead") = m_IsDead; destroyableInfo.PushDebug("Is Smashed") = m_IsSmashed; destroyableInfo.PushDebug("Is Module Assembly") = m_IsModuleAssembly; destroyableInfo.PushDebug("Explode Factor") = m_ExplodeFactor; destroyableInfo.PushDebug("Has Threats") = m_HasThreats; - destroyableInfo.PushDebug("Loot Matrix ID") = m_LootMatrixID; - destroyableInfo.PushDebug("Min Coins") = m_MinCoins; - destroyableInfo.PushDebug("Max Coins") = m_MaxCoins; + destroyableInfo.PushDebug("Killer ID") = std::to_string(m_KillerID); // "Scripts"; idk what to do about scripts yet @@ -1106,7 +1160,25 @@ bool DestroyableComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) { immuneCounts.PushDebug("Quickbuild Interrupt") = m_ImmuneToQuickbuildInterruptCount; immuneCounts.PushDebug("Pull To Point") = m_ImmuneToPullToPointCount; - destroyableInfo.PushDebug("Death Behavior") = m_DeathBehavior; + auto& deathInfo = destroyableInfo.PushDebug("Death Info"); + deathInfo.PushDebug("Is Dead") = m_IsDead; + switch (m_DeathBehavior) { + case 0: + deathInfo.PushDebug("Death Behavior") = "Fade"; + break; + case 1: + deathInfo.PushDebug("Death Behavior") = "Stay"; + break; + case 2: + deathInfo.PushDebug("Death Behavior") = "Immediate"; + break; + case -1: + deathInfo.PushDebug("Death Behavior") = "Invulnerable"; + break; + default: + deathInfo.PushDebug("Death Behavior") = "Other"; + break; + } destroyableInfo.PushDebug("Damage Cooldown Timer") = m_DamageCooldownTimer; return true; diff --git a/dGame/dComponents/DestroyableComponent.h b/dGame/dComponents/DestroyableComponent.h index c6bb0a98..9b3e46af 100644 --- a/dGame/dComponents/DestroyableComponent.h +++ b/dGame/dComponents/DestroyableComponent.h @@ -370,6 +370,8 @@ public: */ uint32_t GetLootMatrixID() const { return m_LootMatrixID; } + void SetCurrencyIndex(int32_t currencyIndex) { m_CurrencyIndex = currencyIndex; } + /** * Returns the ID of the entity that killed this entity, if any * @return the ID of the entity that killed this entity, if any @@ -587,6 +589,9 @@ private: */ uint32_t m_LootMatrixID; + // The currency index to determine how much loot to drop + int32_t m_CurrencyIndex{ -1 }; + /** * The min amount of coins that will drop when this entity is smashed */ diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 5c682075..1b52d67a 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -793,6 +793,7 @@ namespace GameMessages { AMFArrayValue* info{}; AMFArrayValue* subCategory{}; bool bVerbose{}; + LWOOBJID clientID{}; GetObjectReportInfo() : GameMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, eGameMasterLevel::DEVELOPER) {} }; From 002aa896d8d1b810eeaf000ec0931fedd0771beb Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 19 Oct 2025 05:22:45 -0700 Subject: [PATCH 37/39] feat: debug information (#1915) --- dCommon/Amf3.h | 15 +++++ dGame/dComponents/BaseCombatAIComponent.cpp | 75 +++++++++++++++++++++ dGame/dComponents/BaseCombatAIComponent.h | 2 + dGame/dComponents/BouncerComponent.cpp | 69 +++++++++++++++++++ dGame/dComponents/BouncerComponent.h | 32 +++++++++ dGame/dComponents/CollectibleComponent.cpp | 34 ++++++++++ dGame/dComponents/CollectibleComponent.h | 4 +- dGame/dComponents/DestroyableComponent.cpp | 6 +- dGame/dComponents/GhostComponent.cpp | 14 ++++ dGame/dComponents/GhostComponent.h | 2 + dGame/dComponents/SwitchComponent.h | 4 ++ 11 files changed, 253 insertions(+), 4 deletions(-) diff --git a/dCommon/Amf3.h b/dCommon/Amf3.h index 9a34ad59..174ad814 100644 --- a/dCommon/Amf3.h +++ b/dCommon/Amf3.h @@ -374,6 +374,21 @@ public: return value->Insert("value", std::make_unique()); } + AMFArrayValue& PushDebug(const NiPoint3& point) { + PushDebug("X") = point.x; + PushDebug("Y") = point.y; + PushDebug("Z") = point.z; + return *this; + } + + AMFArrayValue& PushDebug(const NiQuaternion& rot) { + PushDebug("W") = rot.w; + PushDebug("X") = rot.x; + PushDebug("Y") = rot.y; + PushDebug("Z") = rot.z; + return *this; + } + private: /** * The associative portion. These values are key'd with strings to an AMFValue. diff --git a/dGame/dComponents/BaseCombatAIComponent.cpp b/dGame/dComponents/BaseCombatAIComponent.cpp index d264801f..73118e36 100644 --- a/dGame/dComponents/BaseCombatAIComponent.cpp +++ b/dGame/dComponents/BaseCombatAIComponent.cpp @@ -27,8 +27,13 @@ #include "CDComponentsRegistryTable.h" #include "CDPhysicsComponentTable.h" #include "dNavMesh.h" +#include "Amf3.h" BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { + { + using namespace GameMessages; + RegisterMsg(this, &BaseCombatAIComponent::MsgGetObjectReportInfo); + } m_Target = LWOOBJID_EMPTY; m_DirtyStateOrTarget = true; m_State = AiState::spawn; @@ -839,3 +844,73 @@ void BaseCombatAIComponent::IgnoreThreat(const LWOOBJID threat, const float valu SetThreat(threat, 0.0f); m_Target = LWOOBJID_EMPTY; } + +bool BaseCombatAIComponent::MsgGetObjectReportInfo(GameMessages::GameMsg& msg) { + using enum AiState; + auto& reportMsg = static_cast(msg); + auto& cmptType = reportMsg.info->PushDebug("Base Combat AI"); + cmptType.PushDebug("Component ID") = GetComponentID(); + auto& targetInfo = cmptType.PushDebug("Current Target Info"); + targetInfo.PushDebug("Current Target ID") = std::to_string(m_Target); + // if (m_Target != LWOOBJID_EMPTY) { + // LWOGameMessages::ObjGetName nameMsg(m_CurrentTarget); + // SEND_GAMEOBJ_MSG(nameMsg); + // if (!nameMsg.msg.name.empty()) targetInfo.PushDebug("Name") = nameMsg.msg.name; + // } + + auto& roundInfo = cmptType.PushDebug("Round Info"); + // roundInfo.PushDebug("Combat Round Time") = m_CombatRoundLength; + // roundInfo.PushDebug("Minimum Time") = m_MinRoundLength; + // roundInfo.PushDebug("Maximum Time") = m_MaxRoundLength; + // roundInfo.PushDebug("Selected Time") = m_SelectedTime; + // roundInfo.PushDebug("Combat Start Delay") = m_CombatStartDelay; + std::string curState; + switch (m_State) { + case idle: curState = "Idling"; break; + case aggro: curState = "Aggroed"; break; + case tether: curState = "Returning to Tether"; break; + case spawn: curState = "Spawn"; break; + case dead: curState = "Dead"; break; + default: curState = "Unknown or Undefined"; break; + } + cmptType.PushDebug("Current Combat State") = curState; + + //switch (m_CombatBehaviorType) { + // case 0: curState = "Passive"; break; + // case 1: curState = "Aggressive"; break; + // case 2: curState = "Passive (Turret)"; break; + // case 3: curState = "Aggressive (Turret)"; break; + // default: curState = "Unknown or Undefined"; break; + //} + //cmptType.PushDebug("Current Combat Behavior State") = curState; + + //switch (m_CombatRole) { + // case 0: curState = "Melee"; break; + // case 1: curState = "Ranged"; break; + // case 2: curState = "Support"; break; + // default: curState = "Unknown or Undefined"; break; + //} + //cmptType.PushDebug("Current Combat Role") = curState; + + auto& tetherPoint = cmptType.PushDebug("Tether Point"); + tetherPoint.PushDebug("X") = m_StartPosition.x; + tetherPoint.PushDebug("Y") = m_StartPosition.y; + tetherPoint.PushDebug("Z") = m_StartPosition.z; + cmptType.PushDebug("Hard Tether Radius") = m_HardTetherRadius; + cmptType.PushDebug("Soft Tether Radius") = m_SoftTetherRadius; + cmptType.PushDebug("Aggro Radius") = m_AggroRadius; + cmptType.PushDebug("Tether Speed") = m_TetherSpeed; + cmptType.PushDebug("Aggro Speed") = m_TetherSpeed; + // cmptType.PushDebug("Specified Min Range") = m_SpecificMinRange; + // cmptType.PushDebug("Specified Max Range") = m_SpecificMaxRange; + auto& threats = cmptType.PushDebug("Target Threats"); + for (const auto& [id, threat] : m_ThreatEntries) { + threats.PushDebug(std::to_string(id)) = threat; + } + + auto& ignoredThreats = cmptType.PushDebug("Temp Ignored Threats"); + for (const auto& [id, threat] : m_ThreatEntries) { + ignoredThreats.PushDebug(std::to_string(id) + " - Time") = threat; + } + return true; +} diff --git a/dGame/dComponents/BaseCombatAIComponent.h b/dGame/dComponents/BaseCombatAIComponent.h index 164b2ef5..009a96d2 100644 --- a/dGame/dComponents/BaseCombatAIComponent.h +++ b/dGame/dComponents/BaseCombatAIComponent.h @@ -234,6 +234,8 @@ public: // Ignore a threat for a certain amount of time void IgnoreThreat(const LWOOBJID target, const float time); + bool MsgGetObjectReportInfo(GameMessages::GameMsg& msg); + private: /** * Returns the current target or the target that currently is the largest threat to this entity diff --git a/dGame/dComponents/BouncerComponent.cpp b/dGame/dComponents/BouncerComponent.cpp index a7c7f1a8..3c535e43 100644 --- a/dGame/dComponents/BouncerComponent.cpp +++ b/dGame/dComponents/BouncerComponent.cpp @@ -8,15 +8,33 @@ #include "GameMessages.h" #include "BitStream.h" #include "eTriggerEventType.h" +#include "Amf3.h" BouncerComponent::BouncerComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { m_PetEnabled = false; m_PetBouncerEnabled = false; m_PetSwitchLoaded = false; + m_Destination = GeneralUtils::TryParse( + GeneralUtils::SplitString(m_Parent->GetVarAsString(u"bouncer_destination"), '\x1f')) + .value_or(NiPoint3Constant::ZERO); + m_Speed = GeneralUtils::TryParse(m_Parent->GetVarAsString(u"bouncer_speed")).value_or(-1.0f); + m_UsesHighArc = GeneralUtils::TryParse(m_Parent->GetVarAsString(u"bouncer_uses_high_arc")).value_or(false); + m_LockControls = GeneralUtils::TryParse(m_Parent->GetVarAsString(u"lock_controls")).value_or(false); + m_IgnoreCollision = !GeneralUtils::TryParse(m_Parent->GetVarAsString(u"ignore_collision")).value_or(true); + m_StickLanding = GeneralUtils::TryParse(m_Parent->GetVarAsString(u"stickLanding")).value_or(false); + m_UsesGroupName = GeneralUtils::TryParse(m_Parent->GetVarAsString(u"uses_group_name")).value_or(false); + m_GroupName = m_Parent->GetVarAsString(u"grp_name"); + m_MinNumTargets = GeneralUtils::TryParse(m_Parent->GetVarAsString(u"num_targets_to_activate")).value_or(1); + m_CinematicPath = m_Parent->GetVarAsString(u"attached_cinematic_path"); if (parent->GetLOT() == 7625) { LookupPetSwitch(); } + + { + using namespace GameMessages; + RegisterMsg(this, &BouncerComponent::MsgGetObjectReportInfo); + } } BouncerComponent::~BouncerComponent() { @@ -94,3 +112,54 @@ void BouncerComponent::LookupPetSwitch() { }); } } + +bool BouncerComponent::MsgGetObjectReportInfo(GameMessages::GameMsg& msg) { + auto& reportMsg = static_cast(msg); + auto& cmptType = reportMsg.info->PushDebug("Bouncer"); + cmptType.PushDebug("Component ID") = GetComponentID(); + auto& destPos = cmptType.PushDebug("Destination Position"); + if (m_Destination != NiPoint3Constant::ZERO) { + destPos.PushDebug(m_Destination); + } else { + destPos.PushDebug("WARNING: Bouncer has no target position, is likely missing config data"); + } + + + if (m_Speed == -1.0f) { + cmptType.PushDebug("WARNING: Bouncer has no speed value, is likely missing config data"); + } else { + cmptType.PushDebug("Bounce Speed") = m_Speed; + } + cmptType.PushDebug("Bounce trajectory arc") = m_UsesHighArc ? "High Arc" : "Low Arc"; + cmptType.PushDebug("Collision Enabled") = m_IgnoreCollision; + cmptType.PushDebug("Stick Landing") = m_StickLanding; + cmptType.PushDebug("Locks character's controls") = m_LockControls; + if (!m_CinematicPath.empty()) cmptType.PushDebug("Cinematic Camera Path (plays during bounce)") = m_CinematicPath; + + auto* switchComponent = m_Parent->GetComponent(); + auto& respondsToFactions = cmptType.PushDebug("Responds to Factions"); + if (!switchComponent || switchComponent->GetFactionsToRespondTo().empty()) respondsToFactions.PushDebug("Faction 1"); + else { + for (const auto faction : switchComponent->GetFactionsToRespondTo()) { + respondsToFactions.PushDebug(("Faction " + std::to_string(faction))); + } + } + + cmptType.PushDebug("Uses a group name for interactions") = m_UsesGroupName; + if (!m_UsesGroupName) { + if (m_MinNumTargets > 1) { + cmptType.PushDebug("WARNING: Bouncer has a required number of objects to activate, but no group for interactions."); + } + + if (!m_GroupName.empty()) { + cmptType.PushDebug("WARNING: Has a group name for interactions , but is marked to not use that name."); + } + } else { + if (m_GroupName.empty()) { + cmptType.PushDebug("WARNING: Set to use a group name for inter actions, but no group name is assigned"); + } + cmptType.PushDebug("Number of interactions to activate bouncer") = m_MinNumTargets; + } + + return true; +} diff --git a/dGame/dComponents/BouncerComponent.h b/dGame/dComponents/BouncerComponent.h index 53ba26fa..b3221e12 100644 --- a/dGame/dComponents/BouncerComponent.h +++ b/dGame/dComponents/BouncerComponent.h @@ -51,6 +51,8 @@ public: */ void LookupPetSwitch(); + bool MsgGetObjectReportInfo(GameMessages::GameMsg& msg); + private: /** * Whether this bouncer needs to be activated by a pet @@ -66,6 +68,36 @@ private: * Whether the pet switch for this bouncer has been located */ bool m_PetSwitchLoaded; + + // The bouncer destination + NiPoint3 m_Destination; + + // The speed at which the player is bounced + float m_Speed{}; + + // Whether to use a high arc for the bounce trajectory + bool m_UsesHighArc{}; + + // Lock controls when bouncing + bool m_LockControls{}; + + // Ignore collision when bouncing + bool m_IgnoreCollision{}; + + // Stick the landing afterwards or let the player slide + bool m_StickLanding{}; + + // Whether or not there is a group name + bool m_UsesGroupName{}; + + // The group name for targets + std::string m_GroupName{}; + + // The number of targets to activate the bouncer + int32_t m_MinNumTargets{}; + + // The cinematic path to play during the bounce + std::string m_CinematicPath{}; }; #endif // BOUNCERCOMPONENT_H diff --git a/dGame/dComponents/CollectibleComponent.cpp b/dGame/dComponents/CollectibleComponent.cpp index f6ba25b2..fce32e93 100644 --- a/dGame/dComponents/CollectibleComponent.cpp +++ b/dGame/dComponents/CollectibleComponent.cpp @@ -1,5 +1,39 @@ #include "CollectibleComponent.h" +#include "MissionComponent.h" +#include "dServer.h" +#include "Amf3.h" + +CollectibleComponent::CollectibleComponent(Entity* parentEntity, const int32_t componentID, const int32_t collectibleId) : + Component(parentEntity, componentID), m_CollectibleId(collectibleId) { + using namespace GameMessages; + RegisterMsg(this, &CollectibleComponent::MsgGetObjectReportInfo); +} + void CollectibleComponent::Serialize(RakNet::BitStream& outBitStream, bool isConstruction) { outBitStream.Write(GetCollectibleId()); } + +bool CollectibleComponent::MsgGetObjectReportInfo(GameMessages::GameMsg& msg) { + auto& reportMsg = static_cast(msg); + auto& cmptType = reportMsg.info->PushDebug("Collectible"); + auto collectibleID = static_cast(m_CollectibleId) + static_cast(Game::server->GetZoneID() << 8); + + cmptType.PushDebug("Component ID") = GetComponentID(); + + cmptType.PushDebug("Collectible ID") = GetCollectibleId(); + cmptType.PushDebug("Mission Tracking ID (for save data)") = collectibleID; + + auto* localCharEntity = Game::entityManager->GetEntity(reportMsg.clientID); + bool collected = false; + if (localCharEntity) { + auto* missionComponent = localCharEntity->GetComponent(); + + if (m_CollectibleId != 0) { + collected = missionComponent->HasCollectible(collectibleID); + } + } + + cmptType.PushDebug("Has been collected") = collected; + return true; +} diff --git a/dGame/dComponents/CollectibleComponent.h b/dGame/dComponents/CollectibleComponent.h index d9356112..ba1a3f28 100644 --- a/dGame/dComponents/CollectibleComponent.h +++ b/dGame/dComponents/CollectibleComponent.h @@ -7,10 +7,12 @@ class CollectibleComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::COLLECTIBLE; - CollectibleComponent(Entity* parentEntity, const int32_t componentID, const int32_t collectibleId) : Component(parentEntity, componentID), m_CollectibleId(collectibleId) {} + CollectibleComponent(Entity* parentEntity, const int32_t componentID, const int32_t collectibleId); int16_t GetCollectibleId() const { return m_CollectibleId; } void Serialize(RakNet::BitStream& outBitStream, bool isConstruction) override; + + bool MsgGetObjectReportInfo(GameMessages::GameMsg& msg); private: int16_t m_CollectibleId = 0; }; diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 0658757c..47aa0c90 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -1122,8 +1122,8 @@ bool DestroyableComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) { stats.PushDebug("Imagination") = m_iImagination; stats.PushDebug("Maximum Imagination") = m_fMaxImagination; stats.PushDebug("Damage Absorption Points") = m_DamageToAbsorb; - destroyableInfo.PushDebug("Is GM Immune") = m_IsGMImmune; - destroyableInfo.PushDebug("Is Shielded") = m_IsShielded; + stats.PushDebug("Is GM Immune") = m_IsGMImmune; + stats.PushDebug("Is Shielded") = m_IsShielded; destroyableInfo.PushDebug("Attacks To Block") = m_AttacksToBlock; destroyableInfo.PushDebug("Damage Reduction") = m_DamageReduction; std::stringstream factionsStream; @@ -1140,7 +1140,7 @@ bool DestroyableComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) { destroyableInfo.PushDebug("Enemy Factions") = factionsStream.str(); - destroyableInfo.PushDebug("Is Smashable") = m_IsSmashable; + destroyableInfo.PushDebug("Is A Smashable") = m_IsSmashable; destroyableInfo.PushDebug("Is Smashed") = m_IsSmashed; destroyableInfo.PushDebug("Is Module Assembly") = m_IsModuleAssembly; destroyableInfo.PushDebug("Explode Factor") = m_ExplodeFactor; diff --git a/dGame/dComponents/GhostComponent.cpp b/dGame/dComponents/GhostComponent.cpp index 4755a1f9..d86de72b 100644 --- a/dGame/dComponents/GhostComponent.cpp +++ b/dGame/dComponents/GhostComponent.cpp @@ -1,9 +1,14 @@ #include "GhostComponent.h" +#include "Amf3.h" +#include "GameMessages.h" + GhostComponent::GhostComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { m_GhostReferencePoint = NiPoint3Constant::ZERO; m_GhostOverridePoint = NiPoint3Constant::ZERO; m_GhostOverride = false; + + RegisterMsg(this, &GhostComponent::MsgGetObjectReportInfo); } GhostComponent::~GhostComponent() { @@ -55,3 +60,12 @@ bool GhostComponent::IsObserved(LWOOBJID id) { void GhostComponent::GhostEntity(LWOOBJID id) { m_ObservedEntities.erase(id); } + +bool GhostComponent::MsgGetObjectReportInfo(GameMessages::GameMsg& msg) { + auto& reportMsg = static_cast(msg); + auto& cmptType = reportMsg.info->PushDebug("Ghost"); + cmptType.PushDebug("Component ID") = GetComponentID(); + cmptType.PushDebug("Is GM Invis") = false; + + return true; +} diff --git a/dGame/dComponents/GhostComponent.h b/dGame/dComponents/GhostComponent.h index edf05c13..75ed3c9d 100644 --- a/dGame/dComponents/GhostComponent.h +++ b/dGame/dComponents/GhostComponent.h @@ -39,6 +39,8 @@ public: void GhostEntity(const LWOOBJID id); + bool MsgGetObjectReportInfo(GameMessages::GameMsg& msg); + private: NiPoint3 m_GhostReferencePoint; diff --git a/dGame/dComponents/SwitchComponent.h b/dGame/dComponents/SwitchComponent.h index ecbdeb73..755d134a 100644 --- a/dGame/dComponents/SwitchComponent.h +++ b/dGame/dComponents/SwitchComponent.h @@ -67,6 +67,10 @@ public: */ static SwitchComponent* GetClosestSwitch(NiPoint3 position); + const std::vector& GetFactionsToRespondTo() const { + return m_FactionsToRespondTo; + } + private: /** * A list of all pet switches. From 281d9762efb842f9cd78777dc3f93e3ed0e30caf Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 19 Oct 2025 05:23:54 -0700 Subject: [PATCH 38/39] fix: tac arc sorting and target acquisition (#1916) --- dGame/dBehaviors/TacArcBehavior.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/dGame/dBehaviors/TacArcBehavior.cpp b/dGame/dBehaviors/TacArcBehavior.cpp index 3e477896..d0bbad8e 100644 --- a/dGame/dBehaviors/TacArcBehavior.cpp +++ b/dGame/dBehaviors/TacArcBehavior.cpp @@ -114,7 +114,6 @@ void TacArcBehavior::Calculate(BehaviorContext* context, RakNet::BitStream& bitS context->FilterTargets(validTargets, this->m_ignoreFactionList, this->m_includeFactionList, this->m_targetSelf, this->m_targetEnemy, this->m_targetFriend, this->m_targetTeam); for (auto validTarget : validTargets) { - if (targets.size() >= this->m_maxTargets) break; if (std::find(targets.begin(), targets.end(), validTarget) != targets.end()) continue; if (validTarget->GetIsDead()) continue; @@ -147,13 +146,28 @@ void TacArcBehavior::Calculate(BehaviorContext* context, RakNet::BitStream& bitS } } - std::sort(targets.begin(), targets.end(), [reference](Entity* a, Entity* b) { + std::sort(targets.begin(), targets.end(), [this, reference, combatAi](Entity* a, Entity* b) { const auto aDistance = Vector3::DistanceSquared(reference, a->GetPosition()); const auto bDistance = Vector3::DistanceSquared(reference, b->GetPosition()); - return aDistance > bDistance; + return aDistance < bDistance; }); + + if (m_useAttackPriority) { + // this should be using the attack priority column on the destroyable component + // We want targets with no threat level to remain the same order as above + // std::stable_sort(targets.begin(), targets.end(), [combatAi](Entity* a, Entity* b) { + // const auto aThreat = combatAi->GetThreat(a->GetObjectID()); + // const auto bThreat = combatAi->GetThreat(b->GetObjectID()); + + // If enabled for this behavior, prioritize threat over distance + // return aThreat > bThreat; + // }); + } + + // After we've sorted and found our closest targets, size the vector down in case there are too many + if (m_maxTargets > 0 && targets.size() > m_maxTargets) targets.resize(m_maxTargets); const auto hit = !targets.empty(); bitStream.Write(hit); From a70c365c23b93d3af08c18e98c24fbe1e65feff8 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 19 Oct 2025 12:00:14 -0700 Subject: [PATCH 39/39] feat banana (#1917) --- dGame/dUtilities/Loot.cpp | 2 ++ dScripts/ai/GF/GfBanana.cpp | 36 +++++++++--------------------- dScripts/ai/GF/GfBananaCluster.cpp | 22 ++++++++++++++++++ dScripts/ai/GF/GfBananaCluster.h | 1 + 4 files changed, 35 insertions(+), 26 deletions(-) diff --git a/dGame/dUtilities/Loot.cpp b/dGame/dUtilities/Loot.cpp index 0b88f5af..d9384db8 100644 --- a/dGame/dUtilities/Loot.cpp +++ b/dGame/dUtilities/Loot.cpp @@ -365,6 +365,7 @@ void DropLoot(Entity* player, const LWOOBJID source, const std::mapGetEntity(member); if (memberEntity) lootMsg.Send(memberEntity->GetSystemAddress()); @@ -377,6 +378,7 @@ void DropLoot(Entity* player, const LWOOBJID source, const std::mapGetSystemAddress()); } diff --git a/dScripts/ai/GF/GfBanana.cpp b/dScripts/ai/GF/GfBanana.cpp index 93741d24..0b436396 100644 --- a/dScripts/ai/GF/GfBanana.cpp +++ b/dScripts/ai/GF/GfBanana.cpp @@ -55,36 +55,20 @@ void GfBanana::OnHit(Entity* self, Entity* attacker) { return; } + bananaEntity->Smash(LWOOBJID_EMPTY, eKillType::SILENT); - bananaEntity->SetPosition(bananaEntity->GetPosition() - NiPoint3Constant::UNIT_Y * 8); - - auto* bananaDestroyable = bananaEntity->GetComponent(); - - bananaDestroyable->SetHealth(0); - - bananaDestroyable->Smash(attacker->GetObjectID()); - - /* - auto position = self->GetPosition(); const auto rotation = self->GetRotation(); - - position.y += 12; - position.x -= rotation.GetRightVector().x * 5; - position.z -= rotation.GetRightVector().z * 5; - - EntityInfo info {}; - - info.pos = position; - info.rot = rotation; + 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(); - - auto* entity = Game::entityManager->CreateEntity(info); - - Game::entityManager->ConstructEntity(entity, UNASSIGNED_SYSTEM_ADDRESS); - */ - - Game::entityManager->SerializeEntity(self); + info.settings = { new LDFData(u"motionType", 5) }; + auto* const newEn = Game::entityManager->CreateEntity(info, nullptr, self); + Game::entityManager->ConstructEntity(newEn); } void GfBanana::OnTimerDone(Entity* self, std::string timerName) { diff --git a/dScripts/ai/GF/GfBananaCluster.cpp b/dScripts/ai/GF/GfBananaCluster.cpp index 6e5e91db..f461c761 100644 --- a/dScripts/ai/GF/GfBananaCluster.cpp +++ b/dScripts/ai/GF/GfBananaCluster.cpp @@ -1,5 +1,9 @@ #include "GfBananaCluster.h" #include "Entity.h" +#include "dpWorld.h" +#include "dNavMesh.h" +#include "Loot.h" +#include "DestroyableComponent.h" void GfBananaCluster::OnStartup(Entity* self) { self->AddTimer("startup", 100); @@ -10,3 +14,21 @@ void GfBananaCluster::OnTimerDone(Entity* self, std::string timerName) { self->ScheduleKillAfterUpdate(nullptr); } } + +// Hack in banana loot dropping from tree area since it seemed to do that in live for some reason +void GfBananaCluster::OnHit(Entity* self, Entity* attacker) { + auto* parentEntity = self->GetParentEntity(); + GameMessages::GetPosition posMsg{}; + if (parentEntity) { + posMsg.target = parentEntity->GetObjectID(); + } + posMsg.Send(); + + const auto rotation = parentEntity ? parentEntity->GetRotation() : self->GetRotation(); + + if (dpWorld::GetNavMesh()) posMsg.pos.y = dpWorld::GetNavMesh()->GetHeightAtPoint(posMsg.pos) + 3.0f; + else posMsg.pos = posMsg.pos - (NiPoint3Constant::UNIT_Y * 8); + posMsg.pos.x -= QuatUtils::Right(rotation).x * 5; + posMsg.pos.z -= QuatUtils::Right(rotation).z * 5; + self->SetPosition(posMsg.pos); +} diff --git a/dScripts/ai/GF/GfBananaCluster.h b/dScripts/ai/GF/GfBananaCluster.h index 81bb8b0b..ceff708c 100644 --- a/dScripts/ai/GF/GfBananaCluster.h +++ b/dScripts/ai/GF/GfBananaCluster.h @@ -7,4 +7,5 @@ public: void OnStartup(Entity* self) override; void OnTimerDone(Entity* self, std::string timerName) override; + void OnHit(Entity* self, Entity* attacker) override; };