clean up PetComponent

This commit is contained in:
jadebenn 2024-04-19 21:53:52 -05:00
parent d88b982904
commit 0b2453241b
8 changed files with 353 additions and 332 deletions

View File

@ -4,6 +4,7 @@ set(DCOMMON_SOURCES
"BinaryIO.cpp"
"dConfig.cpp"
"Diagnostics.cpp"
"Flag.h"
"Logger.cpp"
"Game.cpp"
"GeneralUtils.cpp"

82
dCommon/Flag.h Normal file
View File

@ -0,0 +1,82 @@
#include "GeneralUtils.h"
#include <bit>
// TODO: Test bounds checking
template <typename T>
requires std::is_enum_v<T>
class Flag {
public:
using type = T;
using underlying_type = std::underlying_type_t<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 <std::same_as<T>... 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 <std::same_as<T>... varArg>
constexpr void Reset(varArg... flag) {
m_Flags = (ConvertFlag(flag) | ...);
}
/**
* Unsets one or more flags
* @param flag Flag(s) to unset
*/
template <std::same_as<T>... 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 <std::same_as<T>... 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 <std::same_as<T>... 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;
};

View File

@ -37,7 +37,7 @@
#include "eMissionState.h"
#include "dNavMesh.h"
std::unordered_map<LOT, PetComponent::PetPuzzleData> PetComponent::buildCache{};
std::unordered_map<LOT, PetComponent::PuzzleData> PetComponent::buildCache{};
std::unordered_map<LWOOBJID, LWOOBJID> PetComponent::currentActivities{};
std::unordered_map<LWOOBJID, LWOOBJID> PetComponent::activePets{};
@ -45,7 +45,7 @@ std::unordered_map<LWOOBJID, LWOOBJID> 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<LOT, int32_t> PetComponent::petFlags = {
std::map<LOT, int32_t> PetComponent::petFlags{
{ 3050, 801 }, // Elephant
{ 3054, 803 }, // Cat
{ 3195, 806 }, // Triceratops
@ -78,31 +78,17 @@ std::map<LOT, int32_t> PetComponent::petFlags = {
PetComponent::PetComponent(Entity* parentEntity, uint32_t componentId) : Component{ parentEntity } {
m_PetInfo = CDClientManager::GetTable<CDPetComponentTable>()->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<std::u16string>(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<MovementAIComponent>();
@ -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<CharacterComponent>();
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* 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<PetComponent>();
}
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);
}

View File

@ -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 <optional>
#include <gtest/gtest.h>
// 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<PetFlag>;
return static_cast<PetFlag>(static_cast<underlying_type>(lhs) | static_cast<underlying_type>(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<PetFlag>;
return static_cast<PetFlag>(static_cast<underlying_type>(lhs) & static_cast<underlying_type>(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<PetFlag>;
return static_cast<PetFlag>(~static_cast<underlying_type>(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 <typename... varArg>
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 <typename... varArg>
void SetOnlyFlag(varArg... flag) {
m_Flags = (flag | ...);
}
/**
* Unsets one or more pet flags
* @param flag PetFlag(s) to unset
*/
template <typename... varArg>
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 <typename... varArg>
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 <typename... varArg>
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<LOT, PetComponent::PetPuzzleData> buildCache;
static std::unordered_map<LOT, PetComponent::PuzzleData> 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<PetFlag> 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<PreconditionExpression> m_Preconditions;
/**
* Pet information loaded from the CDClientDatabase

View File

@ -77,10 +77,10 @@ void DamagingPets::MakeUntamable(Entity* self) {
auto* const petComponent = self->GetComponent<PetComponent>();
// 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<bool>(u"bIAmTamable", false);
self->SetVar<bool>(u"IsEvil", true);
petComponent->SetFlag(PetFlag::IDLE);
petComponent->m_Flags.Set(PetFlag::IDLE);
auto* combatAIComponent = self->GetComponent<BaseCombatAIComponent>();
if (combatAIComponent != nullptr) {
@ -110,7 +110,7 @@ void DamagingPets::ClearEffects(Entity* self) {
auto* petComponent = self->GetComponent<PetComponent>();
if (petComponent != nullptr) {
petComponent->SetFlag(PetFlag::TAMEABLE, PetFlag::UNKNOWN2);
petComponent->m_Flags.Set(PetFlag::TAMEABLE, PetFlag::UNKNOWN2);
}
auto* combatAIComponent = self->GetComponent<BaseCombatAIComponent>();

View File

@ -1,6 +1,7 @@
set(DCOMMONTEST_SOURCES
"AMFDeserializeTests.cpp"
"Amf3Tests.cpp"
"FlagTests.cpp"
"ToUnderlyingTests.cpp"
"HeaderSkipTest.cpp"
"TestCDFeatureGatingTable.cpp"

View File

@ -0,0 +1,67 @@
#include <gtest/gtest.h>
#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<TestFlag>{};
// 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));
}

View File

@ -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