diff --git a/dCommon/Logger.cpp b/dCommon/Logger.cpp index 1888f1eb..571b9661 100644 --- a/dCommon/Logger.cpp +++ b/dCommon/Logger.cpp @@ -53,8 +53,8 @@ void Logger::vLog(const char* format, va_list args) { struct tm* time = localtime(&t); char timeStr[70]; strftime(timeStr, sizeof(timeStr), "[%d-%m-%y %H:%M:%S ", time); - char message[2048]; - vsnprintf(message, 2048, format, args); + char message[131072]; + vsnprintf(message, 131072, format, args); for (const auto& writer : m_Writers) { writer->Log(timeStr, message); } 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..02b5178b --- /dev/null +++ b/dDatabase/GameDatabase/ITables/IBehaviors.h @@ -0,0 +1,20 @@ +#ifndef __IBEHAVIORS__H__ +#define __IBEHAVIORS__H__ + +#include + +#include "dCommonVars.h" + +class IBehaviors { +public: + struct Info { + LWOOBJID behaviorId{}; + uint32_t characterId{}; + std::string behaviorInfo; + }; + + virtual void AddBehavior(const Info& info) = 0; + virtual void RemoveBehavior(const uint32_t behaviorId) = 0; +}; + +#endif //!__IBEHAVIORS__H__ diff --git a/dDatabase/GameDatabase/ITables/IPropertyContents.h b/dDatabase/GameDatabase/ITables/IPropertyContents.h index c862ca94..25271302 100644 --- a/dDatabase/GameDatabase/ITables/IPropertyContents.h +++ b/dDatabase/GameDatabase/ITables/IPropertyContents.h @@ -16,6 +16,7 @@ public: LWOOBJID id{}; LOT lot{}; uint32_t ugcId{}; + std::array behaviors{}; }; // Inserts a new UGC model into the database. @@ -32,7 +33,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..29df611c 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,8 @@ 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; + void RemoveBehavior(const uint32_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..91bcbd81 --- /dev/null +++ b/dDatabase/GameDatabase/MySQL/Tables/Behaviors.cpp @@ -0,0 +1,14 @@ +#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 uint32_t behaviorId) { + ExecuteDelete("DELETE FROM behaviors WHERE behavior_id = ?", behaviorId); +} 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..2f80b9df 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/PropertyContents.cpp @@ -32,21 +32,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, propertyId, + behaviors[0].first, behaviors[1].first, behaviors[2].first, behaviors[3].first, behaviors[4].first); } void MySQLDatabase::RemoveModel(const LWOOBJID& modelId) { diff --git a/dGame/Character.cpp b/dGame/Character.cpp index 59a67462..7e0f73ec 100644 --- a/dGame/Character.cpp +++ b/dGame/Character.cpp @@ -239,7 +239,7 @@ void Character::SaveXMLToDatabase() { auto zoneInfo = Game::zoneManager->GetZone()->GetZoneID(); // lzid garbage, binary concat of zoneID, zoneInstance and zoneClone - if (zoneInfo.GetMapID() != 0 && zoneInfo.GetCloneID() == 0 && !Game::zoneManager->GetDisableSaveLocation()) { + // if (zoneInfo.GetMapID() != 0 && zoneInfo.GetCloneID() == 0 && !Game::zoneManager->GetDisableSaveLocation()) { uint64_t lzidConcat = zoneInfo.GetCloneID(); lzidConcat = (lzidConcat << 16) | uint16_t(zoneInfo.GetInstanceID()); lzidConcat = (lzidConcat << 16) | uint16_t(zoneInfo.GetMapID()); @@ -251,7 +251,7 @@ void Character::SaveXMLToDatabase() { // Set the target scene, custom attribute character->SetAttribute("tscene", m_TargetScene.c_str()); - } + // } auto emotes = character->FirstChildElement("ue"); if (!emotes) emotes = m_Doc.NewElement("ue"); diff --git a/dGame/dComponents/ControllablePhysicsComponent.cpp b/dGame/dComponents/ControllablePhysicsComponent.cpp index 18e4b19d..c969a7dd 100644 --- a/dGame/dComponents/ControllablePhysicsComponent.cpp +++ b/dGame/dComponents/ControllablePhysicsComponent.cpp @@ -187,7 +187,7 @@ void ControllablePhysicsComponent::UpdateXml(tinyxml2::XMLDocument& doc) { auto zoneInfo = Game::zoneManager->GetZone()->GetZoneID(); - if (zoneInfo.GetMapID() != 0 && zoneInfo.GetCloneID() == 0 && !Game::zoneManager->GetDisableSaveLocation()) { + // if (zoneInfo.GetMapID() != 0 && zoneInfo.GetCloneID() == 0 && !Game::zoneManager->GetDisableSaveLocation()) { character->SetAttribute("lzx", m_Position.x); character->SetAttribute("lzy", m_Position.y); character->SetAttribute("lzz", m_Position.z); @@ -195,7 +195,7 @@ void ControllablePhysicsComponent::UpdateXml(tinyxml2::XMLDocument& doc) { character->SetAttribute("lzry", m_Rotation.y); character->SetAttribute("lzrz", m_Rotation.z); character->SetAttribute("lzrw", m_Rotation.w); - } + // } } void ControllablePhysicsComponent::SetPosition(const NiPoint3& pos) { diff --git a/dGame/dComponents/ModelComponent.cpp b/dGame/dComponents/ModelComponent.cpp index 75f2a019..91e31e39 100644 --- a/dGame/dComponents/ModelComponent.cpp +++ b/dGame/dComponents/ModelComponent.cpp @@ -6,6 +6,7 @@ #include "BehaviorStates.h" #include "ControlBehaviorMsgs.h" +#include "tinyxml2.h" ModelComponent::ModelComponent(Entity* parent) : Component(parent) { m_OriginalPosition = m_Parent->GetDefaultPosition(); @@ -72,3 +73,22 @@ 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, false, 0); + doc.Print(&printer); + behaviorData = printer.CStr(); + } + return toReturn; +} diff --git a/dGame/dComponents/ModelComponent.h b/dGame/dComponents/ModelComponent.h index dc6810eb..5b76708f 100644 --- a/dGame/dComponents/ModelComponent.h +++ b/dGame/dComponents/ModelComponent.h @@ -109,6 +109,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..6c393df1 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; @@ -610,6 +612,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 +632,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 == LWOOBJID_EMPTY) 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 +657,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..4c917b48 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,15 @@ 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; + + if (m_ValueParameterName == "Message") { + action.SetAttribute(m_ValueParameterName.c_str(), m_ValueParameterString.c_str()); + } else { + action.SetAttribute(m_ValueParameterName.c_str(), m_ValueParameterDouble); + } +} diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.h index 988e616c..2f618316 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,7 @@ public: void SendBehaviorBlocksToClient(AMFArrayValue& args) const; + void Serialize(tinyxml2::XMLElement& action) const; 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..02f64879 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,8 @@ 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); +} diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.h index f202210d..7bc2087e 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,7 @@ public: [[nodiscard]] double GetX() const noexcept { return m_XPosition; } [[nodiscard]] double GetY() const noexcept { return m_YPosition; } + void Serialize(tinyxml2::XMLElement& position) const; 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..278012ea 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,17 @@ 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); + } +} diff --git a/dGame/dPropertyBehaviors/PropertyBehavior.h b/dGame/dPropertyBehaviors/PropertyBehavior.h index c9cb4b98..50585f98 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,7 @@ 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; private: // The states this behavior has. diff --git a/dGame/dPropertyBehaviors/State.cpp b/dGame/dPropertyBehaviors/State.cpp index 0c8a11d9..9b321bad 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,13 @@ 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); + } +} diff --git a/dGame/dPropertyBehaviors/State.h b/dGame/dPropertyBehaviors/State.h index f0425763..e866b196 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,7 @@ public: void SendBehaviorBlocksToClient(AMFArrayValue& args) const; bool IsEmpty() const; + void Serialize(tinyxml2::XMLElement& state) const; private: std::vector m_Strips; }; diff --git a/dGame/dPropertyBehaviors/Strip.cpp b/dGame/dPropertyBehaviors/Strip.cpp index 0f459e46..8271a240 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,13 @@ 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); + } +} diff --git a/dGame/dPropertyBehaviors/Strip.h b/dGame/dPropertyBehaviors/Strip.h index 107fee11..d8c6b9cf 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,7 @@ public: void SendBehaviorBlocksToClient(AMFArrayValue& args) const; bool IsEmpty() const noexcept { return m_Actions.empty(); } + void Serialize(tinyxml2::XMLElement& strip) const; 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;