diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index d3dca79c..faaad97b 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -82,32 +82,41 @@ PetComponent::PetComponent(Entity* parent, uint32_t componentId): Component(pare m_DatabaseId = LWOOBJID_EMPTY; m_Status = PetStatus::TAMEABLE; // Tameable m_Ability = PetAbilityType::Invalid; - m_StartPosition = NiPoint3::ZERO; + m_StartPosition = m_Parent->GetPosition(); //NiPoint3::ZERO; m_MovementAI = nullptr; m_TresureTime = 0; m_Preconditions = nullptr; m_ReadyToDig = false; - m_InInteract = false; + SetPetAiState(PetAiState::spawn); std::string checkPreconditions = GeneralUtils::UTF16ToWTF8(parent->GetVar(u"CheckPrecondition")); if (!checkPreconditions.empty()) { SetPreconditions(checkPreconditions); } - // Get the imagination drain rate from the CDClient - auto query = CDClientDatabase::CreatePreppedStmt("SELECT imaginationDrainRate FROM PetComponent WHERE id = ?;"); - + + // Get pet information from the CDClient + auto query = CDClientDatabase::CreatePreppedStmt( + "SELECT walkSpeed, runSpeed, sprintSpeed, imaginationDrainRate FROM PetComponent WHERE id = ?;"); query.bind(1, static_cast(componentId)); auto result = query.execQuery(); - // Should a result not exist for this pet default to 60 seconds. - if (!result.eof() && !result.fieldIsNull(0)) { - imaginationDrainRate = result.getFloatField(0, 60.0f); - } else { - imaginationDrainRate = 60.0f; + if (!result.eof()) { + if (!result.fieldIsNull(0)) + m_walkSpeed = result.getFloatField(0); + + if (!result.fieldIsNull(1)) + m_RunSpeed = result.getFloatField(1); + + if (!result.fieldIsNull(2)) + m_SprintSpeed = result.getFloatField(2); + + if (!result.fieldIsNull(3)) + imaginationDrainRate = result.getFloatField(3); } + result.finalize(); } @@ -152,19 +161,17 @@ void PetComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpd } } +void PetComponent::SetPetAiState(PetAiState newState) { + if (newState == GetPetAiState()) return; + this->m_State = newState; + LOG_DEBUG("Set pet AI state!"); + //Game::entityManager->SerializeEntity(m_Parent); // Do we need to serialize entity? +} + void PetComponent::OnUse(Entity* originator) { LOG("PET USE!"); - /*if(m_ReadyToDig) { - LOG("Dig initiated!"); - m_TresureTime = 2.0f; - //m_ReadyToDig = false; - SetAbility(PetAbilityType::DigAtPosition); - }*/ - - if (m_Owner != LWOOBJID_EMPTY) { - return; - } + if (m_Owner != LWOOBJID_EMPTY) return; if (m_Tamer != LWOOBJID_EMPTY) { auto* tamer = Game::entityManager->GetEntity(m_Tamer); @@ -336,29 +343,159 @@ void PetComponent::OnUse(Entity* originator) { } void PetComponent::Update(float deltaTime) { - if (m_StartPosition == NiPoint3::ZERO) { - m_StartPosition = m_Parent->GetPosition(); - } - + // If pet does not have an owner, use the UpdateUnowned() loop if (m_Owner == LWOOBJID_EMPTY) { UpdateUnowned(deltaTime); return; } + // Determine pet owner auto* owner = GetOwner(); if (!owner) { - m_Parent->Kill(); + m_Parent->Kill(); // Kill pet if no owner return; } - m_MovementAI = m_Parent->GetComponent(); - if (!m_MovementAI) return; + // Update timer + if (m_Timer > 0) { + m_Timer -= deltaTime; + return; + } + // Handle treasure timer if (m_TresureTime > 0.0f) { //TODO: Find better trigger? InteractDig(deltaTime); return; } + // Handle pet AI states + switch (m_State) { + // Handle idle state + case PetAiState::idle: { + LOG_DEBUG("Pet in idle state!"); + m_Timer = 1.0f; + break; + } + // Handle follow state + case PetAiState::follow: { + // Get movement AI component + m_MovementAI = m_Parent->GetComponent(); + if (!m_MovementAI) return; + + // Get and set destination + //auto position = m_MovementAI->GetParent()->GetPosition(); + auto ownerPos = owner->GetPosition(); + NiPoint3 destination = ownerPos; + NiPoint3 interactPos = NiPoint3::ZERO; + + // Determine if the "Lost Tags" mission has been completed and digging has been unlocked + auto* missionComponent = owner->GetComponent(); + if (!missionComponent) return; + const bool digUnlocked = missionComponent->GetMissionState(842) == eMissionState::COMPLETE; + + // Interactions checks + SwitchComponent* closestSwitch = SwitchComponent::GetClosestSwitch(ownerPos); + Entity* closestTreasure = PetDigServer::GetClosestTresure(ownerPos); + if (closestSwitch != nullptr && !closestSwitch->GetActive()) { + m_Interaction = closestSwitch->GetParentEntity()->GetObjectID(); + interactPos = closestSwitch->GetParentEntity()->GetPosition(); + m_Ability = PetAbilityType::GoToObject; + } + if (closestTreasure != nullptr && digUnlocked) { + m_Interaction = closestTreasure->GetObjectID(); + interactPos = closestTreasure->GetPosition(); + m_Ability = PetAbilityType::GoToObject; + } + + // Trigger interaction if checks are valid + if (m_Ability != PetAbilityType::Invalid) { + float distance = Vector3::DistanceSquared(ownerPos, interactPos); + if (distance < 20 * 20) { + destination = interactPos; + m_MovementAI->SetHaltDistance(0.0f); + SetPetAiState(PetAiState::interact); + } + } + + m_MovementAI->SetDestination(destination); + //LOG_DEBUG("Pet destination: %f %f %f", destination.x, destination.y, destination.z); + m_Timer = 1.0f; + + break; + } + // Handle interact state + case PetAiState::interact: { + LOG_DEBUG("Interacting with object!"); + + // Get movement AI component + m_MovementAI = m_Parent->GetComponent(); + if (!m_MovementAI) return; + + // Get distance from owner + auto ownerPos = owner->GetPosition(); + auto position = m_MovementAI->GetParent()->GetPosition(); + float distanceFromOwner = Vector3::DistanceSquared(position, ownerPos); + + // Switch back to follow AI state if player moves too far away from pet + if (distanceFromOwner > 15 * 15) { + m_MovementAI->SetHaltDistance(5.0f); // TODO: Remove this magic number + m_Interaction = LWOOBJID_EMPTY; + m_Ability = PetAbilityType::Invalid; + SetIsReadyToDig(false); + SetPetAiState(PetAiState::follow); + LOG_DEBUG("Pet interaction aborted due to player distance!"); + break; + } + + // Get distance from interactable + auto destination = m_MovementAI->GetDestination(); + float distanceFromInteract = Vector3::DistanceSquared(position, destination); + + // Handle the interaction + if (m_MovementAI->AtFinalWaypoint()) { + Entity* interactEntity = Game::entityManager->GetEntity(m_Interaction); + + /*auto* switchComponent = interactEntity->GetComponent(); + if (switchComponent != nullptr) { + switchComponent->EntityEnter(m_Parent); + } + else {*/ + Command(NiPoint3::ZERO, LWOOBJID_EMPTY, 1, PetEmote::Bounce, true); // Plays 'bounce' animation + SetIsReadyToDig(true); + //} + } + + m_Timer = 1.0f; + break; + } + // Handle spawn state + case PetAiState::spawn: { + LOG_DEBUG("Pet spawned!"); + + // Get movement AI component + m_MovementAI = m_Parent->GetComponent(); + if (!m_MovementAI) return; + + // Determine the pet start position + if (m_StartPosition == NiPoint3::ZERO) m_StartPosition = m_Parent->GetPosition(); + + // Determine next state; + if (m_Owner == LWOOBJID_EMPTY) { + SetPetAiState(PetAiState::idle); + } + else { + SetPetAiState(PetAiState::follow); + m_MovementAI->SetMaxSpeed(m_SprintSpeed); + m_MovementAI->SetHaltDistance(5.0f); // TODO: Remove magic number + } + break; + } + } + + return; + + /* + auto destination = owner->GetPosition(); NiPoint3 position = m_MovementAI->GetParent()->GetPosition(); @@ -387,18 +524,15 @@ void PetComponent::Update(float deltaTime) { float haltDistance = 5; - if (closestSwitch != nullptr) { - if (!closestSwitch->GetActive()) { - NiPoint3 switchPosition = closestSwitch->GetParentEntity()->GetPosition(); - float distance = Vector3::DistanceSquared(position, switchPosition); - if (distance < 3 * 3) { - m_Interaction = closestSwitch->GetParentEntity()->GetObjectID(); - closestSwitch->EntityEnter(m_Parent); - } else if (distance < 20 * 20) { - haltDistance = 1; - - destination = switchPosition; - } + if (closestSwitch != nullptr && !closestSwitch->GetActive()) { + NiPoint3 switchPosition = closestSwitch->GetParentEntity()->GetPosition(); + float distance = Vector3::DistanceSquared(position, switchPosition); + if (distance < 3 * 3) { + m_Interaction = closestSwitch->GetParentEntity()->GetObjectID(); + closestSwitch->EntityEnter(m_Parent); + } else if (distance < 20 * 20) { + haltDistance = 1; + destination = switchPosition; } } @@ -434,13 +568,13 @@ void PetComponent::Update(float deltaTime) { skipTresure: - //m_MovementAI->SetHaltDistance(haltDistance); + m_MovementAI->SetHaltDistance(haltDistance); //m_MovementAI->SetMaxSpeed(2.5f); m_MovementAI->SetDestination(destination); - m_Timer = 1; + m_Timer = 1;*/ } void PetComponent::UpdateUnowned(float deltaTime) { @@ -473,6 +607,7 @@ void PetComponent::SetIsReadyToDig(bool isReady) { //SetAbility(PetAbilityType::JumpOnObject); SetStatus(PetStatus::IS_NOT_WAITING); // Treasure dig status m_ReadyToDig = true; + Game::entityManager->SerializeEntity(m_Parent); } else { LOG("Dig state ended!"); @@ -480,6 +615,7 @@ void PetComponent::SetIsReadyToDig(bool isReady) { //SetAbility(PetAbilityType::Invalid); SetStatus(0); // TODO: Check status m_ReadyToDig = false; + Game::entityManager->SerializeEntity(m_Parent); } } @@ -1022,7 +1158,7 @@ void PetComponent::Command(NiPoint3 position, LWOOBJID source, int32_t commandTy // Emotes GameMessages::SendPlayEmote(m_Parent->GetObjectID(), typeId, owner->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); } else if (commandType == 3) { - // Follow me, ??? + SetPetAiState(PetAiState::follow); } else if (commandType == 6) { // TODO: Go to player } @@ -1034,7 +1170,7 @@ void PetComponent::Command(NiPoint3 position, LWOOBJID source, int32_t commandTy // Add movement functionality if (position != NiPoint3::ZERO) { m_MovementAI->SetDestination(position); - m_Timer = 9; //Is this setting how long until the next update tick? + //m_Timer = 9; //Is this setting how long until the next update tick? } } diff --git a/dGame/dComponents/PetComponent.h b/dGame/dComponents/PetComponent.h index 808df6a3..d3b21f94 100644 --- a/dGame/dComponents/PetComponent.h +++ b/dGame/dComponents/PetComponent.h @@ -6,6 +6,21 @@ #include "Preconditions.h" #include "eReplicaComponentType.h" +/* +* The current state of the pet AI +*/ +enum class PetAiState : uint { + idle = 0, // Doing nothing + spawn, // Spawning into the world + follow, // Following player + interact, // Beginning interaction + goToObj, // Go to object + despawn // Despawning from world +}; + +/* +* The status of the pet: Governs the icon above their head and the interactions available +*/ enum PetStatus : uint32_t { NONE, BEING_TAMED = 0x10, @@ -41,6 +56,18 @@ public: ~PetComponent() override; void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate) override; + + /** + * Sets the AI state of the pet + * @param newState New pet AI state + */ + void SetPetAiState(PetAiState newState); + + /** + * Gets the AI state of the pet + */ + PetAiState GetPetAiState() { return m_State; }; + void Update(float deltaTime) override; /** @@ -373,6 +400,11 @@ private: */ uint32_t m_Status; + /** + * The current state of the pet AI + */ + PetAiState m_State; + /** * A currently active ability, mostly unused */ @@ -399,11 +431,6 @@ private: */ bool m_ReadyToDig; - /** - * Boolean that sets if a pet is in an interaction - */ - bool m_InInteract; - /** * The position that this pet was spawned at */ @@ -423,4 +450,19 @@ private: * The rate at which imagination is drained from the user for having the pet out. */ float imaginationDrainRate; + + /** + * The walk speed of the pet + */ + float m_walkSpeed; + + /** + * The run speed of the pet + */ + float m_RunSpeed; + + /** + * The sprint speed of the pet + */ + float m_SprintSpeed; };