From afb97a81b5001272a2b9510c993bf79a63e81a86 Mon Sep 17 00:00:00 2001 From: Jonathan Romano Date: Fri, 25 Nov 2022 20:28:20 -0500 Subject: [PATCH 01/28] Address Mac CI using specific versions of OpenSSL Corrected CI to use the version brew links in opt rather than the specific version in Cellar --- CMakePresets.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakePresets.json b/CMakePresets.json index 15eb729e..3968b3ce 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -25,7 +25,7 @@ "description": "Same as default, Used in GitHub actions workflow", "inherits": "default", "cacheVariables": { - "OPENSSL_ROOT_DIR": "/usr/local/Cellar/openssl@3/3.0.5/" + "OPENSSL_ROOT_DIR": "/usr/local/opt/openssl@3/" } }, { From 4569f621007cdd70e1b45c6141e8fba173cd0fb4 Mon Sep 17 00:00:00 2001 From: Jonathan Romano Date: Sat, 26 Nov 2022 05:29:53 -0500 Subject: [PATCH 02/28] Fix three-part names coming across as INVALID with custom client path --- dGame/UserManager.cpp | 49 +++++++++++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/dGame/UserManager.cpp b/dGame/UserManager.cpp index 7da26b9a..f3fd78c4 100644 --- a/dGame/UserManager.cpp +++ b/dGame/UserManager.cpp @@ -20,6 +20,7 @@ #include "Entity.h" #include "EntityManager.h" #include "SkillComponent.h" +#include "AssetManager.h" UserManager* UserManager::m_Address = nullptr; @@ -32,43 +33,59 @@ inline void StripCR(std::string& str) { } void UserManager::Initialize() { - std::string firstNamePath = "./res/names/minifigname_first.txt"; - std::string middleNamePath = "./res/names/minifigname_middle.txt"; - std::string lastNamePath = "./res/names/minifigname_last.txt"; std::string line; - std::fstream fnFile(firstNamePath, std::ios::in); - std::fstream mnFile(middleNamePath, std::ios::in); - std::fstream lnFile(lastNamePath, std::ios::in); - - while (std::getline(fnFile, line, '\n')) { + AssetMemoryBuffer fnBuff = Game::assetManager->GetFileAsBuffer("names/minifigname_first.txt"); + if (!fnBuff.m_Success) { + Game::logger->Log("UserManager", "Failed to load %s", (Game::assetManager->GetResPath() / "names/minifigname_first.txt").string().c_str()); + throw std::runtime_error("Aborting initialization due to missing minifigure name file."); + } + std::istream fnStream = std::istream(&fnBuff); + while (std::getline(fnStream, line, '\n')) { std::string name = line; StripCR(name); m_FirstNames.push_back(name); } + fnBuff.close(); - while (std::getline(mnFile, line, '\n')) { + AssetMemoryBuffer mnBuff = Game::assetManager->GetFileAsBuffer("names/minifigname_middle.txt"); + if (!mnBuff.m_Success) { + Game::logger->Log("UserManager", "Failed to load %s", (Game::assetManager->GetResPath() / "names/minifigname_middle.txt").string().c_str()); + throw std::runtime_error("Aborting initialization due to missing minifigure name file."); + } + std::istream mnStream = std::istream(&mnBuff); + while (std::getline(mnStream, line, '\n')) { std::string name = line; StripCR(name); m_MiddleNames.push_back(name); } + mnBuff.close(); - while (std::getline(lnFile, line, '\n')) { + AssetMemoryBuffer lnBuff = Game::assetManager->GetFileAsBuffer("names/minifigname_last.txt"); + if (!lnBuff.m_Success) { + Game::logger->Log("UserManager", "Failed to load %s", (Game::assetManager->GetResPath() / "names/minifigname_last.txt").string().c_str()); + throw std::runtime_error("Aborting initialization due to missing minifigure name file."); + } + std::istream lnStream = std::istream(&lnBuff); + while (std::getline(lnStream, line, '\n')) { std::string name = line; StripCR(name); m_LastNames.push_back(name); } - - fnFile.close(); - mnFile.close(); - lnFile.close(); + lnBuff.close(); //Load our pre-approved names: - std::fstream chatList("./res/chatplus_en_us.txt", std::ios::in); - while (std::getline(chatList, line, '\n')) { + AssetMemoryBuffer chatListBuff = Game::assetManager->GetFileAsBuffer("chatplus_en_us.txt"); + if (!chatListBuff.m_Success) { + Game::logger->Log("UserManager", "Failed to load %s", (Game::assetManager->GetResPath() / "names/chatplus_en_us.txt").string().c_str()); + throw std::runtime_error("Aborting initialization due to missing chat whitelist file."); + } + std::istream chatListStream = std::istream(&chatListBuff); + while (std::getline(chatListStream, line, '\n')) { StripCR(line); m_PreapprovedNames.push_back(line); } + chatListBuff.close(); } UserManager::~UserManager() { From 36eecd693dde89979d4ab2ddb785ba82b4d87b4e Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Sat, 26 Nov 2022 03:30:53 -0700 Subject: [PATCH 03/28] Brick stack sizes are now unlimited Make brick stacks unlimited as they were in live. If you have more than uint32_max bricks, the extra bricks are trashed. --- dGame/dComponents/InventoryComponent.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 930ace8d..12b6792e 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -211,7 +211,7 @@ void InventoryComponent::AddItem( // info.itemType of 1 is item type brick if (inventoryType == eInventoryType::BRICKS || (stack == 0 && info.itemType == 1)) { - stack = 999; + stack = UINT32_MAX; } else if (stack == 0) { stack = 1; } @@ -232,7 +232,8 @@ void InventoryComponent::AddItem( } } - while (left > 0) { + // If we have some leftover and we aren't bricks, make a new stack + while (left > 0 && !(inventoryType == eInventoryType::BRICKS || (stack == 0 && info.itemType == 1))) { const auto size = std::min(left, stack); left -= size; From e2616c5f11ca6057ded4e5740b036e0c32a76e2e Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sat, 26 Nov 2022 14:22:00 -0800 Subject: [PATCH 04/28] Move enums to a single directory A technical change to move all emum files to a single directory --- CMakeLists.txt | 3 ++- dCommon/{ => dEnums}/AddFriendResponseCode.h | 0 dCommon/{ => dEnums}/AddFriendResponseType.h | 0 .../dInventory => dCommon/dEnums}/ItemSetPassiveAbilityID.h | 0 {dGame/dMission => dCommon/dEnums}/MissionLockState.h | 0 {dGame/dMission => dCommon/dEnums}/MissionState.h | 0 {dGame/dMission => dCommon/dEnums}/MissionTaskType.h | 0 {dGame/dMission => dCommon/dEnums}/RacingTaskParam.h | 0 dCommon/{ => dEnums}/dCommonVars.h | 5 +++++ {dNet => dCommon/dEnums}/dMessageIdentifiers.h | 0 dCommon/{ => dEnums}/dPlatforms.h | 0 {dPhysics => dCommon/dEnums}/dpCollisionGroups.h | 0 {dPhysics => dCommon/dEnums}/dpCommon.h | 0 dCommon/{ => dEnums}/eAninmationFlags.h | 0 dCommon/{ => dEnums}/eItemType.h | 0 dCommon/{ => dEnums}/eUnequippableActiveType.h | 0 16 files changed, 7 insertions(+), 1 deletion(-) rename dCommon/{ => dEnums}/AddFriendResponseCode.h (100%) rename dCommon/{ => dEnums}/AddFriendResponseType.h (100%) rename {dGame/dInventory => dCommon/dEnums}/ItemSetPassiveAbilityID.h (100%) rename {dGame/dMission => dCommon/dEnums}/MissionLockState.h (100%) rename {dGame/dMission => dCommon/dEnums}/MissionState.h (100%) rename {dGame/dMission => dCommon/dEnums}/MissionTaskType.h (100%) rename {dGame/dMission => dCommon/dEnums}/RacingTaskParam.h (100%) rename dCommon/{ => dEnums}/dCommonVars.h (99%) rename {dNet => dCommon/dEnums}/dMessageIdentifiers.h (100%) rename dCommon/{ => dEnums}/dPlatforms.h (100%) rename {dPhysics => dCommon/dEnums}/dpCollisionGroups.h (100%) rename {dPhysics => dCommon/dEnums}/dpCommon.h (100%) rename dCommon/{ => dEnums}/eAninmationFlags.h (100%) rename dCommon/{ => dEnums}/eItemType.h (100%) rename dCommon/{ => dEnums}/eUnequippableActiveType.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7dfef867..644fc383 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -155,6 +155,8 @@ endforeach() # Create our list of include directories set(INCLUDED_DIRECTORIES "dCommon" + "dCommon/dClient" + "dCommon/dEnums" "dChatFilter" "dGame" "dGame/dBehaviors" @@ -165,7 +167,6 @@ set(INCLUDED_DIRECTORIES "dGame/dEntity" "dGame/dPropertyBehaviors" "dGame/dUtilities" - "dCommon/dClient" "dPhysics" "dNavigation" "dNavigation/dTerrain" diff --git a/dCommon/AddFriendResponseCode.h b/dCommon/dEnums/AddFriendResponseCode.h similarity index 100% rename from dCommon/AddFriendResponseCode.h rename to dCommon/dEnums/AddFriendResponseCode.h diff --git a/dCommon/AddFriendResponseType.h b/dCommon/dEnums/AddFriendResponseType.h similarity index 100% rename from dCommon/AddFriendResponseType.h rename to dCommon/dEnums/AddFriendResponseType.h diff --git a/dGame/dInventory/ItemSetPassiveAbilityID.h b/dCommon/dEnums/ItemSetPassiveAbilityID.h similarity index 100% rename from dGame/dInventory/ItemSetPassiveAbilityID.h rename to dCommon/dEnums/ItemSetPassiveAbilityID.h diff --git a/dGame/dMission/MissionLockState.h b/dCommon/dEnums/MissionLockState.h similarity index 100% rename from dGame/dMission/MissionLockState.h rename to dCommon/dEnums/MissionLockState.h diff --git a/dGame/dMission/MissionState.h b/dCommon/dEnums/MissionState.h similarity index 100% rename from dGame/dMission/MissionState.h rename to dCommon/dEnums/MissionState.h diff --git a/dGame/dMission/MissionTaskType.h b/dCommon/dEnums/MissionTaskType.h similarity index 100% rename from dGame/dMission/MissionTaskType.h rename to dCommon/dEnums/MissionTaskType.h diff --git a/dGame/dMission/RacingTaskParam.h b/dCommon/dEnums/RacingTaskParam.h similarity index 100% rename from dGame/dMission/RacingTaskParam.h rename to dCommon/dEnums/RacingTaskParam.h diff --git a/dCommon/dCommonVars.h b/dCommon/dEnums/dCommonVars.h similarity index 99% rename from dCommon/dCommonVars.h rename to dCommon/dEnums/dCommonVars.h index 4c0e15fa..41d6cb67 100644 --- a/dCommon/dCommonVars.h +++ b/dCommon/dEnums/dCommonVars.h @@ -1,5 +1,8 @@ #pragma once +#ifndef __DCOMMONVARS__H__ +#define __DCOMMONVARS__H__ + #include #include #include @@ -648,3 +651,5 @@ inline T const& clamp(const T& val, const T& low, const T& high) { return val; } + +#endif //!__DCOMMONVARS__H__ diff --git a/dNet/dMessageIdentifiers.h b/dCommon/dEnums/dMessageIdentifiers.h similarity index 100% rename from dNet/dMessageIdentifiers.h rename to dCommon/dEnums/dMessageIdentifiers.h diff --git a/dCommon/dPlatforms.h b/dCommon/dEnums/dPlatforms.h similarity index 100% rename from dCommon/dPlatforms.h rename to dCommon/dEnums/dPlatforms.h diff --git a/dPhysics/dpCollisionGroups.h b/dCommon/dEnums/dpCollisionGroups.h similarity index 100% rename from dPhysics/dpCollisionGroups.h rename to dCommon/dEnums/dpCollisionGroups.h diff --git a/dPhysics/dpCommon.h b/dCommon/dEnums/dpCommon.h similarity index 100% rename from dPhysics/dpCommon.h rename to dCommon/dEnums/dpCommon.h diff --git a/dCommon/eAninmationFlags.h b/dCommon/dEnums/eAninmationFlags.h similarity index 100% rename from dCommon/eAninmationFlags.h rename to dCommon/dEnums/eAninmationFlags.h diff --git a/dCommon/eItemType.h b/dCommon/dEnums/eItemType.h similarity index 100% rename from dCommon/eItemType.h rename to dCommon/dEnums/eItemType.h diff --git a/dCommon/eUnequippableActiveType.h b/dCommon/dEnums/eUnequippableActiveType.h similarity index 100% rename from dCommon/eUnequippableActiveType.h rename to dCommon/dEnums/eUnequippableActiveType.h From af28d170fbd394f560c077365d161566402e797c Mon Sep 17 00:00:00 2001 From: Jonathan Romano Date: Sun, 27 Nov 2022 04:06:17 -0500 Subject: [PATCH 05/28] Fix chat whitelist path in UserManager error log (#853) --- dGame/UserManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dGame/UserManager.cpp b/dGame/UserManager.cpp index f3fd78c4..ddbad03a 100644 --- a/dGame/UserManager.cpp +++ b/dGame/UserManager.cpp @@ -77,7 +77,7 @@ void UserManager::Initialize() { //Load our pre-approved names: AssetMemoryBuffer chatListBuff = Game::assetManager->GetFileAsBuffer("chatplus_en_us.txt"); if (!chatListBuff.m_Success) { - Game::logger->Log("UserManager", "Failed to load %s", (Game::assetManager->GetResPath() / "names/chatplus_en_us.txt").string().c_str()); + Game::logger->Log("UserManager", "Failed to load %s", (Game::assetManager->GetResPath() / "chatplus_en_us.txt").string().c_str()); throw std::runtime_error("Aborting initialization due to missing chat whitelist file."); } std::istream chatListStream = std::istream(&chatListBuff); From e40a597f1854bd315953a90c572d74c6d42fc880 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 27 Nov 2022 01:24:35 -0800 Subject: [PATCH 06/28] Property Behavior deserialize definitions (#812) * Implement basic functionality Implements the basic functionality and parsing of property behaviors. Unhandled messages and logged and discarded for the time being. The only implemented message is a basic one that sends the needed info the the client side User Interface to pop up. --- dCommon/dEnums/dCommonVars.h | 2 + dGame/dPropertyBehaviors/BehaviorStates.h | 20 + dGame/dPropertyBehaviors/ControlBehaviors.cpp | 600 ++++++++++++++++-- dGame/dPropertyBehaviors/ControlBehaviors.h | 10 +- 4 files changed, 583 insertions(+), 49 deletions(-) create mode 100644 dGame/dPropertyBehaviors/BehaviorStates.h diff --git a/dCommon/dEnums/dCommonVars.h b/dCommon/dEnums/dCommonVars.h index 41d6cb67..005d7205 100644 --- a/dCommon/dEnums/dCommonVars.h +++ b/dCommon/dEnums/dCommonVars.h @@ -33,6 +33,8 @@ typedef uint32_t LWOCLONEID; //!< Used for Clone IDs typedef uint16_t LWOMAPID; //!< Used for Map IDs typedef uint16_t LWOINSTANCEID; //!< Used for Instance IDs typedef uint32_t PROPERTYCLONELIST; //!< Used for Property Clone IDs +typedef uint32_t STRIPID; +typedef uint32_t BEHAVIORSTATE; typedef int32_t PetTamingPiece; //!< Pet Taming Pieces diff --git a/dGame/dPropertyBehaviors/BehaviorStates.h b/dGame/dPropertyBehaviors/BehaviorStates.h new file mode 100644 index 00000000..e09e45ba --- /dev/null +++ b/dGame/dPropertyBehaviors/BehaviorStates.h @@ -0,0 +1,20 @@ +#pragma once + +#ifndef __BEHAVIORSTATES__H__ +#define __BEHAVIORSTATES__H__ + +#include +#include + +#include "dCommonVars.h" + +enum States : BEHAVIORSTATE { + HOME_STATE = 0, //!< The HOME behavior state + CIRCLE_STATE, //!< The CIRCLE behavior state + SQUARE_STATE, //!< The SQUARE behavior state + DIAMOND_STATE, //!< The DIAMOND behavior state + TRIANGLE_STATE, //!< The TRIANGLE behavior state + STAR_STATE //!< The STAR behavior state +}; + +#endif //!__BEHAVIORSTATES__H__ diff --git a/dGame/dPropertyBehaviors/ControlBehaviors.cpp b/dGame/dPropertyBehaviors/ControlBehaviors.cpp index 4e922ee0..d5b71a3a 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviors.cpp +++ b/dGame/dPropertyBehaviors/ControlBehaviors.cpp @@ -5,50 +5,67 @@ #include "Game.h" #include "GameMessages.h" #include "ModelComponent.h" +#include "../../dWorldServer/ObjectIDManager.h" #include "dLogger.h" -void ControlBehaviors::ProcessCommand(Entity* modelEntity, const SystemAddress& sysAddr, AMFArrayValue* arguments, std::string command, Entity* modelOwner) { - if (!modelEntity || !modelOwner || !arguments) return; +uint32_t GetBehaviorIDFromArgument(AMFArrayValue* arguments, const std::string& key = "BehaviorID") { + auto* behaviorIDValue = arguments->FindValue(key); + uint32_t behaviorID = -1; - if (command == "sendBehaviorListToClient") - SendBehaviorListToClient(modelEntity, sysAddr, modelOwner); - else if (command == "modelTypeChanged") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "toggleExecutionUpdates") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "addStrip") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "removeStrip") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "mergeStrips") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "splitStrip") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "updateStripUI") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "addAction") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "migrateActions") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "rearrangeStrip") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "add") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "removeActions") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "rename") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "sendBehaviorBlocksToClient") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "moveToInventory") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else if (command == "updateAction") - Game::logger->Log("ControlBehaviors", "Got command %s but is not implemented!", command.c_str()); - else - Game::logger->Log("ControlBehaviors", "Unknown behavior command (%s)\n", command.c_str()); + if (behaviorIDValue) { + behaviorID = std::stoul(behaviorIDValue->GetStringValue()); + } else if (arguments->FindValue(key) == nullptr){ + throw std::invalid_argument("Unable to find behavior ID from argument \"" + key + "\""); + } + + return behaviorID; } -void ControlBehaviors::SendBehaviorListToClient( +BEHAVIORSTATE GetBehaviorStateFromArgument(AMFArrayValue* arguments, const std::string& key = "stateID") { + auto* stateIDValue = arguments->FindValue(key); + if (!stateIDValue) throw std::invalid_argument("Unable to find behavior state from argument \"" + key + "\""); + + BEHAVIORSTATE stateID = static_cast(stateIDValue->GetDoubleValue()); + + return stateID; +} + +STRIPID GetStripIDFromArgument(AMFArrayValue* arguments, const std::string& key = "stripID") { + auto* stripIDValue = arguments->FindValue(key); + if (!stripIDValue) throw std::invalid_argument("Unable to find strip ID from argument \"" + key + "\""); + + STRIPID stripID = static_cast(stripIDValue->GetDoubleValue()); + + return stripID; +} + +void RequestUpdatedID(int32_t behaviorID, ModelComponent* modelComponent, Entity* modelOwner, const SystemAddress& sysAddr) { + // auto behavior = modelComponent->FindBehavior(behaviorID); + // if (behavior->GetBehaviorID() == -1 || behavior->GetShouldSetNewID()) { + // ObjectIDManager::Instance()->RequestPersistentID( + // [behaviorID, behavior, modelComponent, modelOwner, sysAddr](uint32_t persistentId) { + // behavior->SetShouldGetNewID(false); + // behavior->SetIsTemplated(false); + // behavior->SetBehaviorID(persistentId); + + // // This updates the behavior ID of the behavior should this be a new behavior + // AMFArrayValue args; + + // AMFStringValue* behaviorIDString = new AMFStringValue(); + // behaviorIDString->SetStringValue(std::to_string(persistentId)); + // args.InsertValue("behaviorID", behaviorIDString); + + // AMFStringValue* objectIDAsString = new AMFStringValue(); + // objectIDAsString->SetStringValue(std::to_string(modelComponent->GetParent()->GetObjectID())); + // args.InsertValue("objectID", objectIDAsString); + + // GameMessages::SendUIMessageServerToSingleClient(modelOwner, sysAddr, "UpdateBehaviorID", &args); + // ControlBehaviors::SendBehaviorListToClient(modelComponent->GetParent(), sysAddr, modelOwner); + // }); + // } +} + +void SendBehaviorListToClient( Entity* modelEntity, const SystemAddress& sysAddr, Entity* modelOwner @@ -57,7 +74,7 @@ void ControlBehaviors::SendBehaviorListToClient( if (!modelComponent) return; - AMFArrayValue behaviorsToSerialize; + AMFArrayValue behaviorsToSerialize; AMFArrayValue* behaviors = new AMFArrayValue(); // Empty for now @@ -79,3 +96,506 @@ void ControlBehaviors::SendBehaviorListToClient( behaviorsToSerialize.InsertValue("objectID", amfStringValueForObjectID); GameMessages::SendUIMessageServerToSingleClient(modelOwner, sysAddr, "UpdateBehaviorList", &behaviorsToSerialize); } + +void ModelTypeChanged(AMFArrayValue* arguments, ModelComponent* ModelComponent) { + auto* modelTypeAmf = arguments->FindValue("ModelType"); + if (!modelTypeAmf) return; + + uint32_t modelType = static_cast(modelTypeAmf->GetDoubleValue()); + + //TODO Update the model type here +} + +void ToggleExecutionUpdates() { + //TODO do something with this info +} + +void AddStrip(AMFArrayValue* arguments) { + auto* strip = arguments->FindValue("strip"); + if (!strip) return; + + auto* actions = strip->FindValue("actions"); + if (!actions) return; + + auto* uiArray = arguments->FindValue("ui"); + if (!uiArray) return; + + auto* xPositionValue = uiArray->FindValue("x"); + if (!xPositionValue) return; + + double xPosition = xPositionValue->GetDoubleValue(); + + auto* yPositionValue = uiArray->FindValue("y"); + if (!yPositionValue) return; + + double yPosition = yPositionValue->GetDoubleValue(); + + STRIPID stripID = GetStripIDFromArgument(arguments); + + BEHAVIORSTATE stateID = GetBehaviorStateFromArgument(arguments); + + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + std::string type = ""; + std::string valueParameterName = ""; + std::string valueParameterString = ""; + double valueParameterDouble = 0.0; + for (uint32_t position = 0; position < actions->GetDenseValueSize(); position++) { + auto* actionAsArray = actions->GetValueAt(position); + if (!actionAsArray) continue; + + for (auto& typeValueMap : actionAsArray->GetAssociativeMap()) { + if (typeValueMap.first == "Type") { + if (typeValueMap.second->GetValueType() != AMFValueType::AMFString) continue; + + type = static_cast(typeValueMap.second)->GetStringValue(); + } else { + valueParameterName = typeValueMap.first; + // Message is the only known string parameter + if (valueParameterName == "Message") { + if (typeValueMap.second->GetValueType() != AMFValueType::AMFString) continue; + valueParameterString = static_cast(typeValueMap.second)->GetStringValue(); + } else { + if (typeValueMap.second->GetValueType() != AMFValueType::AMFDouble) continue; + valueParameterDouble = static_cast(typeValueMap.second)->GetDoubleValue(); + } + } + } + // modelComponent->AddStrip(stateID, stripID, type, behaviorID, valueParameterName, valueParameterString, valueParameterDouble, "", xPosition, yPosition); + type.clear(); + valueParameterName.clear(); + valueParameterString.clear(); + valueParameterDouble = 0.0; + } + // RequestUpdatedID(behaviorID); +} + +void RemoveStrip(AMFArrayValue* arguments) { + STRIPID stripID = GetStripIDFromArgument(arguments); + + BEHAVIORSTATE stateID = GetBehaviorStateFromArgument(arguments); + + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + // modelComponent->RemoveStrip(stateID, stripID, behaviorID); + + // RequestUpdatedID(behaviorID); +} + +void MergeStrips(AMFArrayValue* arguments) { + STRIPID srcStripID = GetStripIDFromArgument(arguments, "srcStripID"); + + BEHAVIORSTATE dstStateID = GetBehaviorStateFromArgument(arguments, "dstStateID"); + + BEHAVIORSTATE srcStateID = GetBehaviorStateFromArgument(arguments, "srcStateID"); + + auto* dstActionIndexValue = arguments->FindValue("dstActionIndex"); + if (!dstActionIndexValue) return; + + uint32_t dstActionIndex = static_cast(dstActionIndexValue->GetDoubleValue()); + + STRIPID dstStripID = GetStripIDFromArgument(arguments, "dstStripID"); + + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + // modelComponent->MergeStrips(srcStripID, dstStripID, srcStateID, dstStateID, behaviorID, dstActionIndex); + + // RequestUpdatedID(behaviorID); +} + +void SplitStrip(AMFArrayValue* arguments) { + auto* srcActionIndexValue = arguments->FindValue("srcActionIndex"); + if (!srcActionIndexValue) return; + + uint32_t srcActionIndex = static_cast(srcActionIndexValue->GetDoubleValue()); + + STRIPID srcStripID = GetStripIDFromArgument(arguments, "srcStripID"); + + BEHAVIORSTATE srcStateID = GetBehaviorStateFromArgument(arguments, "srcStateID"); + + STRIPID dstStripID = GetStripIDFromArgument(arguments, "dstStripID"); + + BEHAVIORSTATE dstStateID = GetBehaviorStateFromArgument(arguments, "dstStateID"); + + auto* dstStripUIArray = arguments->FindValue("dstStripUI"); + if (!dstStripUIArray) return; + + auto* xPositionValue = dstStripUIArray->FindValue("x"); + auto* yPositionValue = dstStripUIArray->FindValue("y"); + if (!xPositionValue || !yPositionValue) return; + + // x and y position 15 are just where the game puts the strip by default if none is given. + double yPosition = yPositionValue->GetDoubleValue(); + double xPosition = xPositionValue->GetDoubleValue(); + + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + // modelComponent->SplitStrip(srcActionIndex, srcStripID, srcStateID, dstStripID, dstStateID, behaviorID, yPosition, xPosition); + + // RequestUpdatedID(behaviorID); +} + +void UpdateStripUI(AMFArrayValue* arguments) { + auto* uiArray = arguments->FindValue("ui"); + if (!uiArray) return; + + auto* xPositionValue = uiArray->FindValue("x"); + auto* yPositionValue = uiArray->FindValue("y"); + if (!xPositionValue || !yPositionValue) return; + + double yPosition = yPositionValue->GetDoubleValue(); + double xPosition = xPositionValue->GetDoubleValue(); + + STRIPID stripID = GetStripIDFromArgument(arguments); + + BEHAVIORSTATE stateID = GetBehaviorStateFromArgument(arguments); + + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + // modelComponent->UpdateUIOfStrip(stateID, stripID, xPosition, yPosition, behaviorID); + + // RequestUpdatedID(behaviorID); +} + +void AddAction(AMFArrayValue* arguments) { + auto* actionIndexAmf = arguments->FindValue("actionIndex"); + if (!actionIndexAmf) return; + + uint32_t actionIndex = static_cast(actionIndexAmf->GetDoubleValue()); + + STRIPID stripID = GetStripIDFromArgument(arguments); + + BEHAVIORSTATE stateID = GetBehaviorStateFromArgument(arguments); + + std::string type = ""; + std::string valueParameterName = ""; + std::string valueParameterString = ""; + double valueParameterDouble = 0.0; + auto* action = arguments->FindValue("action"); + if (!action) return; + + for (auto& typeValueMap : action->GetAssociativeMap()) { + if (typeValueMap.first == "Type") { + if (typeValueMap.second->GetValueType() != AMFValueType::AMFString) continue; + type = static_cast(typeValueMap.second)->GetStringValue(); + } else { + valueParameterName = typeValueMap.first; + // Message is the only known string parameter + if (valueParameterName == "Message") { + if (typeValueMap.second->GetValueType() != AMFValueType::AMFString) continue; + valueParameterString = static_cast(typeValueMap.second)->GetStringValue(); + } else { + if (typeValueMap.second->GetValueType() != AMFValueType::AMFDouble) continue; + valueParameterDouble = static_cast(typeValueMap.second)->GetDoubleValue(); + } + } + } + + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + // modelComponent->AddAction(stateID, stripID, type, valueParameterName, valueParameterString, valueParameterDouble, "", actionIndex, behaviorID); + + // RequestUpdatedID(behaviorID); +} + +void MigrateActions(AMFArrayValue* arguments) { + auto* srcActionIndexAmf = arguments->FindValue("srcActionIndex"); + if (!srcActionIndexAmf) return; + + uint32_t srcActionIndex = static_cast(srcActionIndexAmf->GetDoubleValue()); + + STRIPID srcStripID = GetStripIDFromArgument(arguments, "srcStripID"); + + BEHAVIORSTATE srcStateID = GetBehaviorStateFromArgument(arguments, "srcStateID"); + + auto* dstActionIndexAmf = arguments->FindValue("dstActionIndex"); + if (!dstActionIndexAmf) return; + + uint32_t dstActionIndex = static_cast(dstActionIndexAmf->GetDoubleValue()); + + STRIPID dstStripID = GetStripIDFromArgument(arguments, "dstStripID"); + + BEHAVIORSTATE dstStateID = GetBehaviorStateFromArgument(arguments, "dstStateID"); + + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + // modelComponent->MigrateActions(srcActionIndex, srcStripID, srcStateID, dstActionIndex, dstStripID, dstStateID, behaviorID); + + // RequestUpdatedID(behaviorID); +} + +void RearrangeStrip(AMFArrayValue* arguments) { + auto* srcActionIndexValue = arguments->FindValue("srcActionIndex"); + uint32_t srcActionIndex = static_cast(srcActionIndexValue->GetDoubleValue()); + + uint32_t stripID = GetStripIDFromArgument(arguments); + + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + auto* dstActionIndexValue = arguments->FindValue("dstActionIndex"); + uint32_t dstActionIndex = static_cast(dstActionIndexValue->GetDoubleValue()); + + BEHAVIORSTATE stateID = GetBehaviorStateFromArgument(arguments); + + // modelComponent->RearrangeStrip(stateID, stripID, srcActionIndex, dstActionIndex, behaviorID); + + // RequestUpdatedID(behaviorID); +} + +void Add(AMFArrayValue* arguments) { + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + uint32_t behaviorIndex = 0; + auto* behaviorIndexAmf = arguments->FindValue("BehaviorIndex"); + + if (!behaviorIndexAmf) return; + + behaviorIndex = static_cast(behaviorIndexAmf->GetDoubleValue()); + + // modelComponent->AddBehavior(behaviorID, behaviorIndex, modelOwner); + + // SendBehaviorListToClient(); +} + +void RemoveActions(AMFArrayValue* arguments) { + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + auto* actionIndexAmf = arguments->FindValue("actionIndex"); + if (!actionIndexAmf) return; + + uint32_t actionIndex = static_cast(actionIndexAmf->GetDoubleValue()); + + STRIPID stripID = GetStripIDFromArgument(arguments); + + BEHAVIORSTATE stateID = GetBehaviorStateFromArgument(arguments); + + // modelComponent->RemoveAction(stateID, stripID, actionIndex, behaviorID); + + // RequestUpdatedID(behaviorID); +} + +void Rename(Entity* modelEntity, const SystemAddress& sysAddr, Entity* modelOwner, AMFArrayValue* arguments) { + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + auto* nameAmf = arguments->FindValue("Name"); + if (!nameAmf) return; + + auto name = nameAmf->GetStringValue(); + + // modelComponent->Rename(behaviorID, name); + + SendBehaviorListToClient(modelEntity, sysAddr, modelOwner); + + // RequestUpdatedID(behaviorID); +} + +// TODO This is also supposed to serialize the state of the behaviors in progress but those aren't implemented yet +void SendBehaviorBlocksToClient(ModelComponent* modelComponent, const SystemAddress& sysAddr, Entity* modelOwner, AMFArrayValue* arguments) { + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + // auto modelBehavior = modelComponent->FindBehavior(behaviorID); + + // if (!modelBehavior) return; + + // modelBehavior->VerifyStates(); + + // auto states = modelBehavior->GetBehaviorStates(); + + // // Begin serialization. + + // /** + // * for each state + // * strip id + // * ui info + // * x + // * y + // * actions + // * action1 + // * action2 + // * ... + // * behaviorID of strip + // * objectID of strip + // */ + // LWOOBJID targetObjectID = LWOOBJID_EMPTY; + // behaviorID = 0; + // AMFArrayValue behaviorInfo; + + // AMFArrayValue* stateSerialize = new AMFArrayValue(); + + // for (auto it = states.begin(); it != states.end(); it++) { + // Game::logger->Log("PropertyBehaviors", "Begin serialization of state %i!\n", it->first); + // AMFArrayValue* state = new AMFArrayValue(); + + // AMFDoubleValue* stateAsDouble = new AMFDoubleValue(); + // stateAsDouble->SetDoubleValue(it->first); + // state->InsertValue("id", stateAsDouble); + + // AMFArrayValue* strips = new AMFArrayValue(); + // auto stripsInState = it->second->GetStrips(); + // for (auto strip = stripsInState.begin(); strip != stripsInState.end(); strip++) { + // Game::logger->Log("PropertyBehaviors", "Begin serialization of strip %i!\n", strip->first); + // AMFArrayValue* thisStrip = new AMFArrayValue(); + + // AMFDoubleValue* stripID = new AMFDoubleValue(); + // stripID->SetDoubleValue(strip->first); + // thisStrip->InsertValue("id", stripID); + + // AMFArrayValue* uiArray = new AMFArrayValue(); + // AMFDoubleValue* yPosition = new AMFDoubleValue(); + // yPosition->SetDoubleValue(strip->second->GetYPosition()); + // uiArray->InsertValue("y", yPosition); + + // AMFDoubleValue* xPosition = new AMFDoubleValue(); + // xPosition->SetDoubleValue(strip->second->GetXPosition()); + // uiArray->InsertValue("x", xPosition); + + // thisStrip->InsertValue("ui", uiArray); + // targetObjectID = modelComponent->GetParent()->GetObjectID(); + // behaviorID = modelBehavior->GetBehaviorID(); + + // AMFArrayValue* stripSerialize = new AMFArrayValue(); + // for (auto behaviorAction : strip->second->GetActions()) { + // Game::logger->Log("PropertyBehaviors", "Begin serialization of action %s!\n", behaviorAction->actionName.c_str()); + // AMFArrayValue* thisAction = new AMFArrayValue(); + + // AMFStringValue* actionName = new AMFStringValue(); + // actionName->SetStringValue(behaviorAction->actionName); + // thisAction->InsertValue("Type", actionName); + + // if (behaviorAction->parameterValueString != "") + // { + // AMFStringValue* valueAsString = new AMFStringValue(); + // valueAsString->SetStringValue(behaviorAction->parameterValueString); + // thisAction->InsertValue(behaviorAction->parameterName, valueAsString); + // } + // else if (behaviorAction->parameterValueDouble != 0.0) + // { + // AMFDoubleValue* valueAsDouble = new AMFDoubleValue(); + // valueAsDouble->SetDoubleValue(behaviorAction->parameterValueDouble); + // thisAction->InsertValue(behaviorAction->parameterName, valueAsDouble); + // } + // stripSerialize->PushBackValue(thisAction); + // } + // thisStrip->InsertValue("actions", stripSerialize); + // strips->PushBackValue(thisStrip); + // } + // state->InsertValue("strips", strips); + // stateSerialize->PushBackValue(state); + // } + // behaviorInfo.InsertValue("states", stateSerialize); + + // AMFStringValue* objectidAsString = new AMFStringValue(); + // objectidAsString->SetStringValue(std::to_string(targetObjectID)); + // behaviorInfo.InsertValue("objectID", objectidAsString); + + // AMFStringValue* behaviorIDAsString = new AMFStringValue(); + // behaviorIDAsString->SetStringValue(std::to_string(behaviorID)); + // behaviorInfo.InsertValue("BehaviorID", behaviorIDAsString); + + // GameMessages::SendUIMessageServerToSingleClient(modelOwner, sysAddr, "UpdateBehaviorBlocks", &behaviorInfo); +} + +void UpdateAction(AMFArrayValue* arguments) { + std::string type = ""; + std::string valueParameterName = ""; + std::string valueParameterString = ""; + double valueParameterDouble = 0.0; + auto* actionAsArray = arguments->FindValue("action"); + if (!actionAsArray) return; + for (auto& typeValueMap : actionAsArray->GetAssociativeMap()) { + if (typeValueMap.first == "Type") { + if (typeValueMap.second->GetValueType() != AMFValueType::AMFString) continue; + type = static_cast(typeValueMap.second)->GetStringValue(); + } else { + valueParameterName = typeValueMap.first; + // Message is the only known string parameter + if (valueParameterName == "Message") { + if (typeValueMap.second->GetValueType() != AMFValueType::AMFString) continue; + valueParameterString = static_cast(typeValueMap.second)->GetStringValue(); + } else { + if (typeValueMap.second->GetValueType() != AMFValueType::AMFDouble) continue; + valueParameterDouble = static_cast(typeValueMap.second)->GetDoubleValue(); + } + } + } + + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + auto* actionIndexValue = arguments->FindValue("actionIndex"); + if (!actionIndexValue) return; + + uint32_t actionIndex = static_cast(actionIndexValue->GetDoubleValue()); + + STRIPID stripID = GetStripIDFromArgument(arguments); + + BEHAVIORSTATE stateID = GetBehaviorStateFromArgument(arguments); + + // modelComponent->UpdateAction(stateID, stripID, type, valueParameterName, valueParameterString, valueParameterDouble, "", actionIndex, behaviorID); + + // RequestUpdatedID(behaviorID); +} + +void MoveToInventory(ModelComponent* modelComponent, const SystemAddress& sysAddr, Entity* modelOwner, AMFArrayValue* arguments) { + // This closes the UI menu should it be open while the player is removing behaviors + AMFArrayValue args; + + AMFFalseValue* stateToPop = new AMFFalseValue(); + args.InsertValue("visible", stateToPop); + + GameMessages::SendUIMessageServerToSingleClient(modelOwner, modelOwner->GetParentUser()->GetSystemAddress(), "ToggleBehaviorEditor", &args); + + uint32_t behaviorID = GetBehaviorIDFromArgument(arguments); + + auto* behaviorIndexValue = arguments->FindValue("BehaviorIndex"); + if (!behaviorIndexValue) return; + + uint32_t behaviorIndex = static_cast(behaviorIndexValue->GetDoubleValue()); + + // modelComponent->MoveBehaviorToInventory(behaviorID, behaviorIndex, modelOwner); + + SendBehaviorListToClient(modelComponent->GetParent(), sysAddr, modelOwner); +} + +void ControlBehaviors::ProcessCommand(Entity* modelEntity, const SystemAddress& sysAddr, AMFArrayValue* arguments, std::string command, Entity* modelOwner) { + if (!modelEntity || !modelOwner || !arguments) return; + auto* modelComponent = modelEntity->GetComponent(); + + if (!modelComponent) return; + + if (command == "sendBehaviorListToClient") + SendBehaviorListToClient(modelEntity, sysAddr, modelOwner); + else if (command == "modelTypeChanged") + ModelTypeChanged(arguments, modelComponent); + else if (command == "toggleExecutionUpdates") + ToggleExecutionUpdates(); + else if (command == "addStrip") + AddStrip(arguments); + else if (command == "removeStrip") + RemoveStrip(arguments); + else if (command == "mergeStrips") + MergeStrips(arguments); + else if (command == "splitStrip") + SplitStrip(arguments); + else if (command == "updateStripUI") + UpdateStripUI(arguments); + else if (command == "addAction") + AddAction(arguments); + else if (command == "migrateActions") + MigrateActions(arguments); + else if (command == "rearrangeStrip") + RearrangeStrip(arguments); + else if (command == "add") + Add(arguments); + else if (command == "removeActions") + RemoveActions(arguments); + else if (command == "rename") + Rename(modelEntity, sysAddr, modelOwner, arguments); + else if (command == "sendBehaviorBlocksToClient") + SendBehaviorBlocksToClient(modelComponent, sysAddr, modelOwner, arguments); + else if (command == "moveToInventory") + MoveToInventory(modelComponent, sysAddr, modelOwner, arguments); + else if (command == "updateAction") + UpdateAction(arguments); + else + Game::logger->Log("ControlBehaviors", "Unknown behavior command (%s)\n", command.c_str()); +} diff --git a/dGame/dPropertyBehaviors/ControlBehaviors.h b/dGame/dPropertyBehaviors/ControlBehaviors.h index 7c24da68..d487f929 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviors.h +++ b/dGame/dPropertyBehaviors/ControlBehaviors.h @@ -9,6 +9,7 @@ class Entity; class AMFArrayValue; +class ModelComponent; namespace ControlBehaviors { /** @@ -21,15 +22,6 @@ namespace ControlBehaviors { * @param modelOwner The owner of the model which sent this command */ void ProcessCommand(Entity* modelEntity, const SystemAddress& sysAddr, AMFArrayValue* arguments, std::string command, Entity* modelOwner); - - /** - * @brief Helper function to send the behavior list to the client - * - * @param modelEntity The model that sent this command - * @param sysAddr The SystemAddress to respond to - * @param modelOwner The owner of the model which sent this command - */ - void SendBehaviorListToClient(Entity* modelEntity, const SystemAddress& sysAddr, Entity* modelOwner); }; #endif //!__CONTROLBEHAVIORS__H__ From f8f5b731f189a1a31ceecbb5256bfc4a3da08ed7 Mon Sep 17 00:00:00 2001 From: Jonathan Romano Date: Sun, 27 Nov 2022 06:59:59 -0500 Subject: [PATCH 07/28] Allow servers to be run from directories other than `build`. Read/write files relative to binary instead of cwd (#834) Allows the server to be run from a non-build directory. Also only read or write files relative to the build directory, regardless of where the server is run from --- dAuthServer/AuthServer.cpp | 3 +- dChatServer/ChatServer.cpp | 14 +++-- dCommon/BinaryPathFinder.cpp | 71 ++++++++++++++++++++++++ dCommon/BinaryPathFinder.h | 15 +++++ dCommon/CMakeLists.txt | 1 + dCommon/dClient/AssetManager.cpp | 8 ++- dCommon/dClient/AssetManager.h | 2 +- dCommon/dConfig.cpp | 5 +- dDatabase/MigrationRunner.cpp | 7 ++- dGame/dUtilities/SlashCommandHandler.cpp | 3 +- dGame/dUtilities/VanityUtilities.cpp | 5 +- dGame/dUtilities/dLocale.cpp | 3 +- dMasterServer/InstanceManager.cpp | 11 ++-- dMasterServer/MasterServer.cpp | 40 +++++++------ dNavigation/dNavMesh.cpp | 3 +- dWorldServer/WorldServer.cpp | 13 +++-- 16 files changed, 158 insertions(+), 46 deletions(-) create mode 100644 dCommon/BinaryPathFinder.cpp create mode 100644 dCommon/BinaryPathFinder.h diff --git a/dAuthServer/AuthServer.cpp b/dAuthServer/AuthServer.cpp index 73cb4212..9621f683 100644 --- a/dAuthServer/AuthServer.cpp +++ b/dAuthServer/AuthServer.cpp @@ -11,6 +11,7 @@ #include "Database.h" #include "dConfig.h" #include "Diagnostics.h" +#include "BinaryPathFinder.h" //RakNet includes: #include "RakNetDefines.h" @@ -150,7 +151,7 @@ int main(int argc, char** argv) { } dLogger* SetupLogger() { - std::string logPath = "./logs/AuthServer_" + std::to_string(time(nullptr)) + ".log"; + std::string logPath = (BinaryPathFinder::GetBinaryDir() / ("logs/AuthServer_" + std::to_string(time(nullptr)) + ".log")).string(); bool logToConsole = false; bool logDebugStatements = false; #ifdef _DEBUG diff --git a/dChatServer/ChatServer.cpp b/dChatServer/ChatServer.cpp index fc8bdd5b..e0073747 100644 --- a/dChatServer/ChatServer.cpp +++ b/dChatServer/ChatServer.cpp @@ -13,6 +13,7 @@ #include "dChatFilter.h" #include "Diagnostics.h" #include "AssetManager.h" +#include "BinaryPathFinder.h" #include "PlayerContainer.h" #include "ChatPacketHandler.h" @@ -53,9 +54,14 @@ int main(int argc, char** argv) { Game::logger->SetLogDebugStatements(config.GetValue("log_debug_statements") == "1"); try { - std::string client_path = config.GetValue("client_location"); - if (client_path.empty()) client_path = "./res"; - Game::assetManager = new AssetManager(client_path); + std::string clientPathStr = config.GetValue("client_location"); + if (clientPathStr.empty()) clientPathStr = "./res"; + std::filesystem::path clientPath = std::filesystem::path(clientPathStr); + if (clientPath.is_relative()) { + clientPath = BinaryPathFinder::GetBinaryDir() / clientPath; + } + + Game::assetManager = new AssetManager(clientPath); } catch (std::runtime_error& ex) { Game::logger->Log("ChatServer", "Got an error while setting up assets: %s", ex.what()); @@ -167,7 +173,7 @@ int main(int argc, char** argv) { } dLogger* SetupLogger() { - std::string logPath = "./logs/ChatServer_" + std::to_string(time(nullptr)) + ".log"; + std::string logPath = (BinaryPathFinder::GetBinaryDir() / ("logs/ChatServer_" + std::to_string(time(nullptr)) + ".log")).string(); bool logToConsole = false; bool logDebugStatements = false; #ifdef _DEBUG diff --git a/dCommon/BinaryPathFinder.cpp b/dCommon/BinaryPathFinder.cpp new file mode 100644 index 00000000..b1c2afef --- /dev/null +++ b/dCommon/BinaryPathFinder.cpp @@ -0,0 +1,71 @@ +#include +#include +#include "BinaryPathFinder.h" +#include "dPlatforms.h" + +#if defined(DARKFLAME_PLATFORM_WIN32) +#include +#elif defined(DARKFLAME_PLATFORM_MACOS) || defined(DARKFLAME_PLATFORM_IOS) +#include +#elif defined(DARKFLAME_PLATFORM_FREEBSD) +#include +#include +#include +#endif + +std::filesystem::path BinaryPathFinder::binaryDir; + +std::filesystem::path BinaryPathFinder::GetBinaryDir() { + if (!binaryDir.empty()) { + return binaryDir; + } + + std::string pathStr; + + // Derived from boost::dll::program_location, licensed under the Boost Software License: http://www.boost.org/LICENSE_1_0.txt +#if defined(DARKFLAME_PLATFORM_WIN32) + char path[MAX_PATH]; + GetModuleFileName(NULL, path, MAX_PATH); + pathStr = std::string(path); +#elif defined(DARKFLAME_PLATFORM_MACOS) || defined(DARKFLAME_PLATFORM_IOS) + char path[1024]; + uint32_t size = sizeof(path); + if (_NSGetExecutablePath(path, &size) == 0) { + pathStr = std::string(path); + } else { + // The filepath size is greater than our initial buffer size, so try again with the size + // that _NSGetExecutablePath told us it actually is + char *p = new char[size]; + if (_NSGetExecutablePath(p, &size) != 0) { + throw std::runtime_error("Failed to get binary path from _NSGetExecutablePath"); + } + + pathStr = std::string(p); + delete[] p; + } +#elif defined(DARKFLAME_PLATFORM_FREEBSD) + int mib[4]; + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PATHNAME; + mib[3] = -1; + char buf[10240]; + size_t cb = sizeof(buf); + sysctl(mib, 4, buf, &cb, NULL, 0); + pathStr = std::string(buf); +#else // DARKFLAME_PLATFORM_LINUX || DARKFLAME_PLATFORM_UNIX || DARKFLAME_PLATFORM_ANDROID + pathStr = std::filesystem::read_symlink("/proc/self/exe"); +#endif + + // Some methods like _NSGetExecutablePath could return a symlink + // Either way, we need to get the parent path because we want the directory, not the binary itself + // We also ensure that it is an absolute path so that it is valid if we need to construct a path + // to exucute on unix systems (eg sudo BinaryPathFinder::GetBinaryDir() / WorldServer) + if (std::filesystem::is_symlink(pathStr)) { + binaryDir = std::filesystem::absolute(std::filesystem::read_symlink(pathStr).parent_path()); + } else { + binaryDir = std::filesystem::absolute(std::filesystem::path(pathStr).parent_path()); + } + + return binaryDir; +} diff --git a/dCommon/BinaryPathFinder.h b/dCommon/BinaryPathFinder.h new file mode 100644 index 00000000..c4ca6da2 --- /dev/null +++ b/dCommon/BinaryPathFinder.h @@ -0,0 +1,15 @@ +#pragma once + +#ifndef __BINARYPATHFINDER__H__ +#define __BINARYPATHFINDER__H__ + +#include + +class BinaryPathFinder { +private: + static std::filesystem::path binaryDir; +public: + static std::filesystem::path GetBinaryDir(); +}; + +#endif //!__BINARYPATHFINDER__H__ diff --git a/dCommon/CMakeLists.txt b/dCommon/CMakeLists.txt index 46102b74..8d02186b 100644 --- a/dCommon/CMakeLists.txt +++ b/dCommon/CMakeLists.txt @@ -15,6 +15,7 @@ set(DCOMMON_SOURCES "AMFFormat.cpp" "Type.cpp" "ZCompression.cpp" "BrickByBrickFix.cpp" + "BinaryPathFinder.cpp" ) add_subdirectory(dClient) diff --git a/dCommon/dClient/AssetManager.cpp b/dCommon/dClient/AssetManager.cpp index 349b5271..dc3db0a1 100644 --- a/dCommon/dClient/AssetManager.cpp +++ b/dCommon/dClient/AssetManager.cpp @@ -1,15 +1,17 @@ +#include + #include "AssetManager.h" #include "Game.h" #include "dLogger.h" #include -AssetManager::AssetManager(const std::string& path) { +AssetManager::AssetManager(const std::filesystem::path& path) { if (!std::filesystem::is_directory(path)) { - throw std::runtime_error("Attempted to load asset bundle (" + path + ") however it is not a valid directory."); + throw std::runtime_error("Attempted to load asset bundle (" + path.string() + ") however it is not a valid directory."); } - m_Path = std::filesystem::path(path); + m_Path = path; if (std::filesystem::exists(m_Path / "client") && std::filesystem::exists(m_Path / "versions")) { m_AssetBundleType = eAssetBundleType::Packed; diff --git a/dCommon/dClient/AssetManager.h b/dCommon/dClient/AssetManager.h index 87653845..73b24f18 100644 --- a/dCommon/dClient/AssetManager.h +++ b/dCommon/dClient/AssetManager.h @@ -48,7 +48,7 @@ struct AssetMemoryBuffer : std::streambuf { class AssetManager { public: - AssetManager(const std::string& path); + AssetManager(const std::filesystem::path& path); ~AssetManager(); std::filesystem::path GetResPath(); diff --git a/dCommon/dConfig.cpp b/dCommon/dConfig.cpp index c22c91cc..1bc940e8 100644 --- a/dCommon/dConfig.cpp +++ b/dCommon/dConfig.cpp @@ -1,9 +1,10 @@ #include "dConfig.h" #include +#include "BinaryPathFinder.h" dConfig::dConfig(const std::string& filepath) { m_EmptyString = ""; - std::ifstream in(filepath); + std::ifstream in(BinaryPathFinder::GetBinaryDir() / filepath); if (!in.good()) return; std::string line; @@ -13,7 +14,7 @@ dConfig::dConfig(const std::string& filepath) { } } - std::ifstream sharedConfig("sharedconfig.ini", std::ios::in); + std::ifstream sharedConfig(BinaryPathFinder::GetBinaryDir() / "sharedconfig.ini", std::ios::in); if (!sharedConfig.good()) return; while (std::getline(sharedConfig, line)) { diff --git a/dDatabase/MigrationRunner.cpp b/dDatabase/MigrationRunner.cpp index 25312608..fe0f933a 100644 --- a/dDatabase/MigrationRunner.cpp +++ b/dDatabase/MigrationRunner.cpp @@ -6,12 +6,13 @@ #include "Game.h" #include "GeneralUtils.h" #include "dLogger.h" +#include "BinaryPathFinder.h" #include Migration LoadMigration(std::string path) { Migration migration{}; - std::ifstream file("./migrations/" + path); + std::ifstream file(BinaryPathFinder::GetBinaryDir() / "migrations/" / path); if (file.is_open()) { std::string line; @@ -37,7 +38,7 @@ void MigrationRunner::RunMigrations() { sql::SQLString finalSQL = ""; bool runSd0Migrations = false; - for (const auto& entry : GeneralUtils::GetFileNamesFromFolder("./migrations/dlu/")) { + for (const auto& entry : GeneralUtils::GetFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "./migrations/dlu/").string())) { auto migration = LoadMigration("dlu/" + entry); if (migration.data.empty()) { @@ -97,7 +98,7 @@ void MigrationRunner::RunSQLiteMigrations() { stmt->execute(); delete stmt; - for (const auto& entry : GeneralUtils::GetFileNamesFromFolder("./migrations/cdserver/")) { + for (const auto& entry : GeneralUtils::GetFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "migrations/cdserver/").string())) { auto migration = LoadMigration("cdserver/" + entry); if (migration.data.empty()) continue; diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 8f4ac1a8..2f4d9211 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -64,6 +64,7 @@ #include "ScriptedActivityComponent.h" #include "LevelProgressionComponent.h" #include "AssetManager.h" +#include "BinaryPathFinder.h" void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entity* entity, const SystemAddress& sysAddr) { std::string chatCommand; @@ -248,7 +249,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit } if (chatCommand == "credits" || chatCommand == "info") { - const auto& customText = chatCommand == "credits" ? VanityUtilities::ParseMarkdown("./vanity/CREDITS.md") : VanityUtilities::ParseMarkdown("./vanity/INFO.md"); + const auto& customText = chatCommand == "credits" ? VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/CREDITS.md").string()) : VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/INFO.md").string()); { AMFArrayValue args; diff --git a/dGame/dUtilities/VanityUtilities.cpp b/dGame/dUtilities/VanityUtilities.cpp index 733d94c4..3a1259a2 100644 --- a/dGame/dUtilities/VanityUtilities.cpp +++ b/dGame/dUtilities/VanityUtilities.cpp @@ -13,6 +13,7 @@ #include "tinyxml2.h" #include "Game.h" #include "dLogger.h" +#include "BinaryPathFinder.h" #include @@ -27,7 +28,7 @@ void VanityUtilities::SpawnVanity() { const uint32_t zoneID = Game::server->GetZoneID(); - ParseXML("./vanity/NPC.xml"); + ParseXML((BinaryPathFinder::GetBinaryDir() / "vanity/NPC.xml").string()); // Loop through all parties for (const auto& party : m_Parties) { @@ -131,7 +132,7 @@ void VanityUtilities::SpawnVanity() { info.spawnerID = EntityManager::Instance()->GetZoneControlEntity()->GetObjectID(); info.settings = { new LDFData(u"hasCustomText", true), - new LDFData(u"customText", ParseMarkdown("./vanity/TESTAMENT.md")) }; + new LDFData(u"customText", ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/TESTAMENT.md").string())) }; auto* entity = EntityManager::Instance()->CreateEntity(info); diff --git a/dGame/dUtilities/dLocale.cpp b/dGame/dUtilities/dLocale.cpp index 65ef3a58..de65511b 100644 --- a/dGame/dUtilities/dLocale.cpp +++ b/dGame/dUtilities/dLocale.cpp @@ -9,13 +9,14 @@ #include "tinyxml2.h" #include "Game.h" #include "dConfig.h" +#include "BinaryPathFinder.h" dLocale::dLocale() { if (Game::config->GetValue("locale_enabled") != "1") { return; } - std::ifstream file(m_LocalePath); + std::ifstream file(BinaryPathFinder::GetBinaryDir() / m_LocalePath); if (!file.good()) { return; diff --git a/dMasterServer/InstanceManager.cpp b/dMasterServer/InstanceManager.cpp index 0c605ed6..83378dbb 100644 --- a/dMasterServer/InstanceManager.cpp +++ b/dMasterServer/InstanceManager.cpp @@ -10,6 +10,7 @@ #include "dMessageIdentifiers.h" #include "MasterPackets.h" #include "PacketUtils.h" +#include "BinaryPathFinder.h" InstanceManager::InstanceManager(dLogger* logger, const std::string& externalIP) { mLogger = logger; @@ -48,13 +49,13 @@ Instance* InstanceManager::GetInstance(LWOMAPID mapID, bool isFriendTransfer, LW //Start the actual process: #ifdef _WIN32 - std::string cmd = "start ./WorldServer.exe -zone "; + std::string cmd = "start " + (BinaryPathFinder::GetBinaryDir() / "WorldServer.exe").string() + " -zone "; #else std::string cmd; if (std::atoi(Game::config->GetValue("use_sudo_world").c_str())) { - cmd = "sudo ./WorldServer -zone "; + cmd = "sudo " + (BinaryPathFinder::GetBinaryDir() / "WorldServer").string() + " -zone "; } else { - cmd = "./WorldServer -zone "; + cmd = (BinaryPathFinder::GetBinaryDir() / "WorldServer").string() + " -zone "; } #endif @@ -300,10 +301,10 @@ Instance* InstanceManager::CreatePrivateInstance(LWOMAPID mapID, LWOCLONEID clon instance = new Instance(mExternalIP, port, mapID, ++m_LastInstanceID, cloneID, maxPlayers, maxPlayers, true, password); //Start the actual process: - std::string cmd = "start ./WorldServer.exe -zone "; + std::string cmd = "start " + (BinaryPathFinder::GetBinaryDir() / "WorldServer").string() + " -zone "; #ifndef _WIN32 - cmd = "./WorldServer -zone "; + cmd = (BinaryPathFinder::GetBinaryDir() / "WorldServer").string() + " -zone "; #endif cmd.append(std::to_string(mapID)); diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 061a91c7..34e2f71a 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -26,6 +26,7 @@ #include "dLogger.h" #include "dServer.h" #include "AssetManager.h" +#include "BinaryPathFinder.h" //RakNet includes: #include "RakNetDefines.h" @@ -102,9 +103,14 @@ int main(int argc, char** argv) { } try { - std::string client_path = config.GetValue("client_location"); - if (client_path.empty()) client_path = "./res"; - Game::assetManager = new AssetManager(client_path); + std::string clientPathStr = config.GetValue("client_location"); + if (clientPathStr.empty()) clientPathStr = "./res"; + std::filesystem::path clientPath = std::filesystem::path(clientPathStr); + if (clientPath.is_relative()) { + clientPath = BinaryPathFinder::GetBinaryDir() / clientPath; + } + + Game::assetManager = new AssetManager(clientPath); } catch (std::runtime_error& ex) { Game::logger->Log("MasterServer", "Got an error while setting up assets: %s", ex.what()); @@ -127,18 +133,16 @@ int main(int argc, char** argv) { stmt->executeUpdate(); delete stmt; - std::string res = "python3 ../thirdparty/docker-utils/utils/fdb_to_sqlite.py " + (Game::assetManager->GetResPath() / "cdclient.fdb").string(); + std::string res = "python3 " + + (BinaryPathFinder::GetBinaryDir() / "../thirdparty/docker-utils/utils/fdb_to_sqlite.py").string() + + " --sqlite_path " + (Game::assetManager->GetResPath() / "CDServer.sqlite").string() + + " " + (Game::assetManager->GetResPath() / "cdclient.fdb").string(); int result = system(res.c_str()); if (result != 0) { Game::logger->Log("MasterServer", "Failed to convert fdb to sqlite"); return EXIT_FAILURE; } - - if (std::rename("./cdclient.sqlite", (Game::assetManager->GetResPath() / "CDServer.sqlite").string().c_str()) != 0) { - Game::logger->Log("MasterServer", "Failed to move cdclient file."); - return EXIT_FAILURE; - } } //Connect to CDClient @@ -363,7 +367,7 @@ int main(int argc, char** argv) { dLogger* SetupLogger() { std::string logPath = - "./logs/MasterServer_" + std::to_string(time(nullptr)) + ".log"; + (BinaryPathFinder::GetBinaryDir() / ("logs/MasterServer_" + std::to_string(time(nullptr)) + ".log")).string(); bool logToConsole = false; bool logDebugStatements = false; #ifdef _DEBUG @@ -738,28 +742,28 @@ void HandlePacket(Packet* packet) { void StartChatServer() { #ifdef __APPLE__ //macOS doesn't need sudo to run on ports < 1024 - system("./ChatServer&"); + system(((BinaryPathFinder::GetBinaryDir() / "ChatServer").string() + "&").c_str()); #elif _WIN32 - system("start ./ChatServer.exe"); + system(("start " + (BinaryPathFinder::GetBinaryDir() / "ChatServer.exe").string()).c_str()); #else if (std::atoi(Game::config->GetValue("use_sudo_chat").c_str())) { - system("sudo ./ChatServer&"); + system(("sudo " + (BinaryPathFinder::GetBinaryDir() / "ChatServer").string() + "&").c_str()); } else { - system("./ChatServer&"); + system(((BinaryPathFinder::GetBinaryDir() / "ChatServer").string() + "&").c_str()); } #endif } void StartAuthServer() { #ifdef __APPLE__ - system("./AuthServer&"); + system(((BinaryPathFinder::GetBinaryDir() / "AuthServer").string() + "&").c_str()); #elif _WIN32 - system("start ./AuthServer.exe"); + system(("start " + (BinaryPathFinder::GetBinaryDir() / "AuthServer.exe").string()).c_str()); #else if (std::atoi(Game::config->GetValue("use_sudo_auth").c_str())) { - system("sudo ./AuthServer&"); + system(("sudo " + (BinaryPathFinder::GetBinaryDir() / "AuthServer").string() + "&").c_str()); } else { - system("./AuthServer&"); + system(((BinaryPathFinder::GetBinaryDir() / "AuthServer").string() + "&").c_str()); } #endif } diff --git a/dNavigation/dNavMesh.cpp b/dNavigation/dNavMesh.cpp index e5ba0129..fdba4a2d 100644 --- a/dNavigation/dNavMesh.cpp +++ b/dNavigation/dNavMesh.cpp @@ -7,6 +7,7 @@ #include "dPlatforms.h" #include "NiPoint3.h" #include "BinaryIO.h" +#include "BinaryPathFinder.h" #include "dZoneManager.h" @@ -43,7 +44,7 @@ dNavMesh::~dNavMesh() { void dNavMesh::LoadNavmesh() { - std::string path = "./navmeshes/" + std::to_string(m_ZoneId) + ".bin"; + std::string path = (BinaryPathFinder::GetBinaryDir() / "navmeshes/" / (std::to_string(m_ZoneId) + ".bin")).string(); if (!BinaryIO::DoesFileExist(path)) { return; diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index d4e55cb5..b7d7cc14 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -17,6 +17,7 @@ #include "Metrics.hpp" #include "PerformanceManager.h" #include "Diagnostics.h" +#include "BinaryPathFinder.h" //RakNet includes: #include "RakNetDefines.h" @@ -146,9 +147,13 @@ int main(int argc, char** argv) { if (config.GetValue("disable_chat") == "1") chatDisabled = true; try { - std::string client_path = config.GetValue("client_location"); - if (client_path.empty()) client_path = "./res"; - Game::assetManager = new AssetManager(client_path); + std::string clientPathStr = config.GetValue("client_location"); + if (clientPathStr.empty()) clientPathStr = "./res"; + std::filesystem::path clientPath = std::filesystem::path(clientPathStr); + if (clientPath.is_relative()) { + clientPath = BinaryPathFinder::GetBinaryDir() / clientPath; + } + Game::assetManager = new AssetManager(clientPath); } catch (std::runtime_error& ex) { Game::logger->Log("WorldServer", "Got an error while setting up assets: %s", ex.what()); @@ -489,7 +494,7 @@ int main(int argc, char** argv) { } dLogger* SetupLogger(int zoneID, int instanceID) { - std::string logPath = "./logs/WorldServer_" + std::to_string(zoneID) + "_" + std::to_string(instanceID) + "_" + std::to_string(time(nullptr)) + ".log"; + std::string logPath = (BinaryPathFinder::GetBinaryDir() / ("logs/WorldServer_" + std::to_string(zoneID) + "_" + std::to_string(instanceID) + "_" + std::to_string(time(nullptr)) + ".log")).string(); bool logToConsole = false; bool logDebugStatements = false; #ifdef _DEBUG From d382eb3bc2d97e73d56995c532fb6795b31922a7 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 27 Nov 2022 04:03:30 -0800 Subject: [PATCH 08/28] Fix Boogie Down (#854) - Give entities that have a script component ID of zero a script component still - Progress scripted entity missions within the for loop as we do for script calls Tested that Boogie Down is (finally) completable. Tested that Mission 737 is still completable Checked that missions progressed inside OnEmoteReceived scripts to not double trigger progression --- dGame/Entity.cpp | 4 ++-- dGame/dGameMessages/GameMessages.cpp | 13 +++++++------ dScripts/ai/FV/FvNinjaGuard.cpp | 7 ------- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 62ac8bc2..1f094291 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -454,7 +454,7 @@ void Entity::Initialize() { */ CDScriptComponentTable* scriptCompTable = CDClientManager::Instance()->GetTable("ScriptComponent"); - int scriptComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, COMPONENT_TYPE_SCRIPT); + int32_t scriptComponentID = compRegistryTable->GetByIDAndType(m_TemplateID, COMPONENT_TYPE_SCRIPT, -1); std::string scriptName = ""; bool client = false; @@ -496,7 +496,7 @@ void Entity::Initialize() { scriptName = customScriptServer; } - if (!scriptName.empty() || client || m_Character) { + if (!scriptName.empty() || client || m_Character || scriptComponentID >= 0) { m_Components.insert(std::make_pair(COMPONENT_TYPE_SCRIPT, new ScriptComponent(this, scriptName, true, client && scriptName.empty()))); } diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 18d7e0f6..025c26b3 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -4894,27 +4894,27 @@ void GameMessages::HandlePlayEmote(RakNet::BitStream* inStream, Entity* entity) inStream->Read(emoteID); inStream->Read(targetID); - Game::logger->Log("GameMessages", "Emote (%i) (%llu)", emoteID, targetID); + Game::logger->LogDebug("GameMessages", "Emote (%i) (%llu)", emoteID, targetID); //TODO: If targetID != 0, and we have one of the "perform emote" missions, complete them. if (emoteID == 0) return; std::string sAnimationName = "deaded"; //Default name in case we fail to get the emote - MissionComponent* mission = static_cast(entity->GetComponent(COMPONENT_TYPE_MISSION)); - if (mission) { - mission->Progress(MissionTaskType::MISSION_TASK_TYPE_EMOTE, emoteID, targetID); - } + MissionComponent* missionComponent = entity->GetComponent(); + if (!missionComponent) return; if (targetID != LWOOBJID_EMPTY) { auto* targetEntity = EntityManager::Instance()->GetEntity(targetID); - Game::logger->Log("GameMessages", "Emote target found (%d)", targetEntity != nullptr); + Game::logger->LogDebug("GameMessages", "Emote target found (%d)", targetEntity != nullptr); if (targetEntity != nullptr) { targetEntity->OnEmoteReceived(emoteID, entity); + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_EMOTE, emoteID, targetID); } } else { + Game::logger->LogDebug("GameMessages", "Target ID is empty, using backup"); const auto scriptedEntities = EntityManager::Instance()->GetEntitiesByComponent(COMPONENT_TYPE_SCRIPT); const auto& referencePoint = entity->GetPosition(); @@ -4923,6 +4923,7 @@ void GameMessages::HandlePlayEmote(RakNet::BitStream* inStream, Entity* entity) if (Vector3::DistanceSquared(scripted->GetPosition(), referencePoint) > 5.0f * 5.0f) continue; scripted->OnEmoteReceived(emoteID, entity); + missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_EMOTE, emoteID, scripted->GetObjectID()); } } diff --git a/dScripts/ai/FV/FvNinjaGuard.cpp b/dScripts/ai/FV/FvNinjaGuard.cpp index 6a841ccf..58267999 100644 --- a/dScripts/ai/FV/FvNinjaGuard.cpp +++ b/dScripts/ai/FV/FvNinjaGuard.cpp @@ -19,12 +19,6 @@ void FvNinjaGuard::OnEmoteReceived(Entity* self, const int32_t emote, Entity* ta GameMessages::SendPlayAnimation(self, u"scared"); - auto* missionComponent = target->GetComponent(); - - if (missionComponent != nullptr && missionComponent->HasMission(737)) { - missionComponent->ForceProgressTaskType(737, 5, 1, false); - } - if (self->GetLOT() == 7412) { auto* rightGuard = EntityManager::Instance()->GetEntity(m_RightGuard); @@ -39,4 +33,3 @@ void FvNinjaGuard::OnEmoteReceived(Entity* self, const int32_t emote, Entity* ta } } } - From 1556f580d6ce37f2845a39481066d6bea73b01f5 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 27 Nov 2022 13:47:14 -0800 Subject: [PATCH 09/28] Improve Diagnostics logging (#841) Improve diagnostics to write the file name and signal to the log file should there be a crash. --- dCommon/Diagnostics.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/dCommon/Diagnostics.cpp b/dCommon/Diagnostics.cpp index 4c2c8beb..3025f083 100644 --- a/dCommon/Diagnostics.cpp +++ b/dCommon/Diagnostics.cpp @@ -1,4 +1,6 @@ #include "Diagnostics.h" +#include "Game.h" +#include "dLogger.h" // If we're on Win32, we'll include our minidump writer #ifdef _WIN32 @@ -26,7 +28,7 @@ void make_minidump(EXCEPTION_POINTERS* e) { "_%4d%02d%02d_%02d%02d%02d.dmp", t.wYear, t.wMonth, t.wDay, t.wHour, t.wMinute, t.wSecond); } - + Game::logger->Log("Diagnostics", "Creating crash dump %s", name); auto hFile = CreateFileA(name, GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); if (hFile == INVALID_HANDLE_VALUE) return; @@ -81,6 +83,7 @@ struct bt_ctx { static inline void Bt(struct backtrace_state* state) { std::string fileName = Diagnostics::GetOutDirectory() + "crash_" + Diagnostics::GetProcessName() + "_" + std::to_string(getpid()) + ".log"; + Game::logger->Log("Diagnostics", "backtrace is enabled, crash dump located at %s", fileName.c_str()); FILE* file = fopen(fileName.c_str(), "w+"); if (file != nullptr) { backtrace_print(state, 2, file); @@ -114,6 +117,8 @@ void GenerateDump() { void CatchUnhandled(int sig) { #ifndef __include_backtrace__ + std::string fileName = Diagnostics::GetOutDirectory() + "crash_" + Diagnostics::GetProcessName() + "_" + std::to_string(getpid()) + ".log"; + Game::logger->Log("Diagnostics", "Encountered signal %i, creating crash dump %s", sig, fileName.c_str()); if (Diagnostics::GetProduceMemoryDump()) { GenerateDump(); } @@ -124,7 +129,6 @@ void CatchUnhandled(int sig) { // get void*'s for all entries on the stack size = backtrace(array, 10); - printf("Fatal error %i\nStacktrace:\n", sig); #if defined(__GNUG__) and defined(__dynamic) // Loop through the returned addresses, and get the symbols to be demangled @@ -142,19 +146,18 @@ void CatchUnhandled(int sig) { demangled = demangle(functionName.c_str()); if (demangled.empty()) { - printf("[%02zu] %s\n", i, demangled.c_str()); + Game::logger->Log("Diagnostics", "[%02zu] %s", i, demangled.c_str()); } else { - printf("[%02zu] %s\n", i, functionName.c_str()); + Game::logger->Log("Diagnostics", "[%02zu] %s", i, functionName.c_str()); } } else { - printf("[%02zu] %s\n", i, functionName.c_str()); + Game::logger->Log("Diagnostics", "[%02zu] %s", i, functionName.c_str()); } } #else backtrace_symbols_fd(array, size, STDOUT_FILENO); #endif - std::string fileName = Diagnostics::GetOutDirectory() + "crash_" + Diagnostics::GetProcessName() + "_" + std::to_string(getpid()) + ".log"; FILE* file = fopen(fileName.c_str(), "w+"); if (file != NULL) { // print out all the frames to stderr From 3939f19b08f8c1dc642933e016a56ac373d68244 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 27 Nov 2022 16:40:14 -0800 Subject: [PATCH 10/28] Add Remove Buff Behavior and patch infinite use Imagination Backpack(#845) Testing does not reveal any issues with existing buff removals sending this GM as well and may fix more bugs that were unknown, or cause more. --- dCommon/dEnums/dMessageIdentifiers.h | 1 + dGame/dBehaviors/Behavior.cpp | 5 ++++- dGame/dBehaviors/CMakeLists.txt | 1 + dGame/dBehaviors/RemoveBuffBehavior.cpp | 21 +++++++++++++++++++++ dGame/dBehaviors/RemoveBuffBehavior.h | 22 ++++++++++++++++++++++ dGame/dComponents/BuffComponent.cpp | 4 +++- dGame/dComponents/BuffComponent.h | 3 ++- dGame/dComponents/InventoryComponent.cpp | 1 + dGame/dGameMessages/GameMessages.cpp | 14 ++++++++++++++ dGame/dGameMessages/GameMessages.h | 2 ++ 10 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 dGame/dBehaviors/RemoveBuffBehavior.cpp create mode 100644 dGame/dBehaviors/RemoveBuffBehavior.h diff --git a/dCommon/dEnums/dMessageIdentifiers.h b/dCommon/dEnums/dMessageIdentifiers.h index 7dc08711..5f7e9128 100644 --- a/dCommon/dEnums/dMessageIdentifiers.h +++ b/dCommon/dEnums/dMessageIdentifiers.h @@ -537,6 +537,7 @@ enum GAME_MSG : unsigned short { GAME_MSG_REMOVE_RUN_SPEED_MODIFIER = 1506, GAME_MSG_UPDATE_PROPERTY_PERFORMANCE_COST = 1547, GAME_MSG_PROPERTY_ENTRANCE_BEGIN = 1553, + GAME_MSG_REMOVE_BUFF = 1648, GAME_MSG_REQUEST_MOVE_ITEM_BETWEEN_INVENTORY_TYPES = 1666, GAME_MSG_RESPONSE_MOVE_ITEM_BETWEEN_INVENTORY_TYPES = 1667, GAME_MSG_PLAYER_SET_CAMERA_CYCLING_MODE = 1676, diff --git a/dGame/dBehaviors/Behavior.cpp b/dGame/dBehaviors/Behavior.cpp index 046df117..d0d6192b 100644 --- a/dGame/dBehaviors/Behavior.cpp +++ b/dGame/dBehaviors/Behavior.cpp @@ -42,6 +42,7 @@ #include "SkillCastFailedBehavior.h" #include "SpawnBehavior.h" #include "ForceMovementBehavior.h" +#include "RemoveBuffBehavior.h" #include "ImmunityBehavior.h" #include "InterruptBehavior.h" #include "PlayEffectBehavior.h" @@ -226,7 +227,9 @@ Behavior* Behavior::CreateBehavior(const uint32_t behaviorId) { break; case BehaviorTemplates::BEHAVIOR_ALTER_CHAIN_DELAY: break; case BehaviorTemplates::BEHAVIOR_CAMERA: break; - case BehaviorTemplates::BEHAVIOR_REMOVE_BUFF: break; + case BehaviorTemplates::BEHAVIOR_REMOVE_BUFF: + behavior = new RemoveBuffBehavior(behaviorId); + break; case BehaviorTemplates::BEHAVIOR_GRAB: break; case BehaviorTemplates::BEHAVIOR_MODULAR_BUILD: break; case BehaviorTemplates::BEHAVIOR_NPC_COMBAT_SKILL: diff --git a/dGame/dBehaviors/CMakeLists.txt b/dGame/dBehaviors/CMakeLists.txt index fe8e89b8..297c389b 100644 --- a/dGame/dBehaviors/CMakeLists.txt +++ b/dGame/dBehaviors/CMakeLists.txt @@ -34,6 +34,7 @@ set(DGAME_DBEHAVIORS_SOURCES "AirMovementBehavior.cpp" "PlayEffectBehavior.cpp" "ProjectileAttackBehavior.cpp" "PullToPointBehavior.cpp" + "RemoveBuffBehavior.cpp" "RepairBehavior.cpp" "SkillCastFailedBehavior.cpp" "SkillEventBehavior.cpp" diff --git a/dGame/dBehaviors/RemoveBuffBehavior.cpp b/dGame/dBehaviors/RemoveBuffBehavior.cpp new file mode 100644 index 00000000..be3066ac --- /dev/null +++ b/dGame/dBehaviors/RemoveBuffBehavior.cpp @@ -0,0 +1,21 @@ +#include "RemoveBuffBehavior.h" + +#include "BehaviorBranchContext.h" +#include "BehaviorContext.h" +#include "EntityManager.h" +#include "BuffComponent.h" + +void RemoveBuffBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) { + auto* entity = EntityManager::Instance()->GetEntity(context->caster); + if (!entity) return; + + auto* buffComponent = entity->GetComponent(); + if (!buffComponent) return; + + buffComponent->RemoveBuff(m_BuffId, false, m_RemoveImmunity); +} + +void RemoveBuffBehavior::Load() { + this->m_RemoveImmunity = GetBoolean("remove_immunity"); + this->m_BuffId = GetInt("buff_id"); +} diff --git a/dGame/dBehaviors/RemoveBuffBehavior.h b/dGame/dBehaviors/RemoveBuffBehavior.h new file mode 100644 index 00000000..f2d8547b --- /dev/null +++ b/dGame/dBehaviors/RemoveBuffBehavior.h @@ -0,0 +1,22 @@ +#pragma once +#include "Behavior.h" + +class RemoveBuffBehavior final : public Behavior +{ +public: + + /* + * Inherited + */ + + explicit RemoveBuffBehavior(const uint32_t behaviorId) : Behavior(behaviorId) { + } + + void Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override; + + void Load() override; + +private: + bool m_RemoveImmunity; + uint32_t m_BuffId; +}; diff --git a/dGame/dComponents/BuffComponent.cpp b/dGame/dComponents/BuffComponent.cpp index ecb10017..af20fc00 100644 --- a/dGame/dComponents/BuffComponent.cpp +++ b/dGame/dComponents/BuffComponent.cpp @@ -123,13 +123,15 @@ void BuffComponent::ApplyBuff(const int32_t id, const float duration, const LWOO m_Buffs.emplace(id, buff); } -void BuffComponent::RemoveBuff(int32_t id) { +void BuffComponent::RemoveBuff(int32_t id, bool fromUnEquip, bool removeImmunity) { const auto& iter = m_Buffs.find(id); if (iter == m_Buffs.end()) { return; } + GameMessages::SendRemoveBuff(m_Parent, fromUnEquip, removeImmunity, id); + m_Buffs.erase(iter); RemoveBuffEffect(id); diff --git a/dGame/dComponents/BuffComponent.h b/dGame/dComponents/BuffComponent.h index aa669b13..7f7c6b0f 100644 --- a/dGame/dComponents/BuffComponent.h +++ b/dGame/dComponents/BuffComponent.h @@ -78,8 +78,9 @@ public: /** * Removes a buff from the parent entity, reversing its effects * @param id the id of the buff to remove + * @param removeImmunity whether or not to remove immunity on removing the buff */ - void RemoveBuff(int32_t id); + void RemoveBuff(int32_t id, bool fromUnEquip = false, bool removeImmunity = false); /** * Returns whether or not the entity has a buff identified by `id` diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 12b6792e..9f53cfa3 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -986,6 +986,7 @@ void InventoryComponent::ApplyBuff(Item* item) const { } } +// TODO Something needs to send the remove buff GameMessage as well when it is unequipping items that would remove buffs. void InventoryComponent::RemoveBuff(Item* item) const { const auto buffs = FindBuffs(item, false); diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 025c26b3..896dfa0c 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -3476,6 +3476,20 @@ void GameMessages::SendPlayEmote(LWOOBJID objectId, int32_t emoteID, LWOOBJID ta SEND_PACKET; } +void GameMessages::SendRemoveBuff(Entity* entity, bool fromUnEquip, bool removeImmunity, uint32_t buffId) { + CBITSTREAM; + CMSGHEADER; + + bitStream.Write(entity->GetObjectID()); + bitStream.Write(GAME_MSG::GAME_MSG_REMOVE_BUFF); + + bitStream.Write(false); // bFromRemoveBehavior but setting this to true makes the GM not do anything on the client? + bitStream.Write(fromUnEquip); + bitStream.Write(removeImmunity); + bitStream.Write(buffId); + + SEND_PACKET_BROADCAST; +} void GameMessages::SendBouncerActiveStatus(LWOOBJID objectId, bool bActive, const SystemAddress& sysAddr) { CBITSTREAM; diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 31bdebb3..3594a86a 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -566,6 +566,8 @@ namespace GameMessages { void HandleReportBug(RakNet::BitStream* inStream, Entity* entity); + void SendRemoveBuff(Entity* entity, bool fromUnEquip, bool removeImmunity, uint32_t buffId); + /* Message to synchronize a skill cast */ class EchoSyncSkill { static const GAME_MSG MsgID = GAME_MSG_ECHO_SYNC_SKILL; From 3222e788155e17931e3b0af82f35239722f6c847 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 27 Nov 2022 16:48:46 -0800 Subject: [PATCH 11/28] Implement undo action for pre-built models (#830) Brick building as of right now does not implement the undo action properly. This commit addresses the issue with undoing button being non-functional server side and implements the GM needed for addressing further issues. Implement GameMessage UnUseModel which is called when a model in BrickBuilding is UnUsed. Important for UGC content down the line. Final code has been tested as follows: 1. Placed a model in brick build 2. saved placed a brick 3. repeat 2 and 3 twice more for 6 total models 4. Place a new model in brick mode and then edit all 7 models into one brick model instance 5. Pressing undo returns the converted model to the inventory and properly discards the other 6 without crashing. Intended live behavior is to store this in the inventory instead however behind the scenes work is needed to implement UGC models properly. Implement enum Implement the BlueprintSaveResponseType enum so there are less magic numbers sent via packets. Correct int sizes from unsigned int to uint32_t Add deserialize test Add a test for de-serializing a GM that is sent to the client. Assertions verify the data is in the correct order and has no extra information. --- dCommon/dEnums/dCommonVars.h | 1 + dCommon/dEnums/dMessageIdentifiers.h | 1 + dCommon/eBlueprintSaveResponseType.h | 26 +++++++ dGame/dComponents/InventoryComponent.cpp | 13 ++-- .../PropertyManagementComponent.cpp | 2 +- dGame/dGameMessages/GameMessageHandler.cpp | 4 ++ dGame/dGameMessages/GameMessages.cpp | 70 ++++++++++++++++--- dGame/dGameMessages/GameMessages.h | 11 +++ dGame/dInventory/Inventory.h | 2 +- dMasterServer/ObjectIDManager.cpp | 4 +- dWorldServer/WorldServer.cpp | 7 +- tests/dGameTests/CMakeLists.txt | 3 + tests/dGameTests/GameDependencies.h | 9 ++- .../dGameMessagesTests/CMakeLists.txt | 9 +++ .../dGameMessagesTests/GameMessageTests.cpp | 52 ++++++++++++++ 15 files changed, 188 insertions(+), 26 deletions(-) create mode 100644 dCommon/eBlueprintSaveResponseType.h create mode 100644 tests/dGameTests/dGameMessagesTests/CMakeLists.txt create mode 100644 tests/dGameTests/dGameMessagesTests/GameMessageTests.cpp diff --git a/dCommon/dEnums/dCommonVars.h b/dCommon/dEnums/dCommonVars.h index 005d7205..74d83b11 100644 --- a/dCommon/dEnums/dCommonVars.h +++ b/dCommon/dEnums/dCommonVars.h @@ -434,6 +434,7 @@ enum eInventoryType : uint32_t { ITEMS = 0, VAULT_ITEMS, BRICKS, + MODELS_IN_BBB, TEMP_ITEMS = 4, MODELS, TEMP_MODELS, diff --git a/dCommon/dEnums/dMessageIdentifiers.h b/dCommon/dEnums/dMessageIdentifiers.h index 5f7e9128..5a5c9088 100644 --- a/dCommon/dEnums/dMessageIdentifiers.h +++ b/dCommon/dEnums/dMessageIdentifiers.h @@ -433,6 +433,7 @@ enum GAME_MSG : unsigned short { GAME_MSG_ORIENT_TO_POSITION = 906, GAME_MSG_ORIENT_TO_ANGLE = 907, GAME_MSG_BOUNCER_ACTIVE_STATUS = 942, + GAME_MSG_UN_USE_BBB_MODEL = 999, GAME_MSG_BBB_LOAD_ITEM_REQUEST = 1000, GAME_MSG_BBB_SAVE_REQUEST = 1001, GAME_MSG_BBB_SAVE_RESPONSE = 1006, diff --git a/dCommon/eBlueprintSaveResponseType.h b/dCommon/eBlueprintSaveResponseType.h new file mode 100644 index 00000000..29d15695 --- /dev/null +++ b/dCommon/eBlueprintSaveResponseType.h @@ -0,0 +1,26 @@ +#pragma once + +#ifndef __EBLUEPRINTSAVERESPONSETYPE__H__ +#define __EBLUEPRINTSAVERESPONSETYPE__H__ + +#include + +enum class eBlueprintSaveResponseType : uint32_t { + EverythingWorked = 0, + SaveCancelled, + CantBeginTransaction, + SaveBlueprintFailed, + SaveUgobjectFailed, + CantEndTransaction, + SaveFilesFailed, + BadInput, + NotEnoughBricks, + InventoryFull, + ModelGenerationFailed, + PlacementFailed, + GmLevelInsufficient, + WaitForPreviousSave, + FindMatchesFailed +}; + +#endif //!__EBLUEPRINTSAVERESPONSETYPE__H__ diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 9f53cfa3..77f3f035 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -606,16 +606,17 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument* document) { return; } - std::vector inventories; + std::vector inventoriesToSave; + // Need to prevent some transfer inventories from being saved for (const auto& pair : this->m_Inventories) { auto* inventory = pair.second; - if (inventory->GetType() == VENDOR_BUYBACK) { + if (inventory->GetType() == VENDOR_BUYBACK || inventory->GetType() == eInventoryType::MODELS_IN_BBB) { continue; } - inventories.push_back(inventory); + inventoriesToSave.push_back(inventory); } inventoryElement->SetAttribute("csl", m_Consumable); @@ -630,7 +631,7 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument* document) { bags->DeleteChildren(); - for (const auto* inventory : inventories) { + for (const auto* inventory : inventoriesToSave) { auto* bag = document->NewElement("b"); bag->SetAttribute("t", inventory->GetType()); @@ -649,7 +650,7 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument* document) { items->DeleteChildren(); - for (auto* inventory : inventories) { + for (auto* inventory : inventoriesToSave) { if (inventory->GetSize() == 0) { continue; } @@ -1260,7 +1261,7 @@ BehaviorSlot InventoryComponent::FindBehaviorSlot(const eItemType type) { } bool InventoryComponent::IsTransferInventory(eInventoryType type) { - return type == VENDOR_BUYBACK || type == VAULT_ITEMS || type == VAULT_MODELS || type == TEMP_ITEMS || type == TEMP_MODELS; + return type == VENDOR_BUYBACK || type == VAULT_ITEMS || type == VAULT_MODELS || type == TEMP_ITEMS || type == TEMP_MODELS || type == MODELS_IN_BBB; } uint32_t InventoryComponent::FindSkill(const LOT lot) { diff --git a/dGame/dComponents/PropertyManagementComponent.cpp b/dGame/dComponents/PropertyManagementComponent.cpp index d196e935..eaade8be 100644 --- a/dGame/dComponents/PropertyManagementComponent.cpp +++ b/dGame/dComponents/PropertyManagementComponent.cpp @@ -474,7 +474,7 @@ void PropertyManagementComponent::DeleteModel(const LWOOBJID id, const int delet settings.push_back(propertyObjectID); settings.push_back(modelType); - inventoryComponent->AddItem(6662, 1, eLootSourceType::LOOT_SOURCE_DELETION, eInventoryType::HIDDEN, settings, LWOOBJID_EMPTY, false, false, spawnerId); + inventoryComponent->AddItem(6662, 1, eLootSourceType::LOOT_SOURCE_DELETION, eInventoryType::MODELS_IN_BBB, settings, LWOOBJID_EMPTY, false, false, spawnerId); auto* item = inventoryComponent->FindItemBySubKey(spawnerId); if (item == nullptr) { diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index 2ce79966..b7687c19 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -46,6 +46,10 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream* inStream, const System switch (messageID) { + case GAME_MSG_UN_USE_BBB_MODEL: { + GameMessages::HandleUnUseModel(inStream, entity, sysAddr); + break; + } case GAME_MSG_PLAY_EMOTE: { GameMessages::HandlePlayEmote(inStream, entity); break; diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 896dfa0c..6639197c 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -70,6 +70,7 @@ #include "TradingManager.h" #include "ControlBehaviors.h" #include "AMFDeserialize.h" +#include "eBlueprintSaveResponseType.h" void GameMessages::SendFireEventClientSide(const LWOOBJID& objectID, const SystemAddress& sysAddr, std::u16string args, const LWOOBJID& object, int64_t param1, int param2, const LWOOBJID& sender) { CBITSTREAM; @@ -2139,6 +2140,32 @@ void GameMessages::HandleSetPropertyAccess(RakNet::BitStream* inStream, Entity* PropertyManagementComponent::Instance()->SetPrivacyOption(static_cast(accessType)); } +void GameMessages::HandleUnUseModel(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { + bool unknown{}; + LWOOBJID objIdToAddToInventory{}; + inStream->Read(unknown); + inStream->Read(objIdToAddToInventory); + auto* inventoryComponent = entity->GetComponent(); + if (inventoryComponent) { + auto* inventory = inventoryComponent->GetInventory(eInventoryType::MODELS_IN_BBB); + auto* item = inventory->FindItemById(objIdToAddToInventory); + if (item) { + inventoryComponent->MoveItemToInventory(item, eInventoryType::MODELS, 1); + } else { + Game::logger->Log("GameMessages", "item id %llu not found in MODELS_IN_BBB inventory, likely because it does not exist", objIdToAddToInventory); + } + } + + if (unknown) { + CBITSTREAM; + PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_BLUEPRINT_SAVE_RESPONSE); + bitStream.Write(LWOOBJID_EMPTY); //always zero so that a check on the client passes + bitStream.Write(eBlueprintSaveResponseType::PlacementFailed); // Sending a non-zero error code here prevents the client from deleting its in progress build for some reason? + bitStream.Write(0); + SEND_PACKET; + } +} + void GameMessages::HandleUpdatePropertyOrModelForFilterCheck(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { bool isProperty{}; LWOOBJID objectId{}; @@ -2353,16 +2380,40 @@ void GameMessages::HandleDeletePropertyModel(RakNet::BitStream* inStream, Entity } void GameMessages::HandleBBBLoadItemRequest(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { - LWOOBJID itemID = LWOOBJID_EMPTY; - inStream->Read(itemID); + LWOOBJID previousItemID = LWOOBJID_EMPTY; + inStream->Read(previousItemID); - Game::logger->Log("BBB", "Load item request for: %lld", itemID); + Game::logger->Log("BBB", "Load item request for: %lld", previousItemID); + LWOOBJID newId = previousItemID; + auto* inventoryComponent = entity->GetComponent(); + if (inventoryComponent) { + auto* inventory = inventoryComponent->GetInventory(eInventoryType::MODELS); + auto* itemToMove = inventory->FindItemById(previousItemID); + if (itemToMove) { + LOT previousLot = itemToMove->GetLot(); + inventoryComponent->MoveItemToInventory(itemToMove, eInventoryType::MODELS_IN_BBB, 1, false); + + auto* destinationInventory = inventoryComponent->GetInventory(eInventoryType::MODELS_IN_BBB); + if (destinationInventory) { + auto* movedItem = destinationInventory->FindItemByLot(previousLot); + if (movedItem) newId = movedItem->GetId(); + } + } else { + Game::logger->Log("GameMessages", "item id %llu not found in MODELS inventory, likely because it does not exist", previousItemID); + } + } + + // Second argument always true (successful) for now + SendBlueprintLoadItemResponse(sysAddr, true, previousItemID, newId); +} + +void GameMessages::SendBlueprintLoadItemResponse(const SystemAddress& sysAddr, bool success, LWOOBJID oldItemId, LWOOBJID newItemId) { CBITSTREAM; PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_BLUEPRINT_LOAD_RESPONSE_ITEMID); - bitStream.Write(static_cast(1)); - bitStream.Write(itemID); - bitStream.Write(itemID); + bitStream.Write(static_cast(success)); + bitStream.Write(oldItemId); + bitStream.Write(newItemId); SEND_PACKET; } @@ -2413,7 +2464,7 @@ void GameMessages::HandleControlBehaviors(RakNet::BitStream* inStream, Entity* e } auto owner = PropertyManagementComponent::Instance()->GetOwner(); - if (!owner) return; + if (!owner) return; ControlBehaviors::ProcessCommand(entity, sysAddr, static_cast(amfArguments.get()), command, owner); } @@ -2609,8 +2660,8 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream* inStream, Entity* ent CBITSTREAM; PacketUtils::WriteHeader(bitStream, CLIENT, CLIENT::MSG_CLIENT_BLUEPRINT_SAVE_RESPONSE); bitStream.Write(localId); - bitStream.Write(0); - bitStream.Write(1); + bitStream.Write(eBlueprintSaveResponseType::EverythingWorked); + bitStream.Write(1); bitStream.Write(blueprintID); bitStream.Write(sd0Size); @@ -2620,7 +2671,6 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream* inStream, Entity* ent } SEND_PACKET; - // PacketUtils::SavePacket("MSG_CLIENT_BLUEPRINT_SAVE_RESPONSE.bin", (char*)bitStream.GetData(), bitStream.GetNumberOfBytesUsed()); //Now we have to construct this object: diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 3594a86a..f9968d14 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -121,6 +121,7 @@ namespace GameMessages { void SendMatchResponse(Entity* entity, const SystemAddress& sysAddr, int response); void SendMatchUpdate(Entity* entity, const SystemAddress& sysAddr, std::string data, int type); + void HandleUnUseModel(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr); void SendStartCelebrationEffect(Entity* entity, const SystemAddress& sysAddr, int celebrationID); /** @@ -197,6 +198,16 @@ namespace GameMessages { void SendDownloadPropertyData(LWOOBJID objectId, const PropertyDataMessage& data, const SystemAddress& sysAddr); + /** + * @brief Send an updated item id to the client when they load a blueprint in brick build mode + * + * @param sysAddr SystemAddress to respond to + * @param oldItemId The item ID that was requested to be loaded + * @param newItemId The new item ID of the loaded item + * + */ + void SendBlueprintLoadItemResponse(const SystemAddress& sysAddr, bool success, LWOOBJID oldItemId, LWOOBJID newItemId); + void SendPropertyRentalResponse(LWOOBJID objectId, LWOCLONEID cloneId, uint32_t code, LWOOBJID propertyId, int64_t rentDue, const SystemAddress& sysAddr); void SendLockNodeRotation(Entity* entity, std::string nodeName); diff --git a/dGame/dInventory/Inventory.h b/dGame/dInventory/Inventory.h index 30f753da..6c6a4306 100644 --- a/dGame/dInventory/Inventory.h +++ b/dGame/dInventory/Inventory.h @@ -95,7 +95,7 @@ public: * @param lot the lot to find items for * @param ignoreEquipped ignores equipped items * @param ignoreBound ignores bound items - * @return item in the inventory for the provided LOT + * @return item with the lowest stack count in the inventory for the provided LOT */ Item* FindItemByLot(LOT lot, bool ignoreEquipped = false, bool ignoreBound = false) const; diff --git a/dMasterServer/ObjectIDManager.cpp b/dMasterServer/ObjectIDManager.cpp index 0f3b98c9..83dde8dd 100644 --- a/dMasterServer/ObjectIDManager.cpp +++ b/dMasterServer/ObjectIDManager.cpp @@ -51,13 +51,13 @@ uint32_t ObjectIDManager::GeneratePersistentID(void) { uint32_t toReturn = ++this->currentPersistentID; // So we peroidically save our ObjID to the database: - if (toReturn % 25 == 0) { // TEMP: DISABLED FOR DEBUG / DEVELOPMENT! + // if (toReturn % 25 == 0) { // TEMP: DISABLED FOR DEBUG / DEVELOPMENT! sql::PreparedStatement* stmt = Database::CreatePreppedStmt( "UPDATE object_id_tracker SET last_object_id=?"); stmt->setUInt(1, toReturn); stmt->execute(); delete stmt; - } + // } return toReturn; } diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index b7d7cc14..16e0b1c1 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -57,6 +57,7 @@ #include "Player.h" #include "PropertyManagementComponent.h" #include "AssetManager.h" +#include "eBlueprintSaveResponseType.h" #include "ZCompression.h" @@ -1082,9 +1083,9 @@ void HandlePacket(Packet* packet) { CBITSTREAM; PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_BLUEPRINT_SAVE_RESPONSE); - bitStream.Write(0); //always zero so that a check on the client passes - bitStream.Write(0); - bitStream.Write(1); + 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); diff --git a/tests/dGameTests/CMakeLists.txt b/tests/dGameTests/CMakeLists.txt index 68192b3f..ba7d4d1c 100644 --- a/tests/dGameTests/CMakeLists.txt +++ b/tests/dGameTests/CMakeLists.txt @@ -5,6 +5,9 @@ set(DGAMETEST_SOURCES add_subdirectory(dComponentsTests) list(APPEND DGAMETEST_SOURCES ${DCOMPONENTS_TESTS}) +add_subdirectory(dGameMessagesTests) +list(APPEND DGAMETEST_SOURCES ${DGAMEMESSAGES_TESTS}) + # Add the executable. Remember to add all tests above this! add_executable(dGameTests ${DGAMETEST_SOURCES}) diff --git a/tests/dGameTests/GameDependencies.h b/tests/dGameTests/GameDependencies.h index 593ec0fc..ee52dec6 100644 --- a/tests/dGameTests/GameDependencies.h +++ b/tests/dGameTests/GameDependencies.h @@ -5,15 +5,18 @@ #include "dLogger.h" #include "dServer.h" #include "EntityManager.h" -class dZoneManager; -class AssetManager; #include +class dZoneManager; +class AssetManager; + class dServerMock : public dServer { + RakNet::BitStream* sentBitStream = nullptr; public: dServerMock() {}; ~dServerMock() {}; - void Send(RakNet::BitStream* bitStream, const SystemAddress& sysAddr, bool broadcast) override {}; + RakNet::BitStream* GetMostRecentBitStream() { return sentBitStream; }; + void Send(RakNet::BitStream* bitStream, const SystemAddress& sysAddr, bool broadcast) override { sentBitStream = bitStream; }; }; class GameDependenciesTest : public ::testing::Test { diff --git a/tests/dGameTests/dGameMessagesTests/CMakeLists.txt b/tests/dGameTests/dGameMessagesTests/CMakeLists.txt new file mode 100644 index 00000000..2417d29c --- /dev/null +++ b/tests/dGameTests/dGameMessagesTests/CMakeLists.txt @@ -0,0 +1,9 @@ +SET(DGAMEMESSAGES_TESTS + "GameMessageTests.cpp") + +# Get the folder name and prepend it to the files above +get_filename_component(thisFolderName ${CMAKE_CURRENT_SOURCE_DIR} NAME) +list(TRANSFORM DGAMEMESSAGES_TESTS PREPEND "${thisFolderName}/") + +# Export to parent scope +set(DGAMEMESSAGES_TESTS ${DGAMEMESSAGES_TESTS} PARENT_SCOPE) diff --git a/tests/dGameTests/dGameMessagesTests/GameMessageTests.cpp b/tests/dGameTests/dGameMessagesTests/GameMessageTests.cpp new file mode 100644 index 00000000..3d8b2d04 --- /dev/null +++ b/tests/dGameTests/dGameMessagesTests/GameMessageTests.cpp @@ -0,0 +1,52 @@ +#include "GameMessages.h" +#include "GameDependencies.h" +#include + +class GameMessageTests : public GameDependenciesTest { + protected: + void SetUp() override { + SetUpDependencies(); + } + void TearDown() override { + TearDownDependencies(); + } +}; + +/** + * @brief Tests that the serialization struct BlueprintLoadItemResponse is serialized correctly + * + */ +TEST_F(GameMessageTests, SendBlueprintLoadItemResponse) { + GameMessages::SendBlueprintLoadItemResponse(UNASSIGNED_SYSTEM_ADDRESS, true, 515, 990); + auto* bitStream = static_cast(Game::server)->GetMostRecentBitStream(); + ASSERT_NE(bitStream, nullptr); + ASSERT_EQ(bitStream->GetNumberOfUnreadBits(), 200); + // First read in the packets' header + uint8_t rakNetPacketId{}; + uint16_t remoteConnectionType{}; + uint32_t packetId{}; + uint8_t always0{}; + + bitStream->Read(rakNetPacketId); + bitStream->Read(remoteConnectionType); + bitStream->Read(packetId); + bitStream->Read(always0); + ASSERT_EQ(rakNetPacketId, 0x53); + ASSERT_EQ(remoteConnectionType, 0x05); + ASSERT_EQ(packetId, 0x17); + ASSERT_EQ(always0, 0x00); + + // Next read in packet data + + uint8_t bSuccess{}; // unsigned bool + LWOOBJID previousId{}; + LWOOBJID newId{}; + bitStream->Read(bSuccess); + bitStream->Read(previousId); + bitStream->Read(newId); + ASSERT_EQ(bSuccess, static_cast(true)); + ASSERT_EQ(previousId, 515); + ASSERT_EQ(newId, 990); + + ASSERT_EQ(bitStream->GetNumberOfUnreadBits(), 0); +} From 56da3f8543bdb1b9eb754332dca32b570b07d6c0 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 27 Nov 2022 16:56:55 -0800 Subject: [PATCH 12/28] Remove Locale (#808) * Remove Locale (finally) --- CMakeLists.txt | 3 - dCommon/Game.h | 2 - dGame/dMission/Mission.cpp | 12 ++-- dGame/dUtilities/CMakeLists.txt | 1 - dGame/dUtilities/dLocale.cpp | 82 --------------------------- dGame/dUtilities/dLocale.h | 19 ------- dWorldServer/WorldServer.cpp | 3 - docker/start_server.sh | 2 +- tests/dGameTests/GameDependencies.cpp | 1 - 9 files changed, 5 insertions(+), 120 deletions(-) delete mode 100644 dGame/dUtilities/dLocale.cpp delete mode 100644 dGame/dUtilities/dLocale.h mode change 100755 => 100644 docker/start_server.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 644fc383..163196fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,9 +92,6 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) # Create a /res directory make_directory(${CMAKE_BINARY_DIR}/res) -# Create a /locale directory -make_directory(${CMAKE_BINARY_DIR}/locale) - # Create a /logs directory make_directory(${CMAKE_BINARY_DIR}/logs) diff --git a/dCommon/Game.h b/dCommon/Game.h index 616c7fbf..1c4bbc85 100644 --- a/dCommon/Game.h +++ b/dCommon/Game.h @@ -8,7 +8,6 @@ class InstanceManager; class dpWorld; class dChatFilter; class dConfig; -class dLocale; class RakPeerInterface; class AssetManager; struct SystemAddress; @@ -20,7 +19,6 @@ namespace Game { extern dpWorld* physicsWorld; extern dChatFilter* chatFilter; extern dConfig* config; - extern dLocale* locale; extern std::mt19937 randomEngine; extern RakPeerInterface* chatServer; extern AssetManager* assetManager; diff --git a/dGame/dMission/Mission.cpp b/dGame/dMission/Mission.cpp index 6020e51c..0a8a57fe 100644 --- a/dGame/dMission/Mission.cpp +++ b/dGame/dMission/Mission.cpp @@ -13,7 +13,6 @@ #include "Mail.h" #include "MissionComponent.h" #include "RacingTaskParam.h" -#include "dLocale.h" #include "dLogger.h" #include "dServer.h" #include "dZoneManager.h" @@ -335,13 +334,10 @@ void Mission::Complete(const bool yieldRewards) { for (const auto& email : missionEmails) { const auto missionEmailBase = "MissionEmail_" + std::to_string(email.ID) + "_"; - const auto senderLocale = missionEmailBase + "senderName"; - const auto announceLocale = missionEmailBase + "announceText"; - - if (email.messageType == 1 && Game::locale->HasPhrase(senderLocale)) { - const auto subject = dLocale::GetTemplate(missionEmailBase + "subjectText"); - const auto body = dLocale::GetTemplate(missionEmailBase + "bodyText"); - const auto sender = dLocale::GetTemplate(senderLocale); + if (email.messageType == 1) { + const auto subject = "%[" + missionEmailBase + "subjectText]"; + const auto body = "%[" + missionEmailBase + "bodyText]"; + const auto sender = "%[" + missionEmailBase + "senderName]"; Mail::SendMail(LWOOBJID_EMPTY, sender, GetAssociate(), subject, body, email.attachmentLOT, 1); } diff --git a/dGame/dUtilities/CMakeLists.txt b/dGame/dUtilities/CMakeLists.txt index 0c848bf4..2c453a2e 100644 --- a/dGame/dUtilities/CMakeLists.txt +++ b/dGame/dUtilities/CMakeLists.txt @@ -1,5 +1,4 @@ set(DGAME_DUTILITIES_SOURCES "BrickDatabase.cpp" - "dLocale.cpp" "GameConfig.cpp" "GUID.cpp" "Loot.cpp" diff --git a/dGame/dUtilities/dLocale.cpp b/dGame/dUtilities/dLocale.cpp deleted file mode 100644 index de65511b..00000000 --- a/dGame/dUtilities/dLocale.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "dLocale.h" - -#include -#include -#include -#include -#include - -#include "tinyxml2.h" -#include "Game.h" -#include "dConfig.h" -#include "BinaryPathFinder.h" - -dLocale::dLocale() { - if (Game::config->GetValue("locale_enabled") != "1") { - return; - } - - std::ifstream file(BinaryPathFinder::GetBinaryDir() / m_LocalePath); - - if (!file.good()) { - return; - } - - std::stringstream data; - data << file.rdbuf(); - - if (data.str().empty()) { - return; - } - - auto* doc = new tinyxml2::XMLDocument(); - - if (doc == nullptr) { - return; - } - - if (doc->Parse(data.str().c_str(), data.str().size()) != 0) { - return; - } - - std::hash hash; - - auto* localization = doc->FirstChildElement("localization"); - auto* phrases = localization->FirstChildElement("phrases"); - - auto* phrase = phrases->FirstChildElement("phrase"); - - while (phrase != nullptr) { - // Add the phrase hash to the vector - m_Phrases.push_back(hash(phrase->Attribute("id"))); - phrase = phrase->NextSiblingElement("phrase"); - } - - file.close(); - - delete doc; -} - -dLocale::~dLocale() = default; - -std::string dLocale::GetTemplate(const std::string& phraseID) { - return "%[" + phraseID + "]"; -} - -bool dLocale::HasPhrase(const std::string& phraseID) { - if (Game::config->GetValue("locale_enabled") != "1") { - return true; - } - - // Compute the hash and see if it's in the vector - std::hash hash; - std::size_t hashValue = hash(phraseID); - return std::find(m_Phrases.begin(), m_Phrases.end(), hashValue) != m_Phrases.end(); -} - -/*std::string dLocale::GetPhrase(const std::string& phraseID) { - if (m_Phrases.find(phraseID) == m_Phrases.end()) { - return ""; - } - return m_Phrases[phraseID]; -}*/ diff --git a/dGame/dUtilities/dLocale.h b/dGame/dUtilities/dLocale.h deleted file mode 100644 index db5d2a4f..00000000 --- a/dGame/dUtilities/dLocale.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once -#include -#include -#include -#include - -class dLocale { -public: - dLocale(); - ~dLocale(); - static std::string GetTemplate(const std::string& phraseID); - bool HasPhrase(const std::string& phraseID); - //std::string GetPhrase(const std::string& phraseID); - -private: - std::string m_LocalePath = "./locale/locale.xml"; - std::string m_Locale = "en_US"; // TODO: add to config - std::vector m_Phrases; -}; diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 16e0b1c1..bbba1166 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -48,7 +48,6 @@ #include "GameMessageHandler.h" #include "GameMessages.h" #include "Mail.h" -#include "dLocale.h" #include "TeamManager.h" #include "SkillComponent.h" #include "DestroyableComponent.h" @@ -68,7 +67,6 @@ namespace Game { dpWorld* physicsWorld; dChatFilter* chatFilter; dConfig* config; - dLocale* locale; std::mt19937 randomEngine; AssetManager* assetManager; @@ -223,7 +221,6 @@ int main(int argc, char** argv) { //Set up other things: Game::randomEngine = std::mt19937(time(0)); - Game::locale = new dLocale(); //Run it until server gets a kill message from Master: auto lastTime = std::chrono::high_resolution_clock::now(); diff --git a/docker/start_server.sh b/docker/start_server.sh old mode 100755 new mode 100644 index ca1a49a0..4fd6e2be --- a/docker/start_server.sh +++ b/docker/start_server.sh @@ -7,7 +7,7 @@ function symlink_client_files() { ln -s /client/client/res/chatplus_en_us.txt /app/res/chatplus_en_us.txt ln -s /client/client/res/names/ /app/res/names ln -s /client/client/res/CDServer.sqlite /app/res/CDServer.sqlite - ln -s /client/client/locale/locale.xml /app/locale/locale.xml + # need to create this file so the server knows the client is unpacked (see `dCommon/dClient/AssetManager.cpp`) touch /app/res/cdclient.fdb # need to iterate over entries in maps due to maps already being a directory with navmeshes/ in it diff --git a/tests/dGameTests/GameDependencies.cpp b/tests/dGameTests/GameDependencies.cpp index 8a572668..5ac3339a 100644 --- a/tests/dGameTests/GameDependencies.cpp +++ b/tests/dGameTests/GameDependencies.cpp @@ -7,7 +7,6 @@ namespace Game { dpWorld* physicsWorld; dChatFilter* chatFilter; dConfig* config; - dLocale* locale; std::mt19937 randomEngine; RakPeerInterface* chatServer; AssetManager* assetManager; From 09dfb6df3a53ce3fd3870351e8b3e6633e5a349b Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 27 Nov 2022 22:19:15 -0800 Subject: [PATCH 13/28] Address news feed showing up on every world transfer (#855) Addresses the news feed showing up on every world transfer --- dCommon/dEnums/dCommonVars.h | 1 + dGame/Character.cpp | 51 ++++++++++++++++++++++++++++-------- dGame/Character.h | 15 ++++++++++- dGame/UserManager.cpp | 1 + 4 files changed, 56 insertions(+), 12 deletions(-) diff --git a/dCommon/dEnums/dCommonVars.h b/dCommon/dEnums/dCommonVars.h index 74d83b11..f64d496f 100644 --- a/dCommon/dEnums/dCommonVars.h +++ b/dCommon/dEnums/dCommonVars.h @@ -560,6 +560,7 @@ enum ePlayerFlags { ENTER_BBB_FROM_PROPERTY_EDIT_CONFIRMATION_DIALOG = 64, AG_FIRST_COMBAT_COMPLETE = 65, AG_COMPLETE_BOB_MISSION = 66, + IS_NEWS_SCREEN_VISIBLE = 114, NJ_GARMADON_CINEMATIC_SEEN = 125, ELEPHANT_PET_3050 = 801, CAT_PET_3054 = 802, diff --git a/dGame/Character.cpp b/dGame/Character.cpp index 440c30c1..a9cffc65 100644 --- a/dGame/Character.cpp +++ b/dGame/Character.cpp @@ -264,14 +264,17 @@ void Character::DoQuickXMLDataParse() { if (flags) { auto* currentChild = flags->FirstChildElement(); while (currentChild) { - uint32_t index = 0; - uint64_t value = 0; const auto* temp = currentChild->Attribute("v"); + const auto* id = currentChild->Attribute("id"); + if (temp && id) { + uint32_t index = 0; + uint64_t value = 0; - index = std::stoul(currentChild->Attribute("id")); - value = std::stoull(temp); + index = std::stoul(id); + value = std::stoull(temp); - m_PlayerFlags.insert(std::make_pair(index, value)); + m_PlayerFlags.insert(std::make_pair(index, value)); + } currentChild = currentChild->NextSiblingElement(); } } @@ -351,6 +354,13 @@ void Character::SaveXMLToDatabase() { flags->LinkEndChild(f); } + // Prevents the news feed from showing up on world transfers + if (GetPlayerFlag(ePlayerFlags::IS_NEWS_SCREEN_VISIBLE)) { + auto* s = m_Doc->NewElement("s"); + s->SetAttribute("si", ePlayerFlags::IS_NEWS_SCREEN_VISIBLE); + flags->LinkEndChild(s); + } + SaveXmlRespawnCheckpoints(); //Call upon the entity to update our xmlDoc: @@ -361,6 +371,31 @@ void Character::SaveXMLToDatabase() { m_OurEntity->UpdateXMLDoc(m_Doc); + WriteToDatabase(); + + //For metrics, log the time it took to save: + auto end = std::chrono::system_clock::now(); + std::chrono::duration elapsed = end - start; + Game::logger->Log("Character", "Saved character to Database in: %fs", elapsed.count()); +} + +void Character::SetIsNewLogin() { + // If we dont have a flag element, then we cannot have a s element as a child of flag. + auto* flags = m_Doc->FirstChildElement("obj")->FirstChildElement("flag"); + if (!flags) return; + + auto* currentChild = flags->FirstChildElement(); + while (currentChild) { + if (currentChild->Attribute("si")) { + flags->DeleteChild(currentChild); + Game::logger->Log("Character", "Removed isLoggedIn flag from character %i, saving character to database", GetID()); + WriteToDatabase(); + } + currentChild = currentChild->NextSiblingElement(); + } +} + +void Character::WriteToDatabase() { //Dump our xml into m_XMLData: auto* printer = new tinyxml2::XMLPrinter(0, true, 0); m_Doc->Print(printer); @@ -372,12 +407,6 @@ void Character::SaveXMLToDatabase() { stmt->setUInt(2, m_ID); stmt->execute(); delete stmt; - - //For metrics, log the time it took to save: - auto end = std::chrono::system_clock::now(); - std::chrono::duration elapsed = end - start; - Game::logger->Log("Character", "Saved character to Database in: %fs", elapsed.count()); - delete printer; } diff --git a/dGame/Character.h b/dGame/Character.h index 52bff83d..07bde36c 100644 --- a/dGame/Character.h +++ b/dGame/Character.h @@ -23,6 +23,10 @@ public: Character(uint32_t id, User* parentUser); ~Character(); + /** + * Write the current m_Doc to the database for saving. + */ + void WriteToDatabase(); void SaveXMLToDatabase(); void UpdateFromDatabase(); @@ -32,6 +36,15 @@ public: const std::string& GetXMLData() const { return m_XMLData; } tinyxml2::XMLDocument* GetXMLDoc() const { return m_Doc; } + /** + * Out of abundance of safety and clarity of what this saves, this is its own function. + * + * Clears the s element from the flag element and saves the xml to the database. Used to prevent the news + * feed from showing up on world transfers. + * + */ + void SetIsNewLogin(); + /** * Gets the database ID of the character * @return the database ID of the character @@ -427,7 +440,7 @@ public: /** * @brief Get the flying state - * @return value of the flying state + * @return value of the flying state */ bool GetIsFlying() { return m_IsFlying; } diff --git a/dGame/UserManager.cpp b/dGame/UserManager.cpp index ddbad03a..6779a42c 100644 --- a/dGame/UserManager.cpp +++ b/dGame/UserManager.cpp @@ -227,6 +227,7 @@ void UserManager::RequestCharacterList(const SystemAddress& sysAddr) { while (res->next()) { LWOOBJID objID = res->getUInt64(1); Character* character = new Character(uint32_t(objID), u); + character->SetIsNewLogin(); chars.push_back(character); } } From 63460ea00d944ccb0c1ab7735010c90c028d1e84 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Wed, 30 Nov 2022 01:04:46 -0800 Subject: [PATCH 14/28] Fix bricks not creating new stacks (#860) Unintentionally, bricks were not creating new stacks if you tried to get another stack. This prevents some missions from being completed. This issue is now fixed --- dGame/dComponents/InventoryComponent.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 77f3f035..acf1ae6a 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -209,8 +209,10 @@ void InventoryComponent::AddItem( auto stack = static_cast(info.stackSize); + bool isBrick = inventoryType == eInventoryType::BRICKS || (stack == 0 && info.itemType == 1); + // info.itemType of 1 is item type brick - if (inventoryType == eInventoryType::BRICKS || (stack == 0 && info.itemType == 1)) { + if (isBrick) { stack = UINT32_MAX; } else if (stack == 0) { stack = 1; @@ -233,7 +235,7 @@ void InventoryComponent::AddItem( } // If we have some leftover and we aren't bricks, make a new stack - while (left > 0 && !(inventoryType == eInventoryType::BRICKS || (stack == 0 && info.itemType == 1))) { + while (left > 0 && (!isBrick || (isBrick && !existing))) { const auto size = std::min(left, stack); left -= size; From 2b9c014b86e8c5f8cd8a50577b8a2877563d4b42 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Fri, 2 Dec 2022 01:44:20 -0700 Subject: [PATCH 15/28] Quiet activity manager timer logs (#861) --- dScripts/ActivityManager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dScripts/ActivityManager.cpp b/dScripts/ActivityManager.cpp index 18d72311..36e85b11 100644 --- a/dScripts/ActivityManager.cpp +++ b/dScripts/ActivityManager.cpp @@ -124,7 +124,7 @@ void ActivityManager::ActivityTimerStart(Entity* self, const std::string& timerN auto* timer = new ActivityTimer{ timerName, updateInterval, stopTime }; activeTimers.push_back(timer); - Game::logger->Log("ActivityManager", "Starting timer '%s', %f, %f", timerName.c_str(), updateInterval, stopTime); + Game::logger->LogDebug("ActivityManager", "Starting timer '%s', %f, %f", timerName.c_str(), updateInterval, stopTime); self->AddTimer(GetPrefixedName(timer->name), timer->updateInterval); } @@ -205,10 +205,10 @@ void ActivityManager::OnTimerDone(Entity* self, std::string timerName) { activeTimers.erase(std::remove(activeTimers.begin(), activeTimers.end(), timer), activeTimers.end()); delete timer; - Game::logger->Log("ActivityManager", "Executing timer '%s'", activityTimerName.c_str()); + Game::logger->LogDebug("ActivityManager", "Executing timer '%s'", activityTimerName.c_str()); OnActivityTimerDone(self, activityTimerName); } else { - Game::logger->Log("ActivityManager", "Updating timer '%s'", activityTimerName.c_str()); + Game::logger->LogDebug("ActivityManager", "Updating timer '%s'", activityTimerName.c_str()); OnActivityTimerUpdate(self, timer->name, timer->stopTime - timer->runTime, timer->runTime); self->AddTimer(GetPrefixedName(timer->name), timer->updateInterval); } From d8945e9067fe56175259f943fa432d57c4da471c Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Fri, 2 Dec 2022 04:46:54 -0700 Subject: [PATCH 16/28] Add migration to make play_key_id nullable (#857) since there is an option not to use play_keys --- migrations/dlu/7_make_play_key_id_nullable.sql | 1 + 1 file changed, 1 insertion(+) create mode 100644 migrations/dlu/7_make_play_key_id_nullable.sql diff --git a/migrations/dlu/7_make_play_key_id_nullable.sql b/migrations/dlu/7_make_play_key_id_nullable.sql new file mode 100644 index 00000000..7491874f --- /dev/null +++ b/migrations/dlu/7_make_play_key_id_nullable.sql @@ -0,0 +1 @@ +ALTER TABLE account MODIFY play_key_id INT DEFAULT 0; From e1af528d9b7fa849d99264dcae2971bac305419a Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Fri, 2 Dec 2022 03:47:27 -0800 Subject: [PATCH 17/28] Add SlashCommand for spawngroup (#858) --- dGame/dUtilities/SlashCommandHandler.cpp | 51 ++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 2f4d9211..79e61bae 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -1264,6 +1264,57 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit EntityManager::Instance()->ConstructEntity(newEntity); } + if (chatCommand == "spawngroup" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER && args.size() >= 3) { + auto controllablePhysicsComponent = entity->GetComponent(); + if (!controllablePhysicsComponent) return; + + LOT lot{}; + uint32_t numberToSpawn{}; + float radiusToSpawnWithin{}; + + if (!GeneralUtils::TryParse(args[0], lot)) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid lot."); + return; + } + + if (!GeneralUtils::TryParse(args[1], numberToSpawn) && numberToSpawn > 0) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid number of enemies to spawn."); + return; + } + + // Must spawn within a radius of at least 0.0f + if (!GeneralUtils::TryParse(args[2], radiusToSpawnWithin) && radiusToSpawnWithin < 0.0f) { + ChatPackets::SendSystemMessage(sysAddr, u"Invalid radius to spawn within."); + return; + } + + EntityInfo info; + info.lot = lot; + info.spawner = nullptr; + info.spawnerID = entity->GetObjectID(); + info.spawnerNodeID = 0; + + auto playerPosition = controllablePhysicsComponent->GetPosition(); + while (numberToSpawn > 0) { + auto randomAngle = GeneralUtils::GenerateRandomNumber(0.0f, 2 * PI); + auto randomRadius = GeneralUtils::GenerateRandomNumber(0.0f, radiusToSpawnWithin); + + // Set the position to the generated random position plus the player position. This will + // spawn the entity in a circle around the player. As you get further from the player, the angle chosen will get less accurate. + info.pos = playerPosition + NiPoint3(cos(randomAngle) * randomRadius, 0.0f, sin(randomAngle) * randomRadius); + info.rot = NiQuaternion(); + + auto newEntity = EntityManager::Instance()->CreateEntity(info); + if (newEntity == nullptr) { + ChatPackets::SendSystemMessage(sysAddr, u"Failed to spawn entity."); + return; + } + + EntityManager::Instance()->ConstructEntity(newEntity); + numberToSpawn--; + } + } + if ((chatCommand == "giveuscore") && args.size() == 1 && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER) { int32_t uscore; From ab5adea24c7b603b58b2c57d39fc53a17dde9113 Mon Sep 17 00:00:00 2001 From: Wincent Holm Date: Sat, 3 Dec 2022 13:17:13 +0100 Subject: [PATCH 18/28] Move CDServer migration history table (#867) --- dDatabase/MigrationRunner.cpp | 37 +++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/dDatabase/MigrationRunner.cpp b/dDatabase/MigrationRunner.cpp index fe0f933a..31fb9148 100644 --- a/dDatabase/MigrationRunner.cpp +++ b/dDatabase/MigrationRunner.cpp @@ -94,6 +94,10 @@ void MigrationRunner::RunMigrations() { } void MigrationRunner::RunSQLiteMigrations() { + auto cdstmt = CDClientDatabase::CreatePreppedStmt("CREATE TABLE IF NOT EXISTS migration_history (name TEXT NOT NULL, date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP);"); + cdstmt.execQuery().finalize(); + cdstmt.finalize(); + auto* stmt = Database::CreatePreppedStmt("CREATE TABLE IF NOT EXISTS migration_history (name TEXT NOT NULL, date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP());"); stmt->execute(); delete stmt; @@ -103,13 +107,31 @@ void MigrationRunner::RunSQLiteMigrations() { if (migration.data.empty()) continue; + // Check if there is an entry in the migration history table on the cdclient database. + cdstmt = CDClientDatabase::CreatePreppedStmt("SELECT name FROM migration_history WHERE name = ?;"); + cdstmt.bind((int32_t) 1, migration.name.c_str()); + auto cdres = cdstmt.execQuery(); + bool doExit = !cdres.eof(); + cdres.finalize(); + cdstmt.finalize(); + + if (doExit) continue; + + // Check first if there is entry in the migration history table on the main database. stmt = Database::CreatePreppedStmt("SELECT name FROM migration_history WHERE name = ?;"); stmt->setString(1, migration.name.c_str()); auto* res = stmt->executeQuery(); - bool doExit = res->next(); + doExit = res->next(); delete res; delete stmt; - if (doExit) continue; + if (doExit) { + // Insert into cdclient database if there is an entry in the main database but not the cdclient database. + cdstmt = CDClientDatabase::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);"); + cdstmt.bind((int32_t) 1, migration.name.c_str()); + cdstmt.execQuery().finalize(); + cdstmt.finalize(); + continue; + } // Doing these 1 migration at a time since one takes a long time and some may think it is crashing. // This will at the least guarentee that the full migration needs to be run in order to be counted as "migrated". @@ -122,10 +144,13 @@ void MigrationRunner::RunSQLiteMigrations() { Game::logger->Log("MigrationRunner", "Encountered error running DML command: (%i) : %s", e.errorCode(), e.errorMessage()); } } - stmt = Database::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);"); - stmt->setString(1, migration.name); - stmt->execute(); - delete stmt; + + // Insert into cdclient database. + cdstmt = CDClientDatabase::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);"); + cdstmt.bind((int32_t) 1, migration.name.c_str()); + cdstmt.execQuery().finalize(); + cdstmt.finalize(); } + Game::logger->Log("MigrationRunner", "CDServer database is up to date."); } From de3e53de6cbcbf599d2c6f9fedbd14883ee6b0a1 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 4 Dec 2022 14:25:25 -0800 Subject: [PATCH 19/28] Fix Model Vault (#870) Allow pets, rockets and racecars to be stored in vault --- dGame/dComponents/InventoryComponent.cpp | 6 ++++-- dGame/dGameMessages/GameMessages.cpp | 14 +++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index acf1ae6a..8215664c 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -330,7 +330,9 @@ void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType in const auto lot = item->GetLot(); - if (item->GetConfig().empty() && !item->GetBound() || (item->GetBound() && item->GetInfo().isBOP)) { + const auto subkey = item->GetSubKey(); + + if (subkey == LWOOBJID_EMPTY && item->GetConfig().empty() && (!item->GetBound() || (item->GetBound() && item->GetInfo().isBOP))) { auto left = std::min(count, origin->GetLotCount(lot)); while (left > 0) { @@ -361,7 +363,7 @@ void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType in const auto delta = std::min(item->GetCount(), count); - AddItem(lot, delta, eLootSourceType::LOOT_SOURCE_NONE, inventory, config, LWOOBJID_EMPTY, showFlyingLot, isModMoveAndEquip, LWOOBJID_EMPTY, origin->GetType(), 0, item->GetBound(), preferredSlot); + AddItem(lot, delta, eLootSourceType::LOOT_SOURCE_NONE, inventory, config, LWOOBJID_EMPTY, showFlyingLot, isModMoveAndEquip, subkey, origin->GetType(), 0, item->GetBound(), preferredSlot); item->SetCount(item->GetCount() - delta, false, false); } diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 6639197c..fcc25fdc 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -4462,13 +4462,13 @@ void GameMessages::SendAddBuff(LWOOBJID& objectID, const LWOOBJID& casterID, uin // NT void GameMessages::HandleRequestMoveItemBetweenInventoryTypes(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { - bool bAllowPartial; + bool bAllowPartial{}; int32_t destSlot = -1; int32_t iStackCount = 1; eInventoryType invTypeDst = ITEMS; eInventoryType invTypeSrc = ITEMS; LWOOBJID itemID = LWOOBJID_EMPTY; - bool showFlyingLoot; + bool showFlyingLoot{}; LWOOBJID subkey = LWOOBJID_EMPTY; LOT itemLOT = 0; @@ -4492,12 +4492,12 @@ void GameMessages::HandleRequestMoveItemBetweenInventoryTypes(RakNet::BitStream* if (itemID != LWOOBJID_EMPTY) { auto* item = inventoryComponent->FindItemById(itemID); - if (item == nullptr) { - return; - } + if (!item) return; - if (inventoryComponent->IsPet(item->GetSubKey()) || !item->GetConfig().empty()) { - return; + // Despawn the pet if we are moving that pet to the vault. + auto* petComponent = PetComponent::GetActivePet(entity->GetObjectID()); + if (petComponent && petComponent->GetDatabaseId() == item->GetSubKey()) { + inventoryComponent->DespawnPet(); } inventoryComponent->MoveItemToInventory(item, invTypeDst, iStackCount, showFlyingLoot, false, false, destSlot); From e8ba3357e8c28f22136babdcd6a6a9e489fc520c Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 4 Dec 2022 14:25:58 -0800 Subject: [PATCH 20/28] Add support to reload the config (#868) --- dCommon/dConfig.cpp | 56 ++++++++----------- dCommon/dConfig.h | 24 ++++++-- .../dComponents/ScriptedActivityComponent.cpp | 18 +++++- dGame/dComponents/ScriptedActivityComponent.h | 12 ++++ dGame/dUtilities/SlashCommandHandler.cpp | 15 +++++ dMasterServer/MasterServer.cpp | 29 +++++----- dPhysics/dpGrid.cpp | 2 + dPhysics/dpGrid.h | 10 ++++ dPhysics/dpWorld.cpp | 44 +++++++++++---- dPhysics/dpWorld.h | 4 +- dWorldServer/WorldServer.cpp | 2 - 11 files changed, 150 insertions(+), 66 deletions(-) diff --git a/dCommon/dConfig.cpp b/dCommon/dConfig.cpp index 1bc940e8..f09a44c1 100644 --- a/dCommon/dConfig.cpp +++ b/dCommon/dConfig.cpp @@ -1,61 +1,53 @@ #include "dConfig.h" + #include + #include "BinaryPathFinder.h" +#include "GeneralUtils.h" dConfig::dConfig(const std::string& filepath) { - m_EmptyString = ""; - std::ifstream in(BinaryPathFinder::GetBinaryDir() / filepath); + m_ConfigFilePath = filepath; + LoadConfig(); +} + +void dConfig::LoadConfig() { + std::ifstream in(BinaryPathFinder::GetBinaryDir() / m_ConfigFilePath); if (!in.good()) return; - std::string line; + std::string line{}; while (std::getline(in, line)) { - if (line.length() > 0) { - if (line[0] != '#') ProcessLine(line); - } + if (!line.empty() && line.front() != '#') ProcessLine(line); } std::ifstream sharedConfig(BinaryPathFinder::GetBinaryDir() / "sharedconfig.ini", std::ios::in); if (!sharedConfig.good()) return; + line.clear(); while (std::getline(sharedConfig, line)) { - if (line.length() > 0) { - if (line[0] != '#') ProcessLine(line); - } + if (!line.empty() && line.front() != '#') ProcessLine(line); } } -dConfig::~dConfig(void) { +void dConfig::ReloadConfig() { + this->m_ConfigValues.clear(); + LoadConfig(); } const std::string& dConfig::GetValue(std::string key) { - for (size_t i = 0; i < m_Keys.size(); ++i) { - if (m_Keys[i] == key) return m_Values[i]; - } - - return m_EmptyString; + return this->m_ConfigValues[key]; } void dConfig::ProcessLine(const std::string& line) { - std::stringstream ss(line); - std::string segment; - std::vector seglist; + auto splitLine = GeneralUtils::SplitString(line, '='); - while (std::getline(ss, segment, '=')) { - seglist.push_back(segment); - } - - if (seglist.size() != 2) return; + if (splitLine.size() != 2) return; //Make sure that on Linux, we remove special characters: - if (!seglist[1].empty() && seglist[1][seglist[1].size() - 1] == '\r') - seglist[1].erase(seglist[1].size() - 1); + auto& key = splitLine.at(0); + auto& value = splitLine.at(1); + if (!value.empty() && value.at(value.size() - 1) == '\r') value.erase(value.size() - 1); - for (const auto& key : m_Keys) { - if (seglist[0] == key) { - return; // first loaded key is preferred due to loading shared config secondarily - } - } + if (this->m_ConfigValues.find(key) != this->m_ConfigValues.end()) return; - m_Keys.push_back(seglist[0]); - m_Values.push_back(seglist[1]); + this->m_ConfigValues.insert(std::make_pair(key, value)); } diff --git a/dCommon/dConfig.h b/dCommon/dConfig.h index 7764fa54..a6dd5df7 100644 --- a/dCommon/dConfig.h +++ b/dCommon/dConfig.h @@ -1,20 +1,34 @@ #pragma once #include +#include #include -#include class dConfig { public: dConfig(const std::string& filepath); - ~dConfig(void); + /** + * Gets the specified key from the config. Returns an empty string if the value is not found. + * + * @param key Key to find + * @return The keys value in the config + */ const std::string& GetValue(std::string key); + /** + * Loads the config from a file + */ + void LoadConfig(); + + /** + * Reloads the config file to reset values + */ + void ReloadConfig(); + private: void ProcessLine(const std::string& line); private: - std::vector m_Keys; - std::vector m_Values; - std::string m_EmptyString; + std::map m_ConfigValues; + std::string m_ConfigFilePath; }; diff --git a/dGame/dComponents/ScriptedActivityComponent.cpp b/dGame/dComponents/ScriptedActivityComponent.cpp index 4b5ec41d..f6d50d66 100644 --- a/dGame/dComponents/ScriptedActivityComponent.cpp +++ b/dGame/dComponents/ScriptedActivityComponent.cpp @@ -19,8 +19,9 @@ #include "DestroyableComponent.h" ScriptedActivityComponent::ScriptedActivityComponent(Entity* parent, int activityID) : Component(parent) { + m_ActivityID = activityID; CDActivitiesTable* activitiesTable = CDClientManager::Instance()->GetTable("Activities"); - std::vector activities = activitiesTable->Query([=](CDActivities entry) {return (entry.ActivityID == activityID); }); + std::vector activities = activitiesTable->Query([=](CDActivities entry) {return (entry.ActivityID == m_ActivityID); }); for (CDActivities activity : activities) { m_ActivityInfo = activity; @@ -88,6 +89,21 @@ void ScriptedActivityComponent::Serialize(RakNet::BitStream* outBitStream, bool } } +void ScriptedActivityComponent::ReloadConfig() { + CDActivitiesTable* activitiesTable = CDClientManager::Instance()->GetTable("Activities"); + std::vector activities = activitiesTable->Query([=](CDActivities entry) {return (entry.ActivityID == m_ActivityID); }); + for (auto activity : activities) { + auto mapID = m_ActivityInfo.instanceMapID; + if ((mapID == 1203 || mapID == 1261 || mapID == 1303 || mapID == 1403) && Game::config->GetValue("solo_racing") == "1") { + m_ActivityInfo.minTeamSize = 1; + m_ActivityInfo.minTeams = 1; + } else { + m_ActivityInfo.minTeamSize = activity.minTeamSize; + m_ActivityInfo.minTeams = activity.minTeams; + } + } +} + void ScriptedActivityComponent::HandleMessageBoxResponse(Entity* player, const std::string& id) { if (m_ActivityInfo.ActivityID == 103) { return; diff --git a/dGame/dComponents/ScriptedActivityComponent.h b/dGame/dComponents/ScriptedActivityComponent.h index 66ca799f..8bb17093 100644 --- a/dGame/dComponents/ScriptedActivityComponent.h +++ b/dGame/dComponents/ScriptedActivityComponent.h @@ -276,6 +276,12 @@ public: */ ActivityInstance* GetInstance(const LWOOBJID playerID); + /** + * @brief Reloads the config settings for this component + * + */ + void ReloadConfig(); + /** * Removes all the instances */ @@ -361,6 +367,12 @@ private: * LMIs for team sizes */ std::unordered_map m_ActivityLootMatrices; + + /** + * The activity id + * + */ + int32_t m_ActivityID; }; #endif // SCRIPTEDACTIVITYCOMPONENT_H diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 79e61bae..60862c1a 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -65,6 +65,7 @@ #include "LevelProgressionComponent.h" #include "AssetManager.h" #include "BinaryPathFinder.h" +#include "dConfig.h" void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entity* entity, const SystemAddress& sysAddr) { std::string chatCommand; @@ -1766,6 +1767,20 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit return; } + if (chatCommand == "reloadconfig" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_DEVELOPER) { + Game::config->ReloadConfig(); + VanityUtilities::SpawnVanity(); + dpWorld::Instance().Reload(); + auto entities = EntityManager::Instance()->GetEntitiesByComponent(COMPONENT_TYPE_SCRIPTED_ACTIVITY); + for (auto entity : entities) { + auto* scriptedActivityComponent = entity->GetComponent(); + if (!scriptedActivityComponent) continue; + + scriptedActivityComponent->ReloadConfig(); + } + ChatPackets::SendSystemMessage(sysAddr, u"Successfully reloaded config for world!"); + } + if (chatCommand == "rollloot" && entity->GetGMLevel() >= GAME_MASTER_LEVEL_OPERATOR && args.size() >= 3) { uint32_t lootMatrixIndex = 0; uint32_t targetLot = 0; diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 34e2f71a..0a387d8e 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -82,17 +82,15 @@ int main(int argc, char** argv) { Game::logger->Log("MasterServer", "Version: %i.%i", PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR); Game::logger->Log("MasterServer", "Compiled on: %s", __TIMESTAMP__); - //Read our config: - dConfig config("masterconfig.ini"); - Game::config = &config; - Game::logger->SetLogToConsole(bool(std::stoi(config.GetValue("log_to_console")))); - Game::logger->SetLogDebugStatements(config.GetValue("log_debug_statements") == "1"); + Game::config = new dConfig("masterconfig.ini"); + Game::logger->SetLogToConsole(bool(std::stoi(Game::config->GetValue("log_to_console")))); + Game::logger->SetLogDebugStatements(Game::config->GetValue("log_debug_statements") == "1"); //Connect to the MySQL Database - std::string mysql_host = config.GetValue("mysql_host"); - std::string mysql_database = config.GetValue("mysql_database"); - std::string mysql_username = config.GetValue("mysql_username"); - std::string mysql_password = config.GetValue("mysql_password"); + std::string mysql_host = Game::config->GetValue("mysql_host"); + std::string mysql_database = Game::config->GetValue("mysql_database"); + std::string mysql_username = Game::config->GetValue("mysql_username"); + std::string mysql_password = Game::config->GetValue("mysql_password"); try { Database::Connect(mysql_host, mysql_database, mysql_username, mysql_password); @@ -103,7 +101,7 @@ int main(int argc, char** argv) { } try { - std::string clientPathStr = config.GetValue("client_location"); + std::string clientPathStr = Game::config->GetValue("client_location"); if (clientPathStr.empty()) clientPathStr = "./res"; std::filesystem::path clientPath = std::filesystem::path(clientPathStr); if (clientPath.is_relative()) { @@ -223,16 +221,16 @@ int main(int argc, char** argv) { int maxClients = 999; int ourPort = 1000; - if (config.GetValue("max_clients") != "") maxClients = std::stoi(config.GetValue("max_clients")); - if (config.GetValue("port") != "") ourPort = std::stoi(config.GetValue("port")); + if (Game::config->GetValue("max_clients") != "") maxClients = std::stoi(Game::config->GetValue("max_clients")); + if (Game::config->GetValue("port") != "") ourPort = std::stoi(Game::config->GetValue("port")); - Game::server = new dServer(config.GetValue("external_ip"), ourPort, 0, maxClients, true, false, Game::logger, "", 0, ServerType::Master); + Game::server = new dServer(Game::config->GetValue("external_ip"), ourPort, 0, maxClients, true, false, Game::logger, "", 0, ServerType::Master); //Query for the database for a server labeled "master" auto* masterLookupStatement = Database::CreatePreppedStmt("SELECT id FROM `servers` WHERE `name` = 'master'"); auto* result = masterLookupStatement->executeQuery(); - auto master_server_ip = config.GetValue("master_ip"); + auto master_server_ip = Game::config->GetValue("master_ip"); if (master_server_ip == "") { master_server_ip = Game::server->GetIP(); @@ -260,7 +258,7 @@ int main(int argc, char** argv) { Game::im = new InstanceManager(Game::logger, Game::server->GetIP()); //Depending on the config, start up servers: - if (config.GetValue("prestart_servers") != "" && config.GetValue("prestart_servers") == "1") { + if (Game::config->GetValue("prestart_servers") != "" && Game::config->GetValue("prestart_servers") == "1") { StartChatServer(); Game::im->GetInstance(0, false, 0)->SetIsReady(true); @@ -843,6 +841,7 @@ void ShutdownSequence() { int FinalizeShutdown() { //Delete our objects here: Database::Destroy("MasterServer"); + if (Game::config) delete Game::config; if (Game::im) delete Game::im; if (Game::server) delete Game::server; if (Game::logger) delete Game::logger; diff --git a/dPhysics/dpGrid.cpp b/dPhysics/dpGrid.cpp index 6e44ade9..b4fe385e 100644 --- a/dPhysics/dpGrid.cpp +++ b/dPhysics/dpGrid.cpp @@ -6,6 +6,7 @@ dpGrid::dpGrid(int numCells, int cellSize) { NUM_CELLS = numCells; CELL_SIZE = cellSize; + m_DeleteGrid = true; //dumb method but i can't be bothered @@ -23,6 +24,7 @@ dpGrid::dpGrid(int numCells, int cellSize) { } dpGrid::~dpGrid() { + if (!this->m_DeleteGrid) return; for (auto& x : m_Cells) { //x for (auto& y : x) { //y for (auto en : y) { diff --git a/dPhysics/dpGrid.h b/dPhysics/dpGrid.h index a10f165e..229e7449 100644 --- a/dPhysics/dpGrid.h +++ b/dPhysics/dpGrid.h @@ -23,6 +23,15 @@ public: void Update(float deltaTime); + /** + * Sets the delete grid parameter to value. When false, the grid will not clean up memory. + * + * @param value Whether or not to delete entities on deletion of the grid. + */ + void SetDeleteGrid(bool value) { this->m_DeleteGrid = value; }; + + std::vector>> GetCells() { return this->m_Cells; }; + private: void HandleEntity(dpEntity* entity, dpEntity* other); void HandleCell(int x, int z, float deltaTime); @@ -31,4 +40,5 @@ private: //cells on X, cells on Y for that X, then another vector that contains the entities within that cell. std::vector>> m_Cells; std::map m_GargantuanObjects; + bool m_DeleteGrid = true; }; diff --git a/dPhysics/dpWorld.cpp b/dPhysics/dpWorld.cpp index 510da518..70fbfa3a 100644 --- a/dPhysics/dpWorld.cpp +++ b/dPhysics/dpWorld.cpp @@ -9,7 +9,7 @@ #include "dLogger.h" #include "dConfig.h" -void dpWorld::Initialize(unsigned int zoneID) { +void dpWorld::Initialize(unsigned int zoneID, bool generateNewNavMesh) { phys_sp_tilecount = std::atoi(Game::config->GetValue("phys_sp_tilecount").c_str()); phys_sp_tilesize = std::atoi(Game::config->GetValue("phys_sp_tilesize").c_str()); @@ -21,13 +21,37 @@ void dpWorld::Initialize(unsigned int zoneID) { m_Grid = new dpGrid(phys_sp_tilecount, phys_sp_tilesize); } - m_NavMesh = new dNavMesh(zoneID); + if (generateNewNavMesh) m_NavMesh = new dNavMesh(zoneID); Game::logger->Log("dpWorld", "Physics world initialized!"); + m_ZoneID = zoneID; +} + +void dpWorld::Reload() { + if (m_Grid) { + m_Grid->SetDeleteGrid(false); + auto oldGridCells = m_Grid->GetCells(); + delete m_Grid; + m_Grid = nullptr; + + Initialize(m_ZoneID, false); + for (auto column : oldGridCells) { + for (auto row : column) { + for (auto entity : row) { + AddEntity(entity); + } + } + } + Game::logger->Log("dpWorld", "Successfully reloaded physics world!"); + } else { + Game::logger->Log("dpWorld", "No physics world to reload!"); + } } dpWorld::~dpWorld() { if (m_Grid) { + // Triple check this is true + m_Grid->SetDeleteGrid(true); delete m_Grid; m_Grid = nullptr; } @@ -103,14 +127,14 @@ bool dpWorld::ShouldUseSP(unsigned int zoneID) { // Only large maps should be added as tiling likely makes little difference on small maps. switch (zoneID) { - case 1100: // Avant Gardens - case 1200: // Nimbus Station - case 1300: // Gnarled Forest - case 1400: // Forbidden Valley - case 1800: // Crux Prime - case 1900: // Nexus Tower - case 2000: // Ninjago - return true; + case 1100: // Avant Gardens + case 1200: // Nimbus Station + case 1300: // Gnarled Forest + case 1400: // Forbidden Valley + case 1800: // Crux Prime + case 1900: // Nexus Tower + case 2000: // Ninjago + return true; } return false; diff --git a/dPhysics/dpWorld.h b/dPhysics/dpWorld.h index 45e550cb..d48435d0 100644 --- a/dPhysics/dpWorld.h +++ b/dPhysics/dpWorld.h @@ -19,7 +19,8 @@ class dpGrid; class dpWorld : public Singleton { public: - void Initialize(unsigned int zoneID); + void Initialize(unsigned int zoneID, bool generateNewNavMesh = true); + void Reload(); ~dpWorld(); @@ -43,4 +44,5 @@ private: std::vector m_DynamicEntites; dNavMesh* m_NavMesh = nullptr; + uint32_t m_ZoneID = 0; }; diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index bbba1166..b4be7511 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -250,7 +250,6 @@ int main(int argc, char** argv) { //Load our level: if (zoneID != 0) { dpWorld::Instance().Initialize(zoneID); - Game::physicsWorld = &dpWorld::Instance(); //just in case some old code references it dZoneManager::Instance()->Initialize(LWOZONEID(zoneID, instanceID, cloneID)); g_CloneID = cloneID; @@ -1281,7 +1280,6 @@ void WorldShutdownSequence() { void FinalizeShutdown() { //Delete our objects here: - if (Game::physicsWorld) Game::physicsWorld = nullptr; if (Game::zoneManager) delete Game::zoneManager; Game::logger->Log("WorldServer", "Shutdown complete, zone (%i), instance (%i)", Game::server->GetZoneID(), instanceID); From 2ba3103a0c69252976f5114c67197be38753fabb Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 5 Dec 2022 00:57:58 -0800 Subject: [PATCH 21/28] Implement FDB to SQLite (#872) --- dCommon/CMakeLists.txt | 1 + dCommon/FdbToSqlite.cpp | 248 +++++++++++++++++++++++++++++++ dCommon/FdbToSqlite.h | 49 ++++++ dCommon/dEnums/eSqliteDataType.h | 16 ++ dDatabase/MigrationRunner.cpp | 2 + dMasterServer/MasterServer.cpp | 14 +- 6 files changed, 319 insertions(+), 11 deletions(-) create mode 100644 dCommon/FdbToSqlite.cpp create mode 100644 dCommon/FdbToSqlite.h create mode 100644 dCommon/dEnums/eSqliteDataType.h diff --git a/dCommon/CMakeLists.txt b/dCommon/CMakeLists.txt index 8d02186b..549acfb2 100644 --- a/dCommon/CMakeLists.txt +++ b/dCommon/CMakeLists.txt @@ -16,6 +16,7 @@ set(DCOMMON_SOURCES "AMFFormat.cpp" "ZCompression.cpp" "BrickByBrickFix.cpp" "BinaryPathFinder.cpp" + "FdbToSqlite.cpp" ) add_subdirectory(dClient) diff --git a/dCommon/FdbToSqlite.cpp b/dCommon/FdbToSqlite.cpp new file mode 100644 index 00000000..d98d4962 --- /dev/null +++ b/dCommon/FdbToSqlite.cpp @@ -0,0 +1,248 @@ +#include "FdbToSqlite.h" + +#include +#include +#include +#include + +#include "BinaryIO.h" +#include "CDClientDatabase.h" +#include "GeneralUtils.h" +#include "Game.h" +#include "dLogger.h" + +#include "eSqliteDataType.h" + +std::map FdbToSqlite::Convert::sqliteType = { + { eSqliteDataType::NONE, "none"}, + { eSqliteDataType::INT32, "int32"}, + { eSqliteDataType::REAL, "real"}, + { eSqliteDataType::TEXT_4, "text_4"}, + { eSqliteDataType::INT_BOOL, "int_bool"}, + { eSqliteDataType::INT64, "int64"}, + { eSqliteDataType::TEXT_8, "text_8"} +}; + +FdbToSqlite::Convert::Convert(std::string basePath) { + this->basePath = basePath; +} + +bool FdbToSqlite::Convert::ConvertDatabase() { + fdb.open(basePath + "/cdclient.fdb", std::ios::binary); + + try { + CDClientDatabase::Connect(basePath + "/CDServer.sqlite"); + + CDClientDatabase::ExecuteQuery("BEGIN TRANSACTION;"); + + int32_t numberOfTables = ReadInt32(); + ReadTables(numberOfTables); + + CDClientDatabase::ExecuteQuery("COMMIT;"); + } catch (CppSQLite3Exception& e) { + Game::logger->Log("FdbToSqlite", "Encountered error %s converting FDB to SQLite", e.errorMessage()); + return false; + } + + fdb.close(); + return true; +} + +int32_t FdbToSqlite::Convert::ReadInt32() { + uint32_t numberOfTables{}; + BinaryIO::BinaryRead(fdb, numberOfTables); + return numberOfTables; +} + +int64_t FdbToSqlite::Convert::ReadInt64() { + int32_t prevPosition = SeekPointer(); + + int64_t value{}; + BinaryIO::BinaryRead(fdb, value); + + fdb.seekg(prevPosition); + return value; +} + +std::string FdbToSqlite::Convert::ReadString() { + int32_t prevPosition = SeekPointer(); + + auto readString = BinaryIO::ReadString(fdb); + + fdb.seekg(prevPosition); + return readString; +} + +int32_t FdbToSqlite::Convert::SeekPointer() { + int32_t position{}; + BinaryIO::BinaryRead(fdb, position); + int32_t prevPosition = fdb.tellg(); + fdb.seekg(position); + return prevPosition; +} + +std::string FdbToSqlite::Convert::ReadColumnHeader() { + int32_t prevPosition = SeekPointer(); + + int32_t numberOfColumns = ReadInt32(); + std::string tableName = ReadString(); + + auto columns = ReadColumns(numberOfColumns); + std::string newTable = "CREATE TABLE IF NOT EXISTS '" + tableName + "' (" + columns + ");"; + CDClientDatabase::ExecuteDML(newTable); + + fdb.seekg(prevPosition); + + return tableName; +} + +void FdbToSqlite::Convert::ReadTables(int32_t& numberOfTables) { + int32_t prevPosition = SeekPointer(); + + for (int32_t i = 0; i < numberOfTables; i++) { + auto columnHeader = ReadColumnHeader(); + ReadRowHeader(columnHeader); + } + + fdb.seekg(prevPosition); +} + +std::string FdbToSqlite::Convert::ReadColumns(int32_t& numberOfColumns) { + std::stringstream columnsToCreate; + int32_t prevPosition = SeekPointer(); + + std::string name{}; + eSqliteDataType dataType{}; + for (int32_t i = 0; i < numberOfColumns; i++) { + if (i != 0) columnsToCreate << ", "; + dataType = static_cast(ReadInt32()); + name = ReadString(); + columnsToCreate << "'" << name << "' " << FdbToSqlite::Convert::sqliteType[dataType]; + } + + fdb.seekg(prevPosition); + return columnsToCreate.str(); +} + +void FdbToSqlite::Convert::ReadRowHeader(std::string& tableName) { + int32_t prevPosition = SeekPointer(); + + int32_t numberOfAllocatedRows = ReadInt32(); + if (numberOfAllocatedRows != 0) assert((numberOfAllocatedRows & (numberOfAllocatedRows - 1)) == 0); // assert power of 2 allocation size + ReadRows(numberOfAllocatedRows, tableName); + + fdb.seekg(prevPosition); +} + +void FdbToSqlite::Convert::ReadRows(int32_t& numberOfAllocatedRows, std::string& tableName) { + int32_t prevPosition = SeekPointer(); + + int32_t rowid = 0; + for (int32_t row = 0; row < numberOfAllocatedRows; row++) { + int32_t rowPointer = ReadInt32(); + if (rowPointer == -1) rowid++; + else ReadRow(rowid, rowPointer, tableName); + } + + fdb.seekg(prevPosition); +} + +void FdbToSqlite::Convert::ReadRow(int32_t& rowid, int32_t& position, std::string& tableName) { + int32_t prevPosition = fdb.tellg(); + fdb.seekg(position); + + while (true) { + ReadRowInfo(tableName); + int32_t linked = ReadInt32(); + + rowid += 1; + + if (linked == -1) break; + + fdb.seekg(linked); + } + + fdb.seekg(prevPosition); +} + +void FdbToSqlite::Convert::ReadRowInfo(std::string& tableName) { + int32_t prevPosition = SeekPointer(); + + int32_t numberOfColumns = ReadInt32(); + ReadRowValues(numberOfColumns, tableName); + + fdb.seekg(prevPosition); +} + +void FdbToSqlite::Convert::ReadRowValues(int32_t& numberOfColumns, std::string& tableName) { + int32_t prevPosition = SeekPointer(); + + int32_t emptyValue{}; + int32_t intValue{}; + float_t floatValue{}; + std::string stringValue{}; + int32_t boolValue{}; + int64_t int64Value{}; + bool insertedFirstEntry = false; + std::stringstream insertedRow; + insertedRow << "INSERT INTO " << tableName << " values ("; + + for (int32_t i = 0; i < numberOfColumns; i++) { + if (i != 0) insertedRow << ", "; // Only append comma and space after first entry in row. + switch (static_cast(ReadInt32())) { + case eSqliteDataType::NONE: + BinaryIO::BinaryRead(fdb, emptyValue); + assert(emptyValue == 0); + insertedRow << "\"\""; + break; + + case eSqliteDataType::INT32: + intValue = ReadInt32(); + insertedRow << intValue; + break; + + case eSqliteDataType::REAL: + BinaryIO::BinaryRead(fdb, floatValue); + insertedRow << std::fixed << std::setprecision(34) << floatValue; // maximum precision of floating point number + break; + + case eSqliteDataType::TEXT_4: + case eSqliteDataType::TEXT_8: { + stringValue = ReadString(); + size_t position = 0; + + // Need to escape quote with a double of ". + while (position < stringValue.size()) { + if (stringValue.at(position) == '\"') { + stringValue.insert(position, "\""); + position++; + } + position++; + } + insertedRow << "\"" << stringValue << "\""; + break; + } + + case eSqliteDataType::INT_BOOL: + BinaryIO::BinaryRead(fdb, boolValue); + insertedRow << static_cast(boolValue); + break; + + case eSqliteDataType::INT64: + int64Value = ReadInt64(); + insertedRow << std::to_string(int64Value); + break; + + default: + throw std::invalid_argument("Unsupported SQLite type encountered."); + break; + + } + } + + insertedRow << ");"; + + auto copiedString = insertedRow.str(); + CDClientDatabase::ExecuteDML(copiedString); + fdb.seekg(prevPosition); +} diff --git a/dCommon/FdbToSqlite.h b/dCommon/FdbToSqlite.h new file mode 100644 index 00000000..a9611220 --- /dev/null +++ b/dCommon/FdbToSqlite.h @@ -0,0 +1,49 @@ +#ifndef __FDBTOSQLITE__H__ +#define __FDBTOSQLITE__H__ + +#pragma once + +#include +#include +#include + +enum class eSqliteDataType : int32_t; + +namespace FdbToSqlite { + class Convert { + public: + Convert(std::string inputFile); + + bool ConvertDatabase(); + + int32_t ReadInt32(); + + int64_t ReadInt64(); + + std::string ReadString(); + + int32_t SeekPointer(); + + std::string ReadColumnHeader(); + + void ReadTables(int32_t& numberOfTables); + + std::string ReadColumns(int32_t& numberOfColumns); + + void ReadRowHeader(std::string& tableName); + + void ReadRows(int32_t& numberOfAllocatedRows, std::string& tableName); + + void ReadRow(int32_t& rowid, int32_t& position, std::string& tableName); + + void ReadRowInfo(std::string& tableName); + + void ReadRowValues(int32_t& numberOfColumns, std::string& tableName); + private: + static std::map sqliteType; + std::string basePath{}; + std::ifstream fdb{}; + }; // class FdbToSqlite +}; //! namespace FdbToSqlite + +#endif //!__FDBTOSQLITE__H__ diff --git a/dCommon/dEnums/eSqliteDataType.h b/dCommon/dEnums/eSqliteDataType.h new file mode 100644 index 00000000..26e1233b --- /dev/null +++ b/dCommon/dEnums/eSqliteDataType.h @@ -0,0 +1,16 @@ +#ifndef __ESQLITEDATATYPE__H__ +#define __ESQLITEDATATYPE__H__ + +#include + +enum class eSqliteDataType : int32_t { + NONE = 0, + INT32, + REAL = 3, + TEXT_4, + INT_BOOL, + INT64, + TEXT_8 = 8 +}; + +#endif //!__ESQLITEDATATYPE__H__ diff --git a/dDatabase/MigrationRunner.cpp b/dDatabase/MigrationRunner.cpp index 31fb9148..d39201b2 100644 --- a/dDatabase/MigrationRunner.cpp +++ b/dDatabase/MigrationRunner.cpp @@ -136,6 +136,7 @@ void MigrationRunner::RunSQLiteMigrations() { // Doing these 1 migration at a time since one takes a long time and some may think it is crashing. // This will at the least guarentee that the full migration needs to be run in order to be counted as "migrated". Game::logger->Log("MigrationRunner", "Executing migration: %s. This may take a while. Do not shut down server.", migration.name.c_str()); + CDClientDatabase::ExecuteQuery("BEGIN TRANSACTION;"); for (const auto& dml : GeneralUtils::SplitString(migration.data, ';')) { if (dml.empty()) continue; try { @@ -150,6 +151,7 @@ void MigrationRunner::RunSQLiteMigrations() { cdstmt.bind((int32_t) 1, migration.name.c_str()); cdstmt.execQuery().finalize(); cdstmt.finalize(); + CDClientDatabase::ExecuteQuery("COMMIT;"); } Game::logger->Log("MigrationRunner", "CDServer database is up to date."); diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 0a387d8e..0329d9a0 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -40,6 +40,7 @@ #include "ObjectIDManager.h" #include "PacketUtils.h" #include "dMessageIdentifiers.h" +#include "FdbToSqlite.h" namespace Game { dLogger* logger; @@ -126,18 +127,9 @@ int main(int argc, char** argv) { return EXIT_FAILURE; } - Game::logger->Log("WorldServer", "Found cdclient.fdb. Clearing cdserver migration_history then copying and converting to sqlite."); - auto stmt = Database::CreatePreppedStmt(R"#(DELETE FROM migration_history WHERE name LIKE "%cdserver%";)#"); - stmt->executeUpdate(); - delete stmt; + Game::logger->Log("WorldServer", "Found cdclient.fdb. Converting to SQLite"); - std::string res = "python3 " - + (BinaryPathFinder::GetBinaryDir() / "../thirdparty/docker-utils/utils/fdb_to_sqlite.py").string() - + " --sqlite_path " + (Game::assetManager->GetResPath() / "CDServer.sqlite").string() - + " " + (Game::assetManager->GetResPath() / "cdclient.fdb").string(); - - int result = system(res.c_str()); - if (result != 0) { + if (FdbToSqlite::Convert(Game::assetManager->GetResPath().string()).ConvertDatabase() == false) { Game::logger->Log("MasterServer", "Failed to convert fdb to sqlite"); return EXIT_FAILURE; } From 0a616f891fd4e09bd52c5d68f56099cf62ea7954 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 5 Dec 2022 07:04:59 -0800 Subject: [PATCH 22/28] Change File Finder (#873) --- dCommon/GeneralUtils.cpp | 65 +++++++++++++---------------------- dCommon/GeneralUtils.h | 3 +- dDatabase/MigrationRunner.cpp | 4 +-- 3 files changed, 27 insertions(+), 45 deletions(-) diff --git a/dCommon/GeneralUtils.cpp b/dCommon/GeneralUtils.cpp index 24ea72a0..54ef5661 100644 --- a/dCommon/GeneralUtils.cpp +++ b/dCommon/GeneralUtils.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include template inline size_t MinSize(size_t size, const std::basic_string_view& string) { @@ -290,51 +292,30 @@ std::u16string GeneralUtils::ReadWString(RakNet::BitStream* inStream) { return string; } -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include - -std::vector GeneralUtils::GetFileNamesFromFolder(const std::string& folder) { - std::vector names; - std::string search_path = folder + "/*.*"; - WIN32_FIND_DATA fd; - HANDLE hFind = ::FindFirstFile(search_path.c_str(), &fd); - if (hFind != INVALID_HANDLE_VALUE) { - do { - if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { - names.push_back(fd.cFileName); - } - } while (::FindNextFile(hFind, &fd)); - ::FindClose(hFind); - } - return names; -} -#else -#include -#include -#include -#include -#include -#include - -std::vector GeneralUtils::GetFileNamesFromFolder(const std::string& folder) { - std::vector names; - struct dirent* entry; - DIR* dir = opendir(folder.c_str()); - if (dir == NULL) { - return names; +std::vector GeneralUtils::GetSqlFileNamesFromFolder(const std::string& folder) { + // Because we dont know how large the initial number before the first _ is we need to make it a map like so. + std::map filenames{}; + for (auto& t : std::filesystem::directory_iterator(folder)) { + auto filename = t.path().filename().string(); + auto index = std::stoi(GeneralUtils::SplitString(filename, '_').at(0)); + filenames.insert(std::make_pair(index, filename)); } - while ((entry = readdir(dir)) != NULL) { - std::string value(entry->d_name, strlen(entry->d_name)); - if (value == "." || value == "..") { - continue; + // Now sort the map by the oldest migration. + std::vector sortedFiles{}; + auto fileIterator = filenames.begin(); + std::map::iterator oldest = filenames.begin(); + while (!filenames.empty()) { + if (fileIterator == filenames.end()) { + sortedFiles.push_back(oldest->second); + filenames.erase(oldest); + fileIterator = filenames.begin(); + oldest = filenames.begin(); + continue; } - names.push_back(value); + if (oldest->first > fileIterator->first) oldest = fileIterator; + fileIterator++; } - closedir(dir); - - return names; + return sortedFiles; } -#endif diff --git a/dCommon/GeneralUtils.h b/dCommon/GeneralUtils.h index 898616d2..af7c7012 100644 --- a/dCommon/GeneralUtils.h +++ b/dCommon/GeneralUtils.h @@ -12,6 +12,7 @@ #include #include "Game.h" +#include "dLogger.h" /*! \file GeneralUtils.hpp @@ -138,7 +139,7 @@ namespace GeneralUtils { std::vector SplitString(const std::string& str, char delimiter); - std::vector GetFileNamesFromFolder(const std::string& folder); + std::vector GetSqlFileNamesFromFolder(const std::string& folder); template T Parse(const char* value); diff --git a/dDatabase/MigrationRunner.cpp b/dDatabase/MigrationRunner.cpp index d39201b2..54def9e2 100644 --- a/dDatabase/MigrationRunner.cpp +++ b/dDatabase/MigrationRunner.cpp @@ -38,7 +38,7 @@ void MigrationRunner::RunMigrations() { sql::SQLString finalSQL = ""; bool runSd0Migrations = false; - for (const auto& entry : GeneralUtils::GetFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "./migrations/dlu/").string())) { + for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "./migrations/dlu/").string())) { auto migration = LoadMigration("dlu/" + entry); if (migration.data.empty()) { @@ -102,7 +102,7 @@ void MigrationRunner::RunSQLiteMigrations() { stmt->execute(); delete stmt; - for (const auto& entry : GeneralUtils::GetFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "migrations/cdserver/").string())) { + for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "migrations/cdserver/").string())) { auto migration = LoadMigration("cdserver/" + entry); if (migration.data.empty()) continue; From 18a0ae599bcf3976b01e5550607a9d3abf04b9df Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 5 Dec 2022 16:08:47 -0800 Subject: [PATCH 23/28] Add bandwidth limit of 10kb/s(#863) --- dAuthServer/AuthServer.cpp | 2 +- dChatServer/ChatServer.cpp | 2 +- dGame/dUtilities/SlashCommandHandler.cpp | 1 + dMasterServer/MasterServer.cpp | 2 +- dNet/dServer.cpp | 11 +++++++++-- dNet/dServer.h | 5 ++++- dWorldServer/WorldServer.cpp | 2 +- resources/sharedconfig.ini | 3 +++ 8 files changed, 21 insertions(+), 7 deletions(-) diff --git a/dAuthServer/AuthServer.cpp b/dAuthServer/AuthServer.cpp index 9621f683..a9f02f53 100644 --- a/dAuthServer/AuthServer.cpp +++ b/dAuthServer/AuthServer.cpp @@ -83,7 +83,7 @@ int main(int argc, char** argv) { if (config.GetValue("max_clients") != "") maxClients = std::stoi(config.GetValue("max_clients")); if (config.GetValue("port") != "") ourPort = std::atoi(config.GetValue("port").c_str()); - Game::server = new dServer(config.GetValue("external_ip"), ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Auth); + Game::server = new dServer(config.GetValue("external_ip"), ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Auth, Game::config); //Run it until server gets a kill message from Master: auto t = std::chrono::high_resolution_clock::now(); diff --git a/dChatServer/ChatServer.cpp b/dChatServer/ChatServer.cpp index e0073747..e3c6d6e9 100644 --- a/dChatServer/ChatServer.cpp +++ b/dChatServer/ChatServer.cpp @@ -103,7 +103,7 @@ int main(int argc, char** argv) { if (config.GetValue("max_clients") != "") maxClients = std::stoi(config.GetValue("max_clients")); if (config.GetValue("port") != "") ourPort = std::atoi(config.GetValue("port").c_str()); - Game::server = new dServer(config.GetValue("external_ip"), ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Chat); + Game::server = new dServer(config.GetValue("external_ip"), ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Chat, Game::config); Game::chatFilter = new dChatFilter(Game::assetManager->GetResPath().string() + "/chatplus_en_us", bool(std::stoi(config.GetValue("dont_generate_dcf")))); diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 60862c1a..9b37d4b8 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -1778,6 +1778,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit scriptedActivityComponent->ReloadConfig(); } + Game::server->UpdateBandwidthLimit(); ChatPackets::SendSystemMessage(sysAddr, u"Successfully reloaded config for world!"); } diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 0329d9a0..4acb9b26 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -216,7 +216,7 @@ int main(int argc, char** argv) { if (Game::config->GetValue("max_clients") != "") maxClients = std::stoi(Game::config->GetValue("max_clients")); if (Game::config->GetValue("port") != "") ourPort = std::stoi(Game::config->GetValue("port")); - Game::server = new dServer(Game::config->GetValue("external_ip"), ourPort, 0, maxClients, true, false, Game::logger, "", 0, ServerType::Master); + Game::server = new dServer(Game::config->GetValue("external_ip"), ourPort, 0, maxClients, true, false, Game::logger, "", 0, ServerType::Master, Game::config); //Query for the database for a server labeled "master" auto* masterLookupStatement = Database::CreatePreppedStmt("SELECT id FROM `servers` WHERE `name` = 'master'"); diff --git a/dNet/dServer.cpp b/dNet/dServer.cpp index c46b156c..481667b8 100644 --- a/dNet/dServer.cpp +++ b/dNet/dServer.cpp @@ -2,6 +2,7 @@ #include "dServer.h" #include "dNetCommon.h" #include "dLogger.h" +#include "dConfig.h" #include "RakNetworkFactory.h" #include "MessageIdentifiers.h" @@ -35,7 +36,7 @@ public: } } ReceiveDownloadCompleteCB; -dServer::dServer(const std::string& ip, int port, int instanceID, int maxConnections, bool isInternal, bool useEncryption, dLogger* logger, const std::string masterIP, int masterPort, ServerType serverType, unsigned int zoneID) { +dServer::dServer(const std::string& ip, int port, int instanceID, int maxConnections, bool isInternal, bool useEncryption, dLogger* logger, const std::string masterIP, int masterPort, ServerType serverType, dConfig* config, unsigned int zoneID) { mIP = ip; mPort = port; mZoneID = zoneID; @@ -50,6 +51,7 @@ dServer::dServer(const std::string& ip, int port, int instanceID, int maxConnect mNetIDManager = nullptr; mReplicaManager = nullptr; mServerType = serverType; + mConfig = config; //Attempt to start our server here: mIsOkay = Startup(); @@ -181,7 +183,7 @@ bool dServer::Startup() { if (mIsInternal) { mPeer->SetIncomingPassword("3.25 DARKFLAME1", 15); } else { - //mPeer->SetPerConnectionOutgoingBandwidthLimit(800000); //100Kb/s + UpdateBandwidthLimit(); mPeer->SetIncomingPassword("3.25 ND1", 8); } @@ -191,6 +193,11 @@ bool dServer::Startup() { return true; } +void dServer::UpdateBandwidthLimit() { + auto newBandwidth = mConfig->GetValue("maximum_outgoing_bandwidth"); + mPeer->SetPerConnectionOutgoingBandwidthLimit(!newBandwidth.empty() ? std::stoi(newBandwidth) : 0); +} + void dServer::Shutdown() { if (mPeer) { mPeer->Shutdown(1000); diff --git a/dNet/dServer.h b/dNet/dServer.h index 264932ee..0fbdecce 100644 --- a/dNet/dServer.h +++ b/dNet/dServer.h @@ -5,6 +5,7 @@ #include "NetworkIDManager.h" class dLogger; +class dConfig; enum class ServerType : uint32_t { Master, @@ -17,7 +18,7 @@ class dServer { public: // Default constructor should only used for testing! dServer() {}; - dServer(const std::string& ip, int port, int instanceID, int maxConnections, bool isInternal, bool useEncryption, dLogger* logger, const std::string masterIP, int masterPort, ServerType serverType, unsigned int zoneID = 0); + dServer(const std::string& ip, int port, int instanceID, int maxConnections, bool isInternal, bool useEncryption, dLogger* logger, const std::string masterIP, int masterPort, ServerType serverType, dConfig* config, unsigned int zoneID = 0); ~dServer(); Packet* ReceiveFromMaster(); @@ -42,6 +43,7 @@ public: const int GetInstanceID() const { return mInstanceID; } ReplicaManager* GetReplicaManager() { return mReplicaManager; } void UpdateReplica(); + void UpdateBandwidthLimit(); int GetPing(const SystemAddress& sysAddr) const; int GetLatestPing(const SystemAddress& sysAddr) const; @@ -58,6 +60,7 @@ private: private: dLogger* mLogger = nullptr; + dConfig* mConfig = nullptr; RakPeerInterface* mPeer = nullptr; ReplicaManager* mReplicaManager = nullptr; NetworkIDManager* mNetIDManager = nullptr; diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index b4be7511..acd38ad3 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -208,7 +208,7 @@ int main(int argc, char** argv) { LootGenerator::Instance(); Game::chatFilter = new dChatFilter(Game::assetManager->GetResPath().string() + "/chatplus_en_us", bool(std::stoi(config.GetValue("dont_generate_dcf")))); - Game::server = new dServer(masterIP, ourPort, instanceID, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::World, zoneID); + Game::server = new dServer(masterIP, ourPort, instanceID, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::World, Game::config, zoneID); //Connect to the chat server: int chatPort = 1501; diff --git a/resources/sharedconfig.ini b/resources/sharedconfig.ini index 847a6b7c..439ffe2f 100644 --- a/resources/sharedconfig.ini +++ b/resources/sharedconfig.ini @@ -25,3 +25,6 @@ dump_folder= # The location of the client # Either the folder with /res or with /client and /versions client_location= + +# The maximum outgoing bandwidth in bits +maximum_outgoing_bandwidth=80000 From fde62a47773f02abdb3beed76d959cd86751f050 Mon Sep 17 00:00:00 2001 From: Demetri Van Sickle Date: Tue, 6 Dec 2022 06:36:42 -0800 Subject: [PATCH 24/28] Fixed typo (#875) --- migrations/dlu/7_make_play_key_id_nullable.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/dlu/7_make_play_key_id_nullable.sql b/migrations/dlu/7_make_play_key_id_nullable.sql index 7491874f..11239967 100644 --- a/migrations/dlu/7_make_play_key_id_nullable.sql +++ b/migrations/dlu/7_make_play_key_id_nullable.sql @@ -1 +1 @@ -ALTER TABLE account MODIFY play_key_id INT DEFAULT 0; +ALTER TABLE accounts MODIFY play_key_id INT DEFAULT 0; From 8886bf65475ff3cea82d5229d900e68828975f4f Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Thu, 8 Dec 2022 00:13:25 -0700 Subject: [PATCH 25/28] Address Force movement behaviors triggering twice (#878) --- dGame/dBehaviors/ForceMovementBehavior.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dGame/dBehaviors/ForceMovementBehavior.cpp b/dGame/dBehaviors/ForceMovementBehavior.cpp index 55cda622..e1ac6133 100644 --- a/dGame/dBehaviors/ForceMovementBehavior.cpp +++ b/dGame/dBehaviors/ForceMovementBehavior.cpp @@ -75,5 +75,5 @@ void ForceMovementBehavior::SyncCalculation(BehaviorContext* context, RakNet::Bi this->m_hitAction->Calculate(context, bitStream, branch); this->m_hitEnemyAction->Calculate(context, bitStream, branch); - this->m_hitEnemyAction->Calculate(context, bitStream, branch); + this->m_hitFactionAction->Calculate(context, bitStream, branch); } From d5613b8034ea8594db6818606fe085a47c455611 Mon Sep 17 00:00:00 2001 From: EmosewaMC <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 8 Dec 2022 04:44:56 -0800 Subject: [PATCH 26/28] hot fix --- dCommon/FdbToSqlite.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dCommon/FdbToSqlite.cpp b/dCommon/FdbToSqlite.cpp index d98d4962..6015fd3a 100644 --- a/dCommon/FdbToSqlite.cpp +++ b/dCommon/FdbToSqlite.cpp @@ -49,9 +49,9 @@ bool FdbToSqlite::Convert::ConvertDatabase() { } int32_t FdbToSqlite::Convert::ReadInt32() { - uint32_t numberOfTables{}; - BinaryIO::BinaryRead(fdb, numberOfTables); - return numberOfTables; + int32_t nextInt{}; + BinaryIO::BinaryRead(fdb, nextInt); + return nextInt; } int64_t FdbToSqlite::Convert::ReadInt64() { @@ -193,7 +193,7 @@ void FdbToSqlite::Convert::ReadRowValues(int32_t& numberOfColumns, std::string& case eSqliteDataType::NONE: BinaryIO::BinaryRead(fdb, emptyValue); assert(emptyValue == 0); - insertedRow << "\"\""; + insertedRow << "NULL"; break; case eSqliteDataType::INT32: From da910309a043f50821a388531ace407507cf1e95 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Thu, 8 Dec 2022 13:32:47 -0800 Subject: [PATCH 27/28] Remove unneeded commands (#880) * Remove unneeded commands * Thank you aron --- dGame/dUtilities/SlashCommandHandler.cpp | 16 ---------------- docs/Commands.md | 2 -- 2 files changed, 18 deletions(-) diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 9b37d4b8..89f11346 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -212,22 +212,6 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit return; } - if (chatCommand == "skip-ags") { - auto* missionComponent = entity->GetComponent(); - - if (missionComponent != nullptr && missionComponent->HasMission(479)) { - missionComponent->CompleteMission(479); - } - } - - if (chatCommand == "skip-sg") { - auto* missionComponent = entity->GetComponent(); - - if (missionComponent != nullptr && missionComponent->HasMission(229)) { - missionComponent->CompleteMission(229); - } - } - if (chatCommand == "fix-stats") { // Reset skill component and buff component auto* skillComponent = entity->GetComponent(); diff --git a/docs/Commands.md b/docs/Commands.md index 95ec28f9..7a15bf37 100644 --- a/docs/Commands.md +++ b/docs/Commands.md @@ -14,8 +14,6 @@ Here is a summary of the commands available in-game. All commands are prefixed b |pvp|`/pvp`|Toggle your PVP flag.|| |resurrect|`/resurrect`|Resurrects the player.|| |requestmailcount|`/requestmailcount`|Sends notification with number of unread messages in the player's mailbox.|| -|skip-ags|`/skip-ags`|Skips the Avant Gardens Survival minigame mission, "Impress the Sentinel Faction".|| -|skip-sg|`/skip-sg`|Skips the Shooting Gallery minigame mission, "Monarch of the Sea".|| |who|`/who`|Displays in chat all players on the instance.|| ## Moderation Commands From 5292f36417e94085aa21400f31bf6ed595b12330 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 11 Dec 2022 00:27:01 -0800 Subject: [PATCH 28/28] Packages updates (#864) Update packages to not open if you dont have enough room. Update packages to no longer allow them selves to be open unless you meet the pre-reqs. --- dGame/dInventory/Item.cpp | 67 ++++++++++++++++++++++++------ dGame/dUtilities/Preconditions.cpp | 7 +--- 2 files changed, 56 insertions(+), 18 deletions(-) diff --git a/dGame/dInventory/Item.cpp b/dGame/dInventory/Item.cpp index c05c0672..02739ec2 100644 --- a/dGame/dInventory/Item.cpp +++ b/dGame/dInventory/Item.cpp @@ -253,7 +253,7 @@ bool Item::Consume() { } } - Game::logger->Log("Item", "Consumed (%i) / (%llu) with (%d)", lot, id, success); + Game::logger->LogDebug("Item", "Consumed LOT (%i) itemID (%llu). Success=(%d)", lot, id, success); GameMessages::SendUseItemResult(inventory->GetComponent()->GetParent(), lot, success); @@ -265,14 +265,34 @@ bool Item::Consume() { } void Item::UseNonEquip() { + LOT thisLot = this->GetLot(); + if (!GetInventory()) { + Game::logger->LogDebug("Item", "item %i has no inventory??", this->GetLot()); + return; + } + + auto* playerInventoryComponent = GetInventory()->GetComponent(); + if (!playerInventoryComponent) { + Game::logger->LogDebug("Item", "no inventory component attached to item id %llu lot %i", this->GetId(), this->GetLot()); + return; + } + + auto* playerEntity = playerInventoryComponent->GetParent(); + if (!playerEntity) { + Game::logger->LogDebug("Item", "no player entity attached to inventory? item id is %llu", this->GetId()); + return; + } + const auto type = static_cast(info->itemType); if (type == eItemType::ITEM_TYPE_MOUNT) { - GetInventory()->GetComponent()->HandlePossession(this); + playerInventoryComponent->HandlePossession(this); + // TODO Check if mounts are allowed to be spawned } else if (type == eItemType::ITEM_TYPE_PET_INVENTORY_ITEM && subKey != LWOOBJID_EMPTY) { - const auto& databasePet = GetInventory()->GetComponent()->GetDatabasePet(subKey); + const auto& databasePet = playerInventoryComponent->GetDatabasePet(subKey); if (databasePet.lot != LOT_NULL) { - GetInventory()->GetComponent()->SpawnPet(this); + playerInventoryComponent->SpawnPet(this); } + // This precondition response is taken care of in SpawnPet(). } else { auto* compRegistryTable = CDClientManager::Instance()->GetTable("ComponentsRegistry"); const auto packageComponentId = compRegistryTable->GetByIDAndType(lot, COMPONENT_TYPE_PACKAGE); @@ -282,18 +302,41 @@ void Item::UseNonEquip() { auto* packCompTable = CDClientManager::Instance()->GetTable("PackageComponent"); auto packages = packCompTable->Query([=](const CDPackageComponent entry) {return entry.id == static_cast(packageComponentId); }); - const auto success = !packages.empty(); + auto success = !packages.empty(); if (success) { - auto* entityParent = inventory->GetComponent()->GetParent(); - for (auto& pack : packages) { - std::unordered_map result{}; - result = LootGenerator::Instance().RollLootMatrix(entityParent, pack.LootMatrixIndex); - if (!inventory->GetComponent()->HasSpaceForLoot(result)) { + 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{}; + for (auto& pack : packages) { + auto thisPackage = LootGenerator::Instance().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); + if (existingLoot == rolledLoot.end()) { + rolledLoot.insert(loot); + } else { + existingLoot->second += loot.second; + } + } } - LootGenerator::Instance().GiveLoot(inventory->GetComponent()->GetParent(), result, eLootSourceType::LOOT_SOURCE_CONSUMPTION); + if (playerInventoryComponent->HasSpaceForLoot(rolledLoot)) { + LootGenerator::Instance().GiveLoot(playerInventoryComponent->GetParent(), rolledLoot, eLootSourceType::LOOT_SOURCE_CONSUMPTION); + playerInventoryComponent->RemoveItem(lot, 1); + } else { + success = false; + } + } else { + GameMessages::SendUseItemRequirementsResponse( + playerInventoryComponent->GetParent()->GetObjectID(), + playerInventoryComponent->GetParent()->GetSystemAddress(), + UseItemResponse::FailedPrecondition + ); + success = false; } - inventory->GetComponent()->RemoveItem(lot, 1); } + Game::logger->LogDebug("Item", "Player %llu %s used item %i", playerEntity->GetObjectID(), success ? "successfully" : "unsuccessfully", thisLot); + GameMessages::SendUseItemResult(playerInventoryComponent->GetParent(), thisLot, success); } } diff --git a/dGame/dUtilities/Preconditions.cpp b/dGame/dUtilities/Preconditions.cpp index 1f719433..c23ac53b 100644 --- a/dGame/dUtilities/Preconditions.cpp +++ b/dGame/dUtilities/Preconditions.cpp @@ -154,7 +154,7 @@ bool Precondition::CheckValue(Entity* player, const uint32_t value, bool evaluat case PreconditionType::MissionComplete: mission = missionComponent->GetMission(value); - return mission == nullptr || mission->GetMissionState() >= MissionState::MISSION_STATE_COMPLETE; + return mission == nullptr ? false : mission->GetMissionState() >= MissionState::MISSION_STATE_COMPLETE; case PreconditionType::PetDeployed: return false; // TODO case PreconditionType::HasFlag: @@ -277,11 +277,6 @@ bool PreconditionExpression::Check(Entity* player, bool evaluateCosts) const { return true; } - if (player->GetGMLevel() >= 9) // Developers can skip this for testing - { - return true; - } - const auto a = Preconditions::Check(player, condition, evaluateCosts); if (!a) {