diff --git a/dDatabase/GameDatabase/GameDatabase.h b/dDatabase/GameDatabase/GameDatabase.h index bcd8550b..f52c8c4e 100644 --- a/dDatabase/GameDatabase/GameDatabase.h +++ b/dDatabase/GameDatabase/GameDatabase.h @@ -23,6 +23,7 @@ #include "IActivityLog.h" #include "IIgnoreList.h" #include "IAccountsRewardCodes.h" +#include "IBehaviors.h" namespace sql { class Statement; @@ -40,7 +41,8 @@ class GameDatabase : public IMail, public ICommandLog, public IPlayerCheatDetections, public IBugReports, public IPropertyContents, public IProperty, public IPetNames, public ICharXml, public IMigrationHistory, public IUgc, public IFriends, public ICharInfo, - public IAccounts, public IActivityLog, public IAccountsRewardCodes, public IIgnoreList { + public IAccounts, public IActivityLog, public IAccountsRewardCodes, public IIgnoreList, + public IBehaviors { public: virtual ~GameDatabase() = default; // TODO: These should be made private. diff --git a/dDatabase/GameDatabase/ITables/IBehaviors.h b/dDatabase/GameDatabase/ITables/IBehaviors.h new file mode 100644 index 00000000..531167e2 --- /dev/null +++ b/dDatabase/GameDatabase/ITables/IBehaviors.h @@ -0,0 +1,22 @@ +#ifndef IBEHAVIORS_H +#define IBEHAVIORS_H + +#include + +#include "dCommonVars.h" + +class IBehaviors { +public: + struct Info { + int32_t behaviorId{}; + uint32_t characterId{}; + std::string behaviorInfo; + }; + + // This Add also takes care of updating if it exists. + virtual void AddBehavior(const Info& info) = 0; + virtual std::string GetBehavior(const int32_t behaviorId) = 0; + virtual void RemoveBehavior(const int32_t behaviorId) = 0; +}; + +#endif //!IBEHAVIORS_H diff --git a/dDatabase/GameDatabase/ITables/IPropertyContents.h b/dDatabase/GameDatabase/ITables/IPropertyContents.h index c862ca94..0d8d8b5c 100644 --- a/dDatabase/GameDatabase/ITables/IPropertyContents.h +++ b/dDatabase/GameDatabase/ITables/IPropertyContents.h @@ -1,6 +1,7 @@ #ifndef __IPROPERTIESCONTENTS__H__ #define __IPROPERTIESCONTENTS__H__ +#include #include #include @@ -16,6 +17,7 @@ public: LWOOBJID id{}; LOT lot{}; uint32_t ugcId{}; + std::array behaviors{}; }; // Inserts a new UGC model into the database. @@ -32,7 +34,7 @@ public: virtual void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) = 0; // Update the model position and rotation for the given property id. - virtual void UpdateModelPositionRotation(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation) = 0; + virtual void UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array, 5>& behaviors) = 0; // Remove the model for the given property id. virtual void RemoveModel(const LWOOBJID& modelId) = 0; diff --git a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h index 836ab56c..689622d0 100644 --- a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h +++ b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h @@ -74,7 +74,7 @@ public: std::vector GetPropertyModels(const LWOOBJID& propertyId) override; void RemoveUnreferencedUgcModels() override; void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override; - void UpdateModelPositionRotation(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation) override; + void UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array, 5>& behaviors) override; void RemoveModel(const LWOOBJID& modelId) override; void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override; void InsertNewBugReport(const IBugReports::Info& info) override; @@ -108,6 +108,9 @@ public: std::vector GetIgnoreList(const uint32_t playerId) override; void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override; std::vector GetRewardCodesByAccountID(const uint32_t account_id) override; + void AddBehavior(const IBehaviors::Info& info) override; + std::string GetBehavior(const int32_t behaviorId) override; + void RemoveBehavior(const int32_t characterId) override; private: // Generic query functions that can be used for any query. diff --git a/dDatabase/GameDatabase/MySQL/Tables/Behaviors.cpp b/dDatabase/GameDatabase/MySQL/Tables/Behaviors.cpp new file mode 100644 index 00000000..f4647865 --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/Tables/Behaviors.cpp @@ -0,0 +1,19 @@ +#include "IBehaviors.h" + +#include "MySQLDatabase.h" + +void MySQLDatabase::AddBehavior(const IBehaviors::Info& info) { + ExecuteInsert( + "INSERT INTO behaviors (behavior_info, character_id, behavior_id) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE behavior_info = ?", + info.behaviorInfo, info.characterId, info.behaviorId, info.behaviorInfo + ); +} + +void MySQLDatabase::RemoveBehavior(const int32_t behaviorId) { + ExecuteDelete("DELETE FROM behaviors WHERE behavior_id = ?", behaviorId); +} + +std::string MySQLDatabase::GetBehavior(const int32_t behaviorId) { + auto result = ExecuteSelect("SELECT behavior_info FROM behaviors WHERE behavior_id = ?", behaviorId); + return result->next() ? result->getString("behavior_info").c_str() : ""; +} diff --git a/dDatabase/GameDatabase/MySQL/Tables/CMakeLists.txt b/dDatabase/GameDatabase/MySQL/Tables/CMakeLists.txt index 9f0e7baa..47cd220e 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/CMakeLists.txt +++ b/dDatabase/GameDatabase/MySQL/Tables/CMakeLists.txt @@ -2,6 +2,7 @@ set(DDATABASES_DATABASES_MYSQL_TABLES_SOURCES "Accounts.cpp" "AccountsRewardCodes.cpp" "ActivityLog.cpp" + "Behaviors.cpp" "BugReports.cpp" "CharInfo.cpp" "CharXml.cpp" diff --git a/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp b/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp index dba82d56..05998785 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp @@ -1,7 +1,10 @@ #include "MySQLDatabase.h" std::vector MySQLDatabase::GetPropertyModels(const LWOOBJID& propertyId) { - auto result = ExecuteSelect("SELECT id, lot, x, y, z, rx, ry, rz, rw, ugc_id FROM properties_contents WHERE property_id = ?;", propertyId); + auto result = ExecuteSelect( + "SELECT id, lot, x, y, z, rx, ry, rz, rw, ugc_id, " + "behavior_1, behavior_2, behavior_3, behavior_4, behavior_5 " + "FROM properties_contents WHERE property_id = ?;", propertyId); std::vector toReturn; toReturn.reserve(result->rowsCount()); @@ -17,6 +20,12 @@ std::vector MySQLDatabase::GetPropertyModels(const LWO model.rotation.y = result->getFloat("ry"); model.rotation.z = result->getFloat("rz"); model.ugcId = result->getUInt64("ugc_id"); + model.behaviors[0] = result->getInt("behavior_1"); + model.behaviors[1] = result->getInt("behavior_2"); + model.behaviors[2] = result->getInt("behavior_3"); + model.behaviors[3] = result->getInt("behavior_4"); + model.behaviors[4] = result->getInt("behavior_5"); + toReturn.push_back(std::move(model)); } return toReturn; @@ -32,21 +41,23 @@ void MySQLDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const IPr model.id, propertyId, model.ugcId == 0 ? std::nullopt : std::optional(model.ugcId), static_cast(model.lot), model.position.x, model.position.y, model.position.z, model.rotation.x, model.rotation.y, model.rotation.z, model.rotation.w, name, "", // Model description. TODO implement this. - 0, // behavior 1. TODO implement this. - 0, // behavior 2. TODO implement this. - 0, // behavior 3. TODO implement this. - 0, // behavior 4. TODO implement this. - 0 // behavior 5. TODO implement this. + model.behaviors[0], // behavior 1 + model.behaviors[1], // behavior 2 + model.behaviors[2], // behavior 3 + model.behaviors[3], // behavior 4 + model.behaviors[4] // behavior 5 ); } catch (sql::SQLException& e) { LOG("Error inserting new property model: %s", e.what()); } } -void MySQLDatabase::UpdateModelPositionRotation(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation) { +void MySQLDatabase::UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array, 5>& behaviors) { ExecuteUpdate( - "UPDATE properties_contents SET x = ?, y = ?, z = ?, rx = ?, ry = ?, rz = ?, rw = ? WHERE id = ?;", - position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, rotation.w, propertyId); + "UPDATE properties_contents SET x = ?, y = ?, z = ?, rx = ?, ry = ?, rz = ?, rw = ?, " + "behavior_1 = ?, behavior_2 = ?, behavior_3 = ?, behavior_4 = ?, behavior_5 = ? WHERE id = ?;", + position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, rotation.w, + behaviors[0].first, behaviors[1].first, behaviors[2].first, behaviors[3].first, behaviors[4].first, propertyId); } void MySQLDatabase::RemoveModel(const LWOOBJID& modelId) { diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 2210403c..6699c595 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -225,7 +225,7 @@ void Entity::Initialize() { AddComponent(simplePhysicsComponentID); - AddComponent(); + AddComponent()->LoadBehaviors(); AddComponent(); @@ -649,7 +649,7 @@ void Entity::Initialize() { } if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MODEL, -1) != -1 && !GetComponent()) { - AddComponent(); + AddComponent()->LoadBehaviors(); if (!HasComponent(eReplicaComponentType::DESTROYABLE)) { auto* destroyableComponent = AddComponent(); destroyableComponent->SetHealth(1); diff --git a/dGame/dComponents/ModelComponent.cpp b/dGame/dComponents/ModelComponent.cpp index 75f2a019..91680987 100644 --- a/dGame/dComponents/ModelComponent.cpp +++ b/dGame/dComponents/ModelComponent.cpp @@ -6,6 +6,9 @@ #include "BehaviorStates.h" #include "ControlBehaviorMsgs.h" +#include "tinyxml2.h" + +#include "Database.h" ModelComponent::ModelComponent(Entity* parent) : Component(parent) { m_OriginalPosition = m_Parent->GetDefaultPosition(); @@ -14,6 +17,33 @@ ModelComponent::ModelComponent(Entity* parent) : Component(parent) { m_userModelID = m_Parent->GetVarAs(u"userModelID"); } +void ModelComponent::LoadBehaviors() { + auto behaviors = GeneralUtils::SplitString(m_Parent->GetVar(u"userModelBehaviors"), ','); + for (const auto& behavior : behaviors) { + if (behavior.empty()) continue; + + 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); + + const auto behaviorStr = Database::Get()->GetBehavior(behaviorId.value()); + + tinyxml2::XMLDocument behaviorXml; + auto res = behaviorXml.Parse(behaviorStr.c_str(), behaviorStr.size()); + LOG_DEBUG("Behavior %i %d: %s", res, behaviorId.value(), behaviorStr.c_str()); + + const auto* const behaviorRoot = behaviorXml.FirstChildElement("Behavior"); + if (!behaviorRoot) { + LOG("Failed to load behavior %d due to missing behavior root", behaviorId.value()); + continue; + } + inserted.Deserialize(*behaviorRoot); + } +} + void ModelComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { // ItemComponent Serialization. Pets do not get this serialization. if (!m_Parent->HasComponent(eReplicaComponentType::PET)) { @@ -72,3 +102,23 @@ void ModelComponent::MoveToInventory(MoveToInventoryMessage& msg) { m_Behaviors.erase(m_Behaviors.begin() + msg.GetBehaviorIndex()); // TODO move to the inventory } + +std::array, 5> ModelComponent::GetBehaviorsForSave() const { + std::array, 5> toReturn{}; + for (auto i = 0; i < m_Behaviors.size(); i++) { + const auto& behavior = m_Behaviors.at(i); + 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(); + } + return toReturn; +} diff --git a/dGame/dComponents/ModelComponent.h b/dGame/dComponents/ModelComponent.h index dc6810eb..9e23eafb 100644 --- a/dGame/dComponents/ModelComponent.h +++ b/dGame/dComponents/ModelComponent.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "dCommonVars.h" @@ -28,6 +29,8 @@ public: ModelComponent(Entity* parent); + void LoadBehaviors(); + void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; /** @@ -109,6 +112,8 @@ public: void VerifyBehaviors(); + std::array, 5> GetBehaviorsForSave() const; + private: /** * The behaviors of the model diff --git a/dGame/dComponents/PropertyManagementComponent.cpp b/dGame/dComponents/PropertyManagementComponent.cpp index ad96097b..d7be22ac 100644 --- a/dGame/dComponents/PropertyManagementComponent.cpp +++ b/dGame/dComponents/PropertyManagementComponent.cpp @@ -21,9 +21,11 @@ #include "eObjectBits.h" #include "CharacterComponent.h" #include "PlayerManager.h" +#include "ModelComponent.h" #include #include "CppScripts.h" +#include PropertyManagementComponent* PropertyManagementComponent::instance = nullptr; @@ -593,6 +595,20 @@ void PropertyManagementComponent::Load() { settings.push_back(new LDFData(u"componentWhitelist", 1)); } + std::ostringstream userModelBehavior; + bool firstAdded = false; + for (auto behavior : databaseModel.behaviors) { + if (behavior < 0) { + LOG("Invalid behavior ID: %d, removing behavior reference from model", behavior); + behavior = 0; + } + if (firstAdded) userModelBehavior << ","; + userModelBehavior << behavior; + firstAdded = true; + } + + settings.push_back(new LDFData(u"userModelBehaviors", userModelBehavior.str())); + node->config = settings; const auto spawnerId = Game::zoneManager->MakeSpawner(info); @@ -610,6 +626,12 @@ void PropertyManagementComponent::Save() { return; } + const auto* const owner = GetOwner(); + if (!owner) return; + + const auto* const character = owner->GetCharacter(); + if (!character) return; + auto present = Database::Get()->GetPropertyModels(propertyId); std::vector modelIds; @@ -624,6 +646,20 @@ void PropertyManagementComponent::Save() { if (entity == nullptr) { continue; } + auto* modelComponent = entity->GetComponent(); + if (!modelComponent) continue; + const auto modelBehaviors = modelComponent->GetBehaviorsForSave(); + + // save the behaviors of the model + for (const auto& [behaviorId, behaviorStr] : modelBehaviors) { + if (behaviorStr.empty() || behaviorId == -1 || behaviorId == 0) continue; + IBehaviors::Info info { + .behaviorId = behaviorId, + .characterId = character->GetID(), + .behaviorInfo = behaviorStr + }; + Database::Get()->AddBehavior(info); + } const auto position = entity->GetPosition(); const auto rotation = entity->GetRotation(); @@ -635,10 +671,13 @@ void PropertyManagementComponent::Save() { model.position = position; model.rotation = rotation; model.ugcId = 0; + for (auto i = 0; i < model.behaviors.size(); i++) { + model.behaviors[i] = modelBehaviors[i].first; + } Database::Get()->InsertNewPropertyModel(propertyId, model, "Objects_" + std::to_string(model.lot) + "_name"); } else { - Database::Get()->UpdateModelPositionRotation(id, position, rotation); + Database::Get()->UpdateModel(id, position, rotation, modelBehaviors); } } diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.cpp b/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.cpp index 3e62a2d7..6a21be9b 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.cpp +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.cpp @@ -1,6 +1,8 @@ #include "Action.h" #include "Amf3.h" +#include "tinyxml2.h" + Action::Action(const AMFArrayValue& arguments) { for (const auto& [paramName, paramValue] : arguments.GetAssociative()) { if (paramName == "Type") { @@ -32,3 +34,46 @@ void Action::SendBehaviorBlocksToClient(AMFArrayValue& args) const { actionArgs->Insert(m_ValueParameterName, m_ValueParameterDouble); } } + +void Action::Serialize(tinyxml2::XMLElement& action) const { + action.SetAttribute("Type", m_Type.c_str()); + + if (m_ValueParameterName.empty()) return; + + action.SetAttribute("ValueParameterName", m_ValueParameterName.c_str()); + + if (m_ValueParameterName == "Message") { + action.SetAttribute("Value", m_ValueParameterString.c_str()); + } else { + action.SetAttribute("Value", m_ValueParameterDouble); + } +} + +void Action::Deserialize(const tinyxml2::XMLElement& action) { + const char* type = nullptr; + action.QueryAttribute("Type", &type); + if (!type) { + LOG("No type found for an action?"); + return; + } + + m_Type = type; + + const char* valueParameterName = nullptr; + action.QueryAttribute("ValueParameterName", &valueParameterName); + if (valueParameterName) { + m_ValueParameterName = valueParameterName; + + if (m_ValueParameterName == "Message") { + const char* value = nullptr; + action.QueryAttribute("Value", &value); + if (value) { + m_ValueParameterString = value; + } else { + LOG("No value found for an action message?"); + } + } else { + action.QueryDoubleAttribute("Value", &m_ValueParameterDouble); + } + } +} diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.h index 988e616c..8146e08d 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.h @@ -3,6 +3,10 @@ #include +namespace tinyxml2 { + class XMLElement; +}; + class AMFArrayValue; /** @@ -20,6 +24,8 @@ public: void SendBehaviorBlocksToClient(AMFArrayValue& args) const; + void Serialize(tinyxml2::XMLElement& action) const; + void Deserialize(const tinyxml2::XMLElement& action); private: double m_ValueParameterDouble{ 0.0 }; std::string m_Type{ "" }; diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.cpp b/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.cpp index 56dc43ff..ae153a5f 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.cpp +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.cpp @@ -1,6 +1,7 @@ #include "StripUiPosition.h" #include "Amf3.h" +#include "tinyxml2.h" StripUiPosition::StripUiPosition(const AMFArrayValue& arguments, const std::string& uiKeyName) { const auto* const uiArray = arguments.GetArray(uiKeyName); @@ -21,3 +22,13 @@ void StripUiPosition::SendBehaviorBlocksToClient(AMFArrayValue& args) const { uiArgs->Insert("x", m_XPosition); uiArgs->Insert("y", m_YPosition); } + +void StripUiPosition::Serialize(tinyxml2::XMLElement& position) const { + position.SetAttribute("x", m_XPosition); + position.SetAttribute("y", m_YPosition); +} + +void StripUiPosition::Deserialize(const tinyxml2::XMLElement& position) { + position.QueryDoubleAttribute("x", &m_XPosition); + position.QueryDoubleAttribute("y", &m_YPosition); +} diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.h index f202210d..47501ff7 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.h @@ -3,6 +3,10 @@ class AMFArrayValue; +namespace tinyxml2 { + class XMLElement; +} + /** * @brief The position of the first Action in a Strip * @@ -15,6 +19,8 @@ public: [[nodiscard]] double GetX() const noexcept { return m_XPosition; } [[nodiscard]] double GetY() const noexcept { return m_YPosition; } + void Serialize(tinyxml2::XMLElement& position) const; + void Deserialize(const tinyxml2::XMLElement& position); private: double m_XPosition{ 0.0 }; double m_YPosition{ 0.0 }; diff --git a/dGame/dPropertyBehaviors/PropertyBehavior.cpp b/dGame/dPropertyBehaviors/PropertyBehavior.cpp index 423751c4..5bdb5827 100644 --- a/dGame/dPropertyBehaviors/PropertyBehavior.cpp +++ b/dGame/dPropertyBehaviors/PropertyBehavior.cpp @@ -3,6 +3,7 @@ #include "Amf3.h" #include "BehaviorStates.h" #include "ControlBehaviorMsgs.h" +#include "tinyxml2.h" PropertyBehavior::PropertyBehavior() { m_LastEditedState = BehaviorState::HOME_STATE; @@ -124,3 +125,31 @@ void PropertyBehavior::SendBehaviorBlocksToClient(AMFArrayValue& args) const { // TODO Serialize the execution state of the behavior } + +void PropertyBehavior::Serialize(tinyxml2::XMLElement& behavior) const { + behavior.SetAttribute("id", m_BehaviorId); + behavior.SetAttribute("name", m_Name.c_str()); + behavior.SetAttribute("isLocked", isLocked); + behavior.SetAttribute("isLoot", isLoot); + + for (const auto& [stateId, state] : m_States) { + if (state.IsEmpty()) continue; + auto* const stateElement = behavior.InsertNewChildElement("State"); + stateElement->SetAttribute("id", static_cast(stateId)); + state.Serialize(*stateElement); + } +} + + +void PropertyBehavior::Deserialize(const tinyxml2::XMLElement& behavior) { + m_Name = behavior.Attribute("name"); + behavior.QueryBoolAttribute("isLocked", &isLocked); + behavior.QueryBoolAttribute("isLoot", &isLoot); + + for (const auto* stateElement = behavior.FirstChildElement("State"); stateElement; stateElement = stateElement->NextSiblingElement("State")) { + int32_t stateId = -1; + stateElement->QueryIntAttribute("id", &stateId); + if (stateId < 0 || stateId > 5) continue; + m_States[static_cast(stateId)].Deserialize(*stateElement); + } +} diff --git a/dGame/dPropertyBehaviors/PropertyBehavior.h b/dGame/dPropertyBehaviors/PropertyBehavior.h index c9cb4b98..01eb1968 100644 --- a/dGame/dPropertyBehaviors/PropertyBehavior.h +++ b/dGame/dPropertyBehaviors/PropertyBehavior.h @@ -3,6 +3,10 @@ #include "State.h" +namespace tinyxml2 { + class XMLElement; +} + enum class BehaviorState : uint32_t; class AMFArrayValue; @@ -25,6 +29,8 @@ public: [[nodiscard]] int32_t GetBehaviorId() const noexcept { return m_BehaviorId; } void SetBehaviorId(int32_t id) noexcept { m_BehaviorId = id; } + void Serialize(tinyxml2::XMLElement& behavior) const; + void Deserialize(const tinyxml2::XMLElement& behavior); private: // The states this behavior has. diff --git a/dGame/dPropertyBehaviors/State.cpp b/dGame/dPropertyBehaviors/State.cpp index 0c8a11d9..1fb072c1 100644 --- a/dGame/dPropertyBehaviors/State.cpp +++ b/dGame/dPropertyBehaviors/State.cpp @@ -2,6 +2,7 @@ #include "Amf3.h" #include "ControlBehaviorMsgs.h" +#include "tinyxml2.h" template <> void State::HandleMsg(AddStripMessage& msg) { @@ -134,4 +135,20 @@ void State::SendBehaviorBlocksToClient(AMFArrayValue& args) const { strip.SendBehaviorBlocksToClient(*stripArgs); } -}; +} + +void State::Serialize(tinyxml2::XMLElement& state) const { + for (const auto& strip : m_Strips) { + if (strip.IsEmpty()) continue; + + auto* const stripElement = state.InsertNewChildElement("Strip"); + strip.Serialize(*stripElement); + } +} + +void State::Deserialize(const tinyxml2::XMLElement& state) { + for (const auto* stripElement = state.FirstChildElement("Strip"); stripElement; stripElement = stripElement->NextSiblingElement("Strip")) { + auto& strip = m_Strips.emplace_back(); + strip.Deserialize(*stripElement); + } +} diff --git a/dGame/dPropertyBehaviors/State.h b/dGame/dPropertyBehaviors/State.h index f0425763..3e8c827f 100644 --- a/dGame/dPropertyBehaviors/State.h +++ b/dGame/dPropertyBehaviors/State.h @@ -3,6 +3,10 @@ #include "Strip.h" +namespace tinyxml2 { + class XMLElement; +} + class AMFArrayValue; class State { @@ -13,6 +17,8 @@ public: void SendBehaviorBlocksToClient(AMFArrayValue& args) const; bool IsEmpty() const; + void Serialize(tinyxml2::XMLElement& state) const; + void Deserialize(const tinyxml2::XMLElement& state); private: std::vector m_Strips; }; diff --git a/dGame/dPropertyBehaviors/Strip.cpp b/dGame/dPropertyBehaviors/Strip.cpp index 0f459e46..d6dc050b 100644 --- a/dGame/dPropertyBehaviors/Strip.cpp +++ b/dGame/dPropertyBehaviors/Strip.cpp @@ -2,6 +2,7 @@ #include "Amf3.h" #include "ControlBehaviorMsgs.h" +#include "tinyxml2.h" template <> void Strip::HandleMsg(AddStripMessage& msg) { @@ -83,4 +84,25 @@ void Strip::SendBehaviorBlocksToClient(AMFArrayValue& args) const { for (const auto& action : m_Actions) { action.SendBehaviorBlocksToClient(*actions); } -}; +} + +void Strip::Serialize(tinyxml2::XMLElement& strip) const { + auto* const positionElement = strip.InsertNewChildElement("Position"); + m_Position.Serialize(*positionElement); + for (const auto& action : m_Actions) { + auto* const actionElement = strip.InsertNewChildElement("Action"); + action.Serialize(*actionElement); + } +} + +void Strip::Deserialize(const tinyxml2::XMLElement& strip) { + const auto* positionElement = strip.FirstChildElement("Position"); + if (positionElement) { + m_Position.Deserialize(*positionElement); + } + + for (const auto* actionElement = strip.FirstChildElement("Action"); actionElement; actionElement = actionElement->NextSiblingElement("Action")) { + auto& action = m_Actions.emplace_back(); + action.Deserialize(*actionElement); + } +} diff --git a/dGame/dPropertyBehaviors/Strip.h b/dGame/dPropertyBehaviors/Strip.h index 107fee11..8fd7d0fe 100644 --- a/dGame/dPropertyBehaviors/Strip.h +++ b/dGame/dPropertyBehaviors/Strip.h @@ -6,6 +6,10 @@ #include +namespace tinyxml2 { + class XMLElement; +} + class AMFArrayValue; class Strip { @@ -16,6 +20,8 @@ public: void SendBehaviorBlocksToClient(AMFArrayValue& args) const; bool IsEmpty() const noexcept { return m_Actions.empty(); } + void Serialize(tinyxml2::XMLElement& strip) const; + void Deserialize(const tinyxml2::XMLElement& strip); private: std::vector m_Actions; StripUiPosition m_Position; diff --git a/migrations/dlu/15_behavior_owner.sql b/migrations/dlu/15_behavior_owner.sql new file mode 100644 index 00000000..53e76f82 --- /dev/null +++ b/migrations/dlu/15_behavior_owner.sql @@ -0,0 +1,3 @@ +ALTER TABLE behaviors ADD COLUMN character_id BIGINT NOT NULL DEFAULT 0; +ALTER TABLE behaviors ADD COLUMN behavior_id BIGINT NOT NULL PRIMARY KEY; +ALTER TABLE behaviors DROP COLUMN id;