mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
fix(pets): pet digs wait for the owner and cost imagination
Issue 537. A pet that reached a dig dug it up by itself for free. Now, as on live (2014 captures), the pet goes to the dig with state 0x500 and the Go To Object ability while the owner gets the PR_DIG_TUTORIAL_01 tooltip; at the dig it waits with state 0x120 and the Dig At Position ability, and the owner gets the pet action button (ShowPetActionButton 3). Pressing SHIFT or the button makes the client send RequestUse on the pet (LWOPetControlComponent::msgPetCommand 0x00c5d220, "contextAction"); that costs PetAbilities.ImaginationCost (1 for digging), hides the button, shows PR_DIG_TUTORIAL_03 and starts the dig. The button goes away when the pet leaves the dig. The dig is no longer written into the pet's serialized interaction: live did not serialize one for digs or pet switches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
#include "DashboardNotify.h"
|
||||
#include "GameMessages.h"
|
||||
#include "PetMessages.h"
|
||||
#include "PlayerMessages.h"
|
||||
#include "InventoryMessages.h"
|
||||
#include "EffectsMessages.h"
|
||||
#include "ObjectMessages.h"
|
||||
@@ -118,6 +119,9 @@ void PetComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpd
|
||||
|
||||
void PetComponent::OnUse(Entity* originator) {
|
||||
if (m_Owner != LWOOBJID_EMPTY) {
|
||||
// The owner uses their pet with SHIFT or the pet menu's action button: the client sends RequestUse on the pet
|
||||
// (LWOPetControlComponent::msgPetCommand, "contextAction")
|
||||
if (originator->GetObjectID() == m_Owner) UseAbility();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -308,7 +312,7 @@ void PetComponent::Update(float deltaTime) {
|
||||
}
|
||||
|
||||
if (m_TreasureTime > 0) {
|
||||
auto* treasure = Game::entityManager->GetEntity(m_Interaction);
|
||||
auto* treasure = Game::entityManager->GetEntity(m_AbilityObject);
|
||||
|
||||
if (treasure == nullptr) {
|
||||
m_TreasureTime = 0;
|
||||
@@ -325,7 +329,7 @@ void PetComponent::Update(float deltaTime) {
|
||||
|
||||
treasure->Smash(m_Parent->GetObjectID());
|
||||
|
||||
m_Interaction = LWOOBJID_EMPTY;
|
||||
m_AbilityObject = LWOOBJID_EMPTY;
|
||||
|
||||
m_TreasureTime = 0;
|
||||
}
|
||||
@@ -386,28 +390,26 @@ void PetComponent::Update(float deltaTime) {
|
||||
|
||||
Entity* closestTreasure = PetDigServer::GetClosestTreasure(position);
|
||||
|
||||
if (closestTreasure != nullptr && digUnlocked) {
|
||||
// Skeleton Dragon Pat special case for bone digging
|
||||
if (closestTreasure->GetLOT() == 12192 && m_Parent->GetLOT() != 13067) {
|
||||
goto skipTreasure;
|
||||
}
|
||||
Entity* abilityObject = nullptr;
|
||||
auto objectAbility = ePetAbilityType::Invalid;
|
||||
bool atAbilityObject = false;
|
||||
|
||||
// Skeleton Dragon Pat special case for bone digging
|
||||
if (closestTreasure != nullptr && digUnlocked && (closestTreasure->GetLOT() != 12192 || m_Parent->GetLOT() == 13067)) {
|
||||
NiPoint3 treasurePosition = closestTreasure->GetPosition();
|
||||
float distance = Vector3::DistanceSquared(position, treasurePosition);
|
||||
if (distance < 5 * 5) {
|
||||
m_Interaction = closestTreasure->GetObjectID();
|
||||
if (distance < 10 * 10) {
|
||||
abilityObject = closestTreasure;
|
||||
objectAbility = ePetAbilityType::DigAtPosition;
|
||||
atAbilityObject = distance < 5 * 5;
|
||||
|
||||
Command(NiPoint3Constant::ZERO, LWOOBJID_EMPTY, 1, 202, true);
|
||||
|
||||
m_TreasureTime = 2;
|
||||
} else if (distance < 10 * 10) {
|
||||
haltDistance = 1;
|
||||
|
||||
destination = treasurePosition;
|
||||
}
|
||||
}
|
||||
|
||||
skipTreasure:
|
||||
SetAbilityObject(abilityObject, objectAbility, atAbilityObject);
|
||||
|
||||
m_MovementAI->SetHaltDistance(haltDistance);
|
||||
|
||||
@@ -1166,6 +1168,114 @@ void PetComponent::Command(const NiPoint3& position, const LWOOBJID source, cons
|
||||
}
|
||||
}
|
||||
|
||||
void PetComponent::SetAbilityObject(Entity* object, const ePetAbilityType ability, const bool atObject) {
|
||||
const auto objectId = object ? object->GetObjectID() : LWOOBJID_EMPTY;
|
||||
const auto status = m_Status & ~(PET_STATE_AT_ABILITY_OBJECT | PET_STATE_USING_ABILITY | PET_STATE_GOING_TO_ABILITY_OBJECT);
|
||||
|
||||
// What live did (2014 captures): on the way the pet has state 0x500 and the Go To Object ability, the tutorial
|
||||
// for the object is shown; at the object it has 0x120 and the object's ability, and the owner gets the button.
|
||||
if (objectId != m_AbilityObject) {
|
||||
HideAbilityButton();
|
||||
m_AbilityObject = objectId;
|
||||
|
||||
if (!object) {
|
||||
m_Ability = ePetAbilityType::Invalid;
|
||||
SetStatus(status);
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
return;
|
||||
}
|
||||
|
||||
m_Ability = ePetAbilityType::GoToObject;
|
||||
SetStatus(status | PET_STATE_USING_ABILITY | PET_STATE_GOING_TO_ABILITY_OBJECT);
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
|
||||
if (ability == ePetAbilityType::DigAtPosition) SendHelp(eHelpType::PR_DIG_TUTORIAL_01);
|
||||
}
|
||||
|
||||
if (!atObject || m_AbilityButtonShown) return;
|
||||
|
||||
m_Ability = ability;
|
||||
SetStatus(status | PET_STATE_USING_ABILITY | PET_STATE_AT_ABILITY_OBJECT);
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
|
||||
auto* const owner = GetOwner();
|
||||
if (!owner) return;
|
||||
|
||||
GameMessages::ShowPetActionButton msg;
|
||||
msg.target = m_Owner;
|
||||
msg.ButtonLabel = ability;
|
||||
msg.bShow = true;
|
||||
msg.Send(owner->GetSystemAddress());
|
||||
m_AbilityButtonShown = true;
|
||||
}
|
||||
|
||||
void PetComponent::UseAbility() {
|
||||
if (!m_AbilityButtonShown || m_Despawning) return;
|
||||
|
||||
auto* const object = Game::entityManager->GetEntity(m_AbilityObject);
|
||||
auto* const owner = GetOwner();
|
||||
if (!object || !owner) return;
|
||||
|
||||
auto* const destroyableComponent = owner->GetComponent<DestroyableComponent>();
|
||||
if (!destroyableComponent) return;
|
||||
|
||||
// Using the ability costs imagination (PetAbilities: 2 to jump on a pet switch, 1 to dig)
|
||||
const auto cost = GetAbilityImaginationCost(m_Ability);
|
||||
if (destroyableComponent->GetImagination() < cost) return;
|
||||
destroyableComponent->Imagine(-cost);
|
||||
|
||||
const auto ability = m_Ability;
|
||||
HideAbilityButton();
|
||||
|
||||
if (ability == ePetAbilityType::DigAtPosition) {
|
||||
SendHelp(eHelpType::PR_DIG_TUTORIAL_03);
|
||||
|
||||
// Live dropped the ability states as soon as the owner had the pet dig
|
||||
m_Ability = ePetAbilityType::Invalid;
|
||||
SetStatus(m_Status & ~(PET_STATE_AT_ABILITY_OBJECT | PET_STATE_USING_ABILITY | PET_STATE_GOING_TO_ABILITY_OBJECT));
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
|
||||
Command(NiPoint3Constant::ZERO, LWOOBJID_EMPTY, 1, 202, true);
|
||||
|
||||
m_TreasureTime = 2;
|
||||
}
|
||||
}
|
||||
|
||||
void PetComponent::HideAbilityButton() {
|
||||
if (!m_AbilityButtonShown) return;
|
||||
m_AbilityButtonShown = false;
|
||||
|
||||
auto* const owner = GetOwner();
|
||||
if (!owner) return;
|
||||
|
||||
GameMessages::ShowPetActionButton msg;
|
||||
msg.target = m_Owner;
|
||||
msg.ButtonLabel = m_Ability;
|
||||
msg.bShow = false;
|
||||
msg.Send(owner->GetSystemAddress());
|
||||
}
|
||||
|
||||
void PetComponent::SendHelp(const eHelpType helpId) const {
|
||||
// Sent every time, as live did; the client shows each tooltip only once
|
||||
auto* const owner = GetOwner();
|
||||
if (!owner) return;
|
||||
|
||||
GameMessages::Help msg;
|
||||
msg.target = m_Owner;
|
||||
msg.helpId = helpId;
|
||||
msg.Send(owner->GetSystemAddress());
|
||||
}
|
||||
|
||||
int32_t PetComponent::GetAbilityImaginationCost(const ePetAbilityType ability) {
|
||||
auto query = CDClientDatabase::CreatePreppedStmt("SELECT ImaginationCost FROM PetAbilities WHERE id = ?;");
|
||||
query.bind(1, static_cast<int32_t>(ability));
|
||||
|
||||
auto result = query.execQuery();
|
||||
if (result.eof() || result.fieldIsNull("ImaginationCost")) return 0;
|
||||
|
||||
return result.getIntField("ImaginationCost");
|
||||
}
|
||||
|
||||
LWOOBJID PetComponent::GetOwnerId() const {
|
||||
return m_Owner;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "ePetAbilityType.h"
|
||||
#include "eReplicaComponentType.h"
|
||||
#include "CDPetComponentTable.h"
|
||||
#include "eHelpType.h"
|
||||
|
||||
/**
|
||||
* Represents an entity that is a pet. This pet can be tamed and consequently follows the tamer around, allowing it
|
||||
@@ -25,6 +26,16 @@ public:
|
||||
// constructed summoned pets with 0x84 and cleared this bit once the spawn animation was over.
|
||||
static constexpr uint32_t PET_STATE_SPAWNING = 1 << 7;
|
||||
|
||||
// State 6: the pet is at the object of its ability (a pet switch or a dig) and waits for its owner to use it.
|
||||
static constexpr uint32_t PET_STATE_AT_ABILITY_OBJECT = 1 << 5;
|
||||
|
||||
// State 9: the pet is on its way to, or waiting at, the object of its ability (the client's IsPetUsingAbility
|
||||
// checks it).
|
||||
static constexpr uint32_t PET_STATE_USING_ABILITY = 1 << 8;
|
||||
|
||||
// State 11: the pet is on its way to the object of its ability.
|
||||
static constexpr uint32_t PET_STATE_GOING_TO_ABILITY_OBJECT = 1 << 10;
|
||||
|
||||
explicit PetComponent(Entity* parentEntity, const int32_t componentID);
|
||||
~PetComponent() override;
|
||||
|
||||
@@ -220,6 +231,36 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* Sets the object the pet goes to use its ability on (a dig), or none. The pet first goes to it; once it is there
|
||||
* its owner gets the pet's action button, which uses the ability.
|
||||
* @param object the object, nullptr for none
|
||||
* @param ability the ability the object is for
|
||||
* @param atObject whether the pet has reached the object
|
||||
*/
|
||||
void SetAbilityObject(Entity* object, ePetAbilityType ability, bool atObject);
|
||||
|
||||
/**
|
||||
* Uses the ability the owner's action button shows (the owner used the pet: SHIFT or the pet menu)
|
||||
*/
|
||||
void UseAbility();
|
||||
|
||||
/**
|
||||
* Takes the action button away from the owner, if they have it
|
||||
*/
|
||||
void HideAbilityButton();
|
||||
|
||||
/**
|
||||
* Shows the owner a one-time tutorial tooltip
|
||||
* @param helpId the tooltip
|
||||
*/
|
||||
void SendHelp(eHelpType helpId) const;
|
||||
|
||||
/**
|
||||
* Returns the imagination using an ability costs (PetAbilities.ImaginationCost)
|
||||
*/
|
||||
static int32_t GetAbilityImaginationCost(ePetAbilityType ability);
|
||||
|
||||
/**
|
||||
* Information for the minigame to be completed
|
||||
*/
|
||||
@@ -356,6 +397,16 @@ private:
|
||||
* Set once the pet has been sent back to the backpack; it is removed after its despawn effect has played
|
||||
*/
|
||||
bool m_Despawning{ false };
|
||||
|
||||
/**
|
||||
* The object the pet goes to use its ability on (a dig), if any. Live did not serialize it as the interaction.
|
||||
*/
|
||||
LWOOBJID m_AbilityObject{ LWOOBJID_EMPTY };
|
||||
|
||||
/**
|
||||
* Whether the owner has the pet's action button
|
||||
*/
|
||||
bool m_AbilityButtonShown{ false };
|
||||
};
|
||||
|
||||
#endif // !PETCOMPONENT_H
|
||||
|
||||
Reference in New Issue
Block a user