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/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/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/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/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/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/dCommon/Lxfml.cpp b/dCommon/Lxfml.cpp index e71b2d8e..b950d037 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,345 @@ 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; + + // 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; + 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) { + // 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, partRefs); + }; + + // 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) { + 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 + // 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 + int iteration = 0; + 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) + 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; + } + } + } + } + + // 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) { + // 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 all merged groups' XML) + auto normalized = makeOutput(bricksIncluded, rigidSystemsToInclude, groupsToInclude); + 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/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/dCommon/dConfig.cpp b/dCommon/dConfig.cpp index bed274b0..7ebfad25 100644 --- a/dCommon/dConfig.cpp +++ b/dCommon/dConfig.cpp @@ -47,6 +47,8 @@ void dConfig::LoadConfig() { void dConfig::ReloadConfig() { this->m_ConfigValues.clear(); LoadConfig(); + for (const auto& handler : m_ConfigHandlers) handler(); + LogSettings(); } const std::string& dConfig::GetValue(std::string key) { @@ -58,6 +60,18 @@ 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) { + 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..36c94148 100644 --- a/dCommon/dConfig.h +++ b/dCommon/dConfig.h @@ -1,5 +1,7 @@ #pragma once + #include +#include #include #include @@ -29,10 +31,15 @@ public: * Reloads the config file to reset values */ 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); -private: std::map m_ConfigValues; + std::vector> m_ConfigHandlers; std::string m_ConfigFilePath; }; 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 51e6e5e5..15610c3a 100644 --- a/dCommon/dEnums/eCharacterVersion.h +++ b/dCommon/dEnums/eCharacterVersion.h @@ -18,7 +18,9 @@ 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 + PET_IDS, // Fixes pet ids in player inventories + UP_TO_DATE, // will become INVENTORY_PERSISTENT_IDS }; #endif //!__ECHARACTERVERSION__H__ 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/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/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/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/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/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 9019c176..c4957e5b 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{}; @@ -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 1c51d00c..9a16d5d7 100644 --- a/dDatabase/GameDatabase/ITables/IPropertyContents.h +++ b/dDatabase/GameDatabase/ITables/IPropertyContents.h @@ -16,16 +16,16 @@ public: NiQuaternion rotation = QuatUtils::IDENTITY; LWOOBJID id{}; LOT lot{}; - uint32_t ugcId{}; + LWOOBJID ugcId{}; std::array behaviors{}; }; // 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 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; @@ -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/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..456ab5fa 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; @@ -83,30 +83,30 @@ 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 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; 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; - 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,16 +118,18 @@ 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; - 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: @@ -168,91 +170,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 +262,21 @@ 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); + } +} + +template<> +inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::optional param) { + if (param) { + LOG_DEBUG("%d", param.value()); + stmt->setInt64(index, param.value()); + } else { + LOG_DEBUG("Null"); stmt->setNull(index, sql::DataType::SQLNULL); } } 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/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/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 fcb35751..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->getUInt64("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->getUInt64("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 2d2655f4..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,10 +43,10 @@ void MySQLDatabase::RemoveUnreferencedUgcModels() { } void MySQLDatabase::InsertNewUgcModel( - std:: stringstream& sd0Data, // cant be const sad - const uint32_t blueprintId, + std::stringstream& sd0Data, // cant be const sad + const uint64_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 (?,?,?,?,?,?,?)", @@ -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/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..19a49e5f 100644 --- a/dDatabase/GameDatabase/SQLite/SQLiteDatabase.cpp +++ b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.cpp @@ -61,13 +61,13 @@ 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(); } } -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..3b6dc643 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; @@ -81,30 +81,30 @@ 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 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; 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; - 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,16 +116,18 @@ 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; - 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); @@ -270,4 +272,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/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/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/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 c6410a42..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(); } @@ -47,9 +45,9 @@ 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 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 (?,?,?,?,?,?,?)", @@ -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/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..1cd48ad9 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 uint64_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) { } @@ -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 {}; } @@ -268,15 +264,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 {}; } @@ -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 59f7fe08..2c7890dd 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; @@ -60,30 +60,30 @@ 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 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; 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; - 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,17 +95,19 @@ 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; }; 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/Character.cpp b/dGame/Character.cpp index 0af51a1c..102db91a 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(); } @@ -333,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/Character.h b/dGame/Character.h index bcddfd3e..03086de2 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. @@ -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/Entity.cpp b/dGame/Entity.cpp index 543f324a..7d8046e0 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,14 +194,17 @@ 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 */ const auto triggerInfo = GetVarAsString(u"trigger_id"); - if (!triggerInfo.empty()) AddComponent(triggerInfo); + if (!triggerInfo.empty()) AddComponent(-1, triggerInfo); /** * Setup groups @@ -234,11 +239,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 +259,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 +302,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 +347,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 +396,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); }); @@ -412,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 @@ -473,27 +486,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 +561,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 +570,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 +635,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 +699,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 +711,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 +751,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 +763,7 @@ void Entity::Initialize() { .wanderDelayMax = 5, }; - AddComponent(moveInfo); + AddComponent(-1, moveInfo); } const std::string pathName = GetVarAsString(u"attached_path"); @@ -744,10 +773,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 +788,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 +911,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); } @@ -1639,7 +1669,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; } @@ -2223,6 +2253,8 @@ 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"); auto* table = CDClientManager::GetTable(); @@ -2236,17 +2268,87 @@ 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); 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/EntityManager.cpp b/dGame/EntityManager.cpp index 762529f3..12be2e06 100644 --- a/dGame/EntityManager.cpp +++ b/dGame/EntityManager.cpp @@ -52,6 +52,45 @@ 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 : GeneralUtils::TryParse(hcUscorePercent).value_or(10); + auto hcUscoreMult = Game::config->GetValue("hardcore_uscore_enemies_multiplier"); + 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"); + 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; + auto hcCoinKeep = Game::config->GetValue("hardcore_coin_keep"); + m_HardcoreCoinKeep = hcCoinKeep.empty() ? false : GeneralUtils::TryParse(hcCoinKeep).value_or(0.0f); +} + void EntityManager::Initialize() { // Check if this zone has ghosting enabled m_GhostingEnabled = std::find( @@ -61,15 +100,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..26daf750 100644 --- a/dGame/EntityManager.h +++ b/dGame/EntityManager.h @@ -75,11 +75,19 @@ 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; }; + 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; }; + float GetHardcoreCoinKeep() const { return m_HardcoreCoinKeep; } // Messaging bool SendMessage(GameMessages::GameMsg& msg) const; private: + void ReloadConfig(); void SerializeEntities(); void KillEntities(); void DeleteEntities(); @@ -112,6 +120,13 @@ private: uint32_t m_HardcoreLoseUscoreOnDeathPercent; 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{}; + float m_HardcoreCoinKeep{}; }; #endif // ENTITYMANAGER_H 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/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/User.cpp b/dGame/User.cpp index 806d4611..1f8a41fa 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,18 +32,18 @@ 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: 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); } } } @@ -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 diff --git a/dGame/UserManager.cpp b/dGame/UserManager.cpp index 6b742b27..15fbc2d6 100644 --- a/dGame/UserManager.cpp +++ b/dGame/UserManager.cpp @@ -30,6 +30,8 @@ #include "BitStreamUtils.h" #include "CheatDetection.h" #include "CharacterComponent.h" +#include "dConfig.h" +#include "eCharacterVersion.h" UserManager* UserManager::m_Address = nullptr; @@ -91,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() { @@ -300,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); @@ -318,85 +339,88 @@ 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 { 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 objectID) { - 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(lwoidforshirt, eObjectBits::PERSISTENT); - GeneralUtils::SetBit(lwoidforpants, eObjectBits::CHARACTER); - GeneralUtils::SetBit(lwoidforpants, eObjectBits::PERSISTENT); + xml << ""; + xml << ""; - xml << ""; - xml << ""; + xml << ""; - xml << ""; + //Check to see if our name was pre-approved: + bool nameOk = IsNamePreapproved(name); - //Check to see if our name was pre-approved: - bool nameOk = IsNamePreapproved(name); - if (!nameOk && u->GetMaxGMLevel() > eGameMasterLevel::FORUM_MODERATOR) nameOk = true; + 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) { @@ -409,9 +433,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 +446,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,17 +468,15 @@ 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); - 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( @@ -466,7 +487,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; } @@ -476,19 +497,36 @@ 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)) { - Database::Get()->SetCharacterName(charID, 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); 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 +540,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..0244807e 100644 --- a/dGame/UserManager.h +++ b/dGame/UserManager.h @@ -35,12 +35,17 @@ 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(); 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/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); 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..7567e20b 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,39 +39,12 @@ 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); } - - 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 925b3a8d..dc249068 100644 --- a/dGame/dComponents/ActivityComponent.h +++ b/dGame/dComponents/ActivityComponent.h @@ -341,19 +341,13 @@ 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); /** * The database information for this activity */ - CDActivities m_ActivityInfo; + CDActivities m_ActivityInfo{}; /** * All the active instances of this activity @@ -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/BaseCombatAIComponent.cpp b/dGame/dComponents/BaseCombatAIComponent.cpp index 76228ec7..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 uint32_t id) : Component(parent) { +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; @@ -43,7 +48,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 +116,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; } @@ -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 52adb429..009a96d2 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; @@ -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 d1c63bf4..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) : Component(parent) { +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 441ba58e..b3221e12 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; @@ -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/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..a0c44961 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; @@ -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 @@ -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/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.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 5ecfb97e..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, int32_t collectibleId) : Component(parentEntity), 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/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 39d91045..47aa0c90 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" @@ -37,13 +40,14 @@ #include "eMissionTaskType.h" #include "eStateChangeType.h" #include "eGameActivity.h" +#include #include "CDComponentsRegistryTable.h" 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; @@ -666,11 +670,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); } @@ -698,6 +697,13 @@ 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); + } + if (m_iHealth > 0) { SetArmor(0); SetHealth(0); @@ -705,6 +711,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); @@ -752,40 +759,11 @@ 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 - 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; @@ -798,7 +776,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); } } @@ -981,7 +967,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 +982,18 @@ 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::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); } @@ -1012,32 +1004,55 @@ 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; + 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); + lootMsg.currency = 100'000; + lootMsg.Send(); + lootMsg.Send(m_Parent->GetSystemAddress()); + coinsToDrop -= 100'000; + } + lootMsg.currency = coinsToDrop; + lootMsg.Send(); + lootMsg.Send(m_Parent->GetSystemAddress()); } - - // 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; } //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) { + 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(); - + 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); @@ -1048,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("Is GM Immune") = m_IsGMImmune; - destroyableInfo.PushDebug("Is Shielded") = m_IsShielded; + 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; + 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; - 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("Is Smashable") = m_IsSmashable; - destroyableInfo.PushDebug("Is Dead") = m_IsDead; + + destroyableInfo.PushDebug("Enemy Factions") = factionsStream.str(); + + 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; 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 @@ -1094,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 7ec9bc44..9b3e46af 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; @@ -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 @@ -471,6 +473,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; @@ -585,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/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..d86de72b 100644 --- a/dGame/dComponents/GhostComponent.cpp +++ b/dGame/dComponents/GhostComponent.cpp @@ -1,9 +1,14 @@ #include "GhostComponent.h" -GhostComponent::GhostComponent(Entity* parent) : Component(parent) { +#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 de0fb886..75ed3c9d 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; }; @@ -39,6 +39,8 @@ public: void GhostEntity(const LWOOBJID id); + bool MsgGetObjectReportInfo(GameMessages::GameMsg& msg); + private: NiPoint3 m_GhostReferencePoint; 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 4b50ece2..212572ae 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) : Component(parent) { +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 = {}; @@ -279,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; @@ -440,7 +450,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; @@ -626,7 +636,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 +1797,105 @@ 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(); + } +} + +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 395dd5d6..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; @@ -67,7 +68,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; @@ -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 @@ -402,10 +403,16 @@ 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(); + // Used to migrate a character version, no need to call outside of that context + void RegenerateItemIDs(); + + bool OnGetObjectReportInfo(GameMessages::GameMsg& msg); + ~InventoryComponent() override; private: 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 e430aefc..b3a503d5 100644 --- a/dGame/dComponents/MissionComponent.cpp +++ b/dGame/dComponents/MissionComponent.cpp @@ -19,14 +19,20 @@ #include "MissionPrerequisites.h" #include "AchievementCacheKey.h" #include "eMissionState.h" +#include "StringifiedEnum.h" // MARK: Mission Component std::unordered_map> MissionComponent::m_AchievementCache = {}; //! Initializer -MissionComponent::MissionComponent(Entity* parent) : Component(parent) { +MissionComponent::MissionComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) { + using namespace GameMessages; m_LastUsedMissionOrderUID = Game::zoneManager->GetUniqueMissionIdStartingValue(); + + RegisterMsg(this, &MissionComponent::OnGetObjectReportInfo); + RegisterMsg(this, &MissionComponent::OnGetMissionState); + RegisterMsg(this, &MissionComponent::OnMissionNeedsLot); } //! Destructor @@ -136,6 +142,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); @@ -620,3 +627,123 @@ 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; +} + +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 a01794f0..a5cd058a 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; @@ -171,6 +171,9 @@ 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/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 baf180ce..65b34feb 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(); @@ -25,6 +25,7 @@ ModelComponent::ModelComponent(Entity* parent) : Component(parent) { 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) { @@ -375,3 +376,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 f6277db6..dfd6678e 100644 --- a/dGame/dComponents/ModelComponent.h +++ b/dGame/dComponents/ModelComponent.h @@ -27,13 +27,14 @@ 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; bool OnRequestUse(GameMessages::GameMsg& msg); bool OnResetModelToDefaults(GameMessages::GameMsg& msg); + bool OnGetObjectReportInfo(GameMessages::GameMsg& msg); void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) 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 de805e10..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" @@ -43,9 +46,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; @@ -479,10 +481,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); - GeneralUtils::SetBit(petSubKey, eObjectBits::PERSISTENT); + 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; @@ -528,7 +539,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(); @@ -545,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; @@ -578,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/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 783de9a9..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(); @@ -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/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 ccef695f..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; @@ -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; @@ -171,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); @@ -204,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; @@ -375,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); @@ -622,8 +628,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); - GeneralUtils::SetBit(blueprintID, eObjectBits::PERSISTENT); settings.push_back(new LDFData(u"blueprintid", blueprintID)); settings.push_back(new LDFData(u"componentWhitelist", 1)); @@ -696,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 @@ -824,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/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 d6bff5b5..c9123377 100644 --- a/dGame/dComponents/ScriptComponent.cpp +++ b/dGame/dComponents/ScriptComponent.cpp @@ -5,16 +5,17 @@ #include "Entity.h" #include "ScriptComponent.h" +#include "GameMessages.h" +#include "Amf3.h" -ScriptComponent::ScriptComponent(Entity* parent, 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; SetScript(scriptName); -} - -ScriptComponent::~ScriptComponent() { - + RegisterMsg(this, &ScriptComponent::OnGetObjectReportInfo); } void ScriptComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { @@ -50,3 +51,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..cf4b2537 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 int32_t componentID, 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 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.h b/dGame/dComponents/SimplePhysicsComponent.h index 17b3f74e..1c8500ab 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..755d134a 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; @@ -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. 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/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 08b1817e..522f4741 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; @@ -1066,90 +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)); - } - - //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) { CBITSTREAM; CMSGHEADER; @@ -2555,7 +2472,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; @@ -2565,9 +2482,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. @@ -2579,51 +2493,79 @@ 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); - GeneralUtils::SetBit(newIDL, eObjectBits::PERSISTENT); + if (!entity || !entity->GetCharacter() || !entity->GetCharacter()->GetParentUser()) return; - 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(); - //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, split, and nornmalize the model + const auto asStr = sd0.GetAsStringUncompressed(); - // Uncompress the data and normalize the position - const auto asStr = sd0.GetAsStringUncompressed(); - const auto [newLxfml, newCenter] = Lxfml::NormalizePosition(asStr); + 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(); + } - // Recompress the data and save to the database - sd0.FromData(reinterpret_cast(newLxfml.data()), newLxfml.size()); + auto splitLxfmls = Lxfml::Split(asStr); + LOG_DEBUG("Split into %zu models", splitLxfmls.size()); + + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::BLUEPRINT_SAVE_RESPONSE); + bitStream.Write(localId); + bitStream.Write(eBlueprintSaveResponseType::EverythingWorked); + bitStream.Write(splitLxfmls.size()); + + std::vector blueprintIDs; + std::vector modelIDs; + + 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, 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: + // Insert the new property model IPropertyContents::Model model; - model.id = newIDL; - model.ugcId = blueprintIDSmall; - model.position = newCenter; - model.rotation = NiQuaternion(0.0f, 0.0f, 0.0f, 0.0f); + 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"); @@ -2632,54 +2574,48 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent (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); + // Write the ID and data to the response packet bitStream.Write(blueprintID); - bitStream.Write(sd0Size); - + 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: + SEND_PACKET; + // Create entities for each model + for (size_t i = 0; i < splitLxfmls.size(); ++i) { EntityInfo info; info.lot = 14; - info.pos = newCenter; - info.rot = {}; + info.pos = splitLxfmls[i].center; + info.rot = QuatUtils::IDENTITY; 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"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", newIDL)); - + info.settings.push_back(new LDFData(u"userModelID", modelIDs[i])); Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr); if (newEntity) { Game::entityManager->ConstructEntity(newEntity); @@ -2687,10 +2623,9 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent //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); + PropertyManagementComponent::Instance()->AddModel(newEntity->GetObjectID(), modelIDs[i]); } - - }); + } } void GameMessages::HandlePropertyEntranceSync(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { @@ -3240,12 +3175,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; @@ -3261,7 +3197,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, @@ -5529,55 +5465,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); + } } } @@ -5716,27 +5649,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(); @@ -6320,6 +6232,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; @@ -6414,6 +6331,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) { @@ -6486,4 +6404,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 c4dbb1d3..73c77bf0 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 @@ -151,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); @@ -792,6 +793,7 @@ namespace GameMessages { AMFArrayValue* info{}; AMFArrayValue* subCategory{}; bool bVerbose{}; + LWOOBJID clientID{}; GetObjectReportInfo() : GameMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, eGameMasterLevel::DEVELOPER) {} }; @@ -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; }; @@ -886,5 +888,65 @@ namespace GameMessages { bool bForceFlagDirty{}; }; + + 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/Inventory.cpp b/dGame/dInventory/Inventory.cpp index 35222bea..db83ac58 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,20 @@ Inventory::~Inventory() { items.clear(); } + +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 + 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 ca93cd9a..7ee27f01 100644 --- a/dGame/dInventory/Item.cpp +++ b/dGame/dInventory/Item.cpp @@ -98,22 +98,12 @@ Item::Item( this->preconditions = new PreconditionExpression(this->info->reqPrecondition); this->subKey = subKey; - LWOOBJID id = ObjectIDManager::GenerateRandomObjectID(); - - GeneralUtils::SetBit(id, eObjectBits::CHARACTER); - GeneralUtils::SetBit(id, eObjectBits::PERSISTENT); - - 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) { @@ -121,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()); } } @@ -353,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); @@ -366,6 +356,7 @@ void Item::UseNonEquip(Item* item) { } } } + if (playerInventoryComponent->HasSpaceForLoot(rolledLoot)) { Loot::GiveLoot(playerInventoryComponent->GetParent(), rolledLoot, eLootSourceType::CONSUMPTION); item->SetCount(item->GetCount() - 1); @@ -574,3 +565,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/dMission/Mission.cpp b/dGame/dMission/Mission.cpp index ec430f3e..3d46cde8 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; @@ -82,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"))); @@ -121,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++; @@ -258,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); } @@ -563,12 +581,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 +638,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..edd21d09 100644 --- a/dGame/dMission/Mission.h +++ b/dGame/dMission/Mission.h @@ -241,6 +241,11 @@ public: * Sets the unique mission order ID of this mission */ void SetUniqueMissionOrderID(uint32_t value) { m_UniqueMissionID = value; }; + + 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/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; } 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/dPropertyBehaviors/Strip.cpp b/dGame/dPropertyBehaviors/Strip.cpp index 22ef95f9..58dde659 100644 --- a/dGame/dPropertyBehaviors/Strip.cpp +++ b/dGame/dPropertyBehaviors/Strip.cpp @@ -14,6 +14,7 @@ #include "dChatFilter.h" #include "DluAssert.h" +#include "Loot.h" template <> void Strip::HandleMsg(AddStripMessage& msg) { @@ -149,7 +150,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/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/Loot.cpp b/dGame/dUtilities/Loot.cpp index 5cbdf2d4..d9384db8 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,399 @@ 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) { + if (lootMsg.spawnPos != NiPoint3Constant::ZERO) { + 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; + CalcFinalDropPos(lootMsg); + 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; + CalcFinalDropPos(lootMsg); + 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 +538,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/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/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/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..0fd86512 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) { @@ -559,23 +565,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) { @@ -1306,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) { @@ -1471,52 +1479,62 @@ 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; - auto component = GeneralUtils::TryParse(splitArgs[0]); - if (!component) { - component = eReplicaComponentType::INVALID; + if (!closest) 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; @@ -1664,4 +1682,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/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/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 38abdc71..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" @@ -99,6 +98,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" }; @@ -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"); @@ -358,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 @@ -532,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"); @@ -880,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/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/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/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/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/Map/General/ExplodingAsset.cpp b/dScripts/02_server/Map/General/ExplodingAsset.cpp index ce96014b..bba56f0c 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 || entity->GetObjectID() != attacker->GetObjectID()) 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); }; 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/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/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/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/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/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/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; }; diff --git a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp index d291be8b..e97316e5 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" @@ -274,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); @@ -416,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/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); } diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index cb7fedd5..ad570534 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" @@ -82,6 +81,7 @@ #include "MissionComponent.h" #include "SlashCommandHandler.h" #include "InventoryComponent.h" +#include "Item.h" namespace Game { Logger* logger = nullptr; @@ -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()); @@ -674,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: @@ -861,7 +853,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 +975,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 +983,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 +999,7 @@ void HandlePacket(Packet* packet) { } } - UserManager::Instance()->LoginCharacter(packet->systemAddress, static_cast(playerID)); + UserManager::Instance()->LoginCharacter(packet->systemAddress, playerID); break; } @@ -1040,7 +1029,111 @@ 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()); + // Do charxml fixes here + auto* levelComponent = player->GetComponent(); + auto* const inventoryComponent = player->GetComponent(); + auto* const missionComponent = player->GetComponent(); + if (!levelComponent || !missionComponent || !inventoryComponent) return; + + auto version = levelComponent->GetCharacterVersion(); + LOG("Updating character from version %s", StringifiedEnum::ToString(version).data()); + 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); + } + // 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; + } + } + + // 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); const auto respawnPoint = player->GetCharacter()->GetRespawnPoint(Game::zoneManager->GetZone()->GetWorldID()); @@ -1055,77 +1148,8 @@ void HandlePacket(Packet* packet) { characterComponent->RocketUnEquip(player); - // Do charxml fixes here - auto* levelComponent = player->GetComponent(); - auto* const inventoryComponent = player->GetComponent(); - auto* const missionComponent = player->GetComponent(); - if (!levelComponent || !missionComponent || !inventoryComponent) return; - - auto version = levelComponent->GetCharacterVersion(); - 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 (complete) missionComponent->CompleteMission(937 /* Nexus Force explorer */); - levelComponent->SetCharacterVersion(eCharacterVersion::UP_TO_DATE); - [[fallthrough]]; - } - case eCharacterVersion::UP_TO_DATE: - break; - } - 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(); @@ -1142,34 +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; - 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. - 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/docs/Commands.md b/docs/Commands.md index 2a93e3de..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| @@ -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| 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/mysql/24_remove_persistent_bit.sql b/migrations/dlu/mysql/24_remove_persistent_bit.sql new file mode 100644 index 00000000..5d565a58 --- /dev/null +++ b/migrations/dlu/mysql/24_remove_persistent_bit.sql @@ -0,0 +1,39 @@ +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 +); +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; 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/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/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; 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; 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; diff --git a/resources/sharedconfig.ini b/resources/sharedconfig.ini index 2e004eae..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 @@ -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 diff --git a/resources/worldconfig.ini b/resources/worldconfig.ini index 1e73ceea..5d23a32f 100644 --- a/resources/worldconfig.ini +++ b/resources/worldconfig.ini @@ -80,3 +80,28 @@ 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 + +# 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= + +# 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/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..52e709c2 --- /dev/null +++ b/tests/dCommonTests/LxfmlTestFiles/CMakeLists.txt @@ -0,0 +1,20 @@ +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" + "group_issue.lxfml" + "complex_grouping.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/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/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/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/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..67db1149 --- /dev/null +++ b/tests/dCommonTests/LxfmlTests.cpp @@ -0,0 +1,413 @@ +#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()); +}; + +// 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) << "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) << "Failed to parse " << filename; + DocumentReader reader(doc); + auto lxfml = reader["LXFML"]; + ASSERT_TRUE(lxfml) << "No LXFML element found in " << filename; + + 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; + + 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); + 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); + } + } + } + + // 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 << " 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 << " in " << filename; + } + + // Ensure all original rigid systems and groups were used exactly once + 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 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 + +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"; + } + } + } +} 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 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; }