diff --git a/src/server/game/Entities/Creature/SummonInfo/SummonInfo.cpp b/src/server/game/Entities/Creature/SummonInfo/SummonInfo.cpp index 077c47c5a6..3d3c1d118d 100644 --- a/src/server/game/Entities/Creature/SummonInfo/SummonInfo.cpp +++ b/src/server/game/Entities/Creature/SummonInfo/SummonInfo.cpp @@ -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(); diff --git a/src/server/game/Entities/Creature/SummonInfo/SummonInfo.h b/src/server/game/Entities/Creature/SummonInfo/SummonInfo.h index 9b7296bd9e..08a202f07d 100644 --- a/src/server/game/Entities/Creature/SummonInfo/SummonInfo.h +++ b/src/server/game/Entities/Creature/SummonInfo/SummonInfo.h @@ -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 _flags; SummonPropertiesControl _control; SummonPropertiesSlot _summonSlot; - bool _hasBeenSummonedByCreature; + bool _hasBeenSummonedByPlayer; + bool _isClassPet; // Placeholder for class pet interface }; #endif // _SummonInfo_h__ diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 6a2c6ace7d..e5483af5d6 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -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 slottedSummons = _slottedSummons; - for (SummonInfo* summon : slottedSummons) - if (summon) + std::vector 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 wildSummons = _wildSummons; - for (SummonInfo* summon : wildSummons) - if (summon->DespawnsOnSummonerLogout()) + std::vector slottedSummons = _slottedSummons; + for (SummonInfo* summon : slottedSummons) + if (summon && summon->DespawnsOnSummonerLogout()) summon->GetSummonedCreature()->DespawnOrUnsummon(); } void Unit::DespawnSummonsOnSummonerDeath() { + std::vector unslottedSummons = _unslottedSummons; + for (SummonInfo* summon : unslottedSummons) + if (summon->DespawnsOnSummonerDeath()) + summon->GetSummonedCreature()->DespawnOrUnsummon(); + std::vector slottedSummons = _slottedSummons; for (SummonInfo* summon : slottedSummons) if (summon && summon->DespawnsOnSummonerDeath()) summon->GetSummonedCreature()->DespawnOrUnsummon(); - std::vector 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 Unit::GetSummonsByCreatureId(uint32 creatureId) std::vector 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 Unit::GetSummonsBySpellId(uint32 spellId) std::vector 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 Unit::GetSummonsByControlType(SummonPropertiesControl control) +{ + std::vector 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(); diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index f8327f7b02..70cd079c7e 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -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 GetSummonsByCreatureId(uint32 creatureId); // Returns a vector with all currently active summons which have been created by the specified spell Id std::vector GetSummonsBySpellId(uint32 spellId); + // Returns a vector with all currently active summons which are being controlled in a specific way + std::vector 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 _wildSummons; + std::vector _unslottedSummons; std::vector _slottedSummons; }; diff --git a/src/server/game/Handlers/PetHandler.cpp b/src/server/game/Handlers/PetHandler.cpp index 2b475830ce..cef6336780 100644 --- a/src/server/game/Handlers/PetHandler.cpp +++ b/src/server/game/Handlers/PetHandler.cpp @@ -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 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::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(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(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 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"); diff --git a/src/server/game/Server/Packets/PetPackets.cpp b/src/server/game/Server/Packets/PetPackets.cpp index b88fe2a2d5..2ad3e03698 100644 --- a/src/server/game/Server/Packets/PetPackets.cpp +++ b/src/server/game/Server/Packets/PetPackets.cpp @@ -116,3 +116,11 @@ void WorldPackets::Pet::DismissCritter::Read() { _worldPacket >> CritterGUID; } + +void WorldPackets::Pet::PetAction::Read() +{ + _worldPacket >> PetGUID; + _worldPacket >> Action; + _worldPacket >> TargetGUID; + _worldPacket >> ActionPosition; +} diff --git a/src/server/game/Server/Packets/PetPackets.h b/src/server/game/Server/Packets/PetPackets.h index 90f78b74b9..0cb066b2e1 100644 --- a/src/server/game/Server/Packets/PetPackets.h +++ b/src/server/game/Server/Packets/PetPackets.h @@ -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 ActionPosition; + uint32 Action = 0; + }; } } diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp index 88cd2c82e6..7dd491c37d 100644 --- a/src/server/game/Server/Protocol/Opcodes.cpp +++ b/src/server/game/Server/Protocol/Opcodes.cpp @@ -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 ); diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 6bef844522..2d5150aff7 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -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); diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 5276b9b5a5..a50004bd9b 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -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();