From 3ebc6709dbdbbc6ae3f49447e6ae89c6053f6777 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 5 May 2025 00:17:39 -0700 Subject: [PATCH] feat: Property behaviors partially functional (#1759) * most of gameplay tab works * smash unsmash and wait working * Add pausing of models and behaviors * working basic behaviors * play sound functioning * add resetting * Fix asynchronous actions executing other strips actions * Add comments, remove dead code etc. * Skip Smashes if they coincide with a UnSmash Remove debug logs Comment on return --- dCommon/DluAssert.h | 2 +- dGame/dComponents/ModelComponent.cpp | 78 ++++++++- dGame/dComponents/ModelComponent.h | 33 +++- .../PropertyManagementComponent.cpp | 28 ++++ dGame/dGameMessages/GameMessageHandler.cpp | 6 +- dGame/dGameMessages/GameMessages.cpp | 114 ++++++++------ dGame/dGameMessages/GameMessages.h | 53 ++++++- dGame/dPropertyBehaviors/PropertyBehavior.cpp | 19 +++ dGame/dPropertyBehaviors/PropertyBehavior.h | 6 + dGame/dPropertyBehaviors/State.cpp | 14 ++ dGame/dPropertyBehaviors/State.h | 5 + dGame/dPropertyBehaviors/Strip.cpp | 148 +++++++++++++++++- dGame/dPropertyBehaviors/Strip.h | 23 +++ 13 files changed, 465 insertions(+), 64 deletions(-) diff --git a/dCommon/DluAssert.h b/dCommon/DluAssert.h index c54dd54e..f099443a 100644 --- a/dCommon/DluAssert.h +++ b/dCommon/DluAssert.h @@ -4,7 +4,7 @@ #include #ifdef _DEBUG -# define DluAssert(expression) assert(expression) +# define DluAssert(expression) do { assert(expression) } while(0) #else # define DluAssert(expression) #endif diff --git a/dGame/dComponents/ModelComponent.cpp b/dGame/dComponents/ModelComponent.cpp index 3a273c9b..730aefa2 100644 --- a/dGame/dComponents/ModelComponent.cpp +++ b/dGame/dComponents/ModelComponent.cpp @@ -10,12 +10,50 @@ #include "SimplePhysicsComponent.h" #include "Database.h" +#include "DluAssert.h" ModelComponent::ModelComponent(Entity* parent) : Component(parent) { m_OriginalPosition = m_Parent->GetDefaultPosition(); m_OriginalRotation = m_Parent->GetDefaultRotation(); + m_IsPaused = false; + m_NumListeningInteract = 0; m_userModelID = m_Parent->GetVarAs(u"userModelID"); + RegisterMsg(MessageType::Game::REQUEST_USE, this, &ModelComponent::OnRequestUse); + RegisterMsg(MessageType::Game::RESET_MODEL_TO_DEFAULTS, this, &ModelComponent::OnResetModelToDefaults); +} + +bool ModelComponent::OnResetModelToDefaults(GameMessages::GameMsg& msg) { + auto& reset = static_cast(msg); + for (auto& behavior : m_Behaviors) behavior.HandleMsg(reset); + GameMessages::UnSmash unsmash; + unsmash.target = GetParent()->GetObjectID(); + unsmash.duration = 0.0f; + unsmash.Send(UNASSIGNED_SYSTEM_ADDRESS); + m_NumListeningInteract = 0; + m_NumActiveUnSmash = 0; + m_Dirty = true; + Game::entityManager->SerializeEntity(GetParent()); + return true; +} + +bool ModelComponent::OnRequestUse(GameMessages::GameMsg& msg) { + bool toReturn = false; + if (!m_IsPaused) { + auto& requestUse = static_cast(msg); + for (auto& behavior : m_Behaviors) behavior.HandleMsg(requestUse); + toReturn = true; + } + + return toReturn; +} + +void ModelComponent::Update(float deltaTime) { + if (m_IsPaused) return; + + for (auto& behavior : m_Behaviors) { + behavior.Update(deltaTime, *this); + } } void ModelComponent::LoadBehaviors() { @@ -29,9 +67,9 @@ void ModelComponent::LoadBehaviors() { LOG_DEBUG("Loading behavior %d", behaviorId.value()); auto& inserted = m_Behaviors.emplace_back(); inserted.SetBehaviorId(*behaviorId); - + const auto behaviorStr = Database::Get()->GetBehavior(behaviorId.value()); - + tinyxml2::XMLDocument behaviorXml; auto res = behaviorXml.Parse(behaviorStr.c_str(), behaviorStr.size()); LOG_DEBUG("Behavior %i %d: %s", res, behaviorId.value(), behaviorStr.c_str()); @@ -45,6 +83,11 @@ void ModelComponent::LoadBehaviors() { } } +void ModelComponent::Resume() { + m_Dirty = true; + m_IsPaused = false; +} + void ModelComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { // ItemComponent Serialization. Pets do not get this serialization. if (!m_Parent->HasComponent(eReplicaComponentType::PET)) { @@ -56,14 +99,14 @@ void ModelComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialU //actual model component: outBitStream.Write1(); // Yes we are writing model info - outBitStream.Write0(); // Is pickable + outBitStream.Write(m_NumListeningInteract > 0); // Is pickable outBitStream.Write(2); // Physics type outBitStream.Write(m_OriginalPosition); // Original position outBitStream.Write(m_OriginalRotation); // Original rotation outBitStream.Write1(); // We are writing behavior info - outBitStream.Write(0); // Number of behaviors - outBitStream.Write1(); // Is this model paused + outBitStream.Write(m_Behaviors.size()); // Number of behaviors + outBitStream.Write(m_IsPaused); // Is this model paused if (bIsInitialUpdate) outBitStream.Write0(); // We are not writing model editing info } @@ -135,3 +178,28 @@ std::array, 5> ModelComponent::GetBehaviorsForSa } return toReturn; } + +void ModelComponent::AddInteract() { + LOG_DEBUG("Adding interact %i", m_NumListeningInteract); + m_Dirty = true; + m_NumListeningInteract++; +} + +void ModelComponent::RemoveInteract() { + DluAssert(m_NumListeningInteract > 0); + LOG_DEBUG("Removing interact %i", m_NumListeningInteract); + m_Dirty = true; + m_NumListeningInteract--; +} + +void ModelComponent::AddUnSmash() { + LOG_DEBUG("Adding UnSmash %i", m_NumActiveUnSmash); + m_NumActiveUnSmash++; +} + +void ModelComponent::RemoveUnSmash() { + // Players can assign an UnSmash without a Smash so an assert would be bad here + if (m_NumActiveUnSmash == 0) return; + LOG_DEBUG("Removing UnSmash %i", m_NumActiveUnSmash); + m_NumActiveUnSmash--; +} diff --git a/dGame/dComponents/ModelComponent.h b/dGame/dComponents/ModelComponent.h index 12ef7744..e63a7f0e 100644 --- a/dGame/dComponents/ModelComponent.h +++ b/dGame/dComponents/ModelComponent.h @@ -30,6 +30,10 @@ public: ModelComponent(Entity* parent); void LoadBehaviors(); + void Update(float deltaTime) override; + + bool OnRequestUse(GameMessages::GameMsg& msg); + bool OnResetModelToDefaults(GameMessages::GameMsg& msg); void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override; @@ -59,7 +63,7 @@ public: /** * 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 */ @@ -68,7 +72,7 @@ public: 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()) { + if (behavior.GetBehaviorId() == msg.GetBehaviorId()) { behavior.HandleMsg(msg); return; } @@ -109,12 +113,35 @@ public: void SendBehaviorListToClient(AMFArrayValue& args) const; void SendBehaviorBlocksToClient(int32_t behaviorToSend, AMFArrayValue& args) const; - + void VerifyBehaviors(); std::array, 5> GetBehaviorsForSave() const; + const std::vector& GetBehaviors() const { return m_Behaviors; }; + + void AddInteract(); + void RemoveInteract(); + + void Pause() { m_Dirty = true; m_IsPaused = true; } + + void AddUnSmash(); + void RemoveUnSmash(); + bool IsUnSmashing() const { return m_NumActiveUnSmash != 0; } + + void Resume(); private: + // Number of Actions that are awaiting an UnSmash to finish. + uint32_t m_NumActiveUnSmash{}; + + // Whether or not this component needs to have its extra data serialized. + bool m_Dirty{}; + + // The number of strips listening for a RequestUse GM to come in. + uint32_t m_NumListeningInteract{}; + + // Whether or not the model is paused and should reject all interactions regarding behaviors. + bool m_IsPaused{}; /** * The behaviors of the model * Note: This is a vector because the order of the behaviors matters when serializing to the client. diff --git a/dGame/dComponents/PropertyManagementComponent.cpp b/dGame/dComponents/PropertyManagementComponent.cpp index 4fae6d50..d866e0ad 100644 --- a/dGame/dComponents/PropertyManagementComponent.cpp +++ b/dGame/dComponents/PropertyManagementComponent.cpp @@ -255,6 +255,18 @@ void PropertyManagementComponent::OnStartBuilding() { // Push equipped items if (inventoryComponent) inventoryComponent->PushEquippedItems(); + + for (auto modelID : models | std::views::keys) { + auto* model = Game::entityManager->GetEntity(modelID); + if (model) { + auto* modelComponent = model->GetComponent(); + if (modelComponent) modelComponent->Pause(); + Game::entityManager->SerializeEntity(model); + GameMessages::ResetModelToDefaults reset; + reset.target = modelID; + model->HandleMsg(reset); + } + } } void PropertyManagementComponent::OnFinishBuilding() { @@ -267,6 +279,18 @@ void PropertyManagementComponent::OnFinishBuilding() { UpdateApprovedStatus(false); Save(); + + for (auto modelID : models | std::views::keys) { + auto* model = Game::entityManager->GetEntity(modelID); + if (model) { + auto* modelComponent = model->GetComponent(); + if (modelComponent) modelComponent->Resume(); + Game::entityManager->SerializeEntity(model); + GameMessages::ResetModelToDefaults reset; + reset.target = modelID; + model->HandleMsg(reset); + } + } } void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const NiPoint3 position, NiQuaternion rotation) { @@ -318,6 +342,8 @@ void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const N Entity* newEntity = Game::entityManager->CreateEntity(info); if (newEntity != nullptr) { Game::entityManager->ConstructEntity(newEntity); + auto* modelComponent = newEntity->GetComponent(); + if (modelComponent) modelComponent->Pause(); // Make sure the propMgmt doesn't delete our model after the server dies // Trying to do this after the entity is constructed. Shouldn't really change anything but @@ -363,6 +389,8 @@ void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const N info.nodes[0]->config.push_back(new LDFData(u"componentWhitelist", 1)); auto* model = spawner->Spawn(); + auto* modelComponent = model->GetComponent(); + if (modelComponent) modelComponent->Pause(); models.insert_or_assign(model->GetObjectID(), spawnerId); diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index ef890401..b201f999 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -45,6 +45,7 @@ namespace { using namespace GameMessages; using MessageCreator = std::function()>; std::map g_MessageHandlers = { + { REQUEST_USE, []() { return std::make_unique(); }}, { REQUEST_SERVER_OBJECT_INFO, []() { return std::make_unique(); } }, { SHOOTING_GALLERY_FIRE, []() { return std::make_unique(); } }, }; @@ -118,11 +119,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System break; } - case MessageType::Game::REQUEST_USE: { - GameMessages::HandleRequestUse(inStream, entity, sysAddr); - break; - } - case MessageType::Game::SET_FLAG: { GameMessages::HandleSetFlag(inStream, entity); break; diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 6fd7576f..1f60017b 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -4954,54 +4954,6 @@ void GameMessages::HandleQuickBuildCancel(RakNet::BitStream& inStream, Entity* e quickBuildComponent->CancelQuickBuild(Game::entityManager->GetEntity(userID), eQuickBuildFailReason::CANCELED_EARLY); } -void GameMessages::HandleRequestUse(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { - bool bIsMultiInteractUse = false; - unsigned int multiInteractID; - int multiInteractType; - bool secondary; - LWOOBJID objectID; - - inStream.Read(bIsMultiInteractUse); - inStream.Read(multiInteractID); - inStream.Read(multiInteractType); - inStream.Read(objectID); - inStream.Read(secondary); - - Entity* interactedObject = Game::entityManager->GetEntity(objectID); - - if (interactedObject == nullptr) { - LOG("Object %llu tried to interact, but doesn't exist!", objectID); - - return; - } - - if (interactedObject->GetLOT() == 9524) { - entity->GetCharacter()->SetBuildMode(true); - } - - if (bIsMultiInteractUse) { - if (multiInteractType == 0) { - auto* missionOfferComponent = static_cast(interactedObject->GetComponent(eReplicaComponentType::MISSION_OFFER)); - - if (missionOfferComponent != nullptr) { - missionOfferComponent->OfferMissions(entity, multiInteractID); - } - } else { - interactedObject->OnUse(entity); - } - } else { - interactedObject->OnUse(entity); - } - - //Perform use task if possible: - auto missionComponent = static_cast(entity->GetComponent(eReplicaComponentType::MISSION)); - - if (missionComponent == nullptr) return; - - missionComponent->Progress(eMissionTaskType::TALK_TO_NPC, interactedObject->GetLOT(), interactedObject->GetObjectID()); - missionComponent->Progress(eMissionTaskType::INTERACT, interactedObject->GetLOT(), interactedObject->GetObjectID()); -} - void GameMessages::HandlePlayEmote(RakNet::BitStream& inStream, Entity* entity) { int emoteID; LWOOBJID targetID; @@ -6443,4 +6395,70 @@ namespace GameMessages { auto* handlingEntity = Game::entityManager->GetEntity(targetForReport); if (handlingEntity) handlingEntity->HandleMsg(*this); } + + bool RequestUse::Deserialize(RakNet::BitStream& stream) { + if (!stream.Read(bIsMultiInteractUse)) return false; + if (!stream.Read(multiInteractID)) return false; + if (!stream.Read(multiInteractType)) return false; + if (!stream.Read(object)) return false; + if (!stream.Read(secondary)) return false; + return true; + } + + void RequestUse::Handle(Entity& entity, const SystemAddress& sysAddr) { + Entity* interactedObject = Game::entityManager->GetEntity(object); + + if (interactedObject == nullptr) { + LOG("Object %llu tried to interact, but doesn't exist!", object); + + return; + } + + if (interactedObject->GetLOT() == 9524) { + entity.GetCharacter()->SetBuildMode(true); + } + + if (bIsMultiInteractUse) { + if (multiInteractType == 0) { + auto* missionOfferComponent = static_cast(interactedObject->GetComponent(eReplicaComponentType::MISSION_OFFER)); + + if (missionOfferComponent != nullptr) { + missionOfferComponent->OfferMissions(&entity, multiInteractID); + } + } else { + interactedObject->OnUse(&entity); + } + } else { + interactedObject->OnUse(&entity); + } + + interactedObject->HandleMsg(*this); + + //Perform use task if possible: + auto missionComponent = entity.GetComponent(); + + if (!missionComponent) return; + + missionComponent->Progress(eMissionTaskType::TALK_TO_NPC, interactedObject->GetLOT(), interactedObject->GetObjectID()); + missionComponent->Progress(eMissionTaskType::INTERACT, interactedObject->GetLOT(), interactedObject->GetObjectID()); + } + + void Smash::Serialize(RakNet::BitStream& stream) const { + stream.Write(bIgnoreObjectVisibility); + stream.Write(force); + stream.Write(ghostCapacity); + stream.Write(killerID); + } + + void UnSmash::Serialize(RakNet::BitStream& stream) const { + stream.Write(builderID != LWOOBJID_EMPTY); + if (builderID != LWOOBJID_EMPTY) stream.Write(builderID); + stream.Write(duration != 3.0f); + if (builderID != 3.0f) stream.Write(duration); + } + + void PlayBehaviorSound::Serialize(RakNet::BitStream& stream) const { + stream.Write(soundID != -1); + if (soundID != -1) stream.Write(soundID); + } } diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index c3889674..a53fa647 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -631,7 +631,6 @@ namespace GameMessages { void HandleFireEventServerSide(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); void HandleRequestPlatformResync(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); void HandleQuickBuildCancel(RakNet::BitStream& inStream, Entity* entity); - void HandleRequestUse(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); void HandlePlayEmote(RakNet::BitStream& inStream, Entity* entity); void HandleModularBuildConvertModel(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr); void HandleSetFlag(RakNet::BitStream& inStream, Entity* entity); @@ -782,6 +781,58 @@ namespace GameMessages { bool Deserialize(RakNet::BitStream& bitStream) override; void Handle(Entity& entity, const SystemAddress& sysAddr) override; }; + + struct RequestUse : public GameMsg { + RequestUse() : GameMsg(MessageType::Game::REQUEST_USE) {} + + bool Deserialize(RakNet::BitStream& stream) override; + void Handle(Entity& entity, const SystemAddress& sysAddr) override; + + LWOOBJID object{}; + + bool secondary{ false }; + + // Set to true if this coming from a multi-interaction UI on the client. + bool bIsMultiInteractUse{}; + + // Used only for multi-interaction + unsigned int multiInteractID{}; + + // Used only for multi-interaction, is of the enum type InteractionType + int multiInteractType{}; + }; + + struct Smash : public GameMsg { + Smash() : GameMsg(MessageType::Game::SMASH) {} + + void Serialize(RakNet::BitStream& stream) const; + + bool bIgnoreObjectVisibility{}; + bool force{}; + float ghostCapacity{}; + LWOOBJID killerID{}; + }; + + struct UnSmash : public GameMsg { + UnSmash() : GameMsg(MessageType::Game::UN_SMASH) {} + + void Serialize(RakNet::BitStream& stream) const; + + LWOOBJID builderID{ LWOOBJID_EMPTY }; + float duration{ 3.0f }; + }; + + struct PlayBehaviorSound : public GameMsg { + PlayBehaviorSound() : GameMsg(MessageType::Game::PLAY_BEHAVIOR_SOUND) {} + + void Serialize(RakNet::BitStream& stream) const; + + int32_t soundID{ -1 }; + }; + + struct ResetModelToDefaults : public GameMsg { + ResetModelToDefaults() : GameMsg(MessageType::Game::RESET_MODEL_TO_DEFAULTS) {} + }; }; #endif // GAMEMESSAGES_H diff --git a/dGame/dPropertyBehaviors/PropertyBehavior.cpp b/dGame/dPropertyBehaviors/PropertyBehavior.cpp index 5bdb5827..c4c9d359 100644 --- a/dGame/dPropertyBehaviors/PropertyBehavior.cpp +++ b/dGame/dPropertyBehaviors/PropertyBehavior.cpp @@ -4,9 +4,13 @@ #include "BehaviorStates.h" #include "ControlBehaviorMsgs.h" #include "tinyxml2.h" +#include "ModelComponent.h" + +#include PropertyBehavior::PropertyBehavior() { m_LastEditedState = BehaviorState::HOME_STATE; + m_ActiveState = BehaviorState::HOME_STATE; } template<> @@ -84,6 +88,17 @@ void PropertyBehavior::HandleMsg(AddMessage& msg) { isLoot = m_BehaviorId != 7965; }; +template<> +void PropertyBehavior::HandleMsg(GameMessages::RequestUse& msg) { + m_States[m_ActiveState].HandleMsg(msg); +} + +template<> +void PropertyBehavior::HandleMsg(GameMessages::ResetModelToDefaults& msg) { + m_ActiveState = BehaviorState::HOME_STATE; + for (auto& state : m_States | std::views::values) state.HandleMsg(msg); +} + void PropertyBehavior::SendBehaviorListToClient(AMFArrayValue& args) const { args.Insert("id", std::to_string(m_BehaviorId)); args.Insert("name", m_Name); @@ -153,3 +168,7 @@ void PropertyBehavior::Deserialize(const tinyxml2::XMLElement& behavior) { m_States[static_cast(stateId)].Deserialize(*stateElement); } } + +void PropertyBehavior::Update(float deltaTime, ModelComponent& modelComponent) { + for (auto& state : m_States | std::views::values) state.Update(deltaTime, modelComponent); +} diff --git a/dGame/dPropertyBehaviors/PropertyBehavior.h b/dGame/dPropertyBehaviors/PropertyBehavior.h index 01eb1968..5232d7f0 100644 --- a/dGame/dPropertyBehaviors/PropertyBehavior.h +++ b/dGame/dPropertyBehaviors/PropertyBehavior.h @@ -10,6 +10,7 @@ namespace tinyxml2 { enum class BehaviorState : uint32_t; class AMFArrayValue; +class ModelComponent; /** * Represents the Entity of a Property Behavior and holds data associated with the behavior @@ -31,7 +32,12 @@ public: void Serialize(tinyxml2::XMLElement& behavior) const; void Deserialize(const tinyxml2::XMLElement& behavior); + + void Update(float deltaTime, ModelComponent& modelComponent); + private: + // The current active behavior state. Behaviors can only be in ONE state at a time. + BehaviorState m_ActiveState; // The states this behavior has. std::map m_States; diff --git a/dGame/dPropertyBehaviors/State.cpp b/dGame/dPropertyBehaviors/State.cpp index 1fb072c1..0655c78c 100644 --- a/dGame/dPropertyBehaviors/State.cpp +++ b/dGame/dPropertyBehaviors/State.cpp @@ -117,6 +117,16 @@ void State::HandleMsg(MigrateActionsMessage& msg) { } }; +template<> +void State::HandleMsg(GameMessages::RequestUse& msg) { + for (auto& strip : m_Strips) strip.HandleMsg(msg); +} + +template<> +void State::HandleMsg(GameMessages::ResetModelToDefaults& msg) { + for (auto& strip : m_Strips) strip.HandleMsg(msg); +} + bool State::IsEmpty() const { for (const auto& strip : m_Strips) { if (!strip.IsEmpty()) return false; @@ -152,3 +162,7 @@ void State::Deserialize(const tinyxml2::XMLElement& state) { strip.Deserialize(*stripElement); } } + +void State::Update(float deltaTime, ModelComponent& modelComponent) { + for (auto& strip : m_Strips) strip.Update(deltaTime, modelComponent); +} diff --git a/dGame/dPropertyBehaviors/State.h b/dGame/dPropertyBehaviors/State.h index 3e8c827f..ab6d76a4 100644 --- a/dGame/dPropertyBehaviors/State.h +++ b/dGame/dPropertyBehaviors/State.h @@ -8,6 +8,7 @@ namespace tinyxml2 { } class AMFArrayValue; +class ModelComponent; class State { public: @@ -19,7 +20,11 @@ public: void Serialize(tinyxml2::XMLElement& state) const; void Deserialize(const tinyxml2::XMLElement& state); + + void Update(float deltaTime, ModelComponent& modelComponent); private: + + // The strips contained within this state. std::vector m_Strips; }; diff --git a/dGame/dPropertyBehaviors/Strip.cpp b/dGame/dPropertyBehaviors/Strip.cpp index d6dc050b..b1dd94eb 100644 --- a/dGame/dPropertyBehaviors/Strip.cpp +++ b/dGame/dPropertyBehaviors/Strip.cpp @@ -3,6 +3,11 @@ #include "Amf3.h" #include "ControlBehaviorMsgs.h" #include "tinyxml2.h" +#include "dEntity/EntityInfo.h" +#include "ModelComponent.h" +#include "PlayerManager.h" + +#include "DluAssert.h" template <> void Strip::HandleMsg(AddStripMessage& msg) { @@ -75,7 +80,138 @@ void Strip::HandleMsg(MigrateActionsMessage& msg) { } else { m_Actions.insert(m_Actions.begin() + msg.GetDstActionIndex(), msg.GetMigratedActions().begin(), msg.GetMigratedActions().end()); } -}; +} + +template<> +void Strip::HandleMsg(GameMessages::RequestUse& msg) { + if (m_PausedTime > 0.0f) return; + + if (m_Actions[m_NextActionIndex].GetType() == "OnInteract") { + IncrementAction(); + m_WaitingForAction = false; + } +} + +template<> +void Strip::HandleMsg(GameMessages::ResetModelToDefaults& msg) { + m_WaitingForAction = false; + m_PausedTime = 0.0f; + m_NextActionIndex = 0; +} + +void Strip::IncrementAction() { + if (m_Actions.empty()) return; + m_NextActionIndex++; + m_NextActionIndex %= m_Actions.size(); +} + +void Strip::Spawn(LOT lot, Entity& entity) { + EntityInfo info{}; + info.lot = lot; + info.pos = entity.GetPosition(); + info.rot = NiQuaternionConstant::IDENTITY; + info.spawnerID = entity.GetObjectID(); + Game::entityManager->ConstructEntity(Game::entityManager->CreateEntity(info, nullptr, &entity)); +} + +// Spawns a specific drop for all +void Strip::SpawnDrop(LOT dropLOT, Entity& entity) { + for (auto* const player : PlayerManager::GetAllPlayers()) { + GameMessages::SendDropClientLoot(player, entity.GetObjectID(), dropLOT, 0, entity.GetPosition()); + } +} + +void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent) { + auto& entity = *modelComponent.GetParent(); + auto& nextAction = GetNextAction(); + auto number = nextAction.GetValueParameterDouble(); + auto numberAsInt = static_cast(number); + auto nextActionType = GetNextAction().GetType(); + if (nextActionType == "SpawnStromling") { + Spawn(10495, entity); // Stromling property + } else if (nextActionType == "SpawnPirate") { + Spawn(10497, entity); // Maelstrom Pirate property + } else if (nextActionType == "SpawnRonin") { + Spawn(10498, entity); // Dark Ronin property + } else if (nextActionType == "DropImagination") { + for (; numberAsInt > 0; numberAsInt--) SpawnDrop(935, entity); // 1 Imagination powerup + } else if (nextActionType == "DropHealth") { + for (; numberAsInt > 0; numberAsInt--) SpawnDrop(177, entity); // 1 Life powerup + } else if (nextActionType == "DropArmor") { + for (; numberAsInt > 0; numberAsInt--) SpawnDrop(6431, entity); // 1 Armor powerup + } else if (nextActionType == "Smash") { + if (!modelComponent.IsUnSmashing()) { + GameMessages::Smash smash{}; + smash.target = entity.GetObjectID(); + smash.killerID = entity.GetObjectID(); + smash.Send(UNASSIGNED_SYSTEM_ADDRESS); + } + } else if (nextActionType == "UnSmash") { + GameMessages::UnSmash unsmash{}; + unsmash.target = entity.GetObjectID(); + unsmash.duration = number; + unsmash.builderID = LWOOBJID_EMPTY; + unsmash.Send(UNASSIGNED_SYSTEM_ADDRESS); + modelComponent.AddUnSmash(); + + m_PausedTime = number; + } else if (nextActionType == "Wait") { + m_PausedTime = number; + } else if (nextActionType == "PlaySound") { + GameMessages::PlayBehaviorSound sound; + sound.target = modelComponent.GetParent()->GetObjectID(); + sound.soundID = numberAsInt; + sound.Send(UNASSIGNED_SYSTEM_ADDRESS); + } else { + static std::set g_WarnedActions; + if (!g_WarnedActions.contains(nextActionType.data())) { + LOG("Tried to play action (%s) which is not supported.", nextActionType.data()); + g_WarnedActions.insert(nextActionType.data()); + } + return; + } + + IncrementAction(); +} + +// Decrement references to the previous state if we have progressed to the next one. +void Strip::RemoveStates(ModelComponent& modelComponent) const { + const auto& prevAction = GetPreviousAction(); + const auto prevActionType = prevAction.GetType(); + + if (prevActionType == "OnInteract") { + modelComponent.RemoveInteract(); + Game::entityManager->SerializeEntity(modelComponent.GetParent()); + } else if (prevActionType == "UnSmash") { + modelComponent.RemoveUnSmash(); + } +} + +void Strip::Update(float deltaTime, ModelComponent& modelComponent) { + m_PausedTime -= deltaTime; + if (m_PausedTime > 0.0f) return; + + m_PausedTime = 0.0f; + + if (m_WaitingForAction) return; + + auto& entity = *modelComponent.GetParent(); + auto& nextAction = GetNextAction(); + + RemoveStates(modelComponent); + + // Check for starting blocks and if not a starting block proc this blocks action + if (m_NextActionIndex == 0) { + if (nextAction.GetType() == "OnInteract") { + modelComponent.AddInteract(); + Game::entityManager->SerializeEntity(entity); + m_WaitingForAction = true; + + } + } else { // should be a normal block + ProcNormalAction(deltaTime, modelComponent); + } +} void Strip::SendBehaviorBlocksToClient(AMFArrayValue& args) const { m_Position.SendBehaviorBlocksToClient(args); @@ -106,3 +242,13 @@ void Strip::Deserialize(const tinyxml2::XMLElement& strip) { action.Deserialize(*actionElement); } } + +const Action& Strip::GetNextAction() const { + DluAssert(m_NextActionIndex < m_Actions.size()); return m_Actions[m_NextActionIndex]; +} + +const Action& Strip::GetPreviousAction() const { + DluAssert(m_NextActionIndex < m_Actions.size()); + size_t index = m_NextActionIndex == 0 ? m_Actions.size() - 1 : m_NextActionIndex - 1; + return m_Actions[index]; +} diff --git a/dGame/dPropertyBehaviors/Strip.h b/dGame/dPropertyBehaviors/Strip.h index 8fd7d0fe..3f929a7d 100644 --- a/dGame/dPropertyBehaviors/Strip.h +++ b/dGame/dPropertyBehaviors/Strip.h @@ -11,6 +11,7 @@ namespace tinyxml2 { } class AMFArrayValue; +class ModelComponent; class Strip { public: @@ -22,8 +23,30 @@ public: void Serialize(tinyxml2::XMLElement& strip) const; void Deserialize(const tinyxml2::XMLElement& strip); + + const Action& GetNextAction() const; + const Action& GetPreviousAction() const; + + void IncrementAction(); + void Spawn(LOT object, Entity& entity); + void Update(float deltaTime, ModelComponent& modelComponent); + void SpawnDrop(LOT dropLOT, Entity& entity); + void ProcNormalAction(float deltaTime, ModelComponent& modelComponent); + void RemoveStates(ModelComponent& modelComponent) const; private: + // Indicates this Strip is waiting for an action to be taken upon it to progress to its actions + bool m_WaitingForAction{ false }; + + // The amount of time this strip is paused for. Any interactions with this strip should be bounced if this is greater than 0. + float m_PausedTime{ 0.0f }; + + // The index of the next action to be played. This should always be within range of [0, m_Actions.size()). + size_t m_NextActionIndex{ 0 }; + + // The list of actions to be executed on this behavior. std::vector m_Actions; + + // The location of this strip on the UGBehaviorEditor UI StripUiPosition m_Position; };