DarkflameServer/dGame/dComponents/PetComponent.cpp

1269 lines
35 KiB
C++
Raw Normal View History

#include "PetComponent.h"
#include "GameMessages.h"
#include "BrickDatabase.h"
#include "CDClientDatabase.h"
#include "ChatPackets.h"
#include "EntityManager.h"
#include "Character.h"
#include "CharacterComponent.h"
#include "InventoryComponent.h"
#include "Item.h"
#include "MissionComponent.h"
#include "SwitchComponent.h"
#include "DestroyableComponent.h"
#include "dpWorld.h"
#include "PetDigServer.h"
#include "../dWorldServer/ObjectIDManager.h"
#include "eUnequippableActiveType.h"
#include "eTerminateType.h"
#include "ePetTamingNotifyType.h"
#include "eUseItemResponse.h"
#include "ePlayerFlag.h"
#include "Game.h"
#include "dConfig.h"
#include "dChatFilter.h"
#include "Database.h"
#include "EntityInfo.h"
#include "eMissionTaskType.h"
#include "RenderComponent.h"
#include "eObjectBits.h"
#include "eGameMasterLevel.h"
#include "eMissionState.h"
2022-07-28 13:39:57 +00:00
std::unordered_map<LOT, PetComponent::PetPuzzleData> PetComponent::buildCache{};
std::unordered_map<LWOOBJID, LWOOBJID> PetComponent::currentActivities{};
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 = {
2022-07-28 13:39:57 +00:00
{ 3050, 801 }, // Elephant
{ 3054, 803 }, // Cat
{ 3195, 806 }, // Triceratops
{ 3254, 807 }, // Terrier
{ 3261, 811 }, // Skunk
{ 3672, 813 }, // Bunny
{ 3994, 814 }, // Crocodile
{ 5635, 815 }, // Doberman
{ 5636, 816 }, // Buffalo
{ 5637, 818 }, // Robot Dog
{ 5639, 819 }, // Red Dragon
{ 5640, 820 }, // Tortoise
{ 5641, 821 }, // Green Dragon
{ 5643, 822 }, // Panda, see mission 786
{ 5642, 823 }, // Mantis
{ 6720, 824 }, // Warthog
{ 3520, 825 }, // Lion, see mission 1318
{ 7638, 826 }, // Goat
{ 7694, 827 }, // Crab
{ 12294, 829 }, // Reindeer
{ 12431, 830 }, // Stegosaurus, see mission 1386
{ 12432, 831 }, // Saber cat, see mission 1389
{ 12433, 832 }, // Gryphon, see mission 1392
{ 12434, 833 }, // Alien, see mission 1188
// 834: unknown?, see mission 506, 688
{ 16210, 836 }, // Ninjago Earth Dragon, see mission 1836
{ 13067, 838 }, // Skeleton dragon
};
2023-01-03 17:22:04 +00:00
PetComponent::PetComponent(Entity* parent, uint32_t componentId): Component(parent) {
2022-07-28 13:39:57 +00:00
m_ComponentId = componentId;
m_Interaction = LWOOBJID_EMPTY;
2023-12-12 03:10:29 +00:00
m_InteractType = PetInteractType::none;
2022-07-28 13:39:57 +00:00
m_Owner = LWOOBJID_EMPTY;
m_ModerationStatus = 0;
m_Tamer = LWOOBJID_EMPTY;
m_ModelId = LWOOBJID_EMPTY;
m_Timer = 0;
m_TimerAway = 0;
2023-12-13 06:14:53 +00:00
m_TimerBounce = 0;
2022-07-28 13:39:57 +00:00
m_DatabaseId = LWOOBJID_EMPTY;
2023-11-19 22:46:27 +00:00
m_Status = PetStatus::TAMEABLE; // Tameable
2022-07-28 13:39:57 +00:00
m_Ability = PetAbilityType::Invalid;
2023-12-11 01:55:36 +00:00
m_StartPosition = m_Parent->GetPosition(); //NiPoint3::ZERO;
2022-07-28 13:39:57 +00:00
m_MovementAI = nullptr;
m_Preconditions = nullptr;
2022-05-04 03:38:49 +00:00
2023-12-13 06:14:53 +00:00
m_ReadyToInteract = false;
2023-12-11 01:55:36 +00:00
SetPetAiState(PetAiState::spawn);
2023-12-13 06:14:53 +00:00
//m_FollowRadius = 8.0f;
2022-05-04 03:38:49 +00:00
std::string checkPreconditions = GeneralUtils::UTF16ToWTF8(parent->GetVar<std::u16string>(u"CheckPrecondition"));
if (!checkPreconditions.empty()) {
SetPreconditions(checkPreconditions);
}
2023-12-11 01:55:36 +00:00
// Get pet information from the CDClient
auto query = CDClientDatabase::CreatePreppedStmt(
"SELECT walkSpeed, runSpeed, sprintSpeed, imaginationDrainRate FROM PetComponent WHERE id = ?;");
2022-07-28 13:39:57 +00:00
query.bind(1, static_cast<int>(componentId));
2022-07-28 13:39:57 +00:00
auto result = query.execQuery();
2023-12-11 01:55:36 +00:00
if (!result.eof()) {
2023-12-13 06:17:53 +00:00
m_WalkSpeed = result.getFloatField(0, 2.5f);
2023-12-12 03:10:29 +00:00
m_RunSpeed = result.getFloatField(1, 5.0f);
m_SprintSpeed = result.getFloatField(2, 10.0f);
imaginationDrainRate = result.getFloatField(3, 60.0f);
2022-07-28 13:39:57 +00:00
}
2023-12-11 01:55:36 +00:00
2022-07-28 13:39:57 +00:00
result.finalize();
}
void PetComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate) {
2022-07-28 13:39:57 +00:00
const bool tamed = m_Owner != LWOOBJID_EMPTY;
2022-07-28 13:39:57 +00:00
outBitStream->Write1(); // Always serialize as dirty for now
2022-07-28 13:39:57 +00:00
outBitStream->Write<uint32_t>(static_cast<unsigned int>(m_Status));
outBitStream->Write<uint32_t>(static_cast<uint32_t>(tamed ? m_Ability : PetAbilityType::Invalid)); // Something with the overhead icon?
2022-07-28 13:39:57 +00:00
const bool interacting = m_Interaction != LWOOBJID_EMPTY;
2022-07-28 13:39:57 +00:00
outBitStream->Write(interacting);
if (interacting) {
outBitStream->Write(m_Interaction);
}
2022-07-28 13:39:57 +00:00
outBitStream->Write(tamed);
if (tamed) {
outBitStream->Write(m_Owner);
}
2023-01-03 17:22:04 +00:00
if (bIsInitialUpdate) {
outBitStream->Write(tamed);
if (tamed) {
outBitStream->Write(m_ModerationStatus);
2023-01-03 17:22:04 +00:00
const auto nameData = GeneralUtils::UTF8ToUTF16(m_Name);
const auto ownerNameData = GeneralUtils::UTF8ToUTF16(m_OwnerName);
2023-01-03 17:22:04 +00:00
outBitStream->Write(static_cast<uint8_t>(nameData.size()));
for (const auto c : nameData) {
outBitStream->Write(c);
}
2023-01-03 17:22:04 +00:00
outBitStream->Write(static_cast<uint8_t>(ownerNameData.size()));
for (const auto c : ownerNameData) {
outBitStream->Write(c);
}
2022-07-28 13:39:57 +00:00
}
}
}
2023-12-11 01:55:36 +00:00
void PetComponent::SetPetAiState(PetAiState newState) {
if (newState == GetPetAiState()) return;
this->m_State = newState;
}
2022-07-28 13:39:57 +00:00
void PetComponent::OnUse(Entity* originator) {
LOG("PET USE!");
2023-12-11 01:55:36 +00:00
if (m_Owner != LWOOBJID_EMPTY) return;
2022-07-28 13:39:57 +00:00
if (m_Tamer != LWOOBJID_EMPTY) {
auto* tamer = Game::entityManager->GetEntity(m_Tamer);
2023-12-12 03:10:29 +00:00
if (tamer != nullptr) return;
2022-07-28 13:39:57 +00:00
m_Tamer = LWOOBJID_EMPTY;
}
2022-07-28 13:39:57 +00:00
auto* inventoryComponent = originator->GetComponent<InventoryComponent>();
2023-12-12 03:10:29 +00:00
if (inventoryComponent == nullptr) return;
2023-12-12 03:10:29 +00:00
if (m_Preconditions != nullptr && !m_Preconditions->Check(originator, true)) return;
2022-07-28 13:39:57 +00:00
auto* movementAIComponent = m_Parent->GetComponent<MovementAIComponent>();
2022-07-28 13:39:57 +00:00
if (movementAIComponent != nullptr) {
movementAIComponent->Stop();
}
2022-07-28 13:39:57 +00:00
inventoryComponent->DespawnPet();
2022-07-28 13:39:57 +00:00
const auto& cached = buildCache.find(m_Parent->GetLOT());
int32_t imaginationCost = 0;
2022-07-28 13:39:57 +00:00
std::string buildFile;
2022-07-28 13:39:57 +00:00
if (cached == buildCache.end()) {
auto query = CDClientDatabase::CreatePreppedStmt(
"SELECT ValidPiecesLXF, PuzzleModelLot, Timelimit, NumValidPieces, imagCostPerBuild FROM TamingBuildPuzzles WHERE NPCLot = ?;");
query.bind(1, (int)m_Parent->GetLOT());
2022-07-28 13:39:57 +00:00
auto result = query.execQuery();
2022-07-28 13:39:57 +00:00
if (result.eof()) {
ChatPackets::SendSystemMessage(originator->GetSystemAddress(), u"Failed to find the puzzle minigame for this pet.");
2022-07-28 13:39:57 +00:00
return;
}
2022-07-28 13:39:57 +00:00
if (result.fieldIsNull(0)) {
result.finalize();
2022-07-28 13:39:57 +00:00
return;
}
buildFile = std::string(result.getStringField(0));
2022-07-28 13:39:57 +00:00
PetPuzzleData data;
data.buildFile = buildFile;
data.puzzleModelLot = result.getIntField(1);
data.timeLimit = result.getFloatField(2);
data.numValidPieces = result.getIntField(3);
data.imaginationCost = result.getIntField(4);
if (data.timeLimit <= 0) data.timeLimit = 60;
imaginationCost = data.imaginationCost;
2022-07-28 13:39:57 +00:00
buildCache[m_Parent->GetLOT()] = data;
2022-07-28 13:39:57 +00:00
result.finalize();
} else {
buildFile = cached->second.buildFile;
imaginationCost = cached->second.imaginationCost;
}
2022-07-28 13:39:57 +00:00
auto* destroyableComponent = originator->GetComponent<DestroyableComponent>();
2023-12-12 03:10:29 +00:00
if (destroyableComponent == nullptr) return;
2022-07-28 13:39:57 +00:00
auto imagination = destroyableComponent->GetImagination();
2023-12-12 03:10:29 +00:00
if (imagination < imaginationCost) return;
2022-07-28 13:39:57 +00:00
const auto& bricks = BrickDatabase::GetBricks(buildFile);
2022-07-28 13:39:57 +00:00
if (bricks.empty()) {
ChatPackets::SendSystemMessage(originator->GetSystemAddress(), u"Failed to load the puzzle minigame for this pet.");
LOG("Couldn't find %s for minigame!", buildFile.c_str());
2022-07-28 13:39:57 +00:00
return;
}
2022-07-28 13:39:57 +00:00
auto petPosition = m_Parent->GetPosition();
2022-07-28 13:39:57 +00:00
auto originatorPosition = originator->GetPosition();
2022-07-28 13:39:57 +00:00
m_Parent->SetRotation(NiQuaternion::LookAt(petPosition, originatorPosition));
2022-07-28 13:39:57 +00:00
float interactionDistance = m_Parent->GetVar<float>(u"interaction_distance");
2022-07-28 13:39:57 +00:00
if (interactionDistance <= 0) {
interactionDistance = 15;
}
2022-07-28 13:39:57 +00:00
auto position = originatorPosition;
2022-07-28 13:39:57 +00:00
NiPoint3 forward = NiQuaternion::LookAt(m_Parent->GetPosition(), originator->GetPosition()).GetForwardVector();
forward.y = 0;
2022-07-28 13:39:57 +00:00
if (dpWorld::Instance().IsLoaded()) {
NiPoint3 attempt = petPosition + forward * interactionDistance;
float y = dpWorld::Instance().GetNavMesh()->GetHeightAtPoint(attempt);
2022-07-28 13:39:57 +00:00
while (std::abs(y - petPosition.y) > 4 && interactionDistance > 10) {
const NiPoint3 forward = m_Parent->GetRotation().GetForwardVector();
2022-07-28 13:39:57 +00:00
attempt = originatorPosition + forward * interactionDistance;
y = dpWorld::Instance().GetNavMesh()->GetHeightAtPoint(attempt);
2022-07-28 13:39:57 +00:00
interactionDistance -= 0.5f;
}
2022-07-28 13:39:57 +00:00
position = attempt;
} else {
position = petPosition + forward * interactionDistance;
}
2022-07-28 13:39:57 +00:00
auto rotation = NiQuaternion::LookAt(position, petPosition);
GameMessages::SendNotifyPetTamingMinigame(
originator->GetObjectID(),
m_Parent->GetObjectID(),
LWOOBJID_EMPTY,
true,
ePetTamingNotifyType::BEGIN,
2022-07-28 13:39:57 +00:00
petPosition,
position,
rotation,
UNASSIGNED_SYSTEM_ADDRESS
);
GameMessages::SendNotifyPetTamingMinigame(
m_Parent->GetObjectID(),
LWOOBJID_EMPTY,
originator->GetObjectID(),
true,
ePetTamingNotifyType::BEGIN,
2022-07-28 13:39:57 +00:00
petPosition,
position,
rotation,
UNASSIGNED_SYSTEM_ADDRESS
);
GameMessages::SendNotifyPetTamingPuzzleSelected(originator->GetObjectID(), bricks, originator->GetSystemAddress());
m_Tamer = originator->GetObjectID();
SetStatus(5);
currentActivities.insert_or_assign(m_Tamer, m_Parent->GetObjectID());
// Notify the start of a pet taming minigame
for (CppScripts::Script* script : CppScripts::GetEntityScripts(m_Parent)) {
script->OnNotifyPetTamingMinigame(m_Parent, originator, ePetTamingNotifyType::BEGIN);
2022-07-28 13:39:57 +00:00
}
}
2022-07-28 13:39:57 +00:00
void PetComponent::Update(float deltaTime) {
2023-12-11 01:55:36 +00:00
// If pet does not have an owner, use the UpdateUnowned() loop
2023-12-12 03:10:29 +00:00
/*if (m_Owner == LWOOBJID_EMPTY) {
2023-11-22 03:19:30 +00:00
UpdateUnowned(deltaTime);
2022-07-28 13:39:57 +00:00
return;
2023-12-12 03:10:29 +00:00
}*/
2023-12-13 06:14:53 +00:00
// Update timers
m_TimerBounce -= deltaTime;
2022-07-28 13:39:57 +00:00
2023-12-11 01:55:36 +00:00
if (m_Timer > 0) {
m_Timer -= deltaTime;
return;
}
2022-07-28 13:39:57 +00:00
2023-12-13 06:14:53 +00:00
// Remove "left behind" pets
if (m_Owner != LWOOBJID_EMPTY) {
Entity* owner = GetOwner();
if (!owner) {
m_Parent->Kill();
return;
}
}
// Handle pet AI states
2023-12-11 01:55:36 +00:00
switch (m_State) {
2023-12-12 03:10:29 +00:00
case PetAiState::spawn:
LOG_DEBUG("Pet spawn beginning!");
OnSpawn();
break;
case PetAiState::idle:
Wander();
break;
case PetAiState::follow:
OnFollow();
break;
case PetAiState::goToObj:
if (m_MovementAI->AtFinalWaypoint()) {
LOG_DEBUG("Reached object!");
m_MovementAI->Stop();
SetPetAiState(PetAiState::interact);
2023-12-11 01:55:36 +00:00
}
2023-12-12 03:10:29 +00:00
else {
m_Timer += 0.5f;
2023-12-11 01:55:36 +00:00
}
2023-12-12 03:10:29 +00:00
break;
2023-12-11 01:55:36 +00:00
2023-12-12 03:10:29 +00:00
case PetAiState::interact:
OnInteract();
break;
2023-12-11 01:55:36 +00:00
2023-12-12 03:10:29 +00:00
default:
break;
2023-12-11 01:55:36 +00:00
}
/*
2022-07-28 13:39:57 +00:00
auto destination = owner->GetPosition();
NiPoint3 position = m_MovementAI->GetParent()->GetPosition();
2022-07-28 13:39:57 +00:00
float distanceToOwner = Vector3::DistanceSquared(position, destination);
if (distanceToOwner > 50 * 50 || m_TimerAway > 5) {
m_MovementAI->Warp(destination);
2022-07-28 13:39:57 +00:00
m_Timer = 1;
m_TimerAway = 0;
2022-07-28 13:39:57 +00:00
return;
}
2022-07-28 13:39:57 +00:00
if (distanceToOwner > 15 * 15 || std::abs(destination.y - position.y) >= 3) {
m_TimerAway += deltaTime;
} else {
m_TimerAway = 0;
}
2022-07-28 13:39:57 +00:00
if (m_Timer > 0) {
m_Timer -= deltaTime;
return;
}
2022-07-28 13:39:57 +00:00
SwitchComponent* closestSwitch = SwitchComponent::GetClosestSwitch(position);
2022-07-28 13:39:57 +00:00
float haltDistance = 5;
2023-12-11 01:55:36 +00:00
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;
2022-07-28 13:39:57 +00:00
}
}
*/
}
2023-12-13 06:14:53 +00:00
void PetComponent::UpdateUnowned(float deltaTime) { //TODO: CURRENTLY UNUSED
2023-11-22 03:19:30 +00:00
if (m_Tamer != LWOOBJID_EMPTY) {
if (m_Timer > 0) {
m_Timer -= deltaTime;
if (m_Timer <= 0) {
m_Timer = 0;
ClientFailTamingMinigame();
}
}
}
else {
if (m_Timer > 0) {
m_Timer -= deltaTime;
if (m_Timer <= 0) Wander();
} else {
m_Timer = 5;
}
}
}
2022-04-07 05:21:54 +00:00
void PetComponent::TryBuild(uint32_t numBricks, bool clientFailed) {
2022-07-28 13:39:57 +00:00
if (m_Tamer == LWOOBJID_EMPTY) return;
auto* tamer = Game::entityManager->GetEntity(m_Tamer);
2022-07-28 13:39:57 +00:00
if (tamer == nullptr) {
m_Tamer = LWOOBJID_EMPTY;
2022-07-28 13:39:57 +00:00
return;
}
2022-07-28 13:39:57 +00:00
const auto& cached = buildCache.find(m_Parent->GetLOT());
2022-07-28 13:39:57 +00:00
if (cached == buildCache.end()) return;
2022-07-28 13:39:57 +00:00
auto* destroyableComponent = tamer->GetComponent<DestroyableComponent>();
2022-04-07 05:21:54 +00:00
2022-07-28 13:39:57 +00:00
if (destroyableComponent == nullptr) return;
2022-07-28 13:39:57 +00:00
auto imagination = destroyableComponent->GetImagination();
2022-07-28 13:39:57 +00:00
imagination -= cached->second.imaginationCost;
2022-07-28 13:39:57 +00:00
destroyableComponent->SetImagination(imagination);
Game::entityManager->SerializeEntity(tamer);
2022-07-28 13:39:57 +00:00
if (clientFailed) {
if (imagination < cached->second.imaginationCost) {
ClientFailTamingMinigame();
}
} else {
m_Timer = 0;
}
2022-04-07 05:21:54 +00:00
2022-07-28 13:39:57 +00:00
if (numBricks == 0) return;
2022-04-07 05:21:54 +00:00
2022-07-28 13:39:57 +00:00
GameMessages::SendPetTamingTryBuildResult(m_Tamer, !clientFailed, numBricks, tamer->GetSystemAddress());
}
2022-07-28 13:39:57 +00:00
void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) {
if (m_Tamer == LWOOBJID_EMPTY) return;
auto* tamer = Game::entityManager->GetEntity(m_Tamer);
2022-07-28 13:39:57 +00:00
if (tamer == nullptr) {
m_Tamer = LWOOBJID_EMPTY;
2022-07-28 13:39:57 +00:00
return;
}
2022-07-28 13:39:57 +00:00
const auto& cached = buildCache.find(m_Parent->GetLOT());
2022-07-28 13:39:57 +00:00
if (cached == buildCache.end()) {
return;
}
2022-07-28 13:39:57 +00:00
GameMessages::SendPlayFXEffect(tamer, -1, u"petceleb", "", LWOOBJID_EMPTY, 1, 1, true);
RenderComponent::PlayAnimation(tamer, u"rebuild-celebrate");
2022-07-28 13:39:57 +00:00
EntityInfo info{};
info.lot = cached->second.puzzleModelLot;
info.pos = position;
info.rot = NiQuaternion::IDENTITY;
info.spawnerID = tamer->GetObjectID();
auto* modelEntity = Game::entityManager->CreateEntity(info);
2022-07-28 13:39:57 +00:00
m_ModelId = modelEntity->GetObjectID();
Game::entityManager->ConstructEntity(modelEntity);
2022-07-28 13:39:57 +00:00
GameMessages::SendNotifyTamingModelLoadedOnServer(m_Tamer, tamer->GetSystemAddress());
2022-07-28 13:39:57 +00:00
GameMessages::SendPetResponse(m_Tamer, m_Parent->GetObjectID(), 0, 10, 0, tamer->GetSystemAddress());
2022-07-28 13:39:57 +00:00
auto* inventoryComponent = tamer->GetComponent<InventoryComponent>();
2022-07-28 13:39:57 +00:00
if (inventoryComponent == nullptr) {
return;
}
2022-07-28 13:39:57 +00:00
LWOOBJID petSubKey = ObjectIDManager::Instance()->GenerateRandomObjectID();
GeneralUtils::SetBit(petSubKey, eObjectBits::CHARACTER);
GeneralUtils::SetBit(petSubKey, eObjectBits::PERSISTENT);
2022-07-28 13:39:57 +00:00
m_DatabaseId = petSubKey;
2022-07-28 13:39:57 +00:00
std::string petName = tamer->GetCharacter()->GetName();
petName += "'s Pet";
2022-08-02 13:56:20 +00:00
GameMessages::SendAddPetToPlayer(m_Tamer, 0, GeneralUtils::UTF8ToUTF16(petName), petSubKey, m_Parent->GetLOT(), tamer->GetSystemAddress());
2022-07-28 13:39:57 +00:00
GameMessages::SendRegisterPetID(m_Tamer, m_Parent->GetObjectID(), tamer->GetSystemAddress());
2022-07-28 13:39:57 +00:00
GameMessages::SendRegisterPetDBID(m_Tamer, petSubKey, tamer->GetSystemAddress());
inventoryComponent->AddItem(m_Parent->GetLOT(), 1, eLootSourceType::ACTIVITY, eInventoryType::MODELS, {}, LWOOBJID_EMPTY, true, false, petSubKey);
2022-07-28 13:39:57 +00:00
auto* item = inventoryComponent->FindItemBySubKey(petSubKey, MODELS);
2022-07-28 13:39:57 +00:00
if (item == nullptr) {
return;
}
2022-07-28 13:39:57 +00:00
DatabasePet databasePet{};
2022-07-28 13:39:57 +00:00
databasePet.lot = m_Parent->GetLOT();
databasePet.moderationState = 1;
databasePet.name = petName;
2022-07-28 13:39:57 +00:00
inventoryComponent->SetDatabasePet(petSubKey, databasePet);
2022-07-28 13:39:57 +00:00
Activate(item, false, true);
2022-07-28 13:39:57 +00:00
m_Timer = 0;
2022-07-28 13:39:57 +00:00
GameMessages::SendNotifyPetTamingMinigame(
m_Tamer,
LWOOBJID_EMPTY,
LWOOBJID_EMPTY,
false,
ePetTamingNotifyType::NAMINGPET,
2022-07-28 13:39:57 +00:00
NiPoint3::ZERO,
NiPoint3::ZERO,
NiQuaternion::IDENTITY,
UNASSIGNED_SYSTEM_ADDRESS
);
2022-07-28 13:39:57 +00:00
// Triggers the catch a pet missions
if (petFlags.find(m_Parent->GetLOT()) != petFlags.end()) {
tamer->GetCharacter()->SetPlayerFlag(petFlags.at(m_Parent->GetLOT()), true);
}
2022-07-28 13:39:57 +00:00
auto* missionComponent = tamer->GetComponent<MissionComponent>();
2022-07-28 13:39:57 +00:00
if (missionComponent != nullptr) {
missionComponent->Progress(eMissionTaskType::PET_TAMING, m_Parent->GetLOT());
2022-07-28 13:39:57 +00:00
}
2022-07-28 13:39:57 +00:00
SetStatus(1);
2022-07-28 13:39:57 +00:00
auto* characterComponent = tamer->GetComponent<CharacterComponent>();
if (characterComponent != nullptr) {
characterComponent->UpdatePlayerStatistic(PetsTamed);
}
}
2022-07-28 13:39:57 +00:00
void PetComponent::RequestSetPetName(std::u16string name) {
if (m_Tamer == LWOOBJID_EMPTY) {
if (m_Owner != LWOOBJID_EMPTY) {
auto* owner = GetOwner();
2022-07-28 13:39:57 +00:00
m_ModerationStatus = 1; // Pending
m_Name = "";
2022-07-28 13:39:57 +00:00
//Save our pet's new name to the db:
SetPetNameForModeration(GeneralUtils::UTF16ToWTF8(name));
2022-08-02 13:56:20 +00:00
GameMessages::SendSetPetName(m_Owner, GeneralUtils::UTF8ToUTF16(m_Name), m_DatabaseId, owner->GetSystemAddress());
2022-07-28 13:39:57 +00:00
GameMessages::SendSetPetNameModerated(m_Owner, m_DatabaseId, m_ModerationStatus, owner->GetSystemAddress());
}
2022-07-28 13:39:57 +00:00
return;
}
auto* tamer = Game::entityManager->GetEntity(m_Tamer);
2022-07-28 13:39:57 +00:00
if (tamer == nullptr) {
m_Tamer = LWOOBJID_EMPTY;
2022-07-28 13:39:57 +00:00
return;
}
LOG("Got set pet name (%s)", GeneralUtils::UTF16ToWTF8(name).c_str());
2022-07-28 13:39:57 +00:00
auto* inventoryComponent = tamer->GetComponent<InventoryComponent>();
2022-07-28 13:39:57 +00:00
if (inventoryComponent == nullptr) {
return;
}
2022-07-28 13:39:57 +00:00
m_ModerationStatus = 1; // Pending
m_Name = "";
2022-07-28 13:39:57 +00:00
//Save our pet's new name to the db:
SetPetNameForModeration(GeneralUtils::UTF16ToWTF8(name));
Game::entityManager->SerializeEntity(m_Parent);
2022-08-02 13:56:20 +00:00
std::u16string u16name = GeneralUtils::UTF8ToUTF16(m_Name);
std::u16string u16ownerName = GeneralUtils::UTF8ToUTF16(m_OwnerName);
GameMessages::SendSetPetName(m_Tamer, u16name, m_DatabaseId, tamer->GetSystemAddress());
GameMessages::SendSetPetName(m_Tamer, u16name, LWOOBJID_EMPTY, tamer->GetSystemAddress());
GameMessages::SendPetNameChanged(m_Parent->GetObjectID(), m_ModerationStatus, u16name, u16ownerName, UNASSIGNED_SYSTEM_ADDRESS);
2022-07-28 13:39:57 +00:00
GameMessages::SendSetPetNameModerated(m_Tamer, m_DatabaseId, m_ModerationStatus, tamer->GetSystemAddress());
2022-07-28 13:39:57 +00:00
GameMessages::SendNotifyPetTamingMinigame(
m_Tamer,
m_Parent->GetObjectID(),
m_Tamer,
false,
ePetTamingNotifyType::SUCCESS,
2022-07-28 13:39:57 +00:00
NiPoint3::ZERO,
NiPoint3::ZERO,
NiQuaternion::IDENTITY,
UNASSIGNED_SYSTEM_ADDRESS
);
2022-07-28 13:39:57 +00:00
GameMessages::SendTerminateInteraction(m_Tamer, eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID());
auto* modelEntity = Game::entityManager->GetEntity(m_ModelId);
2022-07-28 13:39:57 +00:00
if (modelEntity != nullptr) {
modelEntity->Smash(m_Tamer);
}
2022-07-28 13:39:57 +00:00
currentActivities.erase(m_Tamer);
2022-07-28 13:39:57 +00:00
m_Tamer = LWOOBJID_EMPTY;
2022-07-28 13:39:57 +00:00
// Notify the end of a pet taming minigame
for (CppScripts::Script* script : CppScripts::GetEntityScripts(m_Parent)) {
script->OnNotifyPetTamingMinigame(m_Parent, tamer, ePetTamingNotifyType::SUCCESS);
2022-07-28 13:39:57 +00:00
}
}
2022-07-28 13:39:57 +00:00
void PetComponent::ClientExitTamingMinigame(bool voluntaryExit) {
if (m_Tamer == LWOOBJID_EMPTY) return;
auto* tamer = Game::entityManager->GetEntity(m_Tamer);
2022-07-28 13:39:57 +00:00
if (tamer == nullptr) {
m_Tamer = LWOOBJID_EMPTY;
2022-07-28 13:39:57 +00:00
return;
}
2022-07-28 13:39:57 +00:00
GameMessages::SendNotifyPetTamingMinigame(
m_Tamer,
m_Parent->GetObjectID(),
m_Tamer,
false,
ePetTamingNotifyType::QUIT,
2022-07-28 13:39:57 +00:00
NiPoint3::ZERO,
NiPoint3::ZERO,
NiQuaternion::IDENTITY,
UNASSIGNED_SYSTEM_ADDRESS
);
2022-07-28 13:39:57 +00:00
GameMessages::SendNotifyTamingModelLoadedOnServer(m_Tamer, tamer->GetSystemAddress());
2022-07-28 13:39:57 +00:00
GameMessages::SendTerminateInteraction(m_Tamer, eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID());
2022-07-28 13:39:57 +00:00
currentActivities.erase(m_Tamer);
2023-11-19 22:46:27 +00:00
SetStatus(PetStatus::TAMEABLE);
2022-07-28 13:39:57 +00:00
m_Tamer = LWOOBJID_EMPTY;
m_Timer = 0;
Game::entityManager->SerializeEntity(m_Parent);
2022-07-28 13:39:57 +00:00
// Notify the end of a pet taming minigame
for (CppScripts::Script* script : CppScripts::GetEntityScripts(m_Parent)) {
script->OnNotifyPetTamingMinigame(m_Parent, tamer, ePetTamingNotifyType::QUIT);
2022-07-28 13:39:57 +00:00
}
}
2022-07-28 13:39:57 +00:00
void PetComponent::StartTimer() {
const auto& cached = buildCache.find(m_Parent->GetLOT());
2022-07-28 13:39:57 +00:00
if (cached == buildCache.end()) {
return;
}
2022-07-28 13:39:57 +00:00
m_Timer = cached->second.timeLimit;
}
2022-07-28 13:39:57 +00:00
void PetComponent::ClientFailTamingMinigame() {
if (m_Tamer == LWOOBJID_EMPTY) return;
auto* tamer = Game::entityManager->GetEntity(m_Tamer);
2022-07-28 13:39:57 +00:00
if (tamer == nullptr) {
m_Tamer = LWOOBJID_EMPTY;
2022-07-28 13:39:57 +00:00
return;
}
2022-07-28 13:39:57 +00:00
GameMessages::SendNotifyPetTamingMinigame(
m_Tamer,
m_Parent->GetObjectID(),
m_Tamer,
false,
ePetTamingNotifyType::FAILED,
2022-07-28 13:39:57 +00:00
NiPoint3::ZERO,
NiPoint3::ZERO,
NiQuaternion::IDENTITY,
UNASSIGNED_SYSTEM_ADDRESS
);
2022-07-28 13:39:57 +00:00
GameMessages::SendNotifyTamingModelLoadedOnServer(m_Tamer, tamer->GetSystemAddress());
2022-07-28 13:39:57 +00:00
GameMessages::SendTerminateInteraction(m_Tamer, eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID());
2022-07-28 13:39:57 +00:00
currentActivities.erase(m_Tamer);
2023-11-19 22:46:27 +00:00
SetStatus(PetStatus::TAMEABLE);
2022-07-28 13:39:57 +00:00
m_Tamer = LWOOBJID_EMPTY;
m_Timer = 0;
Game::entityManager->SerializeEntity(m_Parent);
2022-07-28 13:39:57 +00:00
// Notify the end of a pet taming minigame
for (CppScripts::Script* script : CppScripts::GetEntityScripts(m_Parent)) {
script->OnNotifyPetTamingMinigame(m_Parent, tamer, ePetTamingNotifyType::FAILED);
2022-07-28 13:39:57 +00:00
}
}
2022-07-28 13:39:57 +00:00
void PetComponent::Wander() {
2023-12-13 06:14:53 +00:00
//m_MovementAI = m_Parent->GetComponent<MovementAIComponent>();
2023-12-13 06:14:53 +00:00
if (/*m_MovementAI == nullptr ||*/ !m_MovementAI->AtFinalWaypoint()) return;
m_MovementAI->SetHaltDistance(0);
const auto& info = m_MovementAI->GetInfo();
const auto div = static_cast<int>(info.wanderDelayMax);
m_Timer = (div == 0 ? 0 : GeneralUtils::GenerateRandomNumber<int>(0, div)) + info.wanderDelayMin; //set a random timer to stay put.
const float radius = info.wanderRadius * sqrt(static_cast<double>(GeneralUtils::GenerateRandomNumber<float>(0, 1))); //our wander radius + a bit of random range
const float theta = ((static_cast<double>(GeneralUtils::GenerateRandomNumber<float>(0, 1)) * 2 * PI));
const NiPoint3 delta =
{
radius * cos(theta),
0,
radius * sin(theta)
};
auto destination = m_StartPosition + delta;
if (dpWorld::Instance().IsLoaded()) {
destination.y = dpWorld::Instance().GetNavMesh()->GetHeightAtPoint(destination);
}
if (Vector3::DistanceSquared(destination, m_MovementAI->GetParent()->GetPosition()) < 2 * 2) {
m_MovementAI->Stop();
return;
}
2023-12-13 06:14:53 +00:00
m_MovementAI->SetMaxSpeed(m_SprintSpeed); //info.wanderSpeed);
m_MovementAI->SetDestination(destination);
2023-12-13 06:14:53 +00:00
m_Timer += (m_MovementAI->GetParent()->GetPosition().x - destination.x) / m_SprintSpeed; //info.wanderSpeed;
}
2023-12-12 03:10:29 +00:00
void PetComponent::OnSpawn() {
m_MovementAI = m_Parent->GetComponent<MovementAIComponent>();
2023-12-13 06:14:53 +00:00
//if (!m_MovementAI) return;
if (m_StartPosition == NiPoint3::ZERO) {
m_StartPosition = m_Parent->GetPosition();
}
2023-12-12 03:10:29 +00:00
LOG_DEBUG("Pet spawn complete, setting AI state.");
2023-12-13 06:14:53 +00:00
if (m_Owner != LWOOBJID_EMPTY) {
m_Parent->SetOwnerOverride(m_Owner);
m_MovementAI->SetMaxSpeed(m_SprintSpeed);
m_MovementAI->SetHaltDistance(m_FollowRadius);
SetStatus(PetStatus::NONE);
SetPetAiState(PetAiState::follow);
}
else {
SetPetAiState(PetAiState::idle);
}
2023-12-12 03:10:29 +00:00
}
void PetComponent::OnFollow() {
Entity* owner = GetOwner();
2023-12-13 06:14:53 +00:00
if (!owner) return;
const NiPoint3 ownerPos = owner->GetPosition();
2023-12-12 03:10:29 +00:00
2023-12-13 06:14:53 +00:00
// Find interactions
2023-12-12 03:10:29 +00:00
SwitchComponent* closestSwitch = SwitchComponent::GetClosestSwitch(ownerPos);
if (closestSwitch != nullptr && !closestSwitch->GetActive()) {
2023-12-13 06:14:53 +00:00
const NiPoint3 switchPos = closestSwitch->GetParentEntity()->GetPosition();
const LWOOBJID switchID = closestSwitch->GetParentEntity()->GetObjectID();
2023-12-12 03:10:29 +00:00
const float distance = Vector3::DistanceSquared(ownerPos, switchPos);
2023-12-13 06:14:53 +00:00
if (distance < 16 * 16) {
StartInteract(switchPos, PetInteractType::bouncer, switchID);
2023-12-12 03:10:29 +00:00
return;
}
}
// Determine if the "Lost Tags" mission has been completed and digging has been unlocked
auto* missionComponent = owner->GetComponent<MissionComponent>();
if (!missionComponent) return;
const bool digUnlocked = missionComponent->GetMissionState(842) == eMissionState::COMPLETE;
Entity* closestTreasure = PetDigServer::GetClosestTresure(ownerPos);
const bool nonDragonForBone = closestTreasure->GetLOT() == 12192 && m_Parent->GetLOT() != 13067;
2023-12-13 07:01:34 +00:00
if (!nonDragonForBone && closestTreasure != nullptr && digUnlocked) {
2023-12-13 06:14:53 +00:00
const NiPoint3 treasurePos = closestTreasure->GetPosition();
const LWOOBJID treasureID = closestTreasure->GetObjectID();
2023-12-12 03:10:29 +00:00
const float distance = Vector3::DistanceSquared(ownerPos, treasurePos);
2023-12-13 06:14:53 +00:00
if (distance < 16 * 16) {
StartInteract(treasurePos, PetInteractType::treasure, treasureID);
2023-12-12 03:10:29 +00:00
return;
}
}
2023-12-13 06:14:53 +00:00
// Handle actual following logic
const NiPoint3 currentPos = m_MovementAI->GetParent()->GetPosition();
// If the player's position is within range, stop moving
if (Vector3::DistanceSquared(currentPos, ownerPos) <= m_FollowRadius * m_FollowRadius) {
m_MovementAI->Stop();
}
else { // Chase the player's new position
m_MovementAI->SetDestination(ownerPos);
LOG_DEBUG("New pet destination: %f %f %f", ownerPos.x, ownerPos.y, ownerPos.z);
}
2023-12-12 03:10:29 +00:00
m_Timer += 0.5f;
}
void PetComponent::OnInteract() {
2023-12-13 06:14:53 +00:00
Entity* owner = GetOwner();
if (!owner) return;
2023-12-12 03:10:29 +00:00
2023-12-13 06:14:53 +00:00
const NiPoint3 ownerPos = owner->GetPosition();
const NiPoint3 currentPos = m_MovementAI->GetParent()->GetPosition();
2023-12-12 03:10:29 +00:00
const float distanceFromOwner = Vector3::DistanceSquared(ownerPos, currentPos);
if (distanceFromOwner > 25 * 25) {
LOG_DEBUG("Disengaging from object interaction due to player distance.");
StopInteract();
return;
}
switch (GetInteractType()) {
case PetInteractType::bouncer:
2023-12-13 06:14:53 +00:00
if (GetIsReadyToInteract()) LOG_DEBUG("Add the HandleInteractBouncer()!");
else SetupInteractBouncer();
2023-12-12 03:10:29 +00:00
break;
case PetInteractType::treasure:
2023-12-13 06:14:53 +00:00
if (GetIsReadyToInteract()) HandleInteractTreasureDig();
else SetupInteractTreasureDig();
2023-12-12 03:10:29 +00:00
break;
default:
LOG_DEBUG("INTERACT = NONE! RETURNING!");
StopInteract();
m_Timer += 0.5f;
break;
}
}
2023-12-13 06:14:53 +00:00
void PetComponent::StartInteract(const NiPoint3 position, const PetInteractType interactType, const LWOOBJID interactID) {
SetInteraction(interactID); // TODO: Check if this should be serialized for goToObj
2023-12-12 03:10:29 +00:00
SetInteractType(interactType);
SetAbility(PetAbilityType::GoToObject);
SetPetAiState(PetAiState::goToObj);
m_MovementAI->SetMaxSpeed(m_RunSpeed);
m_MovementAI->SetHaltDistance(0.0f);
m_MovementAI->SetDestination(position);
LOG_DEBUG("Starting interaction!");
Game::entityManager->SerializeEntity(m_Parent);
}
void PetComponent::StopInteract() {
2023-12-13 06:14:53 +00:00
SetInteraction(LWOOBJID_EMPTY);
2023-12-12 03:10:29 +00:00
SetInteractType(PetInteractType::none);
SetAbility(PetAbilityType::Invalid);
SetPetAiState(PetAiState::follow);
2023-12-13 06:14:53 +00:00
SetStatus(PetStatus::NONE);
SetIsReadyToInteract(false);
2023-12-12 03:10:29 +00:00
m_MovementAI->SetMaxSpeed(m_SprintSpeed);
m_MovementAI->SetHaltDistance(m_FollowRadius);
LOG_DEBUG("Stopping interaction!");
Game::entityManager->SerializeEntity(m_Parent);
}
2023-12-13 06:14:53 +00:00
void PetComponent::SetupInteractBouncer() {
// THIS IS ALL BAD, BAD, BAD! FIX IT, ME! >:(
/*SetAbility(PetAbilityType::JumpOnObject);
2023-12-12 03:10:29 +00:00
NiPoint3 destination = m_MovementAI->GetDestination();
SwitchComponent* closestSwitch = SwitchComponent::GetClosestSwitch(destination);
m_Interaction = closestSwitch->GetParentEntity()->GetObjectID();
Game::entityManager->SerializeEntity(m_Parent);
2023-12-13 06:14:53 +00:00
closestSwitch->EntityEnter(m_Parent);*/
2023-12-12 03:10:29 +00:00
}
2023-12-13 06:14:53 +00:00
void PetComponent::SetupInteractTreasureDig() {
LOG_DEBUG("Setting up dig interaction!");
Entity* closestTreasure = Game::entityManager->GetEntity(GetInteraction());
if (!closestTreasure) return;
SetIsReadyToInteract(true);
SetAbility(PetAbilityType::JumpOnObject);
2023-12-13 06:14:53 +00:00
SetStatus(PetStatus::IS_NOT_WAITING); // TODO: Double-check this is the right flag being set
Game::entityManager->SerializeEntity(m_Parent); // TODO: Double-check pet packet captures
m_Timer += 0.5f;
}
void PetComponent::StartInteractTreasureDig() {
SetAbility(PetAbilityType::DigAtPosition);
Game::entityManager->SerializeEntity(m_Parent);
Command(NiPoint3::ZERO, LWOOBJID_EMPTY, 1, PetEmote::DigTreasure, true); // Plays 'dig' animation
m_Timer = 1.5f;
}
void PetComponent::HandleInteractTreasureDig() {
if (GetAbility() == PetAbilityType::DigAtPosition) {
auto* owner = GetOwner();
auto* treasure = Game::entityManager->GetEntity(GetInteraction());
if (!treasure) return;
treasure->Smash(m_Parent->GetObjectID());
LOG_DEBUG("Pet dig completed!");
StopInteract(); //TODO: This may not be totally consistent with live behavior, where the pet seems to stay near the dig and not immediately follow
m_Timer = 1.5f;
return;
}
if (m_TimerBounce <= 0.0f) {
Command(NiPoint3::ZERO, LWOOBJID_EMPTY, 1, PetEmote::Bounce, true); // Plays 'bounce' animation
m_TimerBounce = 1.0f;
}
m_Timer += 0.5f;
2023-12-12 03:10:29 +00:00
}
2022-07-28 13:39:57 +00:00
void PetComponent::Activate(Item* item, bool registerPet, bool fromTaming) {
AddDrainImaginationTimer(item, fromTaming);
2022-07-28 13:39:57 +00:00
m_ItemId = item->GetId();
m_DatabaseId = item->GetSubKey();
2022-07-28 13:39:57 +00:00
auto* inventoryComponent = item->GetInventory()->GetComponent();
2022-07-28 13:39:57 +00:00
if (inventoryComponent == nullptr) return;
2022-07-28 13:39:57 +00:00
inventoryComponent->DespawnPet();
2022-07-28 13:39:57 +00:00
m_Owner = inventoryComponent->GetParent()->GetObjectID();
2022-07-28 13:39:57 +00:00
auto* owner = GetOwner();
2022-07-28 13:39:57 +00:00
if (owner == nullptr) return;
2023-12-13 06:14:53 +00:00
SetStatus(PetStatus::PLAY_SPAWN_ANIM);
2022-07-28 13:39:57 +00:00
auto databaseData = inventoryComponent->GetDatabasePet(m_DatabaseId);
2022-07-28 13:39:57 +00:00
m_ModerationStatus = databaseData.moderationState;
2022-07-28 13:39:57 +00:00
bool updatedModerationStatus = false;
2022-07-28 13:39:57 +00:00
//Load mod status from db:
if (m_ModerationStatus != 2) {
LoadPetNameFromModeration();
2022-07-28 13:39:57 +00:00
databaseData.name = m_Name;
databaseData.moderationState = m_ModerationStatus;
2022-07-28 13:39:57 +00:00
inventoryComponent->SetDatabasePet(m_DatabaseId, databaseData);
2022-07-28 13:39:57 +00:00
updatedModerationStatus = true;
} else {
m_Name = databaseData.name;
}
2022-07-28 13:39:57 +00:00
m_OwnerName = owner->GetCharacter()->GetName();
2022-07-28 13:39:57 +00:00
if (updatedModerationStatus) {
2022-08-02 13:56:20 +00:00
GameMessages::SendSetPetName(m_Owner, GeneralUtils::UTF8ToUTF16(m_Name), m_DatabaseId, owner->GetSystemAddress());
2022-07-28 13:39:57 +00:00
GameMessages::SendSetPetNameModerated(m_Owner, m_DatabaseId, m_ModerationStatus, owner->GetSystemAddress());
}
GameMessages::SendMarkInventoryItemAsActive(m_Owner, true, eUnequippableActiveType::PET, m_ItemId, GetOwner()->GetSystemAddress());
2022-07-28 13:39:57 +00:00
activePets[m_Owner] = m_Parent->GetObjectID();
2023-12-13 06:14:53 +00:00
//m_Timer = 3;
Game::entityManager->SerializeEntity(m_Parent);
owner->GetCharacter()->SetPlayerFlag(ePlayerFlag::FIRST_MANUAL_PET_HIBERNATE, true);
2022-07-28 13:39:57 +00:00
if (registerPet) {
2022-08-02 13:56:20 +00:00
GameMessages::SendAddPetToPlayer(m_Owner, 0, GeneralUtils::UTF8ToUTF16(m_Name), m_DatabaseId, m_Parent->GetLOT(), owner->GetSystemAddress());
2022-07-28 13:39:57 +00:00
GameMessages::SendRegisterPetID(m_Owner, m_Parent->GetObjectID(), owner->GetSystemAddress());
2022-07-28 13:39:57 +00:00
GameMessages::SendRegisterPetDBID(m_Owner, m_DatabaseId, owner->GetSystemAddress());
}
2022-07-28 13:39:57 +00:00
GameMessages::SendShowPetActionButton(m_Owner, 3, true, owner->GetSystemAddress());
}
2022-06-18 07:14:24 +00:00
void PetComponent::AddDrainImaginationTimer(Item* item, bool fromTaming) {
2022-07-28 13:39:57 +00:00
if (Game::config->GetValue("pets_take_imagination") != "1") return;
2022-06-18 07:14:24 +00:00
2022-07-28 13:39:57 +00:00
auto playerInventory = item->GetInventory();
if (!playerInventory) return;
2022-07-28 13:39:57 +00:00
auto playerInventoryComponent = playerInventory->GetComponent();
if (!playerInventoryComponent) return;
2022-07-28 13:39:57 +00:00
auto playerEntity = playerInventoryComponent->GetParent();
if (!playerEntity) return;
2022-07-28 13:39:57 +00:00
auto playerDestroyableComponent = playerEntity->GetComponent<DestroyableComponent>();
if (!playerDestroyableComponent) return;
2022-07-28 13:39:57 +00:00
// Drain by 1 when you summon pet or when this method is called, but not when we have just tamed this pet.
if (!fromTaming) playerDestroyableComponent->Imagine(-1);
2022-07-28 13:39:57 +00:00
// Set this to a variable so when this is called back from the player the timer doesn't fire off.
m_Parent->AddCallbackTimer(imaginationDrainRate, [playerDestroyableComponent, this, item]() {
if (!playerDestroyableComponent) {
LOG("No petComponent and/or no playerDestroyableComponent");
2022-07-28 13:39:57 +00:00
return;
}
2023-01-03 17:22:04 +00:00
// If we are out of imagination despawn the pet.
if (playerDestroyableComponent->GetImagination() == 0) {
this->Deactivate();
auto playerEntity = playerDestroyableComponent->GetParent();
if (!playerEntity) return;
GameMessages::SendUseItemRequirementsResponse(playerEntity->GetObjectID(), playerEntity->GetSystemAddress(), eUseItemResponse::NoImaginationForPet);
2023-01-03 17:22:04 +00:00
}
2023-01-03 17:22:04 +00:00
this->AddDrainImaginationTimer(item);
2022-07-28 13:39:57 +00:00
});
}
2022-07-28 13:39:57 +00:00
void PetComponent::Deactivate() {
GameMessages::SendPlayFXEffect(m_Parent->GetObjectID(), -1, u"despawn", "", LWOOBJID_EMPTY, 1, 1, true);
GameMessages::SendMarkInventoryItemAsActive(m_Owner, false, eUnequippableActiveType::PET, m_ItemId, GetOwner()->GetSystemAddress());
2022-07-28 13:39:57 +00:00
activePets.erase(m_Owner);
2022-07-28 13:39:57 +00:00
m_Parent->Kill();
2022-07-28 13:39:57 +00:00
auto* owner = GetOwner();
2022-07-28 13:39:57 +00:00
if (owner == nullptr) return;
2022-07-28 13:39:57 +00:00
GameMessages::SendAddPetToPlayer(m_Owner, 0, u"", LWOOBJID_EMPTY, LOT_NULL, owner->GetSystemAddress());
2022-07-28 13:39:57 +00:00
GameMessages::SendRegisterPetID(m_Owner, LWOOBJID_EMPTY, owner->GetSystemAddress());
2022-07-28 13:39:57 +00:00
GameMessages::SendRegisterPetDBID(m_Owner, LWOOBJID_EMPTY, owner->GetSystemAddress());
2022-07-28 13:39:57 +00:00
GameMessages::SendShowPetActionButton(m_Owner, 0, false, owner->GetSystemAddress());
}
2022-07-28 13:39:57 +00:00
void PetComponent::Release() {
auto* inventoryComponent = GetOwner()->GetComponent<InventoryComponent>();
2022-07-28 13:39:57 +00:00
if (inventoryComponent == nullptr) {
return;
}
2022-07-28 13:39:57 +00:00
Deactivate();
2022-07-28 13:39:57 +00:00
inventoryComponent->RemoveDatabasePet(m_DatabaseId);
2022-07-28 13:39:57 +00:00
auto* item = inventoryComponent->FindItemBySubKey(m_DatabaseId);
2022-07-28 13:39:57 +00:00
item->SetCount(0, false, false);
}
2022-07-28 13:39:57 +00:00
void PetComponent::Command(NiPoint3 position, LWOOBJID source, int32_t commandType, int32_t typeId, bool overrideObey) {
auto* owner = GetOwner();
2023-11-19 22:46:27 +00:00
if (!owner) return;
2022-07-28 13:39:57 +00:00
if (commandType == 1) {
// Emotes
GameMessages::SendPlayEmote(m_Parent->GetObjectID(), typeId, owner->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS);
} else if (commandType == 3) {
StopInteract(); // TODO: Verify this is necessary
2023-12-11 01:55:36 +00:00
SetPetAiState(PetAiState::follow);
2022-07-28 13:39:57 +00:00
} else if (commandType == 6) {
// TODO: Go to player
}
if (owner->GetGMLevel() >= eGameMasterLevel::DEVELOPER) {
2022-07-28 13:39:57 +00:00
ChatPackets::SendSystemMessage(owner->GetSystemAddress(), u"Commmand Type: " + (GeneralUtils::to_u16string(commandType)) + u" - Type Id: " + (GeneralUtils::to_u16string(typeId)));
}
2023-11-19 23:31:31 +00:00
// Add movement functionality
if (position != NiPoint3::ZERO) {
m_MovementAI->SetDestination(position);
}
}
2022-07-28 13:39:57 +00:00
LWOOBJID PetComponent::GetOwnerId() const {
return m_Owner;
}
2022-07-28 13:39:57 +00:00
Entity* PetComponent::GetOwner() const {
return Game::entityManager->GetEntity(m_Owner);
}
2022-07-28 13:39:57 +00:00
LWOOBJID PetComponent::GetDatabaseId() const {
return m_DatabaseId;
}
2022-07-28 13:39:57 +00:00
LWOOBJID PetComponent::GetInteraction() const {
return m_Interaction;
}
2022-07-28 13:39:57 +00:00
LWOOBJID PetComponent::GetItemId() const {
return m_ItemId;
}
2022-07-28 13:39:57 +00:00
uint32_t PetComponent::GetStatus() const {
return m_Status;
}
2022-07-28 13:39:57 +00:00
PetAbilityType PetComponent::GetAbility() const {
return m_Ability;
}
2022-07-28 13:39:57 +00:00
void PetComponent::SetInteraction(LWOOBJID value) {
m_Interaction = value;
}
2022-07-28 13:39:57 +00:00
void PetComponent::SetStatus(uint32_t value) {
m_Status = value;
}
2022-07-28 13:39:57 +00:00
void PetComponent::SetAbility(PetAbilityType value) {
m_Ability = value;
}
2022-07-28 13:39:57 +00:00
PetComponent* PetComponent::GetTamingPet(LWOOBJID tamer) {
const auto& pair = currentActivities.find(tamer);
2022-07-28 13:39:57 +00:00
if (pair == currentActivities.end()) {
return nullptr;
}
auto* entity = Game::entityManager->GetEntity(pair->second);
2022-07-28 13:39:57 +00:00
if (entity == nullptr) {
currentActivities.erase(tamer);
2022-07-28 13:39:57 +00:00
return nullptr;
}
2022-07-28 13:39:57 +00:00
return entity->GetComponent<PetComponent>();
}
2022-07-28 13:39:57 +00:00
PetComponent* PetComponent::GetActivePet(LWOOBJID owner) {
const auto& pair = activePets.find(owner);
2022-07-28 13:39:57 +00:00
if (pair == activePets.end()) {
return nullptr;
}
auto* entity = Game::entityManager->GetEntity(pair->second);
2022-07-28 13:39:57 +00:00
if (entity == nullptr) {
activePets.erase(owner);
2022-07-28 13:39:57 +00:00
return nullptr;
}
2022-07-28 13:39:57 +00:00
return entity->GetComponent<PetComponent>();
}
2022-07-28 13:39:57 +00:00
Entity* PetComponent::GetParentEntity() const {
return m_Parent;
}
2022-07-28 13:39:57 +00:00
PetComponent::~PetComponent() {
}
void PetComponent::SetPetNameForModeration(const std::string& petName) {
2022-07-28 13:39:57 +00:00
int approved = 1; //default, in mod
2022-07-28 13:39:57 +00:00
//Make sure that the name isn't already auto-approved:
if (Game::chatFilter->IsSentenceOkay(petName, eGameMasterLevel::CIVILIAN).empty()) {
2022-07-28 13:39:57 +00:00
approved = 2; //approved
}
2022-07-28 13:39:57 +00:00
//Save to db:
refactor: Database abstraction and organization of files (#1274) * Database: Convert to proper namespace * Database: Use base class and getter * Database: Move files around * Database: Add property Management query Database: Move over user queries Tested at gm 0 that pre-approved names are pre-approved, unapproved need moderator approval deleting characters deletes the selcted one refreshing the character page shows the last character you logged in as tested all my characters show up when i login tested that you can delete all 4 characters and the correct character is selected each time tested renaming, approving names as gm0 Database: Add ugc model getter Hey it works, look I got around the mariadb issue. Database: Add queries Database: consolidate name query Database: Add friends list query Update name of approved names query Documentation Database: Add name check Database: Add BFF Query Database: Move BFF Setter Database: Move new friend query Database: Add remove friend queries Database: Add activity log Database: Add ugc & prop content removal Database: Add model update Database: Add migration queries Database: Add character and xml queries Database: Add user queries Untested, but compiling code Need to test that new character names are properly assigned in the following scenarios gm 0 and pre-approved name gm 0 and unapproved name gm 9 and pre-approved name gm 9 and unapproved name Database: constify function arguments Database: Add pet queries * Database: Move property model queries Untested. Need to test placing a new model moving existing one removing ugc model placing ugc model moving ugc model(?) changing privacy option variously change description and name approve property can properly travel to property * Property: Move stale reference deletion * Database: Move performance update query * Database: Add bug report query * Database: Add cheat detection query * Database: Add mail send query * Untested code need to test mailing from slash command, from all users of SendMail, getting bbb of a property and sending messages to bffs * Update CDComponentsRegistryTable.h Database: Rename and add further comments Datavbase: Add comments Add some comments Build: Fix PCH directories Database: Fix time thanks apple Database: Fix compiler warnings Overload destructor Define specialty for time_t Use string instead of string_view for temp empty string Update CDTable.h Property: Update queries to use mapId Database: Reorganize Reorganize into CDClient folder and GameDatabase folder for clearer meanings and file structure Folders: Rename to GameDatabase MySQL: Remove MySQL Specifier from table Database: Move Tables to Interfaces Database: Reorder functions in header Database: Simplify property queries Database: Remove unused queries Remove extra query definitions as well Database: Consolidate User getters Database: Comment logs Update MySQLDatabase.cpp Database: Use generic code Playkey: Fix bad optional access Database: Move stuff around WorldServer: Update queries Ugc reduced by many scopes use new queries very fast tested that ugc still loads Database: Add auth queries I tested that only the correct password can sign into an account. Tested that disabled playkeys do not allow the user to play the game Database: Add donation query Database: add objectId queries Database: Add master queries Database: Fix mis-named function Database: Add slash command queries Mail: Fix itemId type CharFilter: Use new query ObjectID: Remove duplicate code SlashCommand: Update query with function Database: Add mail queries Ugc: Fix issues with saving models Resolve large scope blocks as well * Database: Add debug try catch rethrow macro * General fixes * fix play key not working * Further fixes --------- Co-authored-by: Aaron Kimbre <aronwk.aaron@gmail.com>
2023-11-18 00:47:18 +00:00
Database::Get()->SetPetNameModerationStatus(m_DatabaseId, IPetNames::Info{petName, approved});
}
void PetComponent::LoadPetNameFromModeration() {
refactor: Database abstraction and organization of files (#1274) * Database: Convert to proper namespace * Database: Use base class and getter * Database: Move files around * Database: Add property Management query Database: Move over user queries Tested at gm 0 that pre-approved names are pre-approved, unapproved need moderator approval deleting characters deletes the selcted one refreshing the character page shows the last character you logged in as tested all my characters show up when i login tested that you can delete all 4 characters and the correct character is selected each time tested renaming, approving names as gm0 Database: Add ugc model getter Hey it works, look I got around the mariadb issue. Database: Add queries Database: consolidate name query Database: Add friends list query Update name of approved names query Documentation Database: Add name check Database: Add BFF Query Database: Move BFF Setter Database: Move new friend query Database: Add remove friend queries Database: Add activity log Database: Add ugc & prop content removal Database: Add model update Database: Add migration queries Database: Add character and xml queries Database: Add user queries Untested, but compiling code Need to test that new character names are properly assigned in the following scenarios gm 0 and pre-approved name gm 0 and unapproved name gm 9 and pre-approved name gm 9 and unapproved name Database: constify function arguments Database: Add pet queries * Database: Move property model queries Untested. Need to test placing a new model moving existing one removing ugc model placing ugc model moving ugc model(?) changing privacy option variously change description and name approve property can properly travel to property * Property: Move stale reference deletion * Database: Move performance update query * Database: Add bug report query * Database: Add cheat detection query * Database: Add mail send query * Untested code need to test mailing from slash command, from all users of SendMail, getting bbb of a property and sending messages to bffs * Update CDComponentsRegistryTable.h Database: Rename and add further comments Datavbase: Add comments Add some comments Build: Fix PCH directories Database: Fix time thanks apple Database: Fix compiler warnings Overload destructor Define specialty for time_t Use string instead of string_view for temp empty string Update CDTable.h Property: Update queries to use mapId Database: Reorganize Reorganize into CDClient folder and GameDatabase folder for clearer meanings and file structure Folders: Rename to GameDatabase MySQL: Remove MySQL Specifier from table Database: Move Tables to Interfaces Database: Reorder functions in header Database: Simplify property queries Database: Remove unused queries Remove extra query definitions as well Database: Consolidate User getters Database: Comment logs Update MySQLDatabase.cpp Database: Use generic code Playkey: Fix bad optional access Database: Move stuff around WorldServer: Update queries Ugc reduced by many scopes use new queries very fast tested that ugc still loads Database: Add auth queries I tested that only the correct password can sign into an account. Tested that disabled playkeys do not allow the user to play the game Database: Add donation query Database: add objectId queries Database: Add master queries Database: Fix mis-named function Database: Add slash command queries Mail: Fix itemId type CharFilter: Use new query ObjectID: Remove duplicate code SlashCommand: Update query with function Database: Add mail queries Ugc: Fix issues with saving models Resolve large scope blocks as well * Database: Add debug try catch rethrow macro * General fixes * fix play key not working * Further fixes --------- Co-authored-by: Aaron Kimbre <aronwk.aaron@gmail.com>
2023-11-18 00:47:18 +00:00
auto petNameInfo = Database::Get()->GetPetNameInfo(m_DatabaseId);
if (petNameInfo) {
m_ModerationStatus = petNameInfo->approvalStatus;
2022-07-28 13:39:57 +00:00
if (m_ModerationStatus == 2) {
refactor: Database abstraction and organization of files (#1274) * Database: Convert to proper namespace * Database: Use base class and getter * Database: Move files around * Database: Add property Management query Database: Move over user queries Tested at gm 0 that pre-approved names are pre-approved, unapproved need moderator approval deleting characters deletes the selcted one refreshing the character page shows the last character you logged in as tested all my characters show up when i login tested that you can delete all 4 characters and the correct character is selected each time tested renaming, approving names as gm0 Database: Add ugc model getter Hey it works, look I got around the mariadb issue. Database: Add queries Database: consolidate name query Database: Add friends list query Update name of approved names query Documentation Database: Add name check Database: Add BFF Query Database: Move BFF Setter Database: Move new friend query Database: Add remove friend queries Database: Add activity log Database: Add ugc & prop content removal Database: Add model update Database: Add migration queries Database: Add character and xml queries Database: Add user queries Untested, but compiling code Need to test that new character names are properly assigned in the following scenarios gm 0 and pre-approved name gm 0 and unapproved name gm 9 and pre-approved name gm 9 and unapproved name Database: constify function arguments Database: Add pet queries * Database: Move property model queries Untested. Need to test placing a new model moving existing one removing ugc model placing ugc model moving ugc model(?) changing privacy option variously change description and name approve property can properly travel to property * Property: Move stale reference deletion * Database: Move performance update query * Database: Add bug report query * Database: Add cheat detection query * Database: Add mail send query * Untested code need to test mailing from slash command, from all users of SendMail, getting bbb of a property and sending messages to bffs * Update CDComponentsRegistryTable.h Database: Rename and add further comments Datavbase: Add comments Add some comments Build: Fix PCH directories Database: Fix time thanks apple Database: Fix compiler warnings Overload destructor Define specialty for time_t Use string instead of string_view for temp empty string Update CDTable.h Property: Update queries to use mapId Database: Reorganize Reorganize into CDClient folder and GameDatabase folder for clearer meanings and file structure Folders: Rename to GameDatabase MySQL: Remove MySQL Specifier from table Database: Move Tables to Interfaces Database: Reorder functions in header Database: Simplify property queries Database: Remove unused queries Remove extra query definitions as well Database: Consolidate User getters Database: Comment logs Update MySQLDatabase.cpp Database: Use generic code Playkey: Fix bad optional access Database: Move stuff around WorldServer: Update queries Ugc reduced by many scopes use new queries very fast tested that ugc still loads Database: Add auth queries I tested that only the correct password can sign into an account. Tested that disabled playkeys do not allow the user to play the game Database: Add donation query Database: add objectId queries Database: Add master queries Database: Fix mis-named function Database: Add slash command queries Mail: Fix itemId type CharFilter: Use new query ObjectID: Remove duplicate code SlashCommand: Update query with function Database: Add mail queries Ugc: Fix issues with saving models Resolve large scope blocks as well * Database: Add debug try catch rethrow macro * General fixes * fix play key not working * Further fixes --------- Co-authored-by: Aaron Kimbre <aronwk.aaron@gmail.com>
2023-11-18 00:47:18 +00:00
m_Name = petNameInfo->petName;
2022-07-28 13:39:57 +00:00
}
}
}
void PetComponent::SetPreconditions(std::string& preconditions) {
2022-07-28 13:39:57 +00:00
m_Preconditions = new PreconditionExpression(preconditions);
}