/* * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License as published by the * Free Software Foundation; either version 3 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "Common.h" #include "CharmInfo.h" #include "CreatureAI.h" #include "DisableMgr.h" #include "GameTime.h" #include "Group.h" #include "Log.h" #include "ObjectAccessor.h" #include "ObjectMgr.h" #include "Opcodes.h" #include "Pet.h" #include "PetPackets.h" #include "Player.h" #include "Spell.h" #include "SpellInfo.h" #include "SpellMgr.h" #include "Util.h" #include "World.h" #include "WorldPacket.h" #include "WorldSession.h" void WorldSession::HandleDismissCritter(WorldPackets::Pet::DismissCritter& packet) { Unit* pet = ObjectAccessor::GetCreatureOrPetOrVehicle(*_player, packet.CritterGUID); if (!pet) { LOG_DEBUG("network", "Vanitypet ({}) does not exist - player {} ({} / account: {}) attempted to dismiss it (possibly lagged out)", packet.CritterGUID.ToString(), GetPlayer()->GetName(), GetPlayer()->GetGUID().ToString(), GetAccountId()); return; } if (_player->GetCritterGUID() == pet->GetGUID()) { if (pet->IsCreature() && pet->ToCreature()->IsSummon()) pet->ToTempSummon()->UnSummon(); } } void WorldSession::HandlePetAction(WorldPacket& recvData) { ObjectGuid guid1; uint32 data; ObjectGuid guid2; recvData >> guid1; //pet guid recvData >> data; recvData >> guid2; //tag guid 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); LOG_DEBUG("network.opcode", "HandlePetAction: Pet {} - flag: {}, spellId: {}, target: {}.", guid1.ToString(), uint32(flag), spellId, guid2.ToString()); if (!pet) { LOG_ERROR("network.opcode", "HandlePetAction: Pet ({}) doesn't exist for player {}", guid1.ToString(), GetPlayer()->GetName()); return; } if (pet != GetPlayer()->GetFirstControlled()) { LOG_ERROR("network.opcode", "HandlePetAction: Pet ({}) does not belong to player {}", guid1.ToString(), GetPlayer()->GetName()); return; } if (!pet->IsAlive()) { // xinef: allow dissmis dead pets SpellInfo const* spell = (flag == ACT_ENABLED || flag == ACT_PASSIVE) ? sSpellMgr->GetSpellInfo(spellId) : nullptr; if ((flag != ACT_COMMAND || spellId != COMMAND_ABANDON) && (!spell || !spell->HasAttribute(SPELL_ATTR0_ALLOW_CAST_WHILE_DEAD))) return; } // Xinef: allow to controll players if (pet->IsPlayer() && flag != ACT_COMMAND && flag != ACT_REACTION) return; // Do not follow itself vehicle if (spellId == COMMAND_FOLLOW && _player->IsOnVehicle(pet)) { return; } if (GetPlayer()->m_Controlled.size() == 1) HandlePetActionHelper(pet, guid1, spellId, flag, guid2); else { //If a pet is dismissed, m_Controlled will change std::vector controlled; for (Unit::ControlSet::iterator itr = GetPlayer()->m_Controlled.begin(); itr != GetPlayer()->m_Controlled.end(); ++itr) { // xinef: allow to dissmis dead pets if ((*itr)->GetEntry() == pet->GetEntry() && ((*itr)->IsAlive() || (flag == ACT_COMMAND && spellId == COMMAND_ABANDON))) controlled.push_back(*itr); // xinef: mirror image blizzard else if ((*itr)->GetEntry() == NPC_MIRROR_IMAGE && flag == ACT_COMMAND && spellId == COMMAND_FOLLOW) { (*itr)->InterruptNonMeleeSpells(false); } } for (Unit* pet : controlled) if (pet && pet->IsInWorld() && pet->GetMap() == _player->GetMap()) HandlePetActionHelper(pet, guid1, spellId, flag, guid2); } } void WorldSession::HandlePetStopAttack(WorldPackets::Pet::PetStopAttack& packet) { Unit* pet = ObjectAccessor::GetCreatureOrPetOrVehicle(*_player, packet.PetGUID); if (!pet) { LOG_ERROR("network.opcode", "HandlePetStopAttack: Pet {} does not exist", packet.PetGUID.ToString()); return; } if (pet != GetPlayer()->GetPet() && pet != GetPlayer()->GetCharm()) { LOG_ERROR("network.opcode", "HandlePetStopAttack: Pet {} isn't a pet or charmed creature of player {}", packet.PetGUID.ToString(), GetPlayer()->GetName()); return; } if (!pet->IsAlive()) return; pet->AttackStop(); pet->ClearInPetCombat(); } void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spellId, uint16 flag, ObjectGuid guid2) { CharmInfo* charmInfo = pet->GetCharmInfo(); if (!charmInfo) { LOG_ERROR("network.opcode", "WorldSession::HandlePetAction(petGuid: {}, tagGuid: {}, spellId: {}, flag: {}): object ({}) is considered pet-like but doesn't have a charminfo!", guid1.ToString(), guid2.ToString(), spellId, flag, pet->GetGUID().ToString()); return; } switch (flag) { case ACT_COMMAND: //0x07 switch (spellId) { case COMMAND_STAY: //flat=1792 //STAY { bool controlledMotion = pet->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE; if (!controlledMotion) { pet->StopMovingOnCurrentPos(); pet->GetMotionMaster()->Clear(false); pet->GetMotionMaster()->MoveIdle(); } charmInfo->SetCommandState(COMMAND_STAY); charmInfo->SetIsCommandAttack(false); charmInfo->SetIsCommandFollow(false); charmInfo->SetIsFollowing(false); charmInfo->SetIsReturning(false); charmInfo->SetIsAtStay(!controlledMotion); charmInfo->SaveStayPosition(controlledMotion); if (pet->ToPet()) pet->ToPet()->ClearCastWhenWillAvailable(); charmInfo->SetForcedSpell(0); charmInfo->SetForcedTargetGUID(); break; } case COMMAND_FOLLOW: //spellId=1792 //FOLLOW { pet->AttackStop(); pet->InterruptNonMeleeSpells(false); pet->ClearInPetCombat(); pet->GetMotionMaster()->MoveFollow(_player, PET_FOLLOW_DIST, pet->GetFollowAngle()); if (pet->ToPet()) pet->ToPet()->ClearCastWhenWillAvailable(); charmInfo->SetCommandState(COMMAND_FOLLOW); charmInfo->SetIsCommandAttack(false); charmInfo->SetIsAtStay(false); charmInfo->SetIsReturning(true); charmInfo->SetIsCommandFollow(true); charmInfo->SetIsFollowing(false); charmInfo->RemoveStayPosition(); charmInfo->SetForcedSpell(0); charmInfo->SetForcedTargetGUID(); break; } case COMMAND_ATTACK: //spellId=1792 //ATTACK { // Can't attack if owner is pacified if (_player->HasPacifyAura()) { //pet->SendPetCastFail(spellId, SPELL_FAILED_PACIFIED); //TODO: Send proper error message to client return; } // only place where pet can be player Unit* TargetUnit = ObjectAccessor::GetUnit(*_player, guid2); if (!TargetUnit) return; if (Unit* owner = pet->GetOwner()) if (!owner->IsValidAttackTarget(TargetUnit)) return; // pussywizard (excluded charmed) if (!pet->IsCharmed()) if (Creature* creaturePet = pet->ToCreature()) if (!creaturePet->CanCreatureAttack(TargetUnit)) return; // Not let attack through obstructions bool checkLos = !sDisableMgr->IsPathfindingEnabled(pet->GetMap()) || (TargetUnit->IsCreature() && (TargetUnit->ToCreature()->isWorldBoss() || TargetUnit->ToCreature()->IsDungeonBoss())); if (checkLos && !pet->IsWithinLOSInMap(TargetUnit)) { WorldPacket data(SMSG_CAST_FAILED, 1 + 4 + 1); data << uint8(0); data << uint32(7389); data << uint8(SPELL_FAILED_LINE_OF_SIGHT); SendPacket(&data); return; } pet->ClearUnitState(UNIT_STATE_FOLLOW); // This is true if pet has no target or has target but targets differs. if (pet->GetVictim() != TargetUnit || (pet->GetVictim() == TargetUnit && !pet->GetCharmInfo()->IsCommandAttack())) { pet->AttackStop(); if (!pet->IsPlayer() && pet->ToCreature()->IsAIEnabled) { charmInfo->SetIsCommandAttack(true); charmInfo->SetIsAtStay(false); charmInfo->SetIsFollowing(false); charmInfo->SetIsCommandFollow(false); charmInfo->SetIsReturning(false); pet->ToCreature()->AI()->AttackStart(TargetUnit); //10% chance to play special pet attack talk, else growl if (pet->IsPet() && pet->ToPet()->getPetType() == SUMMON_PET && pet != TargetUnit && roll_chance_i(10)) pet->SendPetActionSound(PET_ACTION_ATTACK); else { // 90% chance for pet and 100% chance for charmed creature pet->SendPetAIReaction(guid1); } } else // charmed player { charmInfo->SetIsCommandAttack(true); charmInfo->SetIsAtStay(false); charmInfo->SetIsFollowing(false); charmInfo->SetIsCommandFollow(false); charmInfo->SetIsReturning(false); pet->Attack(TargetUnit, true); pet->SendPetAIReaction(guid1); } } break; } case COMMAND_ABANDON: // abandon (hunter pet) or dismiss (summoned pet) if (pet->GetCharmerGUID() == GetPlayer()->GetGUID()) { _player->StopCastingCharm(); } else if (pet->GetOwnerGUID() == GetPlayer()->GetGUID()) { ASSERT(pet->IsCreature()); if (pet->IsPet()) { if (pet->ToPet()->getPetType() == HUNTER_PET) GetPlayer()->RemovePet(pet->ToPet(), PET_SAVE_AS_DELETED); else { pet->SendPetDismissSound(); //dismissing a summoned pet is like killing them (this prevents returning a soulshard...) pet->setDeathState(DeathState::Corpse); } } else if (pet->HasUnitTypeMask(UNIT_MASK_MINION | UNIT_MASK_SUMMON | UNIT_MASK_GUARDIAN | UNIT_MASK_CONTROLLABLE_GUARDIAN)) { pet->ToTempSummon()->UnSummon(); } } break; default: LOG_ERROR("network.opcode", "WORLD: unknown PET flag Action {} and spellId {}.", uint32(flag), spellId); } break; case ACT_REACTION: // 0x6 switch (spellId) { case REACT_PASSIVE: //passive pet->AttackStop(); if (pet->ToPet()) pet->ToPet()->ClearCastWhenWillAvailable(); pet->ClearInPetCombat(); [[fallthrough]]; /// @todo: Not sure whether the fallthrough was a mistake (forgetting a break) or intended. This should be double-checked. case REACT_DEFENSIVE: //recovery case REACT_AGGRESSIVE: //activete if (pet->IsCreature()) pet->ToCreature()->SetReactState(ReactStates(spellId)); else charmInfo->SetPlayerReactState(ReactStates(spellId)); break; } break; case ACT_DISABLED: // 0x81 spell (disabled), ignore case ACT_PASSIVE: // 0x01 case ACT_ENABLED: // 0xC1 spell { Unit* unit_target = nullptr; // do not cast unknown spells SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) { LOG_ERROR("network.opcode", "WORLD: unknown PET spell id {}", spellId); return; } if (guid2) unit_target = ObjectAccessor::GetUnit(*_player, guid2); else if (!spellInfo->IsPositive()) return; for (uint32 i = 0; i < MAX_SPELL_EFFECTS; ++i) { if (spellInfo->Effects[i].TargetA.GetTarget() == TARGET_UNIT_SRC_AREA_ENEMY || spellInfo->Effects[i].TargetA.GetTarget() == TARGET_UNIT_DEST_AREA_ENEMY || spellInfo->Effects[i].TargetA.GetTarget() == TARGET_DEST_DYNOBJ_ENEMY) return; } TriggerCastFlags triggerCastFlags = TRIGGERED_NONE; if (spellInfo->IsPassive()) return; // cast only learned spells if (!pet->HasSpell(spellId)) { bool allow = false; // allow casting of spells triggered by clientside periodic trigger auras if (pet->HasAuraTypeWithTriggerSpell(SPELL_AURA_PERIODIC_TRIGGER_SPELL_FROM_CLIENT, spellId)) { allow = true; triggerCastFlags = TRIGGERED_FULL_MASK; } if (!allow) return; } Spell* spell = new Spell(pet, spellInfo, triggerCastFlags); spell->LoadScripts(); // xinef: load for CheckPetCast SpellCastResult result = spell->CheckPetCast(unit_target); //auto turn to target unless possessed if (result == SPELL_FAILED_UNIT_NOT_INFRONT && !pet->isPossessed() && !pet->IsVehicle()) { if (unit_target) { pet->SetInFront(unit_target); if (unit_target->IsPlayer()) pet->SendUpdateToPlayer(unit_target->ToPlayer()); } else if (Unit* unit_target2 = spell->m_targets.GetUnitTarget()) { pet->SetInFront(unit_target2); if (unit_target2->IsPlayer()) pet->SendUpdateToPlayer(unit_target2->ToPlayer()); } if (Unit* powner = pet->GetCharmerOrOwner()) if (powner->IsPlayer()) pet->SendUpdateToPlayer(powner->ToPlayer()); result = SPELL_CAST_OK; } if (result == SPELL_CAST_OK) { if (!spellInfo->IsCooldownStartedOnEvent()) { pet->ToCreature()->AddSpellCooldown(spellId, 0, 0); } unit_target = 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->ToPet()->getPetType() == SUMMON_PET) && (pet != unit_target) && roll_chance_i(10)) pet->SendPetActionSound(PET_ACTION_SPECIAL_SPELL); else { pet->SendPetAIReaction(guid1); } if (unit_target && !GetPlayer()->IsFriendlyTo(unit_target) && !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->ToCreature()->IsAIEnabled) pet->ToCreature()->AI()->AttackStart(unit_target); } } spell->prepare(&(spell->m_targets)); // spell->prepare() can delete charm info. // Let's refresh the pointer. charmInfo = pet->GetCharmInfo(); if (charmInfo) { charmInfo->SetForcedSpell(0); charmInfo->SetForcedTargetGUID(); } } else if (pet->ToPet() && (result == SPELL_FAILED_LINE_OF_SIGHT || result == SPELL_FAILED_OUT_OF_RANGE)) { unit_target = spell->m_targets.GetUnitTarget(); bool haspositiveeffect = false; if (!unit_target) { delete spell; return; } // search positive effects for spell for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) { if (spellInfo->_IsPositiveEffect(i, true)) { haspositiveeffect = true; break; } } if (pet->isPossessed() || pet->IsVehicle()) Spell::SendCastResult(GetPlayer(), spellInfo, 0, result); else if (GetPlayer()->IsFriendlyTo(unit_target) && !haspositiveeffect) spell->SendPetCastResult(SPELL_FAILED_TARGET_FRIENDLY); else spell->SendPetCastResult(SPELL_FAILED_DONT_REPORT); if (!pet->HasSpellCooldown(spellId)) if (pet->ToPet()) pet->ToPet()->RemoveSpellCooldown(spellId, true); spell->finish(false); delete spell; if (_player->HasPacifyAura()) return; bool tempspellIsPositive = false; if (!GetPlayer()->IsFriendlyTo(unit_target)) { // only place where pet can be player Unit* TargetUnit = ObjectAccessor::GetUnit(*_player, guid2); if (!TargetUnit) return; if (Unit* owner = pet->GetOwner()) if (!owner->IsValidAttackTarget(TargetUnit)) return; pet->ClearUnitState(UNIT_STATE_FOLLOW); // This is true if pet has no target or has target but targets differs. if (pet->GetVictim() != TargetUnit || (pet->GetVictim() == TargetUnit && !pet->GetCharmInfo()->IsCommandAttack())) { if (pet->GetVictim()) pet->AttackStop(); if (!pet->IsPlayer() && pet->ToCreature() && pet->ToCreature()->IsAIEnabled) { charmInfo->SetIsCommandAttack(true); charmInfo->SetIsAtStay(false); charmInfo->SetIsFollowing(false); charmInfo->SetIsCommandFollow(false); charmInfo->SetIsReturning(false); pet->ToCreature()->AI()->AttackStart(TargetUnit); if (pet->IsPet() && pet->ToPet()->getPetType() == SUMMON_PET && pet != TargetUnit && roll_chance_i(10)) pet->SendPetActionSound(PET_ACTION_SPECIAL_SPELL); else pet->SendPetAIReaction(guid1); } else // charmed player { if (pet->GetVictim() && pet->GetVictim() != TargetUnit) pet->AttackStop(); charmInfo->SetIsCommandAttack(true); charmInfo->SetIsAtStay(false); charmInfo->SetIsFollowing(false); charmInfo->SetIsCommandFollow(false); charmInfo->SetIsReturning(false); pet->Attack(TargetUnit, true); pet->SendPetAIReaction(guid1); } pet->ToPet()->CastWhenWillAvailable(spellId, unit_target, ObjectGuid::Empty, tempspellIsPositive); } } else if (haspositiveeffect) { bool tmpSpellIsPositive = true; pet->ClearUnitState(UNIT_STATE_FOLLOW); // This is true if pet has no target or has target but targets differs. Unit* victim = pet->GetVictim(); if (victim) { pet->AttackStop(); } else victim = nullptr; if (!pet->IsPlayer() && pet->ToCreature() && pet->ToCreature()->IsAIEnabled) { pet->StopMoving(); pet->GetMotionMaster()->Clear(); charmInfo->SetIsCommandAttack(false); charmInfo->SetIsAtStay(false); charmInfo->SetIsFollowing(false); charmInfo->SetIsCommandFollow(false); charmInfo->SetIsReturning(false); pet->GetMotionMaster()->MoveFollow(unit_target, PET_FOLLOW_DIST, rand_norm() * 2 * M_PI); if (pet->IsPet() && pet->ToPet()->getPetType() == SUMMON_PET && pet != unit_target && roll_chance_i(10)) pet->SendPetActionSound(PET_ACTION_SPECIAL_SPELL); else { pet->SendPetAIReaction(guid1); } ObjectGuid oldTarget = ObjectGuid::Empty; if (victim) oldTarget = victim->GetGUID(); pet->ToPet()->CastWhenWillAvailable(spellId, unit_target, oldTarget, tmpSpellIsPositive); } } } else { // dont spam alerts if (!charmInfo->GetForcedSpell()) { if (pet->isPossessed() || pet->IsVehicle()) Spell::SendCastResult(GetPlayer(), spellInfo, 0, result); else spell->SendPetCastResult(result); } if (!pet->ToCreature()->HasSpellCooldown(spellId)) GetPlayer()->SendClearCooldown(spellId, pet); spell->finish(false); delete spell; // reset specific flags in case of spell fail. AI will reset other flags pet->PetSpellFail(spellInfo, unit_target, result); } break; } default: LOG_ERROR("network.opcode", "WORLD: unknown PET flag Action {} and spellId {}.", uint32(flag), spellId); } } void WorldSession::HandlePetNameQuery(WorldPacket& recvData) { LOG_DEBUG("network.opcode", "HandlePetNameQuery. CMSG_PET_NAME_QUERY"); uint32 petnumber; ObjectGuid petguid; recvData >> petnumber; recvData >> petguid; SendPetNameQuery(petguid, petnumber); } void WorldSession::SendPetNameQuery(ObjectGuid petguid, uint32 petnumber) { Creature* pet = ObjectAccessor::GetCreatureOrPetOrVehicle(*_player, petguid); if (!pet) { WorldPacket data(SMSG_PET_NAME_QUERY_RESPONSE, (4 + 1 + 4 + 1)); data << uint32(petnumber); data << uint8(0); data << uint32(0); data << uint8(0); SendPacket(&data); return; } std::string name; if (pet->GetEntry() == NPC_WATER_ELEMENTAL_PERM) { // Use localized creature name for the mage pet LocaleConstant loc_idx = GetSessionDbLocaleIndex(); if (loc_idx != DEFAULT_LOCALE) name = pet->GetNameForLocaleIdx(loc_idx); else name = pet->GetCreatureTemplate()->Name; } else name = pet->GetName(); WorldPacket data(SMSG_PET_NAME_QUERY_RESPONSE, (4 + 4 + name.size() + 1)); data << uint32(petnumber); data << name.c_str(); data << uint32(pet->GetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP)); if (pet->IsPet() && ((Pet*)pet)->GetDeclinedNames()) { data << uint8(1); for (uint8 i = 0; i < MAX_DECLINED_NAME_CASES; ++i) data << ((Pet*)pet)->GetDeclinedNames()->name[i]; } else data << uint8(0); SendPacket(&data); } bool WorldSession::CheckStableMaster(ObjectGuid guid) { // spell case or GM if (guid == GetPlayer()->GetGUID()) { if (!GetPlayer()->IsGameMaster() && !GetPlayer()->HasOpenStableAura()) { LOG_DEBUG("network.opcode", "Player ({}) attempt open stable in cheating way.", guid.ToString()); return false; } } // stable master case else { if (!GetPlayer()->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_STABLEMASTER)) { LOG_DEBUG("network.opcode", "Stablemaster ({}) not found or you can't interact with him.", guid.ToString()); return false; } } return true; } void WorldSession::HandlePetSetAction(WorldPacket& recvData) { LOG_DEBUG("network.opcode", "HandlePetSetAction. CMSG_PET_SET_ACTION"); ObjectGuid petguid; uint8 count; recvData >> petguid; Unit* checkPet = ObjectAccessor::GetUnit(*_player, petguid); if (!checkPet || checkPet != _player->GetFirstControlled()) { LOG_ERROR("network.opcode", "HandlePetSetAction: Unknown pet ({}) or pet owner ({})", petguid.ToString(), _player->GetGUID().ToString()); return; } count = (recvData.size() == 24) ? 2 : 1; uint32 position[2]; uint32 data[2]; bool move_command = false; for (uint8 i = 0; i < count; ++i) { recvData >> position[i]; recvData >> data[i]; uint8 act_state = UNIT_ACTION_BUTTON_TYPE(data[i]); //ignore invalid position if (position[i] >= MAX_UNIT_ACTION_BAR_INDEX) return; // in the normal case, command and reaction buttons can only be moved, not removed // at moving count == 2, at removing count == 1 // ignore attempt to remove command|reaction buttons (not possible at normal case) if (act_state == ACT_COMMAND || act_state == ACT_REACTION) { if (count == 1) return; move_command = true; } } Unit::ControlSet petsSet; if (checkPet->GetEntry() != petguid.GetEntry()) petsSet.insert(checkPet); else petsSet = _player->m_Controlled; // Xinef: loop all pets with same entry (fixes partial state change for feral spirits) for (Unit::ControlSet::const_iterator itr = petsSet.begin(); itr != petsSet.end(); ++itr) { Unit* pet = *itr; if (checkPet->GetEntry() == petguid.GetEntry() && pet->GetEntry() != petguid.GetEntry()) continue; CharmInfo* charmInfo = pet->GetCharmInfo(); if (!charmInfo) { LOG_ERROR("network.opcode", "WorldSession::HandlePetSetAction: object ({} TypeId: {}) is considered pet-like but doesn't have a charminfo!", pet->GetGUID().ToString(), pet->GetTypeId()); continue; } // check swap (at command->spell swap client remove spell first in another packet, so check only command move correctness) if (move_command) { uint8 act_state_0 = UNIT_ACTION_BUTTON_TYPE(data[0]); if (act_state_0 == ACT_COMMAND || act_state_0 == ACT_REACTION) { uint32 spell_id_0 = UNIT_ACTION_BUTTON_ACTION(data[0]); UnitActionBarEntry const* actionEntry_1 = charmInfo->GetActionBarEntry(position[1]); if (!actionEntry_1 || spell_id_0 != actionEntry_1->GetAction() || act_state_0 != actionEntry_1->GetType()) continue; } uint8 act_state_1 = UNIT_ACTION_BUTTON_TYPE(data[1]); if (act_state_1 == ACT_COMMAND || act_state_1 == ACT_REACTION) { uint32 spell_id_1 = UNIT_ACTION_BUTTON_ACTION(data[1]); UnitActionBarEntry const* actionEntry_0 = charmInfo->GetActionBarEntry(position[0]); if (!actionEntry_0 || spell_id_1 != actionEntry_0->GetAction() || act_state_1 != actionEntry_0->GetType()) continue; } } for (uint8 i = 0; i < count; ++i) { uint32 spell_id = UNIT_ACTION_BUTTON_ACTION(data[i]); uint8 act_state = UNIT_ACTION_BUTTON_TYPE(data[i]); //if it's act for spell (en/disable/cast) and there is a spell given (0 = remove spell) which pet doesn't know, don't add if (!((act_state == ACT_ENABLED || act_state == ACT_DISABLED || act_state == ACT_PASSIVE) && spell_id && !pet->HasSpell(spell_id))) { if (SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell_id)) { //sign for autocast if (act_state == ACT_ENABLED) { if (pet->IsCreature() && pet->IsPet()) { ((Pet*)pet)->ToggleAutocast(spellInfo, true); } else { for (auto iterator = GetPlayer()->m_Controlled.begin(); iterator != GetPlayer()->m_Controlled.end(); ++iterator) { if ((*iterator)->GetEntry() == pet->GetEntry()) { (*iterator)->GetCharmInfo()->ToggleCreatureAutocast(spellInfo, true); } } } } //sign for no/turn off autocast else if (act_state == ACT_DISABLED) { if (pet->IsCreature() && pet->IsPet()) { ((Pet*)pet)->ToggleAutocast(spellInfo, false); } else { for (auto iterator = GetPlayer()->m_Controlled.begin(); iterator != GetPlayer()->m_Controlled.end(); ++iterator) { if ((*iterator)->GetEntry() == pet->GetEntry()) { (*iterator)->GetCharmInfo()->ToggleCreatureAutocast(spellInfo, false); } } } } } charmInfo->SetActionBar(position[i], spell_id, ActiveStates(act_state)); } } } } void WorldSession::HandlePetRename(WorldPacket& recvData) { LOG_DEBUG("network.opcode", "HandlePetRename. CMSG_PET_RENAME"); ObjectGuid petguid; uint8 isdeclined; std::string name; DeclinedName declinedname; recvData >> petguid; recvData >> name; recvData >> isdeclined; PetStable* petStable = _player->GetPetStable(); Pet* pet = ObjectAccessor::GetPet(*_player, petguid); // check it! if (!pet || !pet->IsPet() || ((Pet*)pet)->getPetType() != HUNTER_PET || !pet->HasByteFlag(UNIT_FIELD_BYTES_2, 2, UNIT_CAN_BE_RENAMED) || pet->GetOwnerGUID() != _player->GetGUID() || !pet->GetCharmInfo() || !petStable || !petStable->CurrentPet || petStable->CurrentPet->PetNumber != pet->GetCharmInfo()->GetPetNumber()) { return; } PetNameInvalidReason res = ObjectMgr::CheckPetName(name); if (res != PET_NAME_SUCCESS) { SendPetNameInvalid(res, name, nullptr); return; } pet->SetName(name); Unit* owner = pet->GetOwner(); if (owner && (owner->IsPlayer()) && owner->ToPlayer()->GetGroup()) owner->ToPlayer()->SetGroupUpdateFlag(GROUP_UPDATE_FLAG_PET_NAME); pet->RemoveByteFlag(UNIT_FIELD_BYTES_2, 2, UNIT_CAN_BE_RENAMED); petStable->CurrentPet->Name = name; petStable->CurrentPet->WasRenamed = true; if (isdeclined) { for (uint8 i = 0; i < MAX_DECLINED_NAME_CASES; ++i) { recvData >> declinedname.name[i]; } std::wstring wname; Utf8toWStr(name, wname); if (!ObjectMgr::CheckDeclinedNames(wname, declinedname)) { SendPetNameInvalid(PET_NAME_DECLENSION_DOESNT_MATCH_BASE_NAME, name, &declinedname); return; } } CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); if (isdeclined) { if (sWorld->getBoolConfig(CONFIG_DECLINED_NAMES_USED)) { CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_PET_DECLINEDNAME); stmt->SetData(0, pet->GetCharmInfo()->GetPetNumber()); trans->Append(stmt); stmt = CharacterDatabase.GetPreparedStatement(CHAR_ADD_CHAR_PET_DECLINEDNAME); stmt->SetData(0, _player->GetGUID().GetCounter()); for (uint8 i = 0; i < 5; i++) stmt->SetData(i + 1, declinedname.name[i]); trans->Append(stmt); } } CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_PET_NAME); stmt->SetData(0, name); stmt->SetData(1, _player->GetGUID().GetCounter()); stmt->SetData(2, pet->GetCharmInfo()->GetPetNumber()); trans->Append(stmt); CharacterDatabase.CommitTransaction(trans); pet->SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, uint32(GameTime::GetGameTime().count())); // cast can't be helped } void WorldSession::HandlePetAbandon(WorldPackets::Pet::PetAbandon& packet) { if (!_player->IsInWorld()) return; // pet/charmed Creature* pet = ObjectAccessor::GetCreatureOrPetOrVehicle(*_player, packet.PetGUID); if (pet && pet->ToPet() && pet->ToPet()->getPetType() == HUNTER_PET) { if (pet->IsPet()) { if (pet->GetGUID() == _player->GetPetGUID()) { uint32 feelty = pet->GetPower(POWER_HAPPINESS); pet->SetPower(POWER_HAPPINESS, feelty > 50000 ? (feelty - 50000) : 0); } _player->RemovePet(pet->ToPet(), PET_SAVE_AS_DELETED); } else if (pet->GetGUID() == _player->GetCharmGUID()) _player->StopCastingCharm(); } } void WorldSession::HandlePetSpellAutocastOpcode(WorldPackets::Pet::PetSpellAutocast& packet) { Creature* checkPet = ObjectAccessor::GetCreatureOrPetOrVehicle(*_player, packet.PetGUID); if (!checkPet) { LOG_ERROR("entities.pet", "WorldSession::HandlePetSpellAutocastOpcode: Pet {} not found.", packet.PetGUID.ToString()); return; } SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(packet.SpellID); if (!spellInfo) { LOG_ERROR("spells.pet", "WorldSession::HandlePetSpellAutocastOpcode: Unknown spell id {} used by {}.", packet.SpellID, packet.PetGUID.ToString()); return; } if (checkPet != _player->GetGuardianPet() && checkPet != _player->GetCharm()) { LOG_ERROR("entities.pet", "WorldSession::HandlePetSpellAutocastOpcode: {} isn't pet of player {} ({}).", packet.PetGUID.ToString(), GetPlayer()->GetName(), GetPlayer()->GetGUID().ToString()); return; } Unit::ControlSet petsSet; if (checkPet->GetEntry() != packet.PetGUID.GetEntry()) petsSet.insert(checkPet); else petsSet = _player->m_Controlled; // Xinef: loop all pets with same entry (fixes partial state change for feral spirits) for (Unit* pet : petsSet) { if (checkPet->GetEntry() == packet.PetGUID.GetEntry() && pet->GetEntry() != packet.PetGUID.GetEntry()) continue; // do not add not learned spells/ passive spells if (!pet->HasSpell(packet.SpellID) || !spellInfo->IsAutocastable()) continue; CharmInfo* charmInfo = pet->GetCharmInfo(); if (!charmInfo) { LOG_ERROR("network.opcode", "WorldSession::HandlePetSpellAutocastOpcode: object ({} TypeId: {}) is considered pet-like but doesn't have a charminfo!", pet->GetGUID().ToString(), pet->GetTypeId()); continue; } if (Pet* summon = pet->ToPet()) summon->ToggleAutocast(spellInfo, packet.AutocastEnabled); else charmInfo->ToggleCreatureAutocast(spellInfo, packet.AutocastEnabled); charmInfo->SetSpellAutocast(spellInfo, packet.AutocastEnabled); } } void WorldSession::HandlePetCastSpellOpcode(WorldPacket& recvPacket) { LOG_DEBUG("network", "WORLD: CMSG_PET_CAST_SPELL"); ObjectGuid guid; uint8 castCount; uint32 spellId; uint8 castFlags; recvPacket >> guid >> castCount >> spellId >> castFlags; LOG_DEBUG("network", "WORLD: CMSG_PET_CAST_SPELL, guid: {}, castCount: {}, spellId {}, castFlags {}", guid.ToString(), castCount, spellId, castFlags); // This opcode is also sent from charmed and possessed units (players and creatures) if (!_player->GetGuardianPet() && !_player->GetCharm()) return; Unit* caster = ObjectAccessor::GetUnit(*_player, guid); if (!caster || (caster != _player->GetGuardianPet() && caster != _player->GetCharm())) { LOG_ERROR("network.opcode", "HandlePetCastSpellOpcode: Pet {} isn't pet of player {} .", guid.ToString(), GetPlayer()->GetName()); return; } SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) { LOG_ERROR("network.opcode", "WORLD: unknown PET spell id {}", spellId); return; } // do not cast not learned spells if (!caster->HasSpell(spellId) || spellInfo->IsPassive()) return; SpellCastTargets targets; targets.Read(recvPacket, caster); HandleClientCastFlags(recvPacket, castFlags, targets); bool SetFollow = caster->HasUnitState(UNIT_STATE_FOLLOW); caster->ClearUnitState(UNIT_STATE_FOLLOW); Spell* spell = new Spell(caster, spellInfo, TRIGGERED_NONE); spell->m_cast_count = castCount; // probably pending spell cast spell->m_targets = targets; spell->LoadScripts(); // Xinef: Send default target, fixes return on NeedExplicitUnitTarget Unit* target = targets.GetUnitTarget(); if (!target && spell->m_spellInfo->NeedsExplicitUnitTarget()) target = _player->GetSelectedUnit(); SpellCastResult result = spell->CheckPetCast(target); if (result == SPELL_CAST_OK) { if (Creature* creature = caster->ToCreature()) { creature->AddSpellCooldown(spellId, 0, 0); if (Pet* pet = creature->ToPet()) { // 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->getPetType() == SUMMON_PET && roll_chance_i(10)) pet->SendPetActionSound(PET_ACTION_SPECIAL_SPELL); else pet->SendPetAIReaction(guid); } } spell->prepare(&(spell->m_targets)); } else { if (!caster->GetCharmInfo() || !caster->GetCharmInfo()->GetForcedSpell()) spell->SendPetCastResult(result); if (caster->IsPlayer()) { if (!caster->ToPlayer()->HasSpellCooldown(spellId)) GetPlayer()->SendClearCooldown(spellId, caster); } else { if (!caster->ToCreature()->HasSpellCooldown(spellId)) GetPlayer()->SendClearCooldown(spellId, caster); // reset specific flags in case of spell fail. AI will reset other flags if (caster->IsPet()) caster->PetSpellFail(spellInfo, targets.GetUnitTarget(), result); } spell->finish(false); delete spell; } if (SetFollow && !caster->IsInCombat()) caster->AddUnitState(UNIT_STATE_FOLLOW); } void WorldSession::SendPetNameInvalid(uint32 error, const std::string& name, DeclinedName* declinedName) { WorldPacket data(SMSG_PET_NAME_INVALID, 4 + name.size() + 1 + 1); data << uint32(error); data << name; if (declinedName) { data << uint8(1); for (uint32 i = 0; i < MAX_DECLINED_NAME_CASES; ++i) data << declinedName->name[i]; } else data << uint8(0); SendPacket(&data); } void WorldSession::HandlePetLearnTalent(WorldPacket& recvData) { LOG_DEBUG("network", "WORLD: CMSG_PET_LEARN_TALENT"); ObjectGuid guid; uint32 talent_id, requested_rank; recvData >> guid >> talent_id >> requested_rank; _player->LearnPetTalent(guid, talent_id, requested_rank); _player->SendTalentsInfoData(true); } void WorldSession::HandleLearnPreviewTalentsPet(WorldPacket& recvData) { LOG_DEBUG("network", "CMSG_LEARN_PREVIEW_TALENTS_PET"); ObjectGuid guid; recvData >> guid; uint32 talentsCount; recvData >> talentsCount; uint32 talentId, talentRank; // Client has max 24 talents, rounded up : 30 uint32 const MaxTalentsCount = 30; for (uint32 i = 0; i < talentsCount && i < MaxTalentsCount; ++i) { recvData >> talentId >> talentRank; _player->LearnPetTalent(guid, talentId, talentRank); } _player->SendTalentsInfoData(true); recvData.rfinish(); }