Core/Creatures: converted CMSG_PET_ACTION to packet class and moved most parts of it to the SummonInfo API

This commit is contained in:
Ovahlord committed 2025-08-24 18:17:38 +02:00
1 parent cf8bbd68b9
commit 857f79b22a
10 files changed
+221 -172

No files matched your search

@@ -33,7 +33,7 @@ SummonInfo::SummonInfo(Creature* summonedCreature, SummonInfoArgs const& args) :
_summonedCreature(ASSERT_NOTNULL(summonedCreature)), _summonerGUID(args.Summoner ? args.Summoner->GetGUID() : ObjectGuid::Empty),
_remainingDuration(args.Duration), _maxHealth(args.MaxHealth), _creatureLevel(args.CreatureLevel),
_flags(SummonPropertiesFlags::None), _control(SummonPropertiesControl::None), _summonSlot(SummonPropertiesSlot::None),
_hasBeenSummonedByCreature(args.Summoner ? args.Summoner->IsCreature() : false)
_hasBeenSummonedByPlayer(args.Summoner ? args.Summoner->IsPlayer() : false)
{
if (args.SummonPropertiesId.has_value())
InitializeSummonProperties(*args.SummonPropertiesId, Object::ToUnit(args.Summoner));
@@ -247,7 +247,7 @@ bool SummonInfo::DespawnsOnSummonerLogout() const
if (IsControlledBySummoner())
{
// Controlled creatures summoned by a creature will only despawn when not engaged
if (_hasBeenSummonedByCreature)
if (!_hasBeenSummonedByPlayer)
return !_summonedCreature->IsEngaged();
// Player controlled summons, however, always despawn
@@ -273,7 +273,7 @@ bool SummonInfo::DespawnsOnSummonerDeath() const
if (IsControlledBySummoner())
{
// Controlled creatures summoned by a creature will only despawn when not engaged (they will despawn when reaching home)
if (_hasBeenSummonedByCreature)
if (!_hasBeenSummonedByPlayer)
return !_summonedCreature->IsEngaged();
// Player controlled summons, however, always despawn
@@ -332,6 +332,11 @@ SummonPropertiesControl SummonInfo::GetControl() const
return _control;
}
bool SummonInfo::IsClassPet() const
{
return _isClassPet;
}
void SummonInfo::castPassiveSpells()
{
CreatureTemplate const* creatureInfo = _summonedCreature->GetCreatureTemplate();
@@ -371,7 +376,7 @@ void SummonInfo::initializeReactState()
break;
case SummonPropertiesControl::Pet:
// Player pets default to assist while creature summons retain their original state
if (!_hasBeenSummonedByCreature)
if (_hasBeenSummonedByPlayer)
_summonedCreature->SetReactState(REACT_ASSIST);
else if (CharmInfo* charmInfo = _summonedCreature->GetCharmInfo())
charmInfo->RestoreState();
@@ -100,6 +100,8 @@ public:
SummonPropertiesSlot GetSummonSlot() const;
// Returns the summon control type which determines how the summon can be controlled by the summoner
SummonPropertiesControl GetControl() const;
// Returns true when the summon is a class pet (Hunter Pet, Warlock Minion or Death Knight Ghoul)
bool IsClassPet() const;
private:
// Looks up and casts all passive spells of the creature summon. Often used for scaling auras.
@@ -116,7 +118,8 @@ private:
EnumFlag<SummonPropertiesFlags> _flags;
SummonPropertiesControl _control;
SummonPropertiesSlot _summonSlot;
bool _hasBeenSummonedByCreature;
bool _hasBeenSummonedByPlayer;
bool _isClassPet; // Placeholder for class pet interface
};
#endif // _SummonInfo_h__
+45 -20
View File
@@ -8843,10 +8843,10 @@ void Unit::RegisterSummon(SummonInfo* summon)
// causes them to interact with the summoner in some way (e.g. despawning when the summoner died)
if (slot == SummonPropertiesSlot::None)
{
if (advstd::ranges::contains(_wildSummons, summon))
if (advstd::ranges::contains(_unslottedSummons, summon))
return;
_wildSummons.push_back(summon);
_unslottedSummons.push_back(summon);
return;
}
@@ -8867,7 +8867,7 @@ void Unit::UnregisterSummon(SummonInfo* summon)
SummonPropertiesSlot slot = summon->GetSummonSlot();
if (slot == SummonPropertiesSlot::None)
{
std::erase(_wildSummons, summon);
std::erase(_unslottedSummons, summon);
return;
}
@@ -8877,30 +8877,38 @@ void Unit::UnregisterSummon(SummonInfo* summon)
void Unit::DespawnSummonsOnSummonerLogout()
{
// Summons which are stored in a summon slot will always despawn
std::vector<SummonInfo*> slottedSummons = _slottedSummons;
for (SummonInfo* summon : slottedSummons)
if (summon)
std::vector<SummonInfo*> unslottedSummons = _unslottedSummons;
for (SummonInfo* summon : unslottedSummons)
if (summon->DespawnsOnSummonerLogout())
summon->GetSummonedCreature()->DespawnOrUnsummon();
// Wild summons on the other hand only despawn when the according flag is set
std::vector<SummonInfo*> wildSummons = _wildSummons;
for (SummonInfo* summon : wildSummons)
if (summon->DespawnsOnSummonerLogout())
std::vector<SummonInfo*> slottedSummons = _slottedSummons;
for (SummonInfo* summon : slottedSummons)
if (summon && summon->DespawnsOnSummonerLogout())
summon->GetSummonedCreature()->DespawnOrUnsummon();
}
void Unit::DespawnSummonsOnSummonerDeath()
{
std::vector<SummonInfo*> unslottedSummons = _unslottedSummons;
for (SummonInfo* summon : unslottedSummons)
if (summon->DespawnsOnSummonerDeath())
summon->GetSummonedCreature()->DespawnOrUnsummon();
std::vector<SummonInfo*> slottedSummons = _slottedSummons;
for (SummonInfo* summon : slottedSummons)
if (summon && summon->DespawnsOnSummonerDeath())
summon->GetSummonedCreature()->DespawnOrUnsummon();
std::vector<SummonInfo*> wildSummons = _wildSummons;
for (SummonInfo* summon : wildSummons)
if (summon->DespawnsOnSummonerDeath())
summon->GetSummonedCreature()->DespawnOrUnsummon();
}
void Unit::DismissPet()
{
if (!GetMinionGUID())
return;
for (SummonInfo* summonInfo : GetSummonsByControlType(SummonPropertiesControl::Pet))
summonInfo->GetSummonedCreature()->DespawnOrUnsummon();
}
SummonInfo* Unit::GetSummonInSlot(SummonPropertiesSlot slot) const
@@ -8921,14 +8929,14 @@ std::vector<SummonInfo*> Unit::GetSummonsByCreatureId(uint32 creatureId)
std::vector<SummonInfo*> summons;
std::ranges::copy_if(_wildSummons, std::back_inserter(summons), [creatureId](SummonInfo const* summon)
std::ranges::copy_if(_unslottedSummons, std::back_inserter(summons), [creatureId](SummonInfo const* summon)
{
return summon->GetSummonedCreature()->GetEntry() == creatureId;
});
std::ranges::copy_if(_slottedSummons, std::back_inserter(summons), [creatureId](SummonInfo const* summon)
{
return summon->GetSummonedCreature()->GetEntry() == creatureId;
return summon && summon->GetSummonedCreature()->GetEntry() == creatureId;
});
return summons;
@@ -8941,14 +8949,31 @@ std::vector<SummonInfo*> Unit::GetSummonsBySpellId(uint32 spellId)
std::vector<SummonInfo*> summons;
std::ranges::copy_if(_wildSummons, std::back_inserter(summons), [spellId](SummonInfo const* summon)
std::ranges::copy_if(_unslottedSummons, std::back_inserter(summons), [spellId](SummonInfo const* summon)
{
return summon->GetSummonedCreature()->GetUInt32Value(UNIT_CREATED_BY_SPELL) == spellId;
});
std::ranges::copy_if(_slottedSummons, std::back_inserter(summons), [spellId](SummonInfo const* summon)
{
return summon->GetSummonedCreature()->GetUInt32Value(UNIT_CREATED_BY_SPELL) == spellId;
return summon && summon->GetSummonedCreature()->GetUInt32Value(UNIT_CREATED_BY_SPELL) == spellId;
});
return summons;
}
std::vector<SummonInfo*> Unit::GetSummonsByControlType(SummonPropertiesControl control)
{
std::vector<SummonInfo*> summons;
std::ranges::copy_if(_unslottedSummons, std::back_inserter(summons), [control](SummonInfo const* summon)
{
return summon->GetControl() == control;
});
std::ranges::copy_if(_slottedSummons, std::back_inserter(summons), [control](SummonInfo const* summon)
{
return summon && summon->GetControl() == control;
});
return summons;
@@ -9744,7 +9769,7 @@ void Unit::RemoveFromWorld()
}
// Clear all active summon references. If we are about to switch map instances, we want to make sure that we leave all summons behind so we won't do threadunsafe operations
_wildSummons.clear();
_unslottedSummons.clear();
_slottedSummons.clear();
WorldObject::RemoveFromWorld();
+6 -1
View File
@@ -103,6 +103,7 @@ enum ProcFlagsSpellType : uint32;
enum ZLiquidStatus : uint32;
enum CharmType : uint8;
enum class SummonPropertiesControl : uint8;
enum class SummonPropertiesSlot: int8;
namespace Movement
@@ -1341,6 +1342,8 @@ class TC_GAME_API Unit : public WorldObject
void DespawnSummonsOnSummonerLogout();
// Despawns all summons that should despawn when the summoner dies
void DespawnSummonsOnSummonerDeath();
// Dismisses the currently active pet summon(s) (class pets and summons which are considered pets)
void DismissPet();
// Returns the currently active summon that is the summoner's specified summon slot
SummonInfo* GetSummonInSlot(SummonPropertiesSlot slot) const;
@@ -1348,6 +1351,8 @@ class TC_GAME_API Unit : public WorldObject
std::vector<SummonInfo*> GetSummonsByCreatureId(uint32 creatureId);
// Returns a vector with all currently active summons which have been created by the specified spell Id
std::vector<SummonInfo*> GetSummonsBySpellId(uint32 spellId);
// Returns a vector with all currently active summons which are being controlled in a specific way
std::vector<SummonInfo*> GetSummonsByControlType(SummonPropertiesControl control);
ShapeshiftForm GetShapeshiftForm() const { return ShapeshiftForm(GetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_SHAPESHIFT_FORM)); }
void SetShapeshiftForm(ShapeshiftForm form);
@@ -1847,7 +1852,7 @@ class TC_GAME_API Unit : public WorldObject
/* Player Movement fields END*/
// SummonInfo slot handling
std::vector<SummonInfo*> _wildSummons;
std::vector<SummonInfo*> _unslottedSummons;
std::vector<SummonInfo*> _slottedSummons;
};
+130 -139
View File
@@ -47,108 +47,16 @@ void WorldSession::HandleDismissCritter(WorldPackets::Pet::DismissCritter& dismi
summonInfo->GetSummonedCreature()->DespawnOrUnsummon();
}
void WorldSession::HandlePetAction(WorldPacket& recvData)
{
ObjectGuid guid1;
uint32 data;
ObjectGuid guid2;
float x, y, z;
recvData >> guid1; //pet guid
recvData >> data;
recvData >> guid2; //tag guid
// Position
recvData >> x;
recvData >> y;
recvData >> z;
uint32 spellid = UNIT_ACTION_BUTTON_ACTION(data);
uint8 flag = UNIT_ACTION_BUTTON_TYPE(data); //delete = 0x07 CastSpell = C1
// used also for charmed creature
Unit* pet = ObjectAccessor::GetUnit(*_player, guid1);
TC_LOG_DEBUG("entities.pet", "HandlePetAction: %s - flag: %u, spellid: %u, target: %s.", guid1.ToString().c_str(), uint32(flag), spellid, guid2.ToString().c_str());
if (!pet)
{
TC_LOG_DEBUG("entities.pet", "HandlePetAction: %s doesn't exist for %s %s", guid1.ToString().c_str(), GetPlayer()->GetGUID().ToString().c_str(), GetPlayer()->GetName().c_str());
return;
}
if (pet != GetPlayer()->GetFirstControlled())
{
TC_LOG_DEBUG("entities.pet", "HandlePetAction: %s does not belong to %s %s", guid1.ToString().c_str(), GetPlayer()->GetGUID().ToString().c_str(), GetPlayer()->GetName().c_str());
return;
}
if (!pet->IsAlive())
{
SpellInfo const* spell = (flag == ACT_ENABLED || flag == ACT_PASSIVE) ? sSpellMgr->GetSpellInfo(spellid) : nullptr;
if (!spell)
return;
if (!spell->HasAttribute(SPELL_ATTR0_ALLOW_CAST_WHILE_DEAD))
return;
}
/// @todo allow control charmed player?
if (pet->GetTypeId() == TYPEID_PLAYER && !(flag == ACT_COMMAND && spellid == COMMAND_ATTACK))
return;
if (GetPlayer()->m_Controlled.size() == 1)
HandlePetActionHelper(pet, guid1, spellid, flag, guid2, x, y, z);
else
{
//If a pet is dismissed, m_Controlled will change
std::vector<Unit*> controlled;
for (Unit::ControlList::iterator itr = GetPlayer()->m_Controlled.begin(); itr != GetPlayer()->m_Controlled.end(); ++itr)
if ((*itr)->GetEntry() == pet->GetEntry() && (*itr)->IsAlive())
controlled.push_back(*itr);
for (std::vector<Unit*>::iterator itr = controlled.begin(); itr != controlled.end(); ++itr)
HandlePetActionHelper(*itr, guid1, spellid, flag, guid2, x, y, z);
}
}
void WorldSession::HandlePetStopAttack(WorldPacket &recvData)
{
ObjectGuid guid;
recvData >> guid;
TC_LOG_DEBUG("network.opcode", "WORLD: Received CMSG_PET_STOP_ATTACK for %s", guid.ToString().c_str());
Unit* pet = ObjectAccessor::GetCreatureOrPetOrVehicle(*_player, guid);
if (!pet)
{
TC_LOG_ERROR("entities.pet", "HandlePetStopAttack: %s does not exist", guid.ToString().c_str());
return;
}
if (pet != GetPlayer()->GetPet() && pet != GetPlayer()->GetCharmed())
{
TC_LOG_ERROR("entities.pet", "HandlePetStopAttack: %s isn't a pet or charmed creature of player %s",
guid.ToString().c_str(), GetPlayer()->GetName().c_str());
return;
}
if (!pet->IsAlive())
return;
pet->AttackStop();
}
void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spellid, uint16 flag, ObjectGuid guid2, float x, float y, float z)
void HandlePetActionHelper(Player* player, Unit* pet, ObjectGuid petGuid, uint32 actionType, uint32 actionValue, ObjectGuid targetGuid, float x, float y, float z)
{
CharmInfo* charmInfo = pet->GetCharmInfo();
if (!charmInfo)
{
TC_LOG_DEBUG("entities.pet", "WorldSession::HandlePetAction(petGuid: %s, tagGuid: %s, spellId: %u, flag: %u): object (GUID: %u Entry: %u TypeId: %u) is considered pet-like but doesn't have a charminfo!",
guid1.ToString().c_str(), guid2.ToString().c_str(), spellid, flag, pet->GetGUID().GetCounter(), pet->GetEntry(), pet->GetTypeId());
return;
}
switch (flag)
switch (actionType)
{
case ACT_COMMAND: //0x07
switch (spellid)
switch (actionValue)
{
case COMMAND_STAY: //flat=1792 //STAY
if (pet->GetMotionMaster()->GetCurrentSlot() != MOTION_SLOT_CONTROLLED)
@@ -169,7 +77,7 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe
case COMMAND_FOLLOW: //spellid=1792 //FOLLOW
pet->AttackStop();
pet->InterruptNonMeleeSpells(false);
pet->FollowTarget(_player);
pet->FollowTarget(player);
charmInfo->SetCommandState(COMMAND_FOLLOW);
charmInfo->SetIsCommandAttack(false);
@@ -181,7 +89,7 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe
case COMMAND_ATTACK: //spellid=1792 //ATTACK
{
// Can't attack if owner is pacified
if (_player->HasAuraType(SPELL_AURA_MOD_PACIFY))
if (player->HasAuraType(SPELL_AURA_MOD_PACIFY))
{
//pet->SendPetCastFail(spellid, SPELL_FAILED_PACIFIED);
/// @todo Send proper error message to client
@@ -189,7 +97,7 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe
}
// only place where pet can be player
Unit* TargetUnit = ObjectAccessor::GetUnit(*_player, guid2);
Unit* TargetUnit = ObjectAccessor::GetUnit(*player, targetGuid);
if (!TargetUnit)
return;
@@ -224,7 +132,7 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe
else
{
// 90% chance for pet and 100% chance for charmed creature
pet->SendPetAIReaction(guid1);
pet->SendPetAIReaction(petGuid);
}
}
else // charmed player
@@ -236,28 +144,40 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe
charmInfo->SetIsReturning(false);
pet->Attack(TargetUnit, true);
pet->SendPetAIReaction(guid1);
pet->SendPetAIReaction(petGuid);
}
}
break;
}
case COMMAND_ABANDON: // abandon (hunter pet) or dismiss (summoned pet)
if (pet->GetCharmerGUID() == GetPlayer()->GetGUID())
_player->StopCastingCharm();
else if (pet->GetOwnerOrCreatorGUID() == GetPlayer()->GetGUID())
{
// Releasing a currently charmed unit
if (pet->GetCharmerGUID() == player->GetGUID())
{
ASSERT(pet->GetTypeId() == TYPEID_UNIT);
if (pet->IsPet())
{
if (((Pet*)pet)->IsHunterPet())
GetPlayer()->RemovePet((Pet*)pet, PET_SAVE_AS_DELETED);
else
GetPlayer()->RemovePet((Pet*)pet, PET_SAVE_DISMISS);
}
else if (pet->HasUnitTypeMask(UNIT_MASK_MINION))
((Minion*)pet)->UnSummon();
player->StopCastingCharm();
return;
}
if (player->GetMinionGUID().IsEmpty() || !pet->IsCreature())
return;
// Abandoning a hunter pet
Creature* creaturePet = pet->ToCreature();
SummonInfo* summonInfo = creaturePet->GetSummonInfo();
// this should never be possible since we got here from a SummonInfo vector, but for good manners...
if (!summonInfo)
return;
// Hunter pets use this command to abandon their class pets instead of dismissing them.
if (player->getClass() == CLASS_HUNTER && summonInfo->IsClassPet())
{
// @todo: abandoning pets
}
else // otherwise this is just a dismiss command
creaturePet->DespawnOrUnsummon();
break;
}
case COMMAND_MOVE_TO:
pet->GetMotionMaster()->Clear(MOTION_SLOT_IDLE);
pet->GetMotionMaster()->MoveIdle();
@@ -271,11 +191,11 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe
charmInfo->SaveStayPosition();
break;
default:
TC_LOG_ERROR("entities.pet", "WORLD: unknown PET flag Action %i and spellid %i.", uint32(flag), spellid);
break;
}
break;
case ACT_REACTION: // 0x6
switch (spellid)
switch (actionValue)
{
case REACT_PASSIVE: //passive
pet->AttackStop();
@@ -284,7 +204,7 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe
case REACT_AGGRESSIVE: //activete
case REACT_ASSIST:
if (pet->GetTypeId() == TYPEID_UNIT)
pet->ToCreature()->SetReactState(ReactStates(spellid));
pet->ToCreature()->SetReactState(static_cast<ReactStates>(actionValue));
break;
}
break;
@@ -292,18 +212,15 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe
case ACT_PASSIVE: // 0x01
case ACT_ENABLED: // 0xC1 spell
{
Unit* unit_target = nullptr;
Unit* unitTarget = nullptr;
if (guid2)
unit_target = ObjectAccessor::GetUnit(*_player, guid2);
if (!targetGuid.IsEmpty())
unitTarget = ObjectAccessor::GetUnit(*player, targetGuid);
// do not cast unknown spells
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellid);
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(actionValue);
if (!spellInfo)
{
TC_LOG_ERROR("spells.pet", "WORLD: unknown PET spell id %i", spellid);
return;
}
for (uint32 i = 0; i < MAX_SPELL_EFFECTS; ++i)
{
@@ -312,7 +229,7 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe
}
// do not cast not learned spells
if (!pet->HasSpell(spellid) || spellInfo->IsPassive())
if (!pet->HasSpell(actionValue) || spellInfo->IsPassive())
return;
// Clear the flags as if owner clicked 'attack'. AI will reset them
@@ -327,15 +244,15 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe
Spell* spell = new Spell(pet, spellInfo, TRIGGERED_NONE);
SpellCastResult result = spell->CheckPetCast(unit_target);
SpellCastResult result = spell->CheckPetCast(unitTarget);
//auto turn to target unless possessed
if (result == SPELL_FAILED_UNIT_NOT_INFRONT && !pet->isPossessed() && !pet->IsVehicle())
{
if (unit_target)
if (unitTarget)
{
pet->SetOrientationTowards(unit_target);
if (Player* player = unit_target->ToPlayer())
pet->SetOrientationTowards(unitTarget);
if (Player* player = unitTarget->ToPlayer())
pet->SendUpdateToPlayer(player);
}
else if (Unit* unit_target2 = spell->m_targets.GetUnitTarget())
@@ -354,29 +271,29 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe
if (result == SPELL_CAST_OK)
{
unit_target = spell->m_targets.GetUnitTarget();
unitTarget = spell->m_targets.GetUnitTarget();
//10% chance to play special pet attack talk, else growl
//actually this only seems to happen on special spells, fire shield for imp, torment for voidwalker, but it's stupid to check every spell
if (pet->IsPet() && (((Pet*)pet)->getPetType() == SUMMON_PET) && (pet != unit_target) && (urand(0, 100) < 10))
if (pet->IsPet() && (((Pet*)pet)->getPetType() == SUMMON_PET) && (pet != unitTarget) && (urand(0, 100) < 10))
pet->SendPetActionSound(PET_TALK_SPECIAL_SPELL);
else
{
pet->SendPetAIReaction(guid1);
pet->SendPetAIReaction(petGuid);
}
if (unit_target && !GetPlayer()->IsFriendlyTo(unit_target) && !pet->isPossessed() && !pet->IsVehicle())
if (unitTarget && !player->IsFriendlyTo(unitTarget) && !pet->isPossessed() && !pet->IsVehicle())
{
// This is true if pet has no target or has target but targets differs.
if (pet->GetVictim() != unit_target)
if (pet->GetVictim() != unitTarget)
{
pet->GetMotionMaster()->Clear();
if (CreatureAI* AI = pet->ToCreature()->AI())
{
if (PetAI* petAI = dynamic_cast<PetAI*>(AI))
petAI->_AttackStart(unit_target); // force victim switch
petAI->_AttackStart(unitTarget); // force victim switch
else
AI->AttackStart(unit_target);
AI->AttackStart(unitTarget);
}
}
}
@@ -386,12 +303,12 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe
else
{
if (pet->isPossessed() || pet->IsVehicle()) /// @todo: confirm this check
Spell::SendCastResult(GetPlayer(), spellInfo, 0, result);
Spell::SendCastResult(player, spellInfo, 0, result);
else
spell->SendPetCastResult(result);
if (!pet->GetSpellHistory()->HasCooldown(spellid))
pet->GetSpellHistory()->ResetCooldown(spellid, true);
if (!pet->GetSpellHistory()->HasCooldown(actionValue))
pet->GetSpellHistory()->ResetCooldown(actionValue, true);
spell->finish(false);
delete spell;
@@ -403,10 +320,84 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe
break;
}
default:
TC_LOG_ERROR("entities.pet", "WORLD: unknown PET flag Action %i and spellid %i.", uint32(flag), spellid);
break;
}
}
void WorldSession::HandlePetAction(WorldPackets::Pet::PetAction& petAction)
{
// No pet specified, block action
if (petAction.PetGUID.IsEmpty())
return;
// Make sure that we either have a controlled pet or charmed unit
if (_player->GetMinionGUID() != petAction.PetGUID && _player->GetCharmedGUID() != petAction.PetGUID)
return;
// Extract spell Id and action type from packet
uint32 actionValue = UNIT_ACTION_BUTTON_ACTION(petAction.Action);
uint32 actionType = UNIT_ACTION_BUTTON_TYPE(petAction.Action); //delete = 0x07 CastSpell = C1
float x, y, z;
petAction.ActionPosition.Pos.GetPosition(x, y, z);
// Check if the player is currently controlling the pet
if (!_player->GetMinionGUID().IsEmpty())
{
// We might attempt to control multiple pets at once (e.g. Force of Nature treants or Feral Spirit wolves)
std::vector<SummonInfo*> petSummons = _player->GetSummonsByControlType(SummonPropertiesControl::Pet);
// One final check to make sure that we actually are controlling a pet with the provided GUID
bool hasPet = std::ranges::any_of(petSummons, [&](SummonInfo const* summonInfo)
{
return summonInfo->GetSummonedCreature()->GetGUID() == petAction.PetGUID;
});
if (hasPet)
{
for (SummonInfo* summonInfo : petSummons)
HandlePetActionHelper(_player, summonInfo->GetSummonedCreature(), petAction.PetGUID, actionType, actionValue, petAction.TargetGUID, x, y, z);
return;
}
}
// Check if the player is currently charming a unit instead
if (!_player->GetCharmedGUID().IsEmpty())
{
if (Unit* charmedUnit = ObjectAccessor::GetUnit(*_player, petAction.PetGUID))
HandlePetActionHelper(_player, charmedUnit, petAction.PetGUID, actionType, actionValue, petAction.TargetGUID, x, y, z);
}
}
void WorldSession::HandlePetStopAttack(WorldPacket &recvData)
{
ObjectGuid guid;
recvData >> guid;
TC_LOG_DEBUG("network.opcode", "WORLD: Received CMSG_PET_STOP_ATTACK for %s", guid.ToString().c_str());
Unit* pet = ObjectAccessor::GetCreatureOrPetOrVehicle(*_player, guid);
if (!pet)
{
TC_LOG_ERROR("entities.pet", "HandlePetStopAttack: %s does not exist", guid.ToString().c_str());
return;
}
if (pet != GetPlayer()->GetPet() && pet != GetPlayer()->GetCharmed())
{
TC_LOG_ERROR("entities.pet", "HandlePetStopAttack: %s isn't a pet or charmed creature of player %s",
guid.ToString().c_str(), GetPlayer()->GetName().c_str());
return;
}
if (!pet->IsAlive())
return;
pet->AttackStop();
}
void WorldSession::HandlePetNameQuery(WorldPacket& recvData)
{
TC_LOG_DEBUG("network.opcode", "WORLD: Received CMSG_PET_NAME_QUERY");
@@ -116,3 +116,11 @@ void WorldPackets::Pet::DismissCritter::Read()
{
_worldPacket >> CritterGUID;
}
void WorldPackets::Pet::PetAction::Read()
{
_worldPacket >> PetGUID;
_worldPacket >> Action;
_worldPacket >> TargetGUID;
_worldPacket >> ActionPosition;
}
@@ -132,6 +132,19 @@ namespace WorldPackets
ObjectGuid CritterGUID;
};
class PetAction final : public ClientPacket
{
public:
PetAction(WorldPacket&& packet) : ClientPacket(CMSG_PET_ACTION, std::move(packet)) { }
void Read() override;
ObjectGuid PetGUID;
ObjectGuid TargetGUID;
TaggedPosition<Position::XYZ> ActionPosition;
uint32 Action = 0;
};
}
}
+1 -1
View File
@@ -497,7 +497,7 @@ void OpcodeTable::Initialize()
DEFINE_HANDLER(CMSG_PETITION_SHOW_SIGNATURES, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePetitionShowSignOpcode );
DEFINE_HANDLER(CMSG_PETITION_SIGN, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePetitionSignOpcode );
DEFINE_HANDLER(CMSG_PET_ABANDON, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePetAbandon );
DEFINE_HANDLER(CMSG_PET_ACTION, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePetAction );
DEFINE_HANDLER(CMSG_PET_ACTION, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandlePetAction );
DEFINE_HANDLER(CMSG_PET_CANCEL_AURA, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandlePetCancelAuraOpcode );
DEFINE_HANDLER(CMSG_PET_CAST_SPELL, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandlePetCastSpellOpcode );
DEFINE_HANDLER(CMSG_PET_LEARN_TALENT, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandlePetLearnTalent );
+2 -2
View File
@@ -196,6 +196,7 @@ namespace WorldPackets
namespace Pet
{
class DismissCritter;
class PetAction;
}
namespace Party
@@ -1064,9 +1065,8 @@ class TC_GAME_API WorldSession
void HandleTutorialReset(WorldPacket& recvData);
//Pet
void HandlePetAction(WorldPacket& recvData);
void HandlePetAction(WorldPackets::Pet::PetAction& packet);
void HandlePetStopAttack(WorldPacket& recvData);
void HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spellid, uint16 flag, ObjectGuid guid2, float x, float y, float z);
void HandlePetNameQuery(WorldPacket& recvData);
void HandlePetSetAction(WorldPacket& recvData);
void HandlePetAbandon(WorldPacket& recvData);
+3 -4
View File
@@ -3710,10 +3710,9 @@ void Spell::_cast(bool skipCheck)
return;
}
if (Unit* unitCaster = m_caster->ToUnit())
if (m_spellInfo->HasAttribute(SPELL_ATTR1_DISMISS_PET_FIRST))
if (Creature* pet = ObjectAccessor::GetCreature(*m_caster, unitCaster->GetPetGUID()))
pet->DespawnOrUnsummon();
if (m_spellInfo->HasAttribute(SPELL_ATTR1_DISMISS_PET_FIRST))
if (Unit* unitCaster = m_caster->ToUnit())
unitCaster->DismissPet();
PrepareTriggersExecutedOnHit();