diff --git a/dGame/dComponents/ModelComponent.cpp b/dGame/dComponents/ModelComponent.cpp index ccdad2817..3f8858c04 100644 --- a/dGame/dComponents/ModelComponent.cpp +++ b/dGame/dComponents/ModelComponent.cpp @@ -1,6 +1,12 @@ #include "ModelComponent.h" #include "Entity.h" +#include "Game.h" +#include "Logger.h" + +#include "BehaviorStates.h" +#include "ControlBehaviorMsgs.h" + ModelComponent::ModelComponent(Entity* parent) : Component(parent) { m_OriginalPosition = m_Parent->GetDefaultPosition(); m_OriginalRotation = m_Parent->GetDefaultRotation(); @@ -29,3 +35,40 @@ void ModelComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialU outBitStream->Write1(); // Is this model paused if (bIsInitialUpdate) outBitStream->Write0(); // We are not writing model editing info } + +void ModelComponent::UpdatePendingBehaviorId(const int32_t newId) { + for (auto& behavior : m_Behaviors) if (behavior.GetBehaviorId() == -1) behavior.SetBehaviorId(newId); +} + +void ModelComponent::SendBehaviorListToClient(AMFArrayValue& args) const { + args.Insert("objectID", std::to_string(m_Parent->GetObjectID())); + + auto* behaviorArray = args.InsertArray("behaviors"); + for (auto& behavior : m_Behaviors) { + auto* behaviorArgs = behaviorArray->PushArray(); + behavior.SendBehaviorListToClient(*behaviorArgs); + } +} + +void ModelComponent::VerifyBehaviors() { + for (auto& behavior : m_Behaviors) behavior.VerifyLastEditedState(); +} + +void ModelComponent::SendBehaviorBlocksToClient(int32_t behaviorToSend, AMFArrayValue& args) const { + args.Insert("BehaviorID", std::to_string(behaviorToSend)); + args.Insert("objectID", std::to_string(m_Parent->GetObjectID())); + for (auto& behavior : m_Behaviors) if (behavior.GetBehaviorId() == behaviorToSend) behavior.SendBehaviorBlocksToClient(args); +} + +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); +} + +void ModelComponent::MoveToInventory(MoveToInventoryMessage& msg) { + if (msg.GetBehaviorIndex() >= m_Behaviors.size() || m_Behaviors.at(msg.GetBehaviorIndex()).GetBehaviorId() != msg.GetBehaviorId()) return; + m_Behaviors.erase(m_Behaviors.begin() + msg.GetBehaviorIndex()); + // TODO move to the inventory +} diff --git a/dGame/dComponents/ModelComponent.h b/dGame/dComponents/ModelComponent.h index dd9984e72..a36328beb 100644 --- a/dGame/dComponents/ModelComponent.h +++ b/dGame/dComponents/ModelComponent.h @@ -1,4 +1,7 @@ #pragma once + +#include + #include "dCommonVars.h" #include "RakNetTypes.h" #include "NiPoint3.h" @@ -6,7 +9,15 @@ #include "Component.h" #include "eReplicaComponentType.h" +#include "Action.h" +#include "PropertyBehavior.h" +#include "StripUiPosition.h" + +class AddMessage; +class AMFArrayValue; +class BehaviorMessageBase; class Entity; +class MoveToInventoryMessage; /** * Component that represents entities that are a model, e.g. collectible models and BBB models. @@ -43,7 +54,68 @@ class ModelComponent : public Component { */ void SetRotation(const NiQuaternion& rot) { m_OriginalRotation = rot; } + /** + * Main gateway for all behavior messages to be passed to their respective behaviors. + * + * @tparam Msg The message type to pass + * @param args the arguments of the message to be deserialized + */ + template + void HandleControlBehaviorsMsg(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.HandleMsg(msg); + return; + } + } + + // If we somehow added more than 5 behaviors, resize to 5. + 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; + + 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->HandleMsg(msg); + }; + + void AddBehavior(AddMessage& msg); + + void MoveToInventory(MoveToInventoryMessage& msg); + + // Updates the pending behavior ID to the new ID. + void UpdatePendingBehaviorId(const int32_t newId); + + // Sends the behavior list to the client. + + /** + * The behaviors AMFArray will have up to 5 elements in the dense portion. + * Each element in the dense portion will be made up of another AMFArray + * with the following information mapped in the associative portion + * "id": Behavior ID cast to an AMFString + * "isLocked": AMFTrue or AMFFalse of whether or not the behavior is locked + * "isLoot": AMFTrue or AMFFalse of whether or not the behavior is a custom behavior (true if custom) + * "name": The name of the behavior formatted as an AMFString + */ + void SendBehaviorListToClient(AMFArrayValue& args) const; + + void SendBehaviorBlocksToClient(int32_t behaviorToSend, AMFArrayValue& args) const; + + void VerifyBehaviors(); + private: + /** + * The behaviors of the model + * Note: This is a vector because the order of the behaviors matters when serializing to the client. + * Note: No two PropertyBehaviors should have the same behavior ID. + */ + std::vector m_Behaviors; /** * The original position of the model diff --git a/dGame/dPropertyBehaviors/CMakeLists.txt b/dGame/dPropertyBehaviors/CMakeLists.txt index 5e33a5f5c..8bffa9065 100644 --- a/dGame/dPropertyBehaviors/CMakeLists.txt +++ b/dGame/dPropertyBehaviors/CMakeLists.txt @@ -1,4 +1,7 @@ set(DGAME_DPROPERTYBEHAVIORS_SOURCES + "PropertyBehavior.cpp" + "State.cpp" + "Strip.cpp" "BlockDefinition.cpp" "ControlBehaviors.cpp" ) diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.cpp b/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.cpp index eabe76b74..73f1391d5 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.cpp +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.cpp @@ -1,4 +1,5 @@ #include "Action.h" +#include "Amf3.h" Action::Action() { type = ""; @@ -12,20 +13,34 @@ Action::Action(AMFArrayValue* arguments) { valueParameterName = ""; valueParameterString = ""; valueParameterDouble = 0.0; - for (auto& typeValueMap : arguments->GetAssociative()) { - if (typeValueMap.first == "Type") { - if (typeValueMap.second->GetValueType() != eAmf::String) continue; - type = static_cast(typeValueMap.second)->GetValue(); + for (auto& [paramName, paramValue] : arguments->GetAssociative()) { + if (paramName == "Type") { + if (paramValue->GetValueType() != eAmf::String) continue; + type = static_cast(paramValue)->GetValue(); } else { - valueParameterName = typeValueMap.first; + valueParameterName = paramName; // Message is the only known string parameter if (valueParameterName == "Message") { - if (typeValueMap.second->GetValueType() != eAmf::String) continue; - valueParameterString = static_cast(typeValueMap.second)->GetValue(); + if (paramValue->GetValueType() != eAmf::String) continue; + valueParameterString = static_cast(paramValue)->GetValue(); } else { - if (typeValueMap.second->GetValueType() != eAmf::Double) continue; - valueParameterDouble = static_cast(typeValueMap.second)->GetValue(); + if (paramValue->GetValueType() != eAmf::Double) continue; + valueParameterDouble = static_cast(paramValue)->GetValue(); } } } } + +void Action::SendBehaviorBlocksToClient(AMFArrayValue& args) const { + auto* actionArgs = args.PushArray(); + actionArgs->Insert("Type", type); + + auto valueParameterName = GetValueParameterName(); + if (valueParameterName.empty()) return; + + if (valueParameterName == "Message") { + actionArgs->Insert(valueParameterName, valueParameterString); + } else { + actionArgs->Insert(valueParameterName, valueParameterDouble); + } +} diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.h index c97b4050b..df6658896 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/Action.h @@ -1,7 +1,9 @@ #ifndef __ACTION__H__ #define __ACTION__H__ -#include "BehaviorMessageBase.h" +#include + +class AMFArrayValue; /** * @brief Sent if a ControlBehavior message has an Action associated with it @@ -11,10 +13,12 @@ class Action { public: Action(); Action(AMFArrayValue* arguments); - const std::string& GetType() { return type; }; - const std::string& GetValueParameterName() { return valueParameterName; }; - const std::string& GetValueParameterString() { return valueParameterString; }; - const double GetValueParameterDouble() { return valueParameterDouble; }; + const std::string& GetType() const { return type; }; + const std::string& GetValueParameterName() const { return valueParameterName; }; + const std::string& GetValueParameterString() const { return valueParameterString; }; + const double GetValueParameterDouble() const { return valueParameterDouble; }; + + void SendBehaviorBlocksToClient(AMFArrayValue& args) const; private: std::string type; std::string valueParameterName; diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/ActionContext.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/ActionContext.h index 5f46fd8c5..91e91e728 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/ActionContext.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/ActionContext.h @@ -14,8 +14,8 @@ class ActionContext { public: ActionContext(); ActionContext(AMFArrayValue* arguments, std::string customStateKey = "stateID", std::string customStripKey = "stripID"); - const StripId GetStripId() { return stripId; }; - const BehaviorState GetStateId() { return stateId; }; + const StripId GetStripId() const { return stripId; }; + const BehaviorState GetStateId() const { return stateId; }; private: BehaviorState GetBehaviorStateFromArgument(AMFArrayValue* arguments, const std::string& key); StripId GetStripIdFromArgument(AMFArrayValue* arguments, const std::string& key); diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/AddActionMessage.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/AddActionMessage.h index 4faf6a53e..ac3a96122 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/AddActionMessage.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/AddActionMessage.h @@ -14,11 +14,11 @@ class AMFArrayValue; class AddActionMessage : public BehaviorMessageBase { public: AddActionMessage(AMFArrayValue* arguments); - const uint32_t GetActionIndex() { return actionIndex; }; - Action GetAction() { return action; }; - ActionContext GetActionContext() { return actionContext; }; + int32_t GetActionIndex() const { return actionIndex; }; + Action GetAction() const { return action; }; + ActionContext GetActionContext() const { return actionContext; }; private: - uint32_t actionIndex; + int32_t actionIndex = -1; ActionContext actionContext; Action action; }; diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/AddMessage.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/AddMessage.h index a46d5f98a..766276655 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/AddMessage.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/AddMessage.h @@ -10,7 +10,7 @@ class AddMessage : public BehaviorMessageBase { public: AddMessage(AMFArrayValue* arguments); - const uint32_t GetBehaviorIndex() { return behaviorIndex; }; + const uint32_t GetBehaviorIndex() const { return behaviorIndex; }; private: uint32_t behaviorIndex; }; diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/AddStripMessage.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/AddStripMessage.h index db75aef77..0b9a09e3d 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/AddStripMessage.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/AddStripMessage.h @@ -19,9 +19,9 @@ class AMFArrayValue; class AddStripMessage : public BehaviorMessageBase { public: AddStripMessage(AMFArrayValue* arguments); - StripUiPosition GetPosition() { return position; }; - ActionContext GetActionContext() { return actionContext; }; - std::vector GetActionsToAdd() { return actionsToAdd; }; + StripUiPosition GetPosition() const { return position; }; + ActionContext GetActionContext() const { return actionContext; }; + std::vector GetActionsToAdd() const { return actionsToAdd; }; private: StripUiPosition position; ActionContext actionContext; diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/BehaviorMessageBase.cpp b/dGame/dPropertyBehaviors/ControlBehaviorMessages/BehaviorMessageBase.cpp index 3286504a2..cc817cd71 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/BehaviorMessageBase.cpp +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/BehaviorMessageBase.cpp @@ -5,29 +5,27 @@ #include "dCommonVars.h" BehaviorMessageBase::BehaviorMessageBase(AMFArrayValue* arguments) { - behaviorId = 0; behaviorId = GetBehaviorIdFromArgument(arguments); } int32_t BehaviorMessageBase::GetBehaviorIdFromArgument(AMFArrayValue* arguments) { const auto* key = "BehaviorID"; auto* behaviorIDValue = arguments->Get(key); - int32_t behaviorID = -1; if (behaviorIDValue && behaviorIDValue->GetValueType() == eAmf::String) { - behaviorID = std::stoul(behaviorIDValue->GetValue()); - } else if (arguments->Get(key)->GetValueType() != eAmf::Undefined) { + GeneralUtils::TryParse(behaviorIDValue->GetValue(), behaviorId); + } else if (arguments->Get(key) && arguments->Get(key)->GetValueType() != eAmf::Undefined) { throw std::invalid_argument("Unable to find behavior ID"); } - return behaviorID; + return behaviorId; } -uint32_t BehaviorMessageBase::GetActionIndexFromArgument(AMFArrayValue* arguments, const std::string& keyName) { +int32_t BehaviorMessageBase::GetActionIndexFromArgument(AMFArrayValue* arguments, const std::string& keyName) { auto* actionIndexAmf = arguments->Get(keyName); if (!actionIndexAmf) { throw std::invalid_argument("Unable to find actionIndex"); } - return static_cast(actionIndexAmf->GetValue()); + return static_cast(actionIndexAmf->GetValue()); } diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/BehaviorMessageBase.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/BehaviorMessageBase.h index 39246c9d9..8a841d7f8 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/BehaviorMessageBase.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/BehaviorMessageBase.h @@ -7,9 +7,6 @@ #include "Amf3.h" #include "dCommonVars.h" -#include "Game.h" -#include "Logger.h" - enum class BehaviorState : uint32_t; /** @@ -18,12 +15,14 @@ enum class BehaviorState : uint32_t; */ class BehaviorMessageBase { public: - const uint32_t GetBehaviorId() { return behaviorId; }; -protected: + static inline int32_t DefaultBehaviorId = -1; + const int32_t GetBehaviorId() const { return behaviorId; }; + bool IsDefaultBehaviorId() { return behaviorId == DefaultBehaviorId; }; BehaviorMessageBase(AMFArrayValue* arguments); +protected: int32_t GetBehaviorIdFromArgument(AMFArrayValue* arguments); - uint32_t GetActionIndexFromArgument(AMFArrayValue* arguments, const std::string& keyName = "actionIndex"); - int32_t behaviorId = -1; + int32_t GetActionIndexFromArgument(AMFArrayValue* arguments, const std::string& keyName = "actionIndex"); + int32_t behaviorId = DefaultBehaviorId; }; #endif //!__BEHAVIORMESSAGEBASE__H__ diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/ControlBehaviorMsgs.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/ControlBehaviorMsgs.h new file mode 100644 index 000000000..0e17060bc --- /dev/null +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/ControlBehaviorMsgs.h @@ -0,0 +1,23 @@ +#ifndef __CONTROLBEHAVIORMSGS__H__ +#define __CONTROLBEHAVIORMSGS__H__ + +#include "Action.h" +#include "ActionContext.h" +#include "AddActionMessage.h" +#include "AddMessage.h" +#include "AddStripMessage.h" +#include "BehaviorMessageBase.h" +#include "ControlBehaviorMsgs.h" +#include "MergeStripsMessage.h" +#include "MigrateActionsMessage.h" +#include "MoveToInventoryMessage.h" +#include "RearrangeStripMessage.h" +#include "RemoveActionsMessage.h" +#include "RemoveStripMessage.h" +#include "RenameMessage.h" +#include "SplitStripMessage.h" +#include "StripUiPosition.h" +#include "UpdateActionMessage.h" +#include "UpdateStripUiMessage.h" + +#endif //!__CONTROLBEHAVIORMSGS__H__ diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/MergeStripsMessage.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/MergeStripsMessage.h index 0aff7f3ab..7fa4d3a89 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/MergeStripsMessage.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/MergeStripsMessage.h @@ -1,6 +1,7 @@ #ifndef __MERGESTRIPSMESSAGE__H__ #define __MERGESTRIPSMESSAGE__H__ +#include "Action.h" #include "ActionContext.h" #include "BehaviorMessageBase.h" @@ -13,13 +14,16 @@ class AMFArrayValue; class MergeStripsMessage : public BehaviorMessageBase { public: MergeStripsMessage(AMFArrayValue* arguments); - const uint32_t GetDstActionIndex() { return dstActionIndex; }; - ActionContext GetSourceActionContext() { return sourceActionContext; }; - ActionContext GetDestinationActionContext() { return destinationActionContext; }; + int32_t GetDstActionIndex() const { return dstActionIndex; }; + ActionContext GetSourceActionContext() const { return sourceActionContext; }; + ActionContext GetDestinationActionContext() const { return destinationActionContext; }; + const std::vector& GetMigratedActions() const { return migratedActions; }; + void SetMigratedActions(std::vector::const_iterator start, std::vector::const_iterator end) { migratedActions.assign(start, end); }; private: + std::vector migratedActions; ActionContext sourceActionContext; ActionContext destinationActionContext; - uint32_t dstActionIndex; + int32_t dstActionIndex; }; #endif //!__MERGESTRIPSMESSAGE__H__ diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/MigrateActionsMessage.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/MigrateActionsMessage.h index f60e8748a..2f1ac2439 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/MigrateActionsMessage.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/MigrateActionsMessage.h @@ -1,6 +1,7 @@ #ifndef __MIGRATEACTIONSMESSAGE__H__ #define __MIGRATEACTIONSMESSAGE__H__ +#include "Action.h" #include "ActionContext.h" #include "BehaviorMessageBase.h" @@ -13,15 +14,18 @@ class AMFArrayValue; class MigrateActionsMessage : public BehaviorMessageBase { public: MigrateActionsMessage(AMFArrayValue* arguments); - const uint32_t GetSrcActionIndex() { return srcActionIndex; }; - const uint32_t GetDstActionIndex() { return dstActionIndex; }; - ActionContext GetSourceActionContext() { return sourceActionContext; }; - ActionContext GetDestinationActionContext() { return destinationActionContext; }; + int32_t GetSrcActionIndex() const { return srcActionIndex; }; + int32_t GetDstActionIndex() const { return dstActionIndex; }; + ActionContext GetSourceActionContext() const { return sourceActionContext; }; + ActionContext GetDestinationActionContext() const { return destinationActionContext; }; + const std::vector& GetMigratedActions() const { return migratedActions; }; + void SetMigratedActions(std::vector::const_iterator start, std::vector::const_iterator end) { migratedActions.assign(start, end); }; private: + std::vector migratedActions; ActionContext sourceActionContext; ActionContext destinationActionContext; - uint32_t srcActionIndex; - uint32_t dstActionIndex; + int32_t srcActionIndex; + int32_t dstActionIndex; }; #endif //!__MIGRATEACTIONSMESSAGE__H__ diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/MoveToInventoryMessage.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/MoveToInventoryMessage.h index c48f7d173..dc1057666 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/MoveToInventoryMessage.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/MoveToInventoryMessage.h @@ -13,7 +13,7 @@ class AMFArrayValue; class MoveToInventoryMessage : public BehaviorMessageBase { public: MoveToInventoryMessage(AMFArrayValue* arguments); - const uint32_t GetBehaviorIndex() { return behaviorIndex; }; + const uint32_t GetBehaviorIndex() const { return behaviorIndex; }; private: uint32_t behaviorIndex; }; diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/RearrangeStripMessage.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/RearrangeStripMessage.h index 46819404a..db12c046c 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/RearrangeStripMessage.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/RearrangeStripMessage.h @@ -11,13 +11,13 @@ class RearrangeStripMessage : public BehaviorMessageBase { public: RearrangeStripMessage(AMFArrayValue* arguments); - const uint32_t GetSrcActionIndex() { return srcActionIndex; }; - const uint32_t GetDstActionIndex() { return dstActionIndex; }; - ActionContext GetActionContext() { return actionContext; }; + int32_t GetSrcActionIndex() const { return srcActionIndex; }; + int32_t GetDstActionIndex() const { return dstActionIndex; }; + ActionContext GetActionContext() const { return actionContext; }; private: ActionContext actionContext; - uint32_t srcActionIndex; - uint32_t dstActionIndex; + int32_t srcActionIndex; + int32_t dstActionIndex; }; #endif //!__REARRANGESTRIPMESSAGE__H__ diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/RemoveActionsMessage.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/RemoveActionsMessage.h index 457ddba80..860df0af4 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/RemoveActionsMessage.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/RemoveActionsMessage.h @@ -13,11 +13,11 @@ class AMFArrayValue; class RemoveActionsMessage : public BehaviorMessageBase { public: RemoveActionsMessage(AMFArrayValue* arguments); - const uint32_t GetActionIndex() { return actionIndex; }; - ActionContext GetActionContext() { return actionContext; }; + int32_t GetActionIndex() const { return actionIndex; }; + ActionContext GetActionContext() const { return actionContext; }; private: ActionContext actionContext; - uint32_t actionIndex; + int32_t actionIndex; }; #endif //!__REMOVEACTIONSMESSAGE__H__ diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/RemoveStripMessage.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/RemoveStripMessage.h index 36e2e4015..6a32ab0c5 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/RemoveStripMessage.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/RemoveStripMessage.h @@ -11,7 +11,7 @@ class RemoveStripMessage : public BehaviorMessageBase { public: RemoveStripMessage(AMFArrayValue* arguments); - ActionContext GetActionContext() { return actionContext; }; + ActionContext GetActionContext() const { return actionContext; }; private: ActionContext actionContext; }; diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/RenameMessage.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/RenameMessage.h index ba181f630..3f4119d2b 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/RenameMessage.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/RenameMessage.h @@ -12,7 +12,7 @@ class AMFArrayValue; class RenameMessage : public BehaviorMessageBase { public: RenameMessage(AMFArrayValue* arguments); - const std::string& GetName() { return name; }; + const std::string& GetName() const { return name; }; private: std::string name; }; diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/SplitStripMessage.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/SplitStripMessage.h index 9210efb0b..e41d50ebb 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/SplitStripMessage.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/SplitStripMessage.h @@ -1,6 +1,7 @@ #ifndef __SPLITSTRIPMESSAGE__H__ #define __SPLITSTRIPMESSAGE__H__ +#include "Action.h" #include "ActionContext.h" #include "BehaviorMessageBase.h" #include "StripUiPosition.h" @@ -14,15 +15,19 @@ class AMFArrayValue; class SplitStripMessage : public BehaviorMessageBase { public: SplitStripMessage(AMFArrayValue* arguments); - ActionContext GetSourceActionContext() { return sourceActionContext; }; - ActionContext GetDestinationActionContext() { return destinationActionContext; }; - const uint32_t GetSrcActionIndex() { return srcActionIndex; }; - StripUiPosition GetPosition() { return destinationPosition; }; + ActionContext GetSourceActionContext() const { return sourceActionContext; }; + ActionContext GetDestinationActionContext() const { return destinationActionContext; }; + int32_t GetSrcActionIndex() const { return srcActionIndex; }; + StripUiPosition GetPosition() const { return destinationPosition; }; + const std::vector& GetTransferredActions() const { return transferredActions; }; + void SetTransferredActions(std::vector::const_iterator begin, std::vector::const_iterator end) { transferredActions.assign(begin, end); }; private: ActionContext sourceActionContext; ActionContext destinationActionContext; - uint32_t srcActionIndex; + int32_t srcActionIndex; StripUiPosition destinationPosition; + + std::vector transferredActions; }; #endif //!__SPLITSTRIPMESSAGE__H__ diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.cpp b/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.cpp index de612b452..8b2d1d365 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.cpp +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.cpp @@ -20,3 +20,9 @@ StripUiPosition::StripUiPosition(AMFArrayValue* arguments, std::string uiKeyName yPosition = yPositionValue->GetValue(); xPosition = xPositionValue->GetValue(); } + +void StripUiPosition::SendBehaviorBlocksToClient(AMFArrayValue& args) const { + auto* uiArgs = args.InsertArray("ui"); + uiArgs->Insert("x", xPosition); + uiArgs->Insert("y", yPosition); +} diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.h index 809f88909..92578cdc2 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/StripUiPosition.h @@ -11,8 +11,9 @@ class StripUiPosition { public: StripUiPosition(); StripUiPosition(AMFArrayValue* arguments, std::string uiKeyName = "ui"); - double GetX() { return xPosition; }; - double GetY() { return yPosition; }; + void SendBehaviorBlocksToClient(AMFArrayValue& args) const; + double GetX() const { return xPosition; }; + double GetY() const { return yPosition; }; private: double xPosition; double yPosition; diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/UpdateActionMessage.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/UpdateActionMessage.h index 0a03ce9e4..aa34940b6 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/UpdateActionMessage.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/UpdateActionMessage.h @@ -14,11 +14,11 @@ class AMFArrayValue; class UpdateActionMessage : public BehaviorMessageBase { public: UpdateActionMessage(AMFArrayValue* arguments); - const uint32_t GetActionIndex() { return actionIndex; }; - ActionContext GetActionContext() { return actionContext; }; - Action GetAction() { return action; }; + int32_t GetActionIndex() const { return actionIndex; }; + ActionContext GetActionContext() const { return actionContext; }; + Action GetAction() const { return action; }; private: - uint32_t actionIndex; + int32_t actionIndex; ActionContext actionContext; Action action; }; diff --git a/dGame/dPropertyBehaviors/ControlBehaviorMessages/UpdateStripUiMessage.h b/dGame/dPropertyBehaviors/ControlBehaviorMessages/UpdateStripUiMessage.h index 6d96f90c5..0e9afe815 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviorMessages/UpdateStripUiMessage.h +++ b/dGame/dPropertyBehaviors/ControlBehaviorMessages/UpdateStripUiMessage.h @@ -14,8 +14,8 @@ class AMFArrayValue; class UpdateStripUiMessage : public BehaviorMessageBase { public: UpdateStripUiMessage(AMFArrayValue* arguments); - StripUiPosition GetPosition() { return position; }; - ActionContext GetActionContext() { return actionContext; }; + StripUiPosition GetPosition() const { return position; }; + ActionContext GetActionContext() const { return actionContext; }; private: StripUiPosition position; ActionContext actionContext; diff --git a/dGame/dPropertyBehaviors/ControlBehaviors.cpp b/dGame/dPropertyBehaviors/ControlBehaviors.cpp index b143cbc81..248ce4e7d 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviors.cpp +++ b/dGame/dPropertyBehaviors/ControlBehaviors.cpp @@ -30,221 +30,49 @@ #include "UpdateActionMessage.h" #include "UpdateStripUiMessage.h" -void ControlBehaviors::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->SetValue(std::to_string(persistentId)); - // args.InsertValue("behaviorID", behaviorIDString); - - // AMFStringValue* objectIDAsString = new AMFStringValue(); - // objectIDAsString->SetValue(std::to_string(modelComponent->GetParent()->GetObjectID())); - // args.InsertValue("objectID", objectIDAsString); - - // GameMessages::SendUIMessageServerToSingleClient(modelOwner, sysAddr, "UpdateBehaviorID", &args); - // ControlBehaviors::SendBehaviorListToClient(modelComponent->GetParent(), sysAddr, modelOwner); - // }); - // } -} - -void ControlBehaviors::SendBehaviorListToClient(Entity* modelEntity, const SystemAddress& sysAddr, Entity* modelOwner) { - auto* modelComponent = modelEntity->GetComponent(); - - if (!modelComponent) return; - - AMFArrayValue behaviorsToSerialize; - - /** - * The behaviors AMFArray will have up to 5 elements in the dense portion. - * Each element in the dense portion will be made up of another AMFArray - * with the following information mapped in the associative portion - * "id": Behavior ID cast to an AMFString - * "isLocked": AMFTrue or AMFFalse of whether or not the behavior is locked - * "isLoot": AMFTrue or AMFFalse of whether or not the behavior is a custom behavior (true if custom) - * "name": The name of the behavior formatted as an AMFString - */ - - behaviorsToSerialize.Insert("behaviors"); - behaviorsToSerialize.Insert("objectID", std::to_string(modelComponent->GetParent()->GetObjectID())); - - GameMessages::SendUIMessageServerToSingleClient(modelOwner, sysAddr, "UpdateBehaviorList", behaviorsToSerialize); -} - -void ControlBehaviors::ModelTypeChanged(AMFArrayValue* arguments, ModelComponent* ModelComponent) { - auto* modelTypeAmf = arguments->Get("ModelType"); - if (!modelTypeAmf) return; - - uint32_t modelType = static_cast(modelTypeAmf->GetValue()); - - //TODO Update the model type here -} - -void ControlBehaviors::ToggleExecutionUpdates() { - //TODO do something with this info -} - -void ControlBehaviors::AddStrip(AMFArrayValue* arguments) { - AddStripMessage addStripMessage(arguments); -} - -void ControlBehaviors::RemoveStrip(AMFArrayValue* arguments) { - RemoveStripMessage removeStrip(arguments); -} - -void ControlBehaviors::MergeStrips(AMFArrayValue* arguments) { - MergeStripsMessage mergeStripsMessage(arguments); -} - -void ControlBehaviors::SplitStrip(AMFArrayValue* arguments) { - SplitStripMessage splitStripMessage(arguments); -} - -void ControlBehaviors::UpdateStripUI(AMFArrayValue* arguments) { - UpdateStripUiMessage updateStripUiMessage(arguments); -} +void ControlBehaviors::RequestUpdatedID(ControlBehaviorContext& context) { + ObjectIDManager::Instance()->RequestPersistentID( + [context](uint32_t persistentId) { + if (!context) { + LOG("Model to update behavior ID for is null. Cannot update ID."); + return; + } + // This updates the behavior ID of the behavior should this be a new behavior + AMFArrayValue args; -void ControlBehaviors::AddAction(AMFArrayValue* arguments) { - AddActionMessage addActionMessage(arguments); -} + args.Insert("behaviorID", std::to_string(persistentId)); + args.Insert("objectID", std::to_string(context.modelComponent->GetParent()->GetObjectID())); -void ControlBehaviors::MigrateActions(AMFArrayValue* arguments) { - MigrateActionsMessage migrateActionsMessage(arguments); -} + GameMessages::SendUIMessageServerToSingleClient(context.modelOwner, context.modelOwner->GetSystemAddress(), "UpdateBehaviorID", args); + context.modelComponent->UpdatePendingBehaviorId(persistentId); -void ControlBehaviors::RearrangeStrip(AMFArrayValue* arguments) { - RearrangeStripMessage rearrangeStripMessage(arguments); + ControlBehaviors::Instance().SendBehaviorListToClient(context); + }); } -void ControlBehaviors::Add(AMFArrayValue* arguments) { - AddMessage addMessage(arguments); -} +void ControlBehaviors::SendBehaviorListToClient(const ControlBehaviorContext& context) { + if (!context) return; -void ControlBehaviors::RemoveActions(AMFArrayValue* arguments) { - RemoveActionsMessage removeActionsMessage(arguments); -} + AMFArrayValue behaviorsToSerialize; + context.modelComponent->SendBehaviorListToClient(behaviorsToSerialize); -void ControlBehaviors::Rename(Entity* modelEntity, const SystemAddress& sysAddr, Entity* modelOwner, AMFArrayValue* arguments) { - RenameMessage renameMessage(arguments); + GameMessages::SendUIMessageServerToSingleClient(context.modelOwner, context.modelOwner->GetSystemAddress(), "UpdateBehaviorList", behaviorsToSerialize); } // TODO This is also supposed to serialize the state of the behaviors in progress but those aren't implemented yet -void ControlBehaviors::SendBehaviorBlocksToClient(ModelComponent* modelComponent, const SystemAddress& sysAddr, Entity* modelOwner, AMFArrayValue* arguments) { - // uint32_t behaviorID = ControlBehaviors::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++) { - // LOG("Begin serialization of state %i!\n", it->first); - // AMFArrayValue* state = new AMFArrayValue(); - - // AMFDoubleValue* stateAsDouble = new AMFDoubleValue(); - // stateAsDouble->SetValue(it->first); - // state->InsertValue("id", stateAsDouble); - - // AMFArrayValue* strips = new AMFArrayValue(); - // auto stripsInState = it->second->GetStrips(); - // for (auto strip = stripsInState.begin(); strip != stripsInState.end(); strip++) { - // LOG("Begin serialization of strip %i!\n", strip->first); - // AMFArrayValue* thisStrip = new AMFArrayValue(); - - // AMFDoubleValue* stripID = new AMFDoubleValue(); - // stripID->SetValue(strip->first); - // thisStrip->InsertValue("id", stripID); - - // AMFArrayValue* uiArray = new AMFArrayValue(); - // AMFDoubleValue* yPosition = new AMFDoubleValue(); - // yPosition->SetValue(strip->second->GetYPosition()); - // uiArray->InsertValue("y", yPosition); - - // AMFDoubleValue* xPosition = new AMFDoubleValue(); - // xPosition->SetValue(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()) { - // LOG("Begin serialization of action %s!\n", behaviorAction->actionName.c_str()); - // AMFArrayValue* thisAction = new AMFArrayValue(); - - // AMFStringValue* actionName = new AMFStringValue(); - // actionName->SetValue(behaviorAction->actionName); - // thisAction->InsertValue("Type", actionName); - - // if (behaviorAction->parameterValueString != "") - // { - // AMFStringValue* valueAsString = new AMFStringValue(); - // valueAsString->SetValue(behaviorAction->parameterValueString); - // thisAction->InsertValue(behaviorAction->parameterName, valueAsString); - // } - // else if (behaviorAction->parameterValueDouble != 0.0) - // { - // AMFDoubleValue* valueAsDouble = new AMFDoubleValue(); - // valueAsDouble->SetValue(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->SetValue(std::to_string(targetObjectID)); - // behaviorInfo.InsertValue("objectID", objectidAsString); - - // AMFStringValue* behaviorIDAsString = new AMFStringValue(); - // behaviorIDAsString->SetValue(std::to_string(behaviorID)); - // behaviorInfo.InsertValue("BehaviorID", behaviorIDAsString); - - // GameMessages::SendUIMessageServerToSingleClient(modelOwner, sysAddr, "UpdateBehaviorBlocks", &behaviorInfo); +void ControlBehaviors::SendBehaviorBlocksToClient(ControlBehaviorContext& context) { + if (!context) return; + BehaviorMessageBase behaviorMsg(context.arguments); + + context.modelComponent->VerifyBehaviors(); + AMFArrayValue behavior; + context.modelComponent->SendBehaviorBlocksToClient(behaviorMsg.GetBehaviorId(), behavior); + GameMessages::SendUIMessageServerToSingleClient(context.modelOwner, context.modelOwner->GetSystemAddress(), "UpdateBehaviorBlocks", behavior); } void ControlBehaviors::UpdateAction(AMFArrayValue* arguments) { UpdateActionMessage updateActionMessage(arguments); - auto* blockDefinition = GetBlockInfo(updateActionMessage.GetAction().GetType()); + auto blockDefinition = GetBlockInfo(updateActionMessage.GetAction().GetType()); if (!blockDefinition) { LOG("Received undefined block type %s. Ignoring.", updateActionMessage.GetAction().GetType().c_str()); @@ -266,75 +94,83 @@ void ControlBehaviors::UpdateAction(AMFArrayValue* arguments) { } } -void ControlBehaviors::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; - - args.Insert("visible", false); - - GameMessages::SendUIMessageServerToSingleClient(modelOwner, modelOwner->GetParentUser()->GetSystemAddress(), "ToggleBehaviorEditor", args); - - MoveToInventoryMessage moveToInventoryMessage(arguments); - - SendBehaviorListToClient(modelComponent->GetParent(), sysAddr, modelOwner); -} - void ControlBehaviors::ProcessCommand(Entity* modelEntity, const SystemAddress& sysAddr, AMFArrayValue* arguments, std::string command, Entity* modelOwner) { if (!isInitialized || !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 - LOG("Unknown behavior command (%s)\n", command.c_str()); + ControlBehaviorContext context(arguments, modelComponent, modelOwner); + + if (command == "sendBehaviorListToClient") { + SendBehaviorListToClient(context); + } else if (command == "modelTypeChanged") { + auto* modelType = arguments->Get("ModelType"); + if (!modelType) return; + + modelEntity->SetVar(u"modelType", modelType->GetValue()); + } else if (command == "toggleExecutionUpdates") { + // TODO + } else if (command == "addStrip") { + if (BehaviorMessageBase(context.arguments).IsDefaultBehaviorId()) RequestUpdatedID(context); + + context.modelComponent->HandleControlBehaviorsMsg(context.arguments); + } else if (command == "removeStrip") { + context.modelComponent->HandleControlBehaviorsMsg(arguments); + } else if (command == "mergeStrips") { + context.modelComponent->HandleControlBehaviorsMsg(arguments); + } else if (command == "splitStrip") { + context.modelComponent->HandleControlBehaviorsMsg(arguments); + } else if (command == "updateStripUI") { + context.modelComponent->HandleControlBehaviorsMsg(arguments); + } else if (command == "addAction") { + context.modelComponent->HandleControlBehaviorsMsg(arguments); + } else if (command == "migrateActions") { + 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); + } else if (command == "removeActions") { + context.modelComponent->HandleControlBehaviorsMsg(arguments); + } else if (command == "rename") { + 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); + + AMFArrayValue args; + args.Insert("BehaviorID", std::to_string(msg.GetBehaviorId())); + GameMessages::SendUIMessageServerToSingleClient(modelOwner, modelOwner->GetParentUser()->GetSystemAddress(), "BehaviorRemoved", args); + + SendBehaviorListToClient(context); + } else if (command == "updateAction") { + context.modelComponent->HandleControlBehaviorsMsg(arguments); + } else { + LOG("Unknown behavior command (%s)", command.c_str()); + } } ControlBehaviors::ControlBehaviors() { auto blocksBuffer = Game::assetManager->GetFile("ui\\ingame\\blocksdef.xml"); if (!blocksBuffer) { - LOG("Failed to open blocksdef.xml"); + LOG("Failed to open blocksdef.xml, property behaviors will be disabled for this zone! " + "(This is a necessary file for cheat detection and ensuring we do not send unexpected values to the client)"); return; } tinyxml2::XMLDocument m_Doc; - std::string read{}; + std::string read; - std::string buffer{}; + std::string buffer; bool commentBlockStart = false; while (std::getline(blocksBuffer, read)) { // tinyxml2 should handle comment blocks but the client has one that fails the processing. @@ -371,14 +207,14 @@ ControlBehaviors::ControlBehaviors() { while (block) { blockName = block->Name(); - BlockDefinition* blockDefinition = new BlockDefinition(); + auto& blockDefinition = blockTypes[blockName]; std::string name{}; std::string typeName{}; auto* argument = block->FirstChildElement("Argument"); if (argument) { auto* defaultDefinition = argument->FirstChildElement("DefaultValue"); - if (defaultDefinition) blockDefinition->SetDefaultValue(defaultDefinition->GetText()); + if (defaultDefinition) blockDefinition.SetDefaultValue(defaultDefinition->GetText()); auto* typeDefinition = argument->FirstChildElement("Type"); if (typeDefinition) typeName = typeDefinition->GetText(); @@ -388,23 +224,23 @@ ControlBehaviors::ControlBehaviors() { // Now we parse the blocksdef file for the relevant information if (typeName == "String") { - blockDefinition->SetMaximumValue(50); // The client has a hardcoded limit of 50 characters in a string field + blockDefinition.SetMaximumValue(50); // The client has a hardcoded limit of 50 characters in a string field } else if (typeName == "Float" || typeName == "Integer") { auto* maximumDefinition = argument->FirstChildElement("Maximum"); - if (maximumDefinition) blockDefinition->SetMaximumValue(std::stof(maximumDefinition->GetText())); + if (maximumDefinition) blockDefinition.SetMaximumValue(std::stof(maximumDefinition->GetText())); auto* minimumDefinition = argument->FirstChildElement("Minimum"); - if (minimumDefinition) blockDefinition->SetMinimumValue(std::stof(minimumDefinition->GetText())); + if (minimumDefinition) blockDefinition.SetMinimumValue(std::stof(minimumDefinition->GetText())); } else if (typeName == "Enumeration") { auto* values = argument->FirstChildElement("Values"); if (values) { auto* value = values->FirstChildElement("Value"); while (value) { - if (value->GetText() == blockDefinition->GetDefaultValue()) blockDefinition->GetDefaultValue() = std::to_string(blockDefinition->GetMaximumValue()); - blockDefinition->SetMaximumValue(blockDefinition->GetMaximumValue() + 1); + if (value->GetText() == blockDefinition.GetDefaultValue()) blockDefinition.GetDefaultValue() = std::to_string(blockDefinition.GetMaximumValue()); + blockDefinition.SetMaximumValue(blockDefinition.GetMaximumValue() + 1); value = value->NextSiblingElement("Value"); } - blockDefinition->SetMaximumValue(blockDefinition->GetMaximumValue() - 1); // Maximum value is 0 indexed + blockDefinition.SetMaximumValue(blockDefinition.GetMaximumValue() - 1); // Maximum value is 0 indexed } else { values = argument->FirstChildElement("EnumerationSource"); if (!values) { @@ -421,8 +257,8 @@ ControlBehaviors::ControlBehaviors() { std::string serviceName = serviceNameNode->GetText(); if (serviceName == "GetBehaviorSoundList") { auto res = CDClientDatabase::ExecuteQuery("SELECT MAX(id) as countSounds FROM UGBehaviorSounds;"); - blockDefinition->SetMaximumValue(res.getIntField("countSounds")); - blockDefinition->SetDefaultValue("0"); + blockDefinition.SetMaximumValue(res.getIntField("countSounds")); + blockDefinition.SetDefaultValue("0"); } else { LOG("Unsupported Enumeration ServiceType (%s)", serviceName.c_str()); continue; @@ -433,19 +269,18 @@ ControlBehaviors::ControlBehaviors() { continue; } } - blockTypes.insert(std::make_pair(blockName, blockDefinition)); block = block->NextSiblingElement(); } blockSections = blockSections->NextSiblingElement(); } isInitialized = true; LOG_DEBUG("Created all base block classes"); - for (auto b : blockTypes) { - LOG_DEBUG("block name is %s default %s min %f max %f", b.first.c_str(), b.second->GetDefaultValue().c_str(), b.second->GetMinimumValue(), b.second->GetMaximumValue()); + for (auto& [name, block] : blockTypes) { + LOG_DEBUG("block name is %s default %s min %f max %f", name.c_str(), block.GetDefaultValue().c_str(), block.GetMinimumValue(), block.GetMaximumValue()); } } -BlockDefinition* ControlBehaviors::GetBlockInfo(const BlockName& blockName) { +std::optional ControlBehaviors::GetBlockInfo(const BlockName& blockName) { auto blockDefinition = blockTypes.find(blockName); - return blockDefinition != blockTypes.end() ? blockDefinition->second : nullptr; + return blockDefinition != blockTypes.end() ? std::optional(blockDefinition->second) : std::nullopt; } diff --git a/dGame/dPropertyBehaviors/ControlBehaviors.h b/dGame/dPropertyBehaviors/ControlBehaviors.h index a562aafeb..ab7394083 100644 --- a/dGame/dPropertyBehaviors/ControlBehaviors.h +++ b/dGame/dPropertyBehaviors/ControlBehaviors.h @@ -4,12 +4,13 @@ #define __CONTROLBEHAVIORS__H__ #include +#include #include +#include "BlockDefinition.h" #include "Singleton.h" class AMFArrayValue; -class BlockDefinition; class Entity; class ModelComponent; class SystemAddress; @@ -17,6 +18,18 @@ class SystemAddress; // Type definition to clarify what is used where typedef std::string BlockName; //! A block name +struct ControlBehaviorContext { + ControlBehaviorContext(AMFArrayValue* args, ModelComponent* modelComponent, Entity* modelOwner) : arguments(args), modelComponent(modelComponent), modelOwner(modelOwner) {}; + + operator bool() const { + return arguments != nullptr && modelComponent != nullptr && modelOwner != nullptr; + } + + AMFArrayValue* arguments; + Entity* modelOwner; + ModelComponent* modelComponent; +}; + class ControlBehaviors: public Singleton { public: ControlBehaviors(); @@ -39,27 +52,13 @@ class ControlBehaviors: public Singleton { * * @return A pair of the block parameter name to its typing */ - BlockDefinition* GetBlockInfo(const BlockName& blockName); + std::optional GetBlockInfo(const BlockName& blockName); private: - void RequestUpdatedID(int32_t behaviorID, ModelComponent* modelComponent, Entity* modelOwner, const SystemAddress& sysAddr); - void SendBehaviorListToClient(Entity* modelEntity, const SystemAddress& sysAddr, Entity* modelOwner); - void ModelTypeChanged(AMFArrayValue* arguments, ModelComponent* ModelComponent); - void ToggleExecutionUpdates(); - void AddStrip(AMFArrayValue* arguments); - void RemoveStrip(AMFArrayValue* arguments); - void MergeStrips(AMFArrayValue* arguments); - void SplitStrip(AMFArrayValue* arguments); - void UpdateStripUI(AMFArrayValue* arguments); - void AddAction(AMFArrayValue* arguments); - void MigrateActions(AMFArrayValue* arguments); - void RearrangeStrip(AMFArrayValue* arguments); - void Add(AMFArrayValue* arguments); - void RemoveActions(AMFArrayValue* arguments); - void Rename(Entity* modelEntity, const SystemAddress& sysAddr, Entity* modelOwner, AMFArrayValue* arguments); - void SendBehaviorBlocksToClient(ModelComponent* modelComponent, const SystemAddress& sysAddr, Entity* modelOwner, AMFArrayValue* arguments); + void RequestUpdatedID(ControlBehaviorContext& context); + void SendBehaviorListToClient(const ControlBehaviorContext& context); + void SendBehaviorBlocksToClient(ControlBehaviorContext& context); void UpdateAction(AMFArrayValue* arguments); - void MoveToInventory(ModelComponent* modelComponent, const SystemAddress& sysAddr, Entity* modelOwner, AMFArrayValue* arguments); - std::map blockTypes{}; + std::map blockTypes{}; // If false, property behaviors will not be able to be edited. bool isInitialized = false; diff --git a/dGame/dPropertyBehaviors/PropertyBehavior.cpp b/dGame/dPropertyBehaviors/PropertyBehavior.cpp new file mode 100644 index 000000000..f6f6e4f1f --- /dev/null +++ b/dGame/dPropertyBehaviors/PropertyBehavior.cpp @@ -0,0 +1,131 @@ +#include "PropertyBehavior.h" + +#include "Amf3.h" +#include "BehaviorStates.h" +#include "ControlBehaviorMsgs.h" + +PropertyBehavior::PropertyBehavior() { + m_LastEditedState = BehaviorState::HOME_STATE; +} + +template<> +void PropertyBehavior::HandleMsg(AddStripMessage& msg) { + m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg); + m_LastEditedState = msg.GetActionContext().GetStateId(); +}; + +template<> +void PropertyBehavior::HandleMsg(AddActionMessage& msg) { + m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg); + m_LastEditedState = msg.GetActionContext().GetStateId(); +}; + +template<> +void PropertyBehavior::HandleMsg(RearrangeStripMessage& msg) { + m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg); + m_LastEditedState = msg.GetActionContext().GetStateId(); +}; + +template<> +void PropertyBehavior::HandleMsg(UpdateActionMessage& msg) { + m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg); + m_LastEditedState = msg.GetActionContext().GetStateId(); +}; + +template<> +void PropertyBehavior::HandleMsg(UpdateStripUiMessage& msg) { + m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg); + m_LastEditedState = msg.GetActionContext().GetStateId(); +}; + +template<> +void PropertyBehavior::HandleMsg(RemoveStripMessage& msg) { + m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg); + m_LastEditedState = msg.GetActionContext().GetStateId(); +}; + +template<> +void PropertyBehavior::HandleMsg(RemoveActionsMessage& msg) { + m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg); + m_LastEditedState = msg.GetActionContext().GetStateId(); +}; + +template<> +void PropertyBehavior::HandleMsg(SplitStripMessage& msg) { + m_States[msg.GetSourceActionContext().GetStateId()].HandleMsg(msg); + m_States[msg.GetDestinationActionContext().GetStateId()].HandleMsg(msg); + m_LastEditedState = msg.GetDestinationActionContext().GetStateId(); +}; + +template<> +void PropertyBehavior::HandleMsg(MigrateActionsMessage& msg) { + m_States[msg.GetSourceActionContext().GetStateId()].HandleMsg(msg); + m_States[msg.GetDestinationActionContext().GetStateId()].HandleMsg(msg); + m_LastEditedState = msg.GetDestinationActionContext().GetStateId(); +}; + +template<> +void PropertyBehavior::HandleMsg(MergeStripsMessage& msg) { + m_States[msg.GetSourceActionContext().GetStateId()].HandleMsg(msg); + m_States[msg.GetDestinationActionContext().GetStateId()].HandleMsg(msg); + m_LastEditedState = msg.GetDestinationActionContext().GetStateId(); +}; + +template<> +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; +}; + +void PropertyBehavior::SetBehaviorId(int32_t behaviorId) { + m_BehaviorId = behaviorId; +} + +void PropertyBehavior::SendBehaviorListToClient(AMFArrayValue& args) const { + args.Insert("id", std::to_string(m_BehaviorId)); + args.Insert("name", m_Name); + args.Insert("isLocked", isLocked); + args.Insert("isLoot", isLoot); +} + +void PropertyBehavior::VerifyLastEditedState() { + if (!m_States[m_LastEditedState].IsEmpty()) return; + + for (const auto& [stateId, state] : m_States) { + if (state.IsEmpty()) continue; + + LOG_DEBUG("Updating last edited state to %i because %i is empty.", stateId, m_LastEditedState); + m_LastEditedState = stateId; + return; + } + + LOG_DEBUG("No states found, sending default state"); + + m_LastEditedState = BehaviorState::HOME_STATE; +} + +void PropertyBehavior::SendBehaviorBlocksToClient(AMFArrayValue& args) const { + auto* stateArray = args.InsertArray("states"); + + auto lastState = BehaviorState::HOME_STATE; + for (auto& [stateId, state] : m_States) { + if (state.IsEmpty()) continue; + + LOG_DEBUG("Serializing state %i", stateId); + auto* stateArgs = stateArray->PushArray(); + stateArgs->Insert("id", static_cast(stateId)); + state.SendBehaviorBlocksToClient(*stateArgs); + } + + auto* executionState = args.InsertArray("executionState"); + executionState->Insert("stateID", static_cast(m_LastEditedState)); + executionState->InsertArray("strips"); + + // TODO Serialize the execution state of the behavior +} diff --git a/dGame/dPropertyBehaviors/PropertyBehavior.h b/dGame/dPropertyBehaviors/PropertyBehavior.h new file mode 100644 index 000000000..dc53bbed6 --- /dev/null +++ b/dGame/dPropertyBehaviors/PropertyBehavior.h @@ -0,0 +1,49 @@ +#ifndef __PROPERTYBEHAVIOR__H__ +#define __PROPERTYBEHAVIOR__H__ + +#include "State.h" + +enum class BehaviorState : uint32_t; + +class AMFArrayValue; + +/** + * Represents the Entity of a Property Behavior and holds data associated with the behavior + */ +class PropertyBehavior { +public: + PropertyBehavior(); + template + void HandleMsg(Msg& msg); + + // If the last edited state has no strips, this method will set the last edited state to the first state that has strips. + void VerifyLastEditedState(); + void SendBehaviorListToClient(AMFArrayValue& args) const; + void SendBehaviorBlocksToClient(AMFArrayValue& args) const; + + int32_t GetBehaviorId() const { return m_BehaviorId; } + void SetBehaviorId(int32_t id); +private: + + // The states this behavior has. + std::map m_States; + + // The name of this behavior. + std::string m_Name = "New Behavior"; + + // Whether this behavior is locked and cannot be edited. + bool isLocked = false; + + // Whether this behavior is custom or pre-fab. + bool isLoot = false; + + // 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; + + // The behavior id for this behavior. This is expected to be fully unique, however an id of -1 means this behavior was just created + // and needs to be assigned an id. + int32_t m_BehaviorId = -1; +}; + +#endif //!__PROPERTYBEHAVIOR__H__ diff --git a/dGame/dPropertyBehaviors/State.cpp b/dGame/dPropertyBehaviors/State.cpp new file mode 100644 index 000000000..59a9aa8bf --- /dev/null +++ b/dGame/dPropertyBehaviors/State.cpp @@ -0,0 +1,137 @@ +#include "State.h" + +#include "Amf3.h" +#include "ControlBehaviorMsgs.h" + +template<> +void State::HandleMsg(AddStripMessage& msg) { + if (m_Strips.size() <= msg.GetActionContext().GetStripId()) { + m_Strips.resize(msg.GetActionContext().GetStripId() + 1); + } + m_Strips.at(msg.GetActionContext().GetStripId()).HandleMsg(msg); +}; + +template<> +void State::HandleMsg(AddActionMessage& msg) { + if (m_Strips.size() <= msg.GetActionContext().GetStripId()) { + return; + } + + m_Strips.at(msg.GetActionContext().GetStripId()).HandleMsg(msg); +}; + +template<> +void State::HandleMsg(UpdateStripUiMessage& msg) { + if (m_Strips.size() <= msg.GetActionContext().GetStripId()) { + return; + } + + m_Strips.at(msg.GetActionContext().GetStripId()).HandleMsg(msg); +}; + +template<> +void State::HandleMsg(RemoveActionsMessage& msg) { + if (m_Strips.size() <= msg.GetActionContext().GetStripId()) { + return; + } + + m_Strips.at(msg.GetActionContext().GetStripId()).HandleMsg(msg); +}; + +template<> +void State::HandleMsg(RearrangeStripMessage& msg) { + if (m_Strips.size() <= msg.GetActionContext().GetStripId()) { + return; + } + + m_Strips.at(msg.GetActionContext().GetStripId()).HandleMsg(msg); +}; + +template<> +void State::HandleMsg(UpdateActionMessage& msg) { + if (m_Strips.size() <= msg.GetActionContext().GetStripId()) { + return; + } + + m_Strips.at(msg.GetActionContext().GetStripId()).HandleMsg(msg); +}; + +template<> +void State::HandleMsg(RemoveStripMessage& msg) { + if (m_Strips.size() <= msg.GetActionContext().GetStripId()) { + return; + } + + m_Strips.at(msg.GetActionContext().GetStripId()).HandleMsg(msg); +}; + +template<> +void State::HandleMsg(SplitStripMessage& msg) { + if (msg.GetTransferredActions().empty()) { + if (m_Strips.size() <= msg.GetSourceActionContext().GetStripId()) { + return; + } + + m_Strips.at(msg.GetSourceActionContext().GetStripId()).HandleMsg(msg); + } else { + if (m_Strips.size() <= msg.GetDestinationActionContext().GetStripId()) { + m_Strips.resize(msg.GetDestinationActionContext().GetStripId() + 1); + } + + m_Strips.at(msg.GetDestinationActionContext().GetStripId()).HandleMsg(msg); + } +}; + +template<> +void State::HandleMsg(MergeStripsMessage& msg) { + if (msg.GetMigratedActions().empty()) { + if (m_Strips.size() <= msg.GetSourceActionContext().GetStripId()) { + return; + } + + m_Strips.at(msg.GetSourceActionContext().GetStripId()).HandleMsg(msg); + } else { + if (m_Strips.size() <= msg.GetDestinationActionContext().GetStripId()) { + m_Strips.resize(msg.GetDestinationActionContext().GetStripId() + 1); + } + + m_Strips.at(msg.GetDestinationActionContext().GetStripId()).HandleMsg(msg); + } +}; + +template<> +void State::HandleMsg(MigrateActionsMessage& msg) { + if (msg.GetMigratedActions().empty()) { + if (m_Strips.size() <= msg.GetSourceActionContext().GetStripId()) { + return; + } + + m_Strips.at(msg.GetSourceActionContext().GetStripId()).HandleMsg(msg); + } else { + if (m_Strips.size() <= msg.GetDestinationActionContext().GetStripId()) { + m_Strips.resize(msg.GetDestinationActionContext().GetStripId() + 1); + } + + m_Strips.at(msg.GetDestinationActionContext().GetStripId()).HandleMsg(msg); + } +}; + +bool State::IsEmpty() const { + for (auto& strip : m_Strips) { + if (!strip.IsEmpty()) return false; + } + return true; +} + +void State::SendBehaviorBlocksToClient(AMFArrayValue& args) const { + auto* strips = args.InsertArray("strips"); + for (int32_t stripId = 0; stripId < m_Strips.size(); stripId++) { + auto& strip = m_Strips.at(stripId); + if (strip.IsEmpty()) continue; + + auto* stripArgs = strips->PushArray(); + stripArgs->Insert("id", static_cast(stripId)); + + strip.SendBehaviorBlocksToClient(*stripArgs); + } +}; diff --git a/dGame/dPropertyBehaviors/State.h b/dGame/dPropertyBehaviors/State.h new file mode 100644 index 000000000..a6a6d23be --- /dev/null +++ b/dGame/dPropertyBehaviors/State.h @@ -0,0 +1,19 @@ +#ifndef __STATE__H__ +#define __STATE__H__ + +#include "Strip.h" + +class AMFArrayValue; + +class State { +public: + template + void HandleMsg(Msg& msg); + + void SendBehaviorBlocksToClient(AMFArrayValue& args) const; + bool IsEmpty() const; +private: + std::vector m_Strips; +}; + +#endif //!__STATE__H__ diff --git a/dGame/dPropertyBehaviors/Strip.cpp b/dGame/dPropertyBehaviors/Strip.cpp new file mode 100644 index 000000000..7d27cacd4 --- /dev/null +++ b/dGame/dPropertyBehaviors/Strip.cpp @@ -0,0 +1,87 @@ +#include "Strip.h" + +#include "Amf3.h" +#include "ControlBehaviorMsgs.h" + +template<> +void Strip::HandleMsg(AddStripMessage& msg) { + m_Actions = msg.GetActionsToAdd(); + m_Position = msg.GetPosition(); +}; + +template<> +void Strip::HandleMsg(AddActionMessage& msg) { + if (msg.GetActionIndex() == -1) return; + + m_Actions.insert(m_Actions.begin() + msg.GetActionIndex(), msg.GetAction()); +}; + +template<> +void Strip::HandleMsg(UpdateStripUiMessage& msg) { + m_Position = msg.GetPosition(); +}; + +template<> +void Strip::HandleMsg(RemoveStripMessage& msg) { + m_Actions.clear(); +}; + +template<> +void Strip::HandleMsg(RemoveActionsMessage& msg) { + if (msg.GetActionIndex() >= m_Actions.size()) return; + m_Actions.erase(m_Actions.begin() + msg.GetActionIndex(), m_Actions.end()); +}; + +template<> +void Strip::HandleMsg(UpdateActionMessage& msg) { + if (msg.GetActionIndex() >= m_Actions.size()) return; + m_Actions.at(msg.GetActionIndex()) = msg.GetAction(); +}; + +template<> +void Strip::HandleMsg(RearrangeStripMessage& msg) { + if (msg.GetDstActionIndex() >= m_Actions.size() || msg.GetSrcActionIndex() >= m_Actions.size() || msg.GetSrcActionIndex() <= msg.GetDstActionIndex()) return; + std::rotate(m_Actions.begin() + msg.GetDstActionIndex(), m_Actions.begin() + msg.GetSrcActionIndex(), m_Actions.end()); +}; + +template<> +void Strip::HandleMsg(SplitStripMessage& msg) { + if (msg.GetTransferredActions().empty() && !m_Actions.empty()) { + auto startToMove = m_Actions.begin() + msg.GetSrcActionIndex(); + msg.SetTransferredActions(startToMove, m_Actions.end()); + m_Actions.erase(startToMove, m_Actions.end()); + } else { + m_Actions = msg.GetTransferredActions(); + m_Position = msg.GetPosition(); + } +}; + +template<> +void Strip::HandleMsg(MergeStripsMessage& msg) { + if (msg.GetMigratedActions().empty() && !m_Actions.empty()) { + msg.SetMigratedActions(m_Actions.begin(), m_Actions.end()); + m_Actions.erase(m_Actions.begin(), m_Actions.end()); + } else { + m_Actions.insert(m_Actions.begin() + msg.GetDstActionIndex(), msg.GetMigratedActions().begin(), msg.GetMigratedActions().end()); + } +}; + +template<> +void Strip::HandleMsg(MigrateActionsMessage& msg) { + if (msg.GetMigratedActions().empty() && !m_Actions.empty()) { + auto startToMove = m_Actions.begin() + msg.GetSrcActionIndex(); + msg.SetMigratedActions(startToMove, m_Actions.end()); + m_Actions.erase(startToMove, m_Actions.end()); + } else { + m_Actions.insert(m_Actions.begin() + msg.GetDstActionIndex(), msg.GetMigratedActions().begin(), msg.GetMigratedActions().end()); + } +}; + +void Strip::SendBehaviorBlocksToClient(AMFArrayValue& args) const { + m_Position.SendBehaviorBlocksToClient(args); + + auto* actions = args.InsertArray("actions"); + for (auto& action : m_Actions) { + action.SendBehaviorBlocksToClient(*actions); + } +}; diff --git a/dGame/dPropertyBehaviors/Strip.h b/dGame/dPropertyBehaviors/Strip.h new file mode 100644 index 000000000..f3e10964e --- /dev/null +++ b/dGame/dPropertyBehaviors/Strip.h @@ -0,0 +1,23 @@ +#ifndef __STRIP__H__ +#define __STRIP__H__ + +#include "Action.h" +#include "StripUiPosition.h" + +#include + +class AMFArrayValue; + +class Strip { +public: + template + void HandleMsg(Msg& msg); + + void SendBehaviorBlocksToClient(AMFArrayValue& args) const; + bool IsEmpty() const { return m_Actions.empty(); } +private: + std::vector m_Actions; + StripUiPosition m_Position; +}; + +#endif //!__STRIP__H__