diff --git a/dCommon/CMakeLists.txt b/dCommon/CMakeLists.txt index d020ff72..7bb52a67 100644 --- a/dCommon/CMakeLists.txt +++ b/dCommon/CMakeLists.txt @@ -4,6 +4,7 @@ set(DCOMMON_SOURCES "BinaryIO.cpp" "dConfig.cpp" "Diagnostics.cpp" + "Flag.h" "Logger.cpp" "Game.cpp" "GeneralUtils.cpp" diff --git a/dCommon/Flag.h b/dCommon/Flag.h new file mode 100644 index 00000000..a26cbc69 --- /dev/null +++ b/dCommon/Flag.h @@ -0,0 +1,82 @@ +#include "GeneralUtils.h" + +#include +// TODO: Test bounds checking + +template + requires std::is_enum_v +class Flag { +public: + using type = T; + using underlying_type = std::underlying_type_t; + static constexpr auto MAX_FLAG_VAL = std::bit_width(sizeof(underlying_type)); + + /** + * Sets one or more flags + * @param flag Flag(s) to set + */ + template ... varArg> + constexpr void Set(varArg... flag) noexcept { + m_Flags |= (ConvertFlag(flag) | ...); + } + + /** + * Sets ONLY have the specified flag(s), clearing all others + * @param flag Flag(s) to set exclusively + */ + template ... varArg> + constexpr void Reset(varArg... flag) { + m_Flags = (ConvertFlag(flag) | ...); + } + + /** + * Unsets one or more flags + * @param flag Flag(s) to unset + */ + template ... varArg> + constexpr void Unset(varArg... flag) { + m_Flags &= ~(ConvertFlag(flag) | ...); + } + + /** + * Returns true all the specified flag(s) are set + * @param flag Flag(s) to check + */ + template ... varArg> + constexpr bool Has(varArg... flag) const { + return (m_Flags & (ConvertFlag(flag) | ...)) == (ConvertFlag(flag) | ...); + } + + /** + * Returns true ONLY the specified flag(s) are set + * @param flag Flag(s) to check + */ + template ... varArg> + constexpr bool HasOnly(varArg... flag) const { + return m_Flags == (ConvertFlag(flag) | ...); + } + + /** + * Operator overload to allow for '=' assignment + */ + constexpr Flag& operator=(const T value) { + Reset(value); + return *this; + } + +private: + [[nodiscard]] + static constexpr underlying_type ConvertFlag(const T flag) { + auto flag_val = GeneralUtils::ToUnderlying(flag); + if (flag_val != 0) { + return 1 << flag_val; + } + // This should theoeretically be possible to do at compile time, but idk how + if (std::bit_width(flag_val) > MAX_FLAG_VAL) { + throw std::runtime_error{ "Enum value too large to be a flag!" }; + } + return flag_val; + } + + underlying_type m_Flags; +}; diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index 6010b4e6..bc64f3be 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -37,7 +37,7 @@ #include "eMissionState.h" #include "dNavMesh.h" -std::unordered_map PetComponent::buildCache{}; +std::unordered_map PetComponent::buildCache{}; std::unordered_map PetComponent::currentActivities{}; std::unordered_map PetComponent::activePets{}; @@ -45,7 +45,7 @@ std::unordered_map PetComponent::activePets{}; * Maps all the pet lots to a flag indicating that the player has caught it. All basic pets have been guessed by ObjID * while the faction ones could be checked using their respective missions. */ -std::map PetComponent::petFlags = { +std::map PetComponent::petFlags{ { 3050, 801 }, // Elephant { 3054, 803 }, // Cat { 3195, 806 }, // Triceratops @@ -78,31 +78,17 @@ std::map PetComponent::petFlags = { PetComponent::PetComponent(Entity* parentEntity, uint32_t componentId) : Component{ parentEntity } { m_PetInfo = CDClientManager::GetTable()->GetByID(componentId); // TODO: Make reference when safe m_ComponentId = componentId; - m_Interaction = LWOOBJID_EMPTY; - m_InteractType = PetInteractType::none; - m_Owner = LWOOBJID_EMPTY; - m_ModerationStatus = 0; - m_Tamer = LWOOBJID_EMPTY; - m_ModelId = LWOOBJID_EMPTY; - m_Timer = 0; - m_TimerAway = 0; - m_TimerBounce = 0; - m_DatabaseId = LWOOBJID_EMPTY; m_Flags = PetFlag::SPAWNING; // Tameable - m_Ability = ePetAbilityType::Invalid; m_StartPosition = m_Parent->GetPosition(); m_MovementAI = nullptr; - m_Preconditions = nullptr; m_ReadyToInteract = false; - SetPetAiState(PetAiState::spawn); + m_State = PetAiState::spawn; SetIsHandlingInteraction(false); std::string checkPreconditions = GeneralUtils::UTF16ToWTF8(parentEntity->GetVar(u"CheckPrecondition")); - - if (!checkPreconditions.empty()) { - SetPreconditions(checkPreconditions); - } + m_Preconditions = + checkPreconditions.empty() ? std::nullopt : std::make_optional(PreconditionExpression(checkPreconditions)); m_FollowRadius = 8.0f; //Game::zoneManager->GetPetFollowRadius(); // TODO: FIX THIS TO LOAD DYNAMICALLY } @@ -113,13 +99,13 @@ void PetComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpd outBitStream.Write1(); // Always serialize as dirty for now outBitStream.Write(m_Flags); - outBitStream.Write(tamed ? m_Ability : ePetAbilityType::Invalid); // Something with the overhead icon? + outBitStream.Write(tamed ? m_Interaction.ability : ePetAbilityType::Invalid); // Something with the overhead icon? - const bool interacting = m_Interaction != LWOOBJID_EMPTY; + const bool interacting = m_Interaction.obj != LWOOBJID_EMPTY; outBitStream.Write(interacting); if (interacting) { - outBitStream.Write(m_Interaction); + outBitStream.Write(m_Interaction.obj); } outBitStream.Write(tamed); @@ -148,16 +134,11 @@ void PetComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpd } } -void PetComponent::SetPetAiState(PetAiState newState) { - if (newState == GetPetAiState()) return; - this->m_State = newState; -} - void PetComponent::OnUse(Entity* originator) { LOG_DEBUG("PET USE!"); if (IsReadyToInteract()) { - switch (GetAbility()) { + switch (m_Interaction.ability) { case ePetAbilityType::DigAtPosition: // Treasure dig StartInteractTreasureDig(); break; @@ -186,7 +167,7 @@ void PetComponent::OnUse(Entity* originator) { if (!inventoryComponent) return; - if (m_Preconditions != nullptr && !m_Preconditions->Check(originator, true)) return; + if (m_Preconditions.has_value() && !m_Preconditions->Check(originator, true)) return; auto* const movementAIComponent = m_Parent->GetComponent(); @@ -223,7 +204,7 @@ void PetComponent::OnUse(Entity* originator) { buildFile = std::string(result.getStringField("ValidPiecesLXF")); - PetPuzzleData data; + PuzzleData data; data.buildFile = buildFile; data.puzzleModelLot = result.getIntField("PuzzleModelLot"); data.timeLimit = result.getFloatField("Timelimit"); @@ -324,7 +305,7 @@ void PetComponent::OnUse(Entity* originator) { GameMessages::SendNotifyPetTamingPuzzleSelected(originator->GetObjectID(), bricks, originator->GetSystemAddress()); m_Tamer = originator->GetObjectID(); - SetFlag(PetFlag::IDLE, PetFlag::UNKNOWN4); + m_Flags.Set(PetFlag::IDLE, PetFlag::UNKNOWN4); currentActivities.insert_or_assign(m_Tamer, m_Parent->GetObjectID()); @@ -352,7 +333,7 @@ void PetComponent::Update(float deltaTime) { ClientFailTamingMinigame(); // TODO: This is not despawning the built model correctly } - if (HasFlag(PetFlag::SPAWNING)) OnSpawn(); + if (m_Flags.Has(PetFlag::SPAWNING)) OnSpawn(); // Handle pet AI states switch (m_State) { @@ -519,7 +500,7 @@ void PetComponent::NotifyTamingBuildSuccess(const NiPoint3 position) { missionComponent->Progress(eMissionTaskType::PET_TAMING, m_Parent->GetLOT()); } - SetOnlyFlag(PetFlag::IDLE); + m_Flags.Reset(PetFlag::IDLE); auto* const characterComponent = tamer->GetComponent(); if (characterComponent != nullptr) { @@ -630,7 +611,7 @@ void PetComponent::ClientExitTamingMinigame(bool voluntaryExit) { currentActivities.erase(m_Tamer); - SetOnlyFlag(PetFlag::TAMEABLE); + m_Flags.Reset(PetFlag::TAMEABLE); m_Tamer = LWOOBJID_EMPTY; m_Timer = 0; @@ -679,7 +660,7 @@ void PetComponent::ClientFailTamingMinigame() { currentActivities.erase(m_Tamer); - SetOnlyFlag(PetFlag::TAMEABLE); + m_Flags.Reset(PetFlag::TAMEABLE); m_Tamer = LWOOBJID_EMPTY; m_Timer = 0; @@ -742,12 +723,12 @@ void PetComponent::OnSpawn() { //SetOnlyFlag(IDLE); //SetStatus(PetFlag::NONE); SetPetAiState(PetAiState::follow); } else { - SetFlag(PetFlag::TAMEABLE); + m_Flags.Set(PetFlag::TAMEABLE); SetPetAiState(PetAiState::idle); } - SetFlag(PetFlag::IDLE); - UnsetFlag(PetFlag::SPAWNING); + m_Flags.Set(PetFlag::IDLE); + m_Flags.Unset(PetFlag::SPAWNING); Game::entityManager->SerializeEntity(m_Parent); } @@ -825,7 +806,7 @@ void PetComponent::OnInteract() { return; } - switch (GetInteractType()) { + switch (m_Interaction.type) { case PetInteractType::bouncer: if (IsReadyToInteract()) HandleInteractBouncer(); else SetupInteractBouncer(); @@ -844,10 +825,10 @@ void PetComponent::OnInteract() { } } -void PetComponent::StartInteract(const NiPoint3& position, const PetInteractType interactType, const LWOOBJID& interactID) { - SetInteraction(interactID); // TODO: Check if this should be serialized for goToObj - SetInteractType(interactType); - SetAbility(ePetAbilityType::GoToObject); +void PetComponent::StartInteract(const NiPoint3& position, const PetInteractType interactionType, const LWOOBJID& interactID) { + m_Interaction.obj; // TODO: Check if this should be serialized for goToObj + m_Interaction.type = interactionType; + m_Interaction.ability = ePetAbilityType::GoToObject; SetPetAiState(PetAiState::goToObj); m_MovementAI->SetMaxSpeed(m_PetInfo.runSpeed); m_MovementAI->SetHaltDistance(0.0f); @@ -860,13 +841,13 @@ void PetComponent::StopInteract(bool bDontSerialize) { Entity* const owner = GetOwner(); if (!owner) return; - const auto petAbility = ePetAbilityType::Invalid; + constexpr auto petAbility = ePetAbilityType::Invalid; - SetInteraction(LWOOBJID_EMPTY); - SetInteractType(PetInteractType::none); - SetAbility(petAbility); + m_Interaction.obj = LWOOBJID_EMPTY; + m_Interaction.type = PetInteractType::none; + m_Interaction.ability = petAbility; SetPetAiState(PetAiState::follow); - SetOnlyFlag(PetFlag::IDLE); + m_Flags.Reset(PetFlag::IDLE); SetIsReadyToInteract(false); SetIsHandlingInteraction(false); // Needed? m_MovementAI->SetMaxSpeed(m_PetInfo.sprintSpeed); @@ -886,11 +867,11 @@ void PetComponent::SetupInteractBouncer() { LOG_DEBUG("Setting up bouncer interaction!"); SetIsReadyToInteract(true); - const auto petAbility = ePetAbilityType::JumpOnObject; + constexpr auto petAbility = ePetAbilityType::JumpOnObject; - SetAbility(petAbility); - UnsetFlag(PetFlag::IDLE); - SetFlag(PetFlag::ON_SWITCH, PetFlag::NOT_WAITING); //SetStatus(PetFlag::NOT_WAITING); // TODO: Double-check this is the right flag being set + m_Interaction.ability = petAbility; + m_Flags.Unset(PetFlag::IDLE); + m_Flags.Set(PetFlag::ON_SWITCH, PetFlag::NOT_WAITING); //SetStatus(PetFlag::NOT_WAITING); // TODO: Double-check this is the right flag being set LOG_DEBUG("m_Flags = %d", m_Flags); Game::entityManager->SerializeEntity(m_Parent); // TODO: Double-check pet packet captures @@ -969,11 +950,11 @@ void PetComponent::SetupInteractTreasureDig() { LOG_DEBUG("Setting up dig interaction!"); SetIsReadyToInteract(true); - const auto petAbility = ePetAbilityType::DigAtPosition; + constexpr auto petAbility = ePetAbilityType::DigAtPosition; - SetAbility(petAbility); - UnsetFlag(PetFlag::IDLE); - SetFlag(PetFlag::ON_SWITCH, PetFlag::NOT_WAITING); // TODO: Double-check this is the right flag being set + m_Interaction.ability = petAbility; + m_Flags.Unset(PetFlag::IDLE); + m_Flags.Set(PetFlag::ON_SWITCH, PetFlag::NOT_WAITING); // TODO: Double-check this is the right flag being set LOG_DEBUG("m_Flags = %d", m_Flags); Game::entityManager->SerializeEntity(m_Parent); // TODO: Double-check pet packet captures @@ -1007,8 +988,8 @@ void PetComponent::StartInteractTreasureDig() { Game::entityManager->SerializeEntity(user); SetIsHandlingInteraction(true); - UnsetFlag(PetFlag::ON_SWITCH, PetFlag::NOT_WAITING); // TODO: FIND THE CORRECT STATUS TO USE HERE - SetFlag(PetFlag::IDLE); + m_Flags.Unset(PetFlag::ON_SWITCH, PetFlag::NOT_WAITING); // TODO: FIND THE CORRECT STATUS TO USE HERE + m_Flags.Set(PetFlag::IDLE); LOG_DEBUG("StartInteractTreasureDig() m_Flags = %d", m_Flags); Game::entityManager->SerializeEntity(m_Parent); @@ -1057,7 +1038,7 @@ void PetComponent::Activate(Item* item, bool registerPet, bool fromTaming) { // auto* const owner = GetOwner(); if (!owner) return; - SetFlag(PetFlag::SPAWNING); + m_Flags.Set(PetFlag::SPAWNING); auto databaseData = inventoryComponent->GetDatabasePet(m_DatabaseId); @@ -1203,39 +1184,11 @@ void PetComponent::Command(const NiPoint3& position, const LWOOBJID source, cons } } -LWOOBJID PetComponent::GetOwnerId() const { - return m_Owner; -} - Entity* PetComponent::GetOwner() const { return Game::entityManager->GetEntity(m_Owner); } -LWOOBJID PetComponent::GetDatabaseId() const { - return m_DatabaseId; -} - -LWOOBJID PetComponent::GetInteraction() const { - return m_Interaction; -} - -LWOOBJID PetComponent::GetItemId() const { - return m_ItemId; -} - -ePetAbilityType PetComponent::GetAbility() const { - return m_Ability; -} - -void PetComponent::SetInteraction(LWOOBJID value) { - m_Interaction = value; -} - -void PetComponent::SetAbility(ePetAbilityType value) { - m_Ability = value; -} - -PetComponent* PetComponent::GetTamingPet(LWOOBJID tamer) { +PetComponent* PetComponent::GetTamingPet(const LWOOBJID tamer) { const auto& pair = currentActivities.find(tamer); if (pair == currentActivities.end()) { @@ -1253,7 +1206,7 @@ PetComponent* PetComponent::GetTamingPet(LWOOBJID tamer) { return entity->GetComponent(); } -PetComponent* PetComponent::GetActivePet(LWOOBJID owner) { +PetComponent* PetComponent::GetActivePet(const LWOOBJID owner) { const auto& pair = activePets.find(owner); if (pair == activePets.end()) { @@ -1271,13 +1224,6 @@ PetComponent* PetComponent::GetActivePet(LWOOBJID owner) { return entity->GetComponent(); } -Entity* PetComponent::GetParentEntity() const { - return m_Parent; -} - -PetComponent::~PetComponent() { -} - void PetComponent::SetPetNameForModeration(const std::string& petName) { int approved = 1; //default, in mod @@ -1291,7 +1237,7 @@ void PetComponent::SetPetNameForModeration(const std::string& petName) { } void PetComponent::LoadPetNameFromModeration() { - auto petNameInfo = Database::Get()->GetPetNameInfo(m_DatabaseId); + const auto petNameInfo = Database::Get()->GetPetNameInfo(m_DatabaseId); if (petNameInfo) { m_ModerationStatus = petNameInfo->approvalStatus; if (m_ModerationStatus == 2) { @@ -1299,7 +1245,3 @@ void PetComponent::LoadPetNameFromModeration() { } } } - -void PetComponent::SetPreconditions(std::string& preconditions) { - m_Preconditions = new PreconditionExpression(preconditions); -} diff --git a/dGame/dComponents/PetComponent.h b/dGame/dComponents/PetComponent.h index 568b86c0..7244d5cb 100644 --- a/dGame/dComponents/PetComponent.h +++ b/dGame/dComponents/PetComponent.h @@ -2,6 +2,7 @@ #define PETCOMPONENT_H #include "Entity.h" +#include "Flag.h" #include "MovementAIComponent.h" #include "Component.h" #include "Preconditions.h" @@ -11,6 +12,13 @@ #include "CDPetComponentTable.h" #include "CDClientManager.h" +#include +#include + +// Forward declarations +class PetTest; +class PetComponentFlagTest; + /* * The current state of the pet AI */ @@ -37,44 +45,18 @@ enum class PetInteractType : uint8_t { */ enum class PetFlag : uint32_t { NONE, - IDLE = 1 << 0, //0x01 - Seems to be "idle," which the game doesn't differentiate from "follow" - UNKNOWN2 = 1 << 1, //0x02, - UNKNOWN4 = 1 << 2, //0x04 - FOLLOWING(?) - BEING_TAMED = 1 << 4, //0x10, - NOT_WAITING = 1 << 5, //0x20, - IMMOBILE = 1 << 6, //0x40 - Seems to be the "stop moving" flag - called when taming begins and stays active until a name is submitted - SPAWNING = 1 << 7, //0x80 - ON_SWITCH = 1 << 8, //0x100 - UNKNOWN1024 = 1 << 10, //0x400 - TAMEABLE = 1 << 26 //0x4000000 + IDLE, //0x01 - Seems to be "idle," which the game doesn't differentiate from "follow" + UNKNOWN2, //0x02, + UNKNOWN4, //0x04 - FOLLOWING(?) + BEING_TAMED, //0x10, + NOT_WAITING, //0x20, + IMMOBILE, //0x40 - Seems to be the "stop moving" flag - called when taming begins and stays active until a name is submitted + SPAWNING, //0x80 + ON_SWITCH, //0x100 + UNKNOWN1024 = 10, //0x400 + TAMEABLE = 26 //0x4000000 }; -/** - * Define bitwise operators for PetFlag (TODO: Encapsulate into proper class) -*/ -constexpr PetFlag operator|(const PetFlag lhs, const PetFlag rhs) { - using underlying_type = std::underlying_type_t; - return static_cast(static_cast(lhs) | static_cast(rhs)); -} - -constexpr PetFlag& operator|=(PetFlag& lhs, const PetFlag rhs) { - return lhs = lhs | rhs; -} - -constexpr PetFlag operator&(const PetFlag lhs, const PetFlag rhs) { - using underlying_type = std::underlying_type_t; - return static_cast(static_cast(lhs) & static_cast(rhs)); -} - -constexpr PetFlag& operator&=(PetFlag& lhs, const PetFlag rhs) { - return lhs = lhs & rhs; -} - -constexpr PetFlag operator~(const PetFlag flag) { - using underlying_type = std::underlying_type_t; - return static_cast(~static_cast(flag)); -} - /** * The pet emote animation ids that can used in PetComponent::Command() */ @@ -88,8 +70,7 @@ enum class PetEmote : int32_t { * Represents an entity that is a pet. This pet can be tamed and consequently follows the tamer around, allowing it * to dig for treasure and activate pet bouncers. */ -class PetComponent final : public Component -{ +class PetComponent final : public Component { public: static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::PET; @@ -99,8 +80,8 @@ public: * @param componentId The component id */ explicit PetComponent(Entity* parentEntity, uint32_t componentId); - - ~PetComponent() override; + + ~PetComponent() override = default; /** * Serializes the pet @@ -113,56 +94,16 @@ public: * Sets the AI state of the pet * @param newState New pet AI state */ - void SetPetAiState(PetAiState newState); + void SetPetAiState(const PetAiState newState) noexcept { + m_State = newState; + }; /** * Gets the AI state of the pet */ - PetAiState GetPetAiState() const { return m_State; } - - /** - * Sets one or more pet flags - * @param flag PetFlag(s) to set - */ - template - void SetFlag(varArg... flag) { - m_Flags |= (flag | ...); - } - - /** - * Sets the pet to ONLY have the specified flag(s), clearing all others - * @param flag PetFlag(s) to set exclusively - */ - template - void SetOnlyFlag(varArg... flag) { - m_Flags = (flag | ...); - } - - /** - * Unsets one or more pet flags - * @param flag PetFlag(s) to unset - */ - template - void UnsetFlag(varArg... flag) { - m_Flags &= ~(flag | ...); - } - - /** - * Returns true if the pet has all the specified flag(s) - * @param flag PetFlag(s) to check - */ - template - bool HasFlag(varArg... flag) const { - return (m_Flags & (flag | ...)) == (flag | ...); - } - - /** - * Returns true if the pet has ONLY the specified flag(s) - * @param flag PetFlag(s) to check if the pet has exclusively - */ - template - bool HasOnlyFlag(varArg... flag) const { - return m_Flags == (flag | ...); + [[nodiscard]] + PetAiState GetPetAiState() const noexcept { + return m_State; } /** @@ -238,7 +179,7 @@ public: /** * Start a pet interaction with an object at a given position */ - void StartInteract(const NiPoint3& position, const PetInteractType interactType, const LWOOBJID& interactID); + void StartInteract(const NiPoint3& position, const PetInteractType interactionType, const LWOOBJID& interactID); /** * Stop a pet interaction with an object @@ -246,16 +187,6 @@ public: */ void StopInteract(bool bDontSerialize = false); - /** - * Set the type of interaction the pet is executing - */ - void SetInteractType(PetInteractType interactType) { m_InteractType = interactType; }; - - /** - * Get the type of interaction the pet is executing - */ - PetInteractType GetInteractType() { return m_InteractType; }; - /** * Spawns a pet from an item in the inventory of an owner * @param item the item to create the pet from @@ -287,90 +218,75 @@ public: * Returns the ID of the owner of this pet (if any) * @return the ID of the owner of this pet */ - LWOOBJID GetOwnerId() const; + [[nodiscard]] + LWOOBJID GetOwnerId() const noexcept { + return m_Owner; + }; /** * Returns the entity that owns this pet (if any) * @return the entity that owns this pet */ + [[nodiscard]] Entity* GetOwner() const; /** * Returns the ID that is stored in the database with regards to this pet, only set for pets that are tamed * @return the ID that is stored in the database with regards to this pet */ - LWOOBJID GetDatabaseId() const; - - /** - * Returns the ID of the object that the pet is currently interacting with, could be a treasure chest or a switch - * @return the ID of the object that the pet is currently interacting with - */ - LWOOBJID GetInteraction() const; - - /** - * Sets the ID that the pet is interacting with - * @param value the ID that the pet is interacting with - */ - void SetInteraction(LWOOBJID value); + [[nodiscard]] + LWOOBJID GetDatabaseId() const noexcept { + return m_DatabaseId; + } /** * Returns the ID that this pet was spawned from, only set for tamed pets * @return the ID that this pet was spawned from */ - LWOOBJID GetItemId() const; - - /** - * Returns the status of this pet, e.g. tamable or tamed. The values here are still a bit of mystery and likely a - * bit map - * @return the status of this pet - */ - /*uint32_t GetStatus() const;*/ - - /** - * Sets the current status of the pet - * @param value the current status of the pet to set - */ - /*void SetStatus(uint32_t value);*/ - - /** - * Returns an ability the pet may perform, currently unused - * @return an ability the pet may perform - */ - ePetAbilityType GetAbility() const; - - /** - * Sets the ability of the pet, currently unused - * @param value the ability to set - */ - void SetAbility(ePetAbilityType value); + [[nodiscard]] + LWOOBJID GetItemId() const noexcept { + return m_ItemId; + } /** * Sets preconditions for the pet that need to be met before it can be tamed * @param conditions the preconditions to set */ - void SetPreconditions(std::string& conditions); + void SetPreconditions(const std::string& preconditions) { + m_Preconditions = PreconditionExpression(preconditions); + } /** * Sets if the pet is ready to interact with an object * @param isReady whether the pet is ready to interact (true) or not (false) */ - void SetIsReadyToInteract(bool isReady) { m_ReadyToInteract = isReady; }; + void SetIsReadyToInteract(const bool isReady) { + m_ReadyToInteract = isReady; + }; /** * @return is pet ready to interact with an object */ - bool IsReadyToInteract() { return m_ReadyToInteract; }; + [[nodiscard]] + bool IsReadyToInteract() const noexcept { + return m_ReadyToInteract; + } /** * Sets if the pet is currently handling an interaction with an object * @param isHandlingInteraction whether the pet is currently handling an interaction with an object */ - void SetIsHandlingInteraction(bool isHandlingInteraction) { m_IsHandlingInteraction = isHandlingInteraction; }; + void SetIsHandlingInteraction(const bool isHandlingInteraction) { + m_IsHandlingInteraction = isHandlingInteraction; + } /** * @return is pet currently handling an interaction with an object */ - bool IsHandlingInteraction() { return m_IsHandlingInteraction; }; + [[nodiscard]] + bool IsHandlingInteraction() const noexcept { + return m_IsHandlingInteraction; + }; /** * Set up the pet bouncer interaction @@ -406,7 +322,10 @@ public: * Returns the entity that this component belongs to * @return the entity that this component belongs to */ - Entity* GetParentEntity() const; + [[nodiscard]] + Entity* GetParentEntity() const noexcept { + return m_Parent; + } /** * Sets the name of the pet to be moderated @@ -425,6 +344,7 @@ public: * @param tamer the entity that's currently taming * @return the pet component of the entity that's being tamed */ + [[nodiscard]] static PetComponent* GetTamingPet(LWOOBJID tamer); /** @@ -432,6 +352,7 @@ public: * @param owner the owner of the pet that's spawned * @return the pet component of the entity that was spawned by the owner */ + [[nodiscard]] static PetComponent* GetActivePet(LWOOBJID owner); /** @@ -443,11 +364,15 @@ public: void AddDrainImaginationTimer(Item* item, bool fromTaming = false); private: + // Needed so these can access flags + friend class DamagingPets; + friend class PetTest; + FRIEND_TEST(PetTest, PetComponentFlagTest); /** * Information for the minigame to be completed */ - struct PetPuzzleData + struct PuzzleData { /** * The LOT of the object that is to be created @@ -475,6 +400,28 @@ private: int32_t numValidPieces; }; + struct Interaction { + /** + * The type of object that the pet is currently interacting with (e.g. a treasure chest or switch) + */ + PetInteractType type = PetInteractType::none; + + /** + * The interaction ability + */ + ePetAbilityType ability = ePetAbilityType::Invalid; + + /** + * The ID of the object that the pet is currently interacting with (e.g. a treasure chest or switch) + */ + LWOOBJID obj = LWOOBJID_EMPTY; + }; + + /** + * Pet interaction info + */ + Interaction m_Interaction{}; + /** * Cache of all the pets that are currently spawned, indexed by tamer */ @@ -488,7 +435,7 @@ private: /** * Cache of all the minigames and their information from the database */ - static std::unordered_map buildCache; + static std::unordered_map buildCache; /** * Flags that indicate that a player has tamed a pet, indexed by the LOT of the pet @@ -508,32 +455,22 @@ private: /** * The ID of the model that was built to complete the taming minigame for this pet */ - LWOOBJID m_ModelId; - - /** - * The ID of the object that the pet is currently interacting with (e.g. a treasure chest or switch) - */ - LWOOBJID m_Interaction; - - /** - * The type of object that the pet is currently interacting with (e.g. a treasure chest or switch) - */ - PetInteractType m_InteractType; + LWOOBJID m_ModelId = LWOOBJID_EMPTY; /** * The ID of the entity that owns this pet */ - LWOOBJID m_Owner; + LWOOBJID m_Owner = LWOOBJID_EMPTY; /** * The ID of the entity that is currently taming this pet */ - LWOOBJID m_Tamer; + LWOOBJID m_Tamer = LWOOBJID_EMPTY; /** * The ID under which this pet is stored in the database (if it's tamed) */ - LWOOBJID m_DatabaseId; + LWOOBJID m_DatabaseId = LWOOBJID_EMPTY; /** * The ID of the item from which this pet was created @@ -543,7 +480,7 @@ private: /** * The moderation status for the name of this pet */ - uint32_t m_ModerationStatus; + uint32_t m_ModerationStatus = 0; /** * The name of this pet @@ -558,32 +495,27 @@ private: /** * The current flags of the pet (e.g. tamable, tamed, etc). */ - PetFlag m_Flags; + Flag m_Flags; /** * The current state of the pet AI */ PetAiState m_State; - /** - * A currently active ability, mostly unused - */ - ePetAbilityType m_Ability; - /** * The time an entity has left to complete the minigame */ - float m_Timer; + float m_Timer = 0; /** * A timer that tracks how long a tamed pet has been to far away from its owner, triggering a teleport after timeout */ - float m_TimerAway; + float m_TimerAway = 0; /** * A timer that tracks how long until a tamed pet will bounce again when standing over a treasure dig site */ - float m_TimerBounce; + float m_TimerBounce = 0; /** * Boolean that sets if a pet is ready to interact with an object @@ -608,7 +540,7 @@ private: /** * Preconditions that need to be met before an entity can tame this pet */ - PreconditionExpression* m_Preconditions; + std::optional m_Preconditions; /** * Pet information loaded from the CDClientDatabase diff --git a/dScripts/02_server/Pets/DamagingPets.cpp b/dScripts/02_server/Pets/DamagingPets.cpp index 2702fa78..a289e261 100644 --- a/dScripts/02_server/Pets/DamagingPets.cpp +++ b/dScripts/02_server/Pets/DamagingPets.cpp @@ -77,10 +77,10 @@ void DamagingPets::MakeUntamable(Entity* self) { auto* const petComponent = self->GetComponent(); // If the pet is currently not being tamed, make it hostile - if (petComponent != nullptr && !petComponent->HasFlag(PetFlag::NOT_WAITING)) { + if (petComponent != nullptr && !petComponent->m_Flags.Has(PetFlag::NOT_WAITING)) { self->SetNetworkVar(u"bIAmTamable", false); self->SetVar(u"IsEvil", true); - petComponent->SetFlag(PetFlag::IDLE); + petComponent->m_Flags.Set(PetFlag::IDLE); auto* combatAIComponent = self->GetComponent(); if (combatAIComponent != nullptr) { @@ -110,7 +110,7 @@ void DamagingPets::ClearEffects(Entity* self) { auto* petComponent = self->GetComponent(); if (petComponent != nullptr) { - petComponent->SetFlag(PetFlag::TAMEABLE, PetFlag::UNKNOWN2); + petComponent->m_Flags.Set(PetFlag::TAMEABLE, PetFlag::UNKNOWN2); } auto* combatAIComponent = self->GetComponent(); diff --git a/tests/dCommonTests/CMakeLists.txt b/tests/dCommonTests/CMakeLists.txt index ef7c4cba..b83f4ddd 100644 --- a/tests/dCommonTests/CMakeLists.txt +++ b/tests/dCommonTests/CMakeLists.txt @@ -1,6 +1,7 @@ set(DCOMMONTEST_SOURCES "AMFDeserializeTests.cpp" "Amf3Tests.cpp" + "FlagTests.cpp" "ToUnderlyingTests.cpp" "HeaderSkipTest.cpp" "TestCDFeatureGatingTable.cpp" diff --git a/tests/dCommonTests/FlagTests.cpp b/tests/dCommonTests/FlagTests.cpp new file mode 100644 index 00000000..0e9f5acd --- /dev/null +++ b/tests/dCommonTests/FlagTests.cpp @@ -0,0 +1,67 @@ +#include +#include "Flag.h" + +enum class TestFlag : uint8_t { + FLAG0, + FLAG1, + FLAG2, + FLAG3, + FLAG4 +}; + +/** + * Test bitset flags +*/ +TEST(FlagTests, FlagMethodTest) { + using enum TestFlag; + + auto flag = Flag{}; + + // Test setting and reading single flags, exclusively + flag.Reset(FLAG0); + ASSERT_TRUE(flag.HasOnly(FLAG0)); + flag.Reset(FLAG2); + ASSERT_TRUE(flag.HasOnly(FLAG2)); + ASSERT_FALSE(flag.HasOnly(FLAG1)); + + // Test setting and reading multiple flags, exclusively + flag.Reset(FLAG3, FLAG1); + ASSERT_FALSE(flag.Has(FLAG2)); + ASSERT_TRUE(flag.Has(FLAG3)); + ASSERT_TRUE(flag.Has(FLAG1)); + ASSERT_TRUE(flag.Has(FLAG3, FLAG1)); + ASSERT_FALSE(flag.HasOnly(FLAG3)); + ASSERT_FALSE(flag.HasOnly(FLAG1)); + ASSERT_TRUE(flag.HasOnly(FLAG3, FLAG1)); + + // Test flags are being properly reset for next batch of tests + flag.Reset(FLAG0); + ASSERT_TRUE(flag.Has(FLAG0)); + ASSERT_TRUE(flag.HasOnly(FLAG0)); + ASSERT_FALSE(flag.Has(FLAG3)); + ASSERT_FALSE(flag.Has(FLAG3, FLAG1, FLAG2)); + + // Test setting and reading single flags, non-exclusively + flag.Set(FLAG3); + ASSERT_TRUE(flag.Has(FLAG3)); + ASSERT_FALSE(flag.Has(FLAG1)); + + // Test setting and reading multiple flags, non-exclusively + flag.Set(FLAG2, FLAG4); + ASSERT_TRUE(flag.Has(FLAG2, FLAG4)); + ASSERT_TRUE(flag.Has(FLAG3)); + ASSERT_FALSE(flag.Has(FLAG3, FLAG1)); + ASSERT_TRUE(flag.Has(FLAG3, FLAG2, FLAG4)); + ASSERT_FALSE(flag.Has(FLAG1)); + ASSERT_FALSE(flag.Has(FLAG1, FLAG3, FLAG2, FLAG4)); + + // Test unsetting and reading multiple flags, non-exclusively + flag.Unset(FLAG3, FLAG1); + ASSERT_FALSE(flag.Has(FLAG3, FLAG1)); + ASSERT_FALSE(flag.Has(FLAG3)); + ASSERT_TRUE(flag.Has(FLAG2, FLAG4)); + ASSERT_FALSE(flag.Has(FLAG3, FLAG2, FLAG4)); + ASSERT_FALSE(flag.Has(FLAG1)); + ASSERT_FALSE(flag.Has(FLAG1, FLAG3, FLAG2, FLAG4)); +} + diff --git a/tests/dGameTests/dComponentsTests/PetComponentTests.cpp b/tests/dGameTests/dComponentsTests/PetComponentTests.cpp index 05eeab75..295b37d5 100644 --- a/tests/dGameTests/dComponentsTests/PetComponentTests.cpp +++ b/tests/dGameTests/dComponentsTests/PetComponentTests.cpp @@ -31,6 +31,60 @@ protected: } }; +/** + * Test bitset pet flags +*/ +TEST_F(PetTest, PetComponentFlagTest) { + using enum PetFlag; + + // Test setting and reading single flags, exclusively + petComponent->m_Flags.Reset(NONE); + ASSERT_TRUE(petComponent->m_Flags.HasOnly(NONE)); + petComponent->m_Flags.Reset(TAMEABLE); + ASSERT_TRUE(petComponent->m_Flags.HasOnly(TAMEABLE)); + ASSERT_FALSE(petComponent->m_Flags.HasOnly(SPAWNING)); + + // Test setting and reading multiple flags, exclusively + petComponent->m_Flags.Reset(NOT_WAITING, SPAWNING); + ASSERT_FALSE(petComponent->m_Flags.Has(TAMEABLE)); + ASSERT_TRUE(petComponent->m_Flags.Has(NOT_WAITING)); + ASSERT_TRUE(petComponent->m_Flags.Has(SPAWNING)); + ASSERT_TRUE(petComponent->m_Flags.Has(NOT_WAITING, SPAWNING)); + ASSERT_FALSE(petComponent->m_Flags.HasOnly(NOT_WAITING)); + ASSERT_FALSE(petComponent->m_Flags.HasOnly(SPAWNING)); + ASSERT_TRUE(petComponent->m_Flags.HasOnly(NOT_WAITING, SPAWNING)); + + // Test flags are being properly reset for next batch of tests + petComponent->m_Flags.Reset(NONE); + ASSERT_TRUE(petComponent->m_Flags.Has(NONE)); + ASSERT_TRUE(petComponent->m_Flags.HasOnly(NONE)); + ASSERT_FALSE(petComponent->m_Flags.Has(NOT_WAITING)); + ASSERT_FALSE(petComponent->m_Flags.Has(NOT_WAITING, SPAWNING, TAMEABLE)); + + // Test setting and reading single flags, non-exclusively + petComponent->m_Flags.Set(NOT_WAITING); + ASSERT_TRUE(petComponent->m_Flags.Has(NOT_WAITING)); + ASSERT_FALSE(petComponent->m_Flags.Has(SPAWNING)); + + // Test setting and reading multiple flags, non-exclusively + petComponent->m_Flags.Set(TAMEABLE, BEING_TAMED); + ASSERT_TRUE(petComponent->m_Flags.Has(TAMEABLE, BEING_TAMED)); + ASSERT_TRUE(petComponent->m_Flags.Has(NOT_WAITING)); + ASSERT_FALSE(petComponent->m_Flags.Has(NOT_WAITING, SPAWNING)); + ASSERT_TRUE(petComponent->m_Flags.Has(NOT_WAITING, TAMEABLE, BEING_TAMED)); + ASSERT_FALSE(petComponent->m_Flags.Has(SPAWNING)); + ASSERT_FALSE(petComponent->m_Flags.Has(SPAWNING, NOT_WAITING, TAMEABLE, BEING_TAMED)); + + // Test unsetting and reading multiple flags, non-exclusively + petComponent->m_Flags.Unset(NOT_WAITING, SPAWNING); + ASSERT_FALSE(petComponent->m_Flags.Has(NOT_WAITING, SPAWNING)); + ASSERT_FALSE(petComponent->m_Flags.Has(NOT_WAITING)); + ASSERT_TRUE(petComponent->m_Flags.Has(TAMEABLE, BEING_TAMED)); + ASSERT_FALSE(petComponent->m_Flags.Has(NOT_WAITING, TAMEABLE, BEING_TAMED)); + ASSERT_FALSE(petComponent->m_Flags.Has(SPAWNING)); + ASSERT_FALSE(petComponent->m_Flags.Has(SPAWNING, NOT_WAITING, TAMEABLE, BEING_TAMED)); +} + TEST_F(PetTest, PlacementNewAddComponentTest) { using enum PetFlag; @@ -41,64 +95,9 @@ TEST_F(PetTest, PlacementNewAddComponentTest) { // Test getting initial status ASSERT_EQ(petComponent->GetParent()->GetObjectID(), 15); - ASSERT_TRUE(petComponent->HasFlag(NONE)); ASSERT_EQ(petComponent->GetPetAiState(), PetAiState::spawn); - ASSERT_EQ(petComponent->GetAbility(), ePetAbilityType::Invalid); } -/** - * Test bitset pet flags -*/ -TEST_F(PetTest, PetComponentFlagTest) { - using enum PetFlag; - - // Test setting and reading single flags, exclusively - petComponent->SetOnlyFlag(NONE); - ASSERT_TRUE(petComponent->HasOnlyFlag(NONE)); - petComponent->SetOnlyFlag(TAMEABLE); - ASSERT_TRUE(petComponent->HasOnlyFlag(TAMEABLE)); - ASSERT_FALSE(petComponent->HasOnlyFlag(SPAWNING)); - - // Test setting and reading multiple flags, exclusively - petComponent->SetOnlyFlag(NOT_WAITING, SPAWNING); - ASSERT_FALSE(petComponent->HasFlag(TAMEABLE)); - ASSERT_TRUE(petComponent->HasFlag(NOT_WAITING)); - ASSERT_TRUE(petComponent->HasFlag(SPAWNING)); - ASSERT_TRUE(petComponent->HasFlag(NOT_WAITING, SPAWNING)); - ASSERT_FALSE(petComponent->HasOnlyFlag(NOT_WAITING)); - ASSERT_FALSE(petComponent->HasOnlyFlag(SPAWNING)); - ASSERT_TRUE(petComponent->HasOnlyFlag(NOT_WAITING, SPAWNING)); - - // Test flags are being properly reset for next batch of tests - petComponent->SetOnlyFlag(NONE); - ASSERT_TRUE(petComponent->HasFlag(NONE)); - ASSERT_TRUE(petComponent->HasOnlyFlag(NONE)); - ASSERT_FALSE(petComponent->HasFlag(NOT_WAITING)); - ASSERT_FALSE(petComponent->HasFlag(NOT_WAITING, SPAWNING, TAMEABLE)); - - // Test setting and reading single flags, non-exclusively - petComponent->SetFlag(NOT_WAITING); - ASSERT_TRUE(petComponent->HasFlag(NOT_WAITING)); - ASSERT_FALSE(petComponent->HasFlag(SPAWNING)); - - // Test setting and reading multiple flags, non-exclusively - petComponent->SetFlag(TAMEABLE, BEING_TAMED); - ASSERT_TRUE(petComponent->HasFlag(TAMEABLE, BEING_TAMED)); - ASSERT_TRUE(petComponent->HasFlag(NOT_WAITING)); - ASSERT_FALSE(petComponent->HasFlag(NOT_WAITING, SPAWNING)); - ASSERT_TRUE(petComponent->HasFlag(NOT_WAITING, TAMEABLE, BEING_TAMED)); - ASSERT_FALSE(petComponent->HasFlag(SPAWNING)); - ASSERT_FALSE(petComponent->HasFlag(SPAWNING, NOT_WAITING, TAMEABLE, BEING_TAMED)); - - // Test unsetting and reading multiple flags, non-exclusively - petComponent->UnsetFlag(NOT_WAITING, SPAWNING); - ASSERT_FALSE(petComponent->HasFlag(NOT_WAITING, SPAWNING)); - ASSERT_FALSE(petComponent->HasFlag(NOT_WAITING)); - ASSERT_TRUE(petComponent->HasFlag(TAMEABLE, BEING_TAMED)); - ASSERT_FALSE(petComponent->HasFlag(NOT_WAITING, TAMEABLE, BEING_TAMED)); - ASSERT_FALSE(petComponent->HasFlag(SPAWNING)); - ASSERT_FALSE(petComponent->HasFlag(SPAWNING, NOT_WAITING, TAMEABLE, BEING_TAMED)); -} TEST_F(PetTest, PetAiState) { const auto initialState = petComponent->GetPetAiState(); @@ -126,9 +125,6 @@ TEST_F(PetTest, PetUse) { // Test bouncer logic ASSERT_FALSE(petComponent->IsHandlingInteraction()); - petComponent->SetAbility(ePetAbilityType::JumpOnObject); - ASSERT_EQ(petComponent->GetAbility(), ePetAbilityType::JumpOnObject); - petComponent->SetInteractType(PetInteractType::bouncer); petComponent->OnUse(baseEntity); // need to add a destroyable component to the test entity and test the imagination drain