From 8ba35be64de3a01e55e0f0523fb23942e086d88e Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 22 Jun 2025 18:45:49 -0700 Subject: [PATCH] feat: add saving behaviors to the inventory (#1822) * change behavior id to LWOOBJID Convert behavior ID to LWOOBJID length missed header fix sqlite field names sqlite brother * feat: add saving behaviors to the inventory consolidate copied code consolidate copied code Update ModelComponent.cpp remove ability to save loot behaviors --- dGame/dComponents/ModelComponent.cpp | 94 ++++++++++++++----- dGame/dComponents/ModelComponent.h | 20 +++- .../ControlBehaviorMessages/AddMessage.cpp | 2 +- .../ControlBehaviorMessages/AddMessage.h | 4 +- .../BehaviorMessageBase.h | 3 + .../MoveToInventoryMessage.cpp | 2 +- .../MoveToInventoryMessage.h | 4 +- dGame/dPropertyBehaviors/ControlBehaviors.cpp | 57 ++++++----- dGame/dPropertyBehaviors/PropertyBehavior.cpp | 24 +++-- dGame/dPropertyBehaviors/PropertyBehavior.h | 12 ++- .../dGameMessagesTests/GameMessageTests.cpp | 2 +- 11 files changed, 156 insertions(+), 68 deletions(-) diff --git a/dGame/dComponents/ModelComponent.cpp b/dGame/dComponents/ModelComponent.cpp index c74a67b6..128b014f 100644 --- a/dGame/dComponents/ModelComponent.cpp +++ b/dGame/dComponents/ModelComponent.cpp @@ -7,7 +7,9 @@ #include "BehaviorStates.h" #include "ControlBehaviorMsgs.h" #include "tinyxml2.h" +#include "InventoryComponent.h" #include "SimplePhysicsComponent.h" +#include "eObjectBits.h" #include "Database.h" #include "DluAssert.h" @@ -70,22 +72,27 @@ void ModelComponent::LoadBehaviors() { const auto behaviorId = GeneralUtils::TryParse(behavior); if (!behaviorId.has_value() || behaviorId.value() == 0) continue; - LOG_DEBUG("Loading behavior %d", behaviorId.value()); - auto& inserted = m_Behaviors.emplace_back(); - inserted.SetBehaviorId(*behaviorId); + // add behavior at the back + LoadBehavior(behaviorId.value(), m_Behaviors.size(), false); + } +} - const auto behaviorStr = Database::Get()->GetBehavior(behaviorId.value()); +void ModelComponent::LoadBehavior(const LWOOBJID behaviorID, const size_t index, const bool isIndexed) { + LOG_DEBUG("Loading behavior %d", behaviorID); + auto& inserted = *m_Behaviors.emplace(m_Behaviors.begin() + index, PropertyBehavior(isIndexed)); + inserted.SetBehaviorId(behaviorID); - tinyxml2::XMLDocument behaviorXml; - auto res = behaviorXml.Parse(behaviorStr.c_str(), behaviorStr.size()); - LOG_DEBUG("Behavior %llu %d: %s", res, behaviorId.value(), behaviorStr.c_str()); + const auto behaviorStr = Database::Get()->GetBehavior(behaviorID); - const auto* const behaviorRoot = behaviorXml.FirstChildElement("Behavior"); - if (!behaviorRoot) { - LOG("Failed to load behavior %d due to missing behavior root", behaviorId.value()); - continue; - } + tinyxml2::XMLDocument behaviorXml; + auto res = behaviorXml.Parse(behaviorStr.c_str(), behaviorStr.size()); + LOG_DEBUG("Behavior %i %llu: %s", res, behaviorID, behaviorStr.c_str()); + + const auto* const behaviorRoot = behaviorXml.FirstChildElement("Behavior"); + if (behaviorRoot) { inserted.Deserialize(*behaviorRoot); + } else { + LOG("Failed to load behavior %d due to missing behavior root", behaviorID); } } @@ -120,6 +127,7 @@ void ModelComponent::UpdatePendingBehaviorId(const LWOOBJID newId, const LWOOBJI for (auto& behavior : m_Behaviors) { if (behavior.GetBehaviorId() != oldId) continue; behavior.SetBehaviorId(newId); + behavior.SetIsLoot(false); } } @@ -146,8 +154,21 @@ void ModelComponent::SendBehaviorBlocksToClient(const LWOOBJID behaviorToSend, A void ModelComponent::AddBehavior(AddMessage& msg) { // Can only have 1 of the loot behaviors for (auto& behavior : m_Behaviors) if (behavior.GetBehaviorId() == msg.GetBehaviorId()) return; - m_Behaviors.insert(m_Behaviors.begin() + msg.GetBehaviorIndex(), PropertyBehavior()); - m_Behaviors.at(msg.GetBehaviorIndex()).HandleMsg(msg); + + // If we're loading a behavior from an ADD, it is from the database. + // Mark it as not modified by default to prevent wasting persistentIDs. + LoadBehavior(msg.GetBehaviorId(), msg.GetBehaviorIndex(), true); + auto& insertedBehavior = m_Behaviors[msg.GetBehaviorIndex()]; + + auto* const playerEntity = Game::entityManager->GetEntity(msg.GetOwningPlayerID()); + if (playerEntity) { + auto* inventoryComponent = playerEntity->GetComponent(); + if (inventoryComponent) { + // Check if this behavior is able to be found via lot (if so, its a loot behavior). + insertedBehavior.SetIsLoot(inventoryComponent->FindItemByLot(msg.GetBehaviorId(), eInventoryType::BEHAVIORS)); + } + } + auto* const simplePhysComponent = m_Parent->GetComponent(); if (simplePhysComponent) { simplePhysComponent->SetPhysicsMotionState(1); @@ -155,8 +176,41 @@ void ModelComponent::AddBehavior(AddMessage& msg) { } } -void ModelComponent::MoveToInventory(MoveToInventoryMessage& msg) { +std::string ModelComponent::SaveBehavior(const PropertyBehavior& behavior) const { + tinyxml2::XMLDocument doc; + auto* root = doc.NewElement("Behavior"); + behavior.Serialize(*root); + doc.InsertFirstChild(root); + + tinyxml2::XMLPrinter printer(0, true, 0); + doc.Print(&printer); + return printer.CStr(); +} + +void ModelComponent::RemoveBehavior(MoveToInventoryMessage& msg, const bool keepItem) { if (msg.GetBehaviorIndex() >= m_Behaviors.size() || m_Behaviors.at(msg.GetBehaviorIndex()).GetBehaviorId() != msg.GetBehaviorId()) return; + const auto behavior = m_Behaviors[msg.GetBehaviorIndex()]; + if (keepItem) { + auto* const playerEntity = Game::entityManager->GetEntity(msg.GetOwningPlayerID()); + if (playerEntity) { + auto* const inventoryComponent = playerEntity->GetComponent(); + if (inventoryComponent && !behavior.GetIsLoot()) { + // config is owned by the item + std::vector config; + config.push_back(new LDFData(u"userModelName", behavior.GetName())); + inventoryComponent->AddItem(7965, 1, eLootSourceType::PROPERTY, eInventoryType::BEHAVIORS, config, LWOOBJID_EMPTY, true, false, msg.GetBehaviorId()); + } + } + } + + // save the behavior before deleting it so players can re-add them + IBehaviors::Info info{}; + info.behaviorId = msg.GetBehaviorId(); + info.behaviorInfo = SaveBehavior(behavior); + info.characterId = msg.GetOwningPlayerID(); + + Database::Get()->AddBehavior(info); + m_Behaviors.erase(m_Behaviors.begin() + msg.GetBehaviorIndex()); // TODO move to the inventory if (m_Behaviors.empty()) { @@ -175,15 +229,7 @@ std::array, 5> ModelComponent::GetBehaviorsForS if (behavior.GetBehaviorId() == -1) continue; auto& [id, behaviorData] = toReturn[i]; id = behavior.GetBehaviorId(); - - tinyxml2::XMLDocument doc; - auto* root = doc.NewElement("Behavior"); - behavior.Serialize(*root); - doc.InsertFirstChild(root); - - tinyxml2::XMLPrinter printer(0, true, 0); - doc.Print(&printer); - behaviorData = printer.CStr(); + behaviorData = SaveBehavior(behavior); } return toReturn; } diff --git a/dGame/dComponents/ModelComponent.h b/dGame/dComponents/ModelComponent.h index 029bc371..d70718de 100644 --- a/dGame/dComponents/ModelComponent.h +++ b/dGame/dComponents/ModelComponent.h @@ -66,15 +66,18 @@ public: * * @tparam Msg The message type to pass * @param args the arguments of the message to be deserialized + * + * @return returns true if a new behaviorID is needed. */ template - void HandleControlBehaviorsMsg(const AMFArrayValue& args) { + bool HandleControlBehaviorsMsg(const AMFArrayValue& args) { static_assert(std::is_base_of_v, "Msg must be a BehaviorMessageBase"); Msg msg{ args }; for (auto&& behavior : m_Behaviors) { if (behavior.GetBehaviorId() == msg.GetBehaviorId()) { + behavior.CheckModifyState(msg); behavior.HandleMsg(msg); - return; + return msg.GetNeedsNewBehaviorID(); } } @@ -82,19 +85,21 @@ public: if (m_Behaviors.size() > 5) m_Behaviors.resize(5); // Do not allow more than 5 to be added. The client UI will break if you do! - if (m_Behaviors.size() == 5) return; + if (m_Behaviors.size() == 5) return false; auto newBehavior = m_Behaviors.insert(m_Behaviors.begin(), PropertyBehavior()); // Generally if we are inserting a new behavior, it is because the client is creating a new behavior. // However if we are testing behaviors the behavior will not exist on the initial pass, so we set the ID here to that of the msg. // This will either set the ID to -1 (no change in the current default) or set the ID to the ID of the behavior we are testing. newBehavior->SetBehaviorId(msg.GetBehaviorId()); + newBehavior->CheckModifyState(msg); newBehavior->HandleMsg(msg); + return msg.GetNeedsNewBehaviorID(); }; void AddBehavior(AddMessage& msg); - void MoveToInventory(MoveToInventoryMessage& msg); + void RemoveBehavior(MoveToInventoryMessage& msg, const bool keepItem); // Updates the pending behavior ID to the new ID. void UpdatePendingBehaviorId(const LWOOBJID newId, const LWOOBJID oldId); @@ -141,6 +146,13 @@ public: void OnChatMessageReceived(const std::string& sMessage); private: + + // Loads a behavior from the database. + void LoadBehavior(const LWOOBJID behaviorID, const size_t index, const bool isIndexed); + + // Writes a behavior to a string so it can be saved. + std::string SaveBehavior(const PropertyBehavior& behavior) const; + // Number of Actions that are awaiting an UnSmash to finish. uint32_t m_NumActiveUnSmash{}; diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/AddMessage.cpp b/dGame/dPropertyBehaviors/ControlBehaviorMessages/AddMessage.cpp index 895f6103..45233104 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/AddMessage.cpp +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/AddMessage.cpp @@ -1,6 +1,6 @@ #include "AddMessage.h" -AddMessage::AddMessage(const AMFArrayValue& arguments) : BehaviorMessageBase{ arguments } { +AddMessage::AddMessage(const AMFArrayValue& arguments, const LWOOBJID _owningPlayerID) : m_OwningPlayerID{ _owningPlayerID }, BehaviorMessageBase { arguments } { const auto* const behaviorIndexValue = arguments.Get("BehaviorIndex"); if (!behaviorIndexValue) return; diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/AddMessage.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/AddMessage.h index 8bf0b70c..38d73a42 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/AddMessage.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/AddMessage.h @@ -9,11 +9,13 @@ */ class AddMessage : public BehaviorMessageBase { public: - AddMessage(const AMFArrayValue& arguments); + AddMessage(const AMFArrayValue& arguments, const LWOOBJID _owningPlayerID); [[nodiscard]] uint32_t GetBehaviorIndex() const noexcept { return m_BehaviorIndex; }; + [[nodiscard]] LWOOBJID GetOwningPlayerID() const noexcept { return m_OwningPlayerID; }; private: uint32_t m_BehaviorIndex{ 0 }; + LWOOBJID m_OwningPlayerID{}; }; #endif //!__ADDMESSAGE__H__ diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/BehaviorMessageBase.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/BehaviorMessageBase.h index 9c13b2d4..b5ab86ce 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/BehaviorMessageBase.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/BehaviorMessageBase.h @@ -19,11 +19,14 @@ public: BehaviorMessageBase(const AMFArrayValue& arguments) : m_BehaviorId{ GetBehaviorIdFromArgument(arguments) } {} [[nodiscard]] LWOOBJID GetBehaviorId() const noexcept { return m_BehaviorId; } [[nodiscard]] bool IsDefaultBehaviorId() const noexcept { return m_BehaviorId == DefaultBehaviorId; } + [[nodiscard]] bool GetNeedsNewBehaviorID() const noexcept { return m_NeedsNewBehaviorID; } + void SetNeedsNewBehaviorID(const bool val) noexcept { m_NeedsNewBehaviorID = val; } protected: [[nodiscard]] LWOOBJID GetBehaviorIdFromArgument(const AMFArrayValue& arguments); [[nodiscard]] int32_t GetActionIndexFromArgument(const AMFArrayValue& arguments, const std::string_view keyName = "actionIndex") const; LWOOBJID m_BehaviorId{ DefaultBehaviorId }; + bool m_NeedsNewBehaviorID{ false }; }; #endif //!__BEHAVIORMESSAGEBASE__H__ diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/MoveToInventoryMessage.cpp b/dGame/dPropertyBehaviors/ControlBehaviorMessages/MoveToInventoryMessage.cpp index e8d16283..022299f9 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/MoveToInventoryMessage.cpp +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/MoveToInventoryMessage.cpp @@ -1,6 +1,6 @@ #include "MoveToInventoryMessage.h" -MoveToInventoryMessage::MoveToInventoryMessage(const AMFArrayValue& arguments) : BehaviorMessageBase{ arguments } { +MoveToInventoryMessage::MoveToInventoryMessage(const AMFArrayValue& arguments, const LWOOBJID _owningPlayerID) : m_OwningPlayerID{ _owningPlayerID }, BehaviorMessageBase{ arguments } { const auto* const behaviorIndexValue = arguments.Get("BehaviorIndex"); if (!behaviorIndexValue) return; diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/MoveToInventoryMessage.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/MoveToInventoryMessage.h index afd7a14c..e3b66558 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/MoveToInventoryMessage.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/MoveToInventoryMessage.h @@ -10,11 +10,13 @@ class AMFArrayValue; */ class MoveToInventoryMessage : public BehaviorMessageBase { public: - MoveToInventoryMessage(const AMFArrayValue& arguments); + MoveToInventoryMessage(const AMFArrayValue& arguments, const LWOOBJID owningPlayerID); [[nodiscard]] uint32_t GetBehaviorIndex() const noexcept { return m_BehaviorIndex; }; + [[nodiscard]] LWOOBJID GetOwningPlayerID() const noexcept { return m_OwningPlayerID; }; private: uint32_t m_BehaviorIndex; + LWOOBJID m_OwningPlayerID{}; }; #endif //!__MOVETOINVENTORYMESSAGE__H__ diff --git a/dGame/dPropertyBehaviors/ControlBehaviors.cpp b/dGame/dPropertyBehaviors/ControlBehaviors.cpp index 27ff0d2f..1a142273 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviors.cpp +++ b/dGame/dPropertyBehaviors/ControlBehaviors.cpp @@ -107,9 +107,12 @@ void ControlBehaviors::ProcessCommand(Entity* const modelEntity, const AMFArrayV if (!modelComponent) return; ControlBehaviorContext context{ arguments, modelComponent, modelOwner }; + bool needsNewBehaviorID = false; if (command == "sendBehaviorListToClient") { SendBehaviorListToClient(context); + } else if (command == "sendBehaviorBlocksToClient") { + SendBehaviorBlocksToClient(context); } else if (command == "modelTypeChanged") { const auto* const modelType = arguments.Get("ModelType"); if (!modelType) return; @@ -118,52 +121,54 @@ void ControlBehaviors::ProcessCommand(Entity* const modelEntity, const AMFArrayV } else if (command == "toggleExecutionUpdates") { // TODO } else if (command == "addStrip") { - if (BehaviorMessageBase(context.arguments).IsDefaultBehaviorId()) RequestUpdatedID(context); - - context.modelComponent->HandleControlBehaviorsMsg(context.arguments); + needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg(context.arguments); } else if (command == "removeStrip") { - context.modelComponent->HandleControlBehaviorsMsg(arguments); + needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg(arguments); } else if (command == "mergeStrips") { - context.modelComponent->HandleControlBehaviorsMsg(arguments); + needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg(arguments); } else if (command == "splitStrip") { - context.modelComponent->HandleControlBehaviorsMsg(arguments); + needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg(arguments); } else if (command == "updateStripUI") { - context.modelComponent->HandleControlBehaviorsMsg(arguments); + needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg(arguments); } else if (command == "addAction") { - context.modelComponent->HandleControlBehaviorsMsg(arguments); + needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg(arguments); } else if (command == "migrateActions") { - context.modelComponent->HandleControlBehaviorsMsg(arguments); + needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg(arguments); } else if (command == "rearrangeStrip") { - context.modelComponent->HandleControlBehaviorsMsg(arguments); - } else if (command == "add") { - AddMessage msg{ context.arguments }; - context.modelComponent->AddBehavior(msg); - SendBehaviorListToClient(context); + needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg(arguments); } else if (command == "removeActions") { - context.modelComponent->HandleControlBehaviorsMsg(arguments); + needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg(arguments); + } else if (command == "updateAction") { + needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg(arguments); } else if (command == "rename") { - context.modelComponent->HandleControlBehaviorsMsg(arguments); + needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg(arguments); // Send the list back to the client so the name is updated. SendBehaviorListToClient(context); - } else if (command == "sendBehaviorBlocksToClient") { - SendBehaviorBlocksToClient(context); - } else if (command == "moveToInventory") { - MoveToInventoryMessage msg{ arguments }; - context.modelComponent->MoveToInventory(msg); + } else if (command == "add") { + AddMessage msg{ context.arguments, context.modelOwner->GetObjectID() }; + context.modelComponent->AddBehavior(msg); + SendBehaviorListToClient(context); + } else if (command == "moveToInventory" || command == "remove") { + // both moveToInventory and remove use the same args + const bool isRemove = command != "remove"; + MoveToInventoryMessage msg{ arguments, modelOwner->GetObjectID() }; + context.modelComponent->RemoveBehavior(msg, isRemove); auto* characterComponent = modelOwner->GetComponent(); if (!characterComponent) return; - AMFArrayValue args; - args.Insert("BehaviorID", std::to_string(msg.GetBehaviorId())); - GameMessages::SendUIMessageServerToSingleClient(modelOwner, characterComponent->GetSystemAddress(), "BehaviorRemoved", args); + if (!isRemove) { + AMFArrayValue args; + args.Insert("BehaviorID", std::to_string(msg.GetBehaviorId())); + GameMessages::SendUIMessageServerToSingleClient(modelOwner, characterComponent->GetSystemAddress(), "BehaviorRemoved", args); + } SendBehaviorListToClient(context); - } else if (command == "updateAction") { - context.modelComponent->HandleControlBehaviorsMsg(arguments); } else { LOG("Unknown behavior command (%s)", command.data()); } + + if (needsNewBehaviorID) RequestUpdatedID(context); } ControlBehaviors::ControlBehaviors() { diff --git a/dGame/dPropertyBehaviors/PropertyBehavior.cpp b/dGame/dPropertyBehaviors/PropertyBehavior.cpp index 1a9bb867..b1b1a30f 100644 --- a/dGame/dPropertyBehaviors/PropertyBehavior.cpp +++ b/dGame/dPropertyBehaviors/PropertyBehavior.cpp @@ -8,9 +8,12 @@ #include -PropertyBehavior::PropertyBehavior() { +PropertyBehavior::PropertyBehavior(bool _isTemplated) { m_LastEditedState = BehaviorState::HOME_STATE; m_ActiveState = BehaviorState::HOME_STATE; + + // Starts off as true so that only specific ways of adding behaviors allow a new id to be requested. + isTemplated = _isTemplated; } template<> @@ -81,13 +84,6 @@ void PropertyBehavior::HandleMsg(RenameMessage& msg) { m_Name = msg.GetName(); }; -template<> -void PropertyBehavior::HandleMsg(AddMessage& msg) { - // TODO Parse the corresponding behavior xml file. - m_BehaviorId = msg.GetBehaviorId(); - isLoot = m_BehaviorId != 7965; -}; - template<> void PropertyBehavior::HandleMsg(GameMessages::RequestUse& msg) { m_States[m_ActiveState].HandleMsg(msg); @@ -99,6 +95,12 @@ void PropertyBehavior::HandleMsg(GameMessages::ResetModelToDefaults& msg) { for (auto& state : m_States | std::views::values) state.HandleMsg(msg); } +void PropertyBehavior::CheckModifyState(BehaviorMessageBase& msg) { + if (!isTemplated && m_BehaviorId != BehaviorMessageBase::DefaultBehaviorId) return; + isTemplated = false; + msg.SetNeedsNewBehaviorID(true); +} + void PropertyBehavior::SendBehaviorListToClient(AMFArrayValue& args) const { args.Insert("id", std::to_string(m_BehaviorId)); args.Insert("name", m_Name); @@ -147,6 +149,9 @@ void PropertyBehavior::Serialize(tinyxml2::XMLElement& behavior) const { behavior.SetAttribute("isLocked", isLocked); behavior.SetAttribute("isLoot", isLoot); + // CUSTOM XML ATTRIBUTE + behavior.SetAttribute("isTemplated", isTemplated); + for (const auto& [stateId, state] : m_States) { if (state.IsEmpty()) continue; auto* const stateElement = behavior.InsertNewChildElement("State"); @@ -161,6 +166,9 @@ void PropertyBehavior::Deserialize(const tinyxml2::XMLElement& behavior) { behavior.QueryBoolAttribute("isLocked", &isLocked); behavior.QueryBoolAttribute("isLoot", &isLoot); + // CUSTOM XML ATTRIBUTE + if (!isTemplated) behavior.QueryBoolAttribute("isTemplated", &isTemplated); + for (const auto* stateElement = behavior.FirstChildElement("State"); stateElement; stateElement = stateElement->NextSiblingElement("State")) { int32_t stateId = -1; stateElement->QueryIntAttribute("id", &stateId); diff --git a/dGame/dPropertyBehaviors/PropertyBehavior.h b/dGame/dPropertyBehaviors/PropertyBehavior.h index 124d3f0e..49d75d1a 100644 --- a/dGame/dPropertyBehaviors/PropertyBehavior.h +++ b/dGame/dPropertyBehaviors/PropertyBehavior.h @@ -10,6 +10,7 @@ namespace tinyxml2 { enum class BehaviorState : uint32_t; class AMFArrayValue; +class BehaviorMessageBase; class ModelComponent; /** @@ -17,7 +18,7 @@ class ModelComponent; */ class PropertyBehavior { public: - PropertyBehavior(); + PropertyBehavior(bool _isTemplated = false); template void HandleMsg(Msg& msg); @@ -26,10 +27,16 @@ public: void VerifyLastEditedState(); void SendBehaviorListToClient(AMFArrayValue& args) const; void SendBehaviorBlocksToClient(AMFArrayValue& args) const; + void CheckModifyState(BehaviorMessageBase& msg); [[nodiscard]] LWOOBJID GetBehaviorId() const noexcept { return m_BehaviorId; } void SetBehaviorId(LWOOBJID id) noexcept { m_BehaviorId = id; } + bool GetIsLoot() const noexcept { return isLoot; } + void SetIsLoot(const bool val) noexcept { isLoot = val; } + + const std::string& GetName() const noexcept { return m_Name; } + void Serialize(tinyxml2::XMLElement& behavior) const; void Deserialize(const tinyxml2::XMLElement& behavior); @@ -52,6 +59,9 @@ private: // Whether this behavior is custom or pre-fab. bool isLoot = false; + // Whether or not the behavior has been modified from its original state. + bool isTemplated; + // The last state that was edited. This is used so when the client re-opens the behavior editor, it will open to the last edited state. // If the last edited state has no strips, it will open to the first state that has strips. BehaviorState m_LastEditedState; diff --git a/tests/dGameTests/dGameMessagesTests/GameMessageTests.cpp b/tests/dGameTests/dGameMessagesTests/GameMessageTests.cpp index 7c33c6d0..5108cdec 100644 --- a/tests/dGameTests/dGameMessagesTests/GameMessageTests.cpp +++ b/tests/dGameTests/dGameMessagesTests/GameMessageTests.cpp @@ -213,7 +213,7 @@ TEST_F(GameMessageTests, ControlBehaviorAdd) { RakNet::BitStream inStream(reinterpret_cast(&data[0]), data.length(), true); const auto arr = ReadArrayFromBitStream(inStream); - AddMessage add(*arr); + AddMessage add(*arr, LWOOBJID_EMPTY); ASSERT_EQ(add.GetBehaviorId(), 10446); ASSERT_EQ(add.GetBehaviorIndex(), 0);