feat: add saving behaviors to the inventory (#1822)

* change behavior id to LWOOBJID

Convert behavior ID to LWOOBJID length

missed header

fix sqlite field names

sqlite brother

* feat: add saving behaviors to the inventory

consolidate copied code

consolidate copied code

Update ModelComponent.cpp

remove ability to save loot behaviors
This commit is contained in:
David Markowitz
2025-06-22 18:45:49 -07:00
committed by GitHub
parent f7c9267ba4
commit 8ba35be64d
11 changed files with 156 additions and 68 deletions

View File

@@ -1,6 +1,6 @@
#include "AddMessage.h"
AddMessage::AddMessage(const AMFArrayValue& arguments) : BehaviorMessageBase{ arguments } {
AddMessage::AddMessage(const AMFArrayValue& arguments, const LWOOBJID _owningPlayerID) : m_OwningPlayerID{ _owningPlayerID }, BehaviorMessageBase { arguments } {
const auto* const behaviorIndexValue = arguments.Get<double>("BehaviorIndex");
if (!behaviorIndexValue) return;

View File

@@ -9,11 +9,13 @@
*/
class AddMessage : public BehaviorMessageBase {
public:
AddMessage(const AMFArrayValue& arguments);
AddMessage(const AMFArrayValue& arguments, const LWOOBJID _owningPlayerID);
[[nodiscard]] uint32_t GetBehaviorIndex() const noexcept { return m_BehaviorIndex; };
[[nodiscard]] LWOOBJID GetOwningPlayerID() const noexcept { return m_OwningPlayerID; };
private:
uint32_t m_BehaviorIndex{ 0 };
LWOOBJID m_OwningPlayerID{};
};
#endif //!__ADDMESSAGE__H__

View File

@@ -19,11 +19,14 @@ public:
BehaviorMessageBase(const AMFArrayValue& arguments) : m_BehaviorId{ GetBehaviorIdFromArgument(arguments) } {}
[[nodiscard]] LWOOBJID GetBehaviorId() const noexcept { return m_BehaviorId; }
[[nodiscard]] bool IsDefaultBehaviorId() const noexcept { return m_BehaviorId == DefaultBehaviorId; }
[[nodiscard]] bool GetNeedsNewBehaviorID() const noexcept { return m_NeedsNewBehaviorID; }
void SetNeedsNewBehaviorID(const bool val) noexcept { m_NeedsNewBehaviorID = val; }
protected:
[[nodiscard]] LWOOBJID GetBehaviorIdFromArgument(const AMFArrayValue& arguments);
[[nodiscard]] int32_t GetActionIndexFromArgument(const AMFArrayValue& arguments, const std::string_view keyName = "actionIndex") const;
LWOOBJID m_BehaviorId{ DefaultBehaviorId };
bool m_NeedsNewBehaviorID{ false };
};
#endif //!__BEHAVIORMESSAGEBASE__H__

View File

@@ -1,6 +1,6 @@
#include "MoveToInventoryMessage.h"
MoveToInventoryMessage::MoveToInventoryMessage(const AMFArrayValue& arguments) : BehaviorMessageBase{ arguments } {
MoveToInventoryMessage::MoveToInventoryMessage(const AMFArrayValue& arguments, const LWOOBJID _owningPlayerID) : m_OwningPlayerID{ _owningPlayerID }, BehaviorMessageBase{ arguments } {
const auto* const behaviorIndexValue = arguments.Get<double>("BehaviorIndex");
if (!behaviorIndexValue) return;

View File

@@ -10,11 +10,13 @@ class AMFArrayValue;
*/
class MoveToInventoryMessage : public BehaviorMessageBase {
public:
MoveToInventoryMessage(const AMFArrayValue& arguments);
MoveToInventoryMessage(const AMFArrayValue& arguments, const LWOOBJID owningPlayerID);
[[nodiscard]] uint32_t GetBehaviorIndex() const noexcept { return m_BehaviorIndex; };
[[nodiscard]] LWOOBJID GetOwningPlayerID() const noexcept { return m_OwningPlayerID; };
private:
uint32_t m_BehaviorIndex;
LWOOBJID m_OwningPlayerID{};
};
#endif //!__MOVETOINVENTORYMESSAGE__H__

View File

@@ -107,9 +107,12 @@ void ControlBehaviors::ProcessCommand(Entity* const modelEntity, const AMFArrayV
if (!modelComponent) return;
ControlBehaviorContext context{ arguments, modelComponent, modelOwner };
bool needsNewBehaviorID = false;
if (command == "sendBehaviorListToClient") {
SendBehaviorListToClient(context);
} else if (command == "sendBehaviorBlocksToClient") {
SendBehaviorBlocksToClient(context);
} else if (command == "modelTypeChanged") {
const auto* const modelType = arguments.Get<double>("ModelType");
if (!modelType) return;
@@ -118,52 +121,54 @@ void ControlBehaviors::ProcessCommand(Entity* const modelEntity, const AMFArrayV
} else if (command == "toggleExecutionUpdates") {
// TODO
} else if (command == "addStrip") {
if (BehaviorMessageBase(context.arguments).IsDefaultBehaviorId()) RequestUpdatedID(context);
context.modelComponent->HandleControlBehaviorsMsg<AddStripMessage>(context.arguments);
needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg<AddStripMessage>(context.arguments);
} else if (command == "removeStrip") {
context.modelComponent->HandleControlBehaviorsMsg<RemoveStripMessage>(arguments);
needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg<RemoveStripMessage>(arguments);
} else if (command == "mergeStrips") {
context.modelComponent->HandleControlBehaviorsMsg<MergeStripsMessage>(arguments);
needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg<MergeStripsMessage>(arguments);
} else if (command == "splitStrip") {
context.modelComponent->HandleControlBehaviorsMsg<SplitStripMessage>(arguments);
needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg<SplitStripMessage>(arguments);
} else if (command == "updateStripUI") {
context.modelComponent->HandleControlBehaviorsMsg<UpdateStripUiMessage>(arguments);
needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg<UpdateStripUiMessage>(arguments);
} else if (command == "addAction") {
context.modelComponent->HandleControlBehaviorsMsg<AddActionMessage>(arguments);
needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg<AddActionMessage>(arguments);
} else if (command == "migrateActions") {
context.modelComponent->HandleControlBehaviorsMsg<MigrateActionsMessage>(arguments);
needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg<MigrateActionsMessage>(arguments);
} else if (command == "rearrangeStrip") {
context.modelComponent->HandleControlBehaviorsMsg<RearrangeStripMessage>(arguments);
} else if (command == "add") {
AddMessage msg{ context.arguments };
context.modelComponent->AddBehavior(msg);
SendBehaviorListToClient(context);
needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg<RearrangeStripMessage>(arguments);
} else if (command == "removeActions") {
context.modelComponent->HandleControlBehaviorsMsg<RemoveActionsMessage>(arguments);
needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg<RemoveActionsMessage>(arguments);
} else if (command == "updateAction") {
needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg<UpdateActionMessage>(arguments);
} else if (command == "rename") {
context.modelComponent->HandleControlBehaviorsMsg<RenameMessage>(arguments);
needsNewBehaviorID = context.modelComponent->HandleControlBehaviorsMsg<RenameMessage>(arguments);
// Send the list back to the client so the name is updated.
SendBehaviorListToClient(context);
} else if (command == "sendBehaviorBlocksToClient") {
SendBehaviorBlocksToClient(context);
} else if (command == "moveToInventory") {
MoveToInventoryMessage msg{ arguments };
context.modelComponent->MoveToInventory(msg);
} else if (command == "add") {
AddMessage msg{ context.arguments, context.modelOwner->GetObjectID() };
context.modelComponent->AddBehavior(msg);
SendBehaviorListToClient(context);
} else if (command == "moveToInventory" || command == "remove") {
// both moveToInventory and remove use the same args
const bool isRemove = command != "remove";
MoveToInventoryMessage msg{ arguments, modelOwner->GetObjectID() };
context.modelComponent->RemoveBehavior(msg, isRemove);
auto* characterComponent = modelOwner->GetComponent<CharacterComponent>();
if (!characterComponent) return;
AMFArrayValue args;
args.Insert("BehaviorID", std::to_string(msg.GetBehaviorId()));
GameMessages::SendUIMessageServerToSingleClient(modelOwner, characterComponent->GetSystemAddress(), "BehaviorRemoved", args);
if (!isRemove) {
AMFArrayValue args;
args.Insert("BehaviorID", std::to_string(msg.GetBehaviorId()));
GameMessages::SendUIMessageServerToSingleClient(modelOwner, characterComponent->GetSystemAddress(), "BehaviorRemoved", args);
}
SendBehaviorListToClient(context);
} else if (command == "updateAction") {
context.modelComponent->HandleControlBehaviorsMsg<UpdateActionMessage>(arguments);
} else {
LOG("Unknown behavior command (%s)", command.data());
}
if (needsNewBehaviorID) RequestUpdatedID(context);
}
ControlBehaviors::ControlBehaviors() {

View File

@@ -8,9 +8,12 @@
#include <ranges>
PropertyBehavior::PropertyBehavior() {
PropertyBehavior::PropertyBehavior(bool _isTemplated) {
m_LastEditedState = BehaviorState::HOME_STATE;
m_ActiveState = BehaviorState::HOME_STATE;
// Starts off as true so that only specific ways of adding behaviors allow a new id to be requested.
isTemplated = _isTemplated;
}
template<>
@@ -81,13 +84,6 @@ void PropertyBehavior::HandleMsg(RenameMessage& msg) {
m_Name = msg.GetName();
};
template<>
void PropertyBehavior::HandleMsg(AddMessage& msg) {
// TODO Parse the corresponding behavior xml file.
m_BehaviorId = msg.GetBehaviorId();
isLoot = m_BehaviorId != 7965;
};
template<>
void PropertyBehavior::HandleMsg(GameMessages::RequestUse& msg) {
m_States[m_ActiveState].HandleMsg(msg);
@@ -99,6 +95,12 @@ void PropertyBehavior::HandleMsg(GameMessages::ResetModelToDefaults& msg) {
for (auto& state : m_States | std::views::values) state.HandleMsg(msg);
}
void PropertyBehavior::CheckModifyState(BehaviorMessageBase& msg) {
if (!isTemplated && m_BehaviorId != BehaviorMessageBase::DefaultBehaviorId) return;
isTemplated = false;
msg.SetNeedsNewBehaviorID(true);
}
void PropertyBehavior::SendBehaviorListToClient(AMFArrayValue& args) const {
args.Insert("id", std::to_string(m_BehaviorId));
args.Insert("name", m_Name);
@@ -147,6 +149,9 @@ void PropertyBehavior::Serialize(tinyxml2::XMLElement& behavior) const {
behavior.SetAttribute("isLocked", isLocked);
behavior.SetAttribute("isLoot", isLoot);
// CUSTOM XML ATTRIBUTE
behavior.SetAttribute("isTemplated", isTemplated);
for (const auto& [stateId, state] : m_States) {
if (state.IsEmpty()) continue;
auto* const stateElement = behavior.InsertNewChildElement("State");
@@ -161,6 +166,9 @@ void PropertyBehavior::Deserialize(const tinyxml2::XMLElement& behavior) {
behavior.QueryBoolAttribute("isLocked", &isLocked);
behavior.QueryBoolAttribute("isLoot", &isLoot);
// CUSTOM XML ATTRIBUTE
if (!isTemplated) behavior.QueryBoolAttribute("isTemplated", &isTemplated);
for (const auto* stateElement = behavior.FirstChildElement("State"); stateElement; stateElement = stateElement->NextSiblingElement("State")) {
int32_t stateId = -1;
stateElement->QueryIntAttribute("id", &stateId);

View File

@@ -10,6 +10,7 @@ namespace tinyxml2 {
enum class BehaviorState : uint32_t;
class AMFArrayValue;
class BehaviorMessageBase;
class ModelComponent;
/**
@@ -17,7 +18,7 @@ class ModelComponent;
*/
class PropertyBehavior {
public:
PropertyBehavior();
PropertyBehavior(bool _isTemplated = false);
template <typename Msg>
void HandleMsg(Msg& msg);
@@ -26,10 +27,16 @@ public:
void VerifyLastEditedState();
void SendBehaviorListToClient(AMFArrayValue& args) const;
void SendBehaviorBlocksToClient(AMFArrayValue& args) const;
void CheckModifyState(BehaviorMessageBase& msg);
[[nodiscard]] LWOOBJID GetBehaviorId() const noexcept { return m_BehaviorId; }
void SetBehaviorId(LWOOBJID id) noexcept { m_BehaviorId = id; }
bool GetIsLoot() const noexcept { return isLoot; }
void SetIsLoot(const bool val) noexcept { isLoot = val; }
const std::string& GetName() const noexcept { return m_Name; }
void Serialize(tinyxml2::XMLElement& behavior) const;
void Deserialize(const tinyxml2::XMLElement& behavior);
@@ -52,6 +59,9 @@ private:
// Whether this behavior is custom or pre-fab.
bool isLoot = false;
// Whether or not the behavior has been modified from its original state.
bool isTemplated;
// The last state that was edited. This is used so when the client re-opens the behavior editor, it will open to the last edited state.
// If the last edited state has no strips, it will open to the first state that has strips.
BehaviorState m_LastEditedState;