void PetAI::AttackStart(Unit* u) { Pet* pet = (m_unit->GetTypeId() == TYPEID_UNIT && static_cast<Creature*>(m_unit)->IsPet()) ? static_cast<Pet*>(m_unit) : nullptr; if (!u || (pet && pet->GetModeFlags() & PET_MODE_DISABLE_ACTIONS)) return; if (m_unit->Attack(u, true)) { // TMGs call CreatureRelocation which via MoveInLineOfSight can call this function // thus with the following clear the original TMG gets invalidated and crash, doh // hope it doesn't start to leak memory without this :-/ // i_pet->Clear(); HandleMovementOnAttackStart(u); inCombat = true; } }
void PetAI::MoveInLineOfSight(Unit* who) { if (Unit* victim = m_unit->getVictim()) if (victim->isAlive()) return; Pet* pet = (m_unit->GetTypeId() == TYPEID_UNIT && static_cast<Creature*>(m_unit)->IsPet()) ? static_cast<Pet*>(m_unit) : nullptr; if (HasReactState(REACT_AGGRESSIVE) && !(pet && pet->GetModeFlags() & PET_MODE_DISABLE_ACTIONS) && m_creature->CanAttackOnSight(who) && who->isInAccessablePlaceFor(m_unit) && m_unit->IsWithinDistInMap(who, m_unit->GetAttackDistance(who)) && m_unit->GetDistanceZ(who) <= CREATURE_Z_ATTACK_RANGE && m_unit->IsWithinLOSInMap(who)) { AttackStart(who); if (Unit* owner = m_unit->GetOwner()) owner->SetInCombatState(true, who); } }
void PetAI::MoveInLineOfSight(Unit* u) { if (Unit* victim = m_unit->getVictim()) if (victim->isAlive()) return; Pet* pet = (m_unit->GetTypeId() == TYPEID_UNIT && static_cast<Creature*>(m_unit)->IsPet()) ? static_cast<Pet*>(m_unit) : nullptr; if (CharmInfo* charmInfo = m_unit->GetCharmInfo()) if (charmInfo->HasReactState(REACT_AGGRESSIVE) && !(pet && pet->GetModeFlags() & PET_MODE_DISABLE_ACTIONS) && u && (m_unit->IsHostileTo(u) || u->IsHostileTo(m_unit->GetMaster())) && u->isTargetableForAttack() && u->isInAccessablePlaceFor(m_unit) && m_unit->IsWithinDistInMap(u, m_unit->GetAttackDistance(u)) && m_unit->GetDistanceZ(u) <= CREATURE_Z_ATTACK_RANGE && m_unit->IsWithinLOSInMap(u)) { AttackStart(u); if (Unit* owner = m_unit->GetOwner()) owner->SetInCombatState(true, u); } }
void WorldSession::HandlePetSetAction(WorldPacket& recv_data) { DETAIL_LOG("HandlePetSetAction. CMSG_PET_SET_ACTION"); ObjectGuid petGuid; recv_data >> petGuid; Unit* petUnit = _player->GetMap()->GetUnit(petGuid); if (!petUnit || (petUnit != _player->GetPet() && petUnit != _player->GetCharm())) { sLog.outError("HandlePetSetAction: Unknown pet or pet owner."); return; } Creature* petCreature = petUnit->GetTypeId() == TYPEID_UNIT ? static_cast<Creature*>(petUnit) : nullptr; Pet* pet = (petCreature && petCreature->IsPet()) ? static_cast<Pet*>(petUnit) : nullptr; // pet can have action bar disabled if (pet && (pet->GetModeFlags() & PET_MODE_DISABLE_ACTIONS)) return; CharmInfo* charmInfo = petUnit->GetCharmInfo(); if (!charmInfo) { sLog.outError("WorldSession::HandlePetSetAction: %s is considered pet-like but doesn't have a charminfo!", petUnit->GetGuidStr().c_str()); return; } uint8 count = (recv_data.size() == 24) ? 2 : 1; uint32 position[2]; uint32 data[2]; bool move_command = false; for (uint8 i = 0; i < count; ++i) { recv_data >> position[i]; recv_data >> 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; } } // 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()) return; } 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()) return; } } 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]); DETAIL_LOG("Player %s has changed pet spell action. Position: %u, Spell: %u, State: 0x%X", _player->GetName(), position[i], spell_id, uint32(act_state)); // 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 && !petUnit->HasSpell(spell_id))) { // sign for autocast if (act_state == ACT_ENABLED && spell_id) { if (petUnit->HasCharmer()) charmInfo->ToggleCreatureAutocast(spell_id, true); else if (pet) pet->ToggleAutocast(spell_id, true); } // sign for no/turn off autocast else if (act_state == ACT_DISABLED && spell_id) { if (petUnit->HasCharmer()) charmInfo->ToggleCreatureAutocast(spell_id, false); else if (pet) pet->ToggleAutocast(spell_id, false); } charmInfo->SetActionBar(position[i], spell_id, ActiveStates(act_state)); } } }
void WorldSession::HandlePetAction(WorldPacket& recv_data) { ObjectGuid petGuid; uint32 data; ObjectGuid targetGuid; recv_data >> petGuid; recv_data >> data; recv_data >> targetGuid; uint32 spellid = UNIT_ACTION_BUTTON_ACTION(data); uint8 flag = UNIT_ACTION_BUTTON_TYPE(data); // delete = 0x07 CastSpell = C1 DETAIL_LOG("HandlePetAction: %s flag is %u, spellid is %u, target %s.", petGuid.GetString().c_str(), uint32(flag), spellid, targetGuid.GetString().c_str()); // used also for charmed creature/player Unit* petUnit = _player->GetMap()->GetUnit(petGuid); if (!petUnit) { sLog.outError("HandlePetAction: %s not exist.", petGuid.GetString().c_str()); return; } if (_player->GetObjectGuid() != petUnit->GetMasterGuid()) { sLog.outError("HandlePetAction: %s isn't controlled by %s.", petGuid.GetString().c_str(), _player->GetGuidStr().c_str()); return; } if (!petUnit->isAlive()) return; CharmInfo* charmInfo = petUnit->GetCharmInfo(); if (!charmInfo) { sLog.outError("WorldSession::HandlePetAction: object (GUID: %u TypeId: %u) is considered pet-like but doesn't have a charminfo!", petUnit->GetGUIDLow(), petUnit->GetTypeId()); return; } Pet* pet = nullptr; Creature* creature = nullptr; if (petUnit->GetTypeId() == TYPEID_UNIT) { creature = static_cast<Creature*>(petUnit); if (creature->IsPet()) { pet = static_cast<Pet*>(petUnit); if (pet->GetModeFlags() & PET_MODE_DISABLE_ACTIONS) return; } } if (!pet) { if (petUnit->hasUnitState(UNIT_STAT_POSSESSED)) { // possess case if (flag != uint8(ACT_COMMAND)) { sLog.outError("PetHAndler: unknown PET flag Action %i and spellid %i. For possessed %s", uint32(flag), spellid, petUnit->GetGuidStr().c_str()); return; } switch (spellid) { case COMMAND_STAY: case COMMAND_FOLLOW: charmInfo->SetCommandState(CommandStates(spellid)); break; case COMMAND_ATTACK: { Unit* targetUnit = targetGuid ? _player->GetMap()->GetUnit(targetGuid) : nullptr; if (targetUnit && targetUnit != petUnit && petUnit->CanAttack(targetUnit)) { // This is true if pet has no target or has target but targets differs. if (petUnit->getVictim() != targetUnit) petUnit->Attack(targetUnit, true); } break; } case COMMAND_DISMISS: _player->BreakCharmOutgoing(petUnit); break; default: sLog.outError("PetHandler: Not allowed action %i and spellid %i. Pet %s owner is %s", uint32(flag), spellid, petUnit->GetGuidStr().c_str(), _player->GetGuidStr().c_str()); break; } } if (!petUnit->HasCharmer()) return; } switch (flag) { case ACT_COMMAND: // 0x07 switch (spellid) { case COMMAND_STAY: // flat=1792 // STAY { if (!petUnit->hasUnitState(UNIT_STAT_POSSESSED)) { petUnit->StopMoving(); petUnit->GetMotionMaster()->Clear(); } petUnit->AttackStop(true, true); charmInfo->SetCommandState(COMMAND_STAY); break; } case COMMAND_FOLLOW: // spellid=1792 // FOLLOW { if (!petUnit->hasUnitState(UNIT_STAT_POSSESSED)) { petUnit->StopMoving(); petUnit->GetMotionMaster()->Clear(); charmInfo->SetIsRetreating(true); } petUnit->AttackStop(true, true); charmInfo->SetCommandState(COMMAND_FOLLOW); break; } case COMMAND_ATTACK: // spellid=1792 // ATTACK { charmInfo->SetIsRetreating(); charmInfo->SetSpellOpener(); Unit* targetUnit = targetGuid ? _player->GetMap()->GetUnit(targetGuid) : nullptr; if (targetUnit && targetUnit != petUnit && petUnit->CanAttack(targetUnit) && targetUnit->isInAccessablePlaceFor((Creature*)petUnit)) { // This is true if pet has no target or has target but targets differs. if (petUnit->getVictim() != targetUnit) { petUnit->AttackStop(); if (!petUnit->hasUnitState(UNIT_STAT_POSSESSED)) { petUnit->GetMotionMaster()->Clear(); petUnit->AI()->AttackStart(targetUnit); if (pet) { // 10% chance to play special warlock pet attack talk, else growl if (pet->getPetType() == SUMMON_PET && roll_chance_i(10)) pet->SendPetTalk((uint32)PET_TALK_ATTACK); pet->SendPetAIReaction(); } } else petUnit->Attack(targetUnit, true); } } break; } case COMMAND_DISMISS: // dismiss permanent pet, remove temporary pet, uncharm unit { if (pet) { pet->PlayDismissSound(); // No action for Hunter pets, Hunters must use their Dismiss Pet spell if (pet->getPetType() != HUNTER_PET) pet->ForcedDespawn(); } else { // dismissing a summoned pet is like killing them (this prevents returning a soulshard...) if (creature && creature->IsTemporarySummon()) creature->ForcedDespawn(); else _player->BreakCharmOutgoing(petUnit); } charmInfo->SetStayPosition(); break; } default: sLog.outError("WORLD: unknown PET flag Action %i and spellid %i.", uint32(flag), spellid); } break; case ACT_REACTION: // 0x6 switch (spellid) { case REACT_PASSIVE: // passive { petUnit->AttackStop(true, true); charmInfo->SetSpellOpener(); } case REACT_DEFENSIVE: // recovery case REACT_AGGRESSIVE: // activete { petUnit->AI()->SetReactState(ReactStates(spellid)); break; } } break; case ACT_DISABLED: // 0x81 spell (disabled), ignore case ACT_PASSIVE: // 0x01 case ACT_ENABLED: // 0xC1 spell { charmInfo->SetIsRetreating(); charmInfo->SetSpellOpener(); Unit* unit_target = targetGuid ? _player->GetMap()->GetUnit(targetGuid) : nullptr; // do not cast unknown spells SpellEntry const* spellInfo = sSpellTemplate.LookupEntry<SpellEntry>(spellid); if (!spellInfo) { sLog.outError("WORLD: unknown PET spell id %i", spellid); return; } // do not cast not learned spells if (IsPassiveSpell(spellInfo) || !petUnit->HasSpell(spellid)) return; if (!petUnit->IsSpellReady(*spellInfo)) return; for (unsigned int i : spellInfo->EffectImplicitTargetA) { if (i == TARGET_ENUM_UNITS_ENEMY_AOE_AT_SRC_LOC || i == TARGET_ENUM_UNITS_ENEMY_AOE_AT_DEST_LOC || i == TARGET_ENUM_UNITS_ENEMY_AOE_AT_DYNOBJ_LOC) return; } petUnit->clearUnitState(UNIT_STAT_MOVING); uint32 flags = TRIGGERED_NONE; if (!petUnit->hasUnitState(UNIT_STAT_POSSESSED)) flags |= TRIGGERED_PET_CAST; Spell* spell = new Spell(petUnit, spellInfo, flags); SpellCastResult result = spell->CheckPetCast(unit_target); const SpellRangeEntry* sRange = sSpellRangeStore.LookupEntry(spellInfo->rangeIndex); if (unit_target && !(petUnit->IsWithinDistInMap(unit_target, sRange->maxRange) && petUnit->IsWithinLOSInMap(unit_target)) && petUnit->CanAttackNow(unit_target)) { charmInfo->SetSpellOpener(spellid, sRange->minRange, sRange->maxRange); spell->finish(false); delete spell; petUnit->AttackStop(); if (!petUnit->hasUnitState(UNIT_STAT_POSSESSED)) { petUnit->GetMotionMaster()->Clear(); petUnit->AI()->AttackStart(unit_target); // 10% chance to play special warlock pet attack talk, else growl if (pet && pet->getPetType() == SUMMON_PET && pet != unit_target && roll_chance_i(10)) petUnit->SendPetTalk((uint32)PET_TALK_ATTACK); petUnit->SendPetAIReaction(); } else petUnit->Attack(unit_target, true); return; } // auto turn to target unless possessed if (result == SPELL_FAILED_UNIT_NOT_INFRONT && !petUnit->hasUnitState(UNIT_STAT_POSSESSED)) { if (unit_target) { petUnit->SetInFront(unit_target); if (unit_target->GetTypeId() == TYPEID_PLAYER) petUnit->SendCreateUpdateToPlayer((Player*)unit_target); } else if (Unit* unit_target2 = spell->m_targets.getUnitTarget()) { petUnit->SetInFront(unit_target2); if (unit_target2->GetTypeId() == TYPEID_PLAYER) petUnit->SendCreateUpdateToPlayer((Player*)unit_target2); } if (Unit* powner = petUnit->GetMaster()) if (powner->GetTypeId() == TYPEID_PLAYER) petUnit->SendCreateUpdateToPlayer((Player*)powner); result = SPELL_CAST_OK; } if (result == SPELL_CAST_OK) { charmInfo->SetSpellOpener(); spell->SpellStart(&(spell->m_targets)); } else { if (creature && creature->IsSpellReady(*spellInfo)) GetPlayer()->SendClearCooldown(spellid, petUnit); charmInfo->SetSpellOpener(); spell->finish(false); delete spell; } break; } default: sLog.outError("WORLD: unknown PET flag Action %i and spellid %i.", uint32(flag), spellid); } }
void PetAI::UpdateAI(const uint32 diff) { if (!m_unit->isAlive()) return; Creature* creature = (m_unit->GetTypeId() == TYPEID_UNIT) ? static_cast<Creature*>(m_unit) : nullptr; Pet* pet = (creature && creature->IsPet()) ? static_cast<Pet*>(m_unit) : nullptr; Unit* owner = m_unit->GetMaster(); if (!owner) return; Unit* victim = (pet && pet->GetModeFlags() & PET_MODE_DISABLE_ACTIONS) ? nullptr : m_unit->getVictim(); if (m_updateAlliesTimer <= diff) // UpdateAllies self set update timer UpdateAllies(); else m_updateAlliesTimer -= diff; if (inCombat && !victim) { m_unit->AttackStop(true, true); inCombat = false; } CharmInfo* charminfo = m_unit->GetCharmInfo(); MANGOS_ASSERT(charminfo); if (charminfo->GetIsRetreating()) { if (!owner->IsWithinDistInMap(m_unit, (PET_FOLLOW_DIST * 2))) { if (!m_unit->hasUnitState(UNIT_STAT_FOLLOW)) m_unit->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); return; } else charminfo->SetIsRetreating(); } else if (charminfo->GetSpellOpener() != 0) // have opener stored { uint32 minRange = charminfo->GetSpellOpenerMinRange(); if (!(victim = m_unit->getVictim()) || (minRange != 0 && m_unit->IsWithinDistInMap(victim, minRange))) charminfo->SetSpellOpener(); else if (m_unit->IsWithinDistInMap(victim, charminfo->GetSpellOpenerMaxRange()) && m_unit->IsWithinLOSInMap(victim)) { // stop moving m_unit->clearUnitState(UNIT_STAT_MOVING); // auto turn to target m_unit->SetInFront(victim); if (victim->GetTypeId() == TYPEID_PLAYER) m_unit->SendCreateUpdateToPlayer((Player*)victim); if (owner->GetTypeId() == TYPEID_PLAYER) m_unit->SendCreateUpdateToPlayer((Player*)owner); uint32 spell_id = charminfo->GetSpellOpener(); SpellEntry const* spellInfo = sSpellTemplate.LookupEntry<SpellEntry>(spell_id); Spell* spell = new Spell(m_unit, spellInfo, false); SpellCastResult result = spell->CheckPetCast(victim); if (result == SPELL_CAST_OK) spell->SpellStart(&(spell->m_targets)); else delete spell; charminfo->SetSpellOpener(); } else return; } // Auto cast (casted only in combat or persistent spells in any state) else if (!m_unit->IsNonMeleeSpellCasted(false)) { typedef std::vector<std::pair<Unit*, Spell*> > TargetSpellList; TargetSpellList targetSpellStore; if (pet) { for (uint8 i = 0; i < pet->GetPetAutoSpellSize(); ++i) { uint32 spellID = pet->GetPetAutoSpellOnPos(i); if (!spellID) continue; SpellEntry const* spellInfo = sSpellTemplate.LookupEntry<SpellEntry>(spellID); if (!spellInfo) continue; if (!m_unit->IsSpellReady(*spellInfo)) continue; // ignore some combinations of combat state and combat/non combat spells if (!inCombat) { // ignore attacking spells, and allow only self/around spells if (!IsPositiveSpell(spellInfo->Id)) continue; // non combat spells allowed // only pet spells have IsNonCombatSpell and not fit this requirements: // Consume Shadows, Lesser Invisibility, so ignore checks for its if (!IsNonCombatSpell(spellInfo)) { int32 duration = GetSpellDuration(spellInfo); int32 cooldown = GetSpellRecoveryTime(spellInfo); // allow only spell not on cooldown if (cooldown != 0 && duration < cooldown) continue; // not allow instant kill auto casts as full health cost if (IsSpellHaveEffect(spellInfo, SPELL_EFFECT_INSTAKILL)) continue; } } // just ignore non-combat spells else if (IsNonCombatSpell(spellInfo)) continue; Spell* spell = new Spell(m_unit, spellInfo, false); if (inCombat && !m_unit->hasUnitState(UNIT_STAT_FOLLOW) && spell->CanAutoCast(victim)) { targetSpellStore.push_back(TargetSpellList::value_type(victim, spell)); continue; } else { bool spellUsed = false; for (GuidSet::const_iterator tar = m_AllySet.begin(); tar != m_AllySet.end(); ++tar) { Unit* Target = m_unit->GetMap()->GetUnit(*tar); // only buff targets that are in combat, unless the spell can only be cast while out of combat if (!Target) continue; if (spell->CanAutoCast(Target)) { targetSpellStore.push_back(TargetSpellList::value_type(Target, spell)); spellUsed = true; break; } } if (!spellUsed) delete spell; } } } // found units to cast on to if (!targetSpellStore.empty()) { uint32 index = urand(0, targetSpellStore.size() - 1); Spell* spell = targetSpellStore[index].second; Unit* target = targetSpellStore[index].first; targetSpellStore.erase(targetSpellStore.begin() + index); SpellCastTargets targets; targets.setUnitTarget(target); if (!m_unit->HasInArc(M_PI_F, target)) { m_unit->SetInFront(target); if (target->GetTypeId() == TYPEID_PLAYER) m_unit->SendCreateUpdateToPlayer((Player*)target); if (owner && owner->GetTypeId() == TYPEID_PLAYER) m_unit->SendCreateUpdateToPlayer((Player*)owner); } spell->SpellStart(&targets); } // deleted cached Spell objects for (TargetSpellList::const_iterator itr = targetSpellStore.begin(); itr != targetSpellStore.end(); ++itr) delete itr->second; } // Stop here if casting spell (No melee and no movement) if (m_unit->IsNonMeleeSpellCasted(false)) return; // we may get our actions disabled during spell casting, so do entire recheck for victim victim = (pet && pet->GetModeFlags() & PET_MODE_DISABLE_ACTIONS) ? nullptr : m_unit->getVictim(); if (victim) { // i_pet.getVictim() can't be used for check in case stop fighting, i_pet.getVictim() clear at Unit death etc. // This is needed for charmed creatures, as once their target was reset other effects can trigger threat if (!victim->isTargetableForAttack()) { DEBUG_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "PetAI (guid = %u) is stopping attack.", m_unit->GetGUIDLow()); m_unit->CombatStop(); inCombat = false; return; } // if pet misses its target, it will also be the first in threat list if ((!creature || !(creature->GetCreatureInfo()->ExtraFlags & CREATURE_EXTRA_FLAG_NO_MELEE)) && m_unit->CanReachWithMeleeAttack(victim)) { if (!m_unit->HasInArc(2 * M_PI_F / 3, victim)) { m_unit->SetInFront(victim); if (victim->GetTypeId() == TYPEID_PLAYER) m_unit->SendCreateUpdateToPlayer((Player*)victim); if (owner && owner->GetTypeId() == TYPEID_PLAYER) m_unit->SendCreateUpdateToPlayer((Player*)owner); } DoMeleeAttackIfReady(); } else if (!m_unit->hasUnitState(UNIT_STAT_MOVING)) AttackStart(victim); } else if (owner) { CharmInfo* charmInfo = m_unit->GetCharmInfo(); if (owner->isInCombat() && !(charmInfo && charmInfo->HasReactState(REACT_PASSIVE))) AttackStart(owner->getAttackerForHelper()); else { if (charmInfo && charmInfo->HasCommandState(COMMAND_STAY)) { //if stay command is set but we don't have stay pos set then we need to establish current pos as stay position if (!charminfo->IsStayPosSet()) charminfo->SetStayPosition(true); float stayPosX = charminfo->GetStayPosX(); float stayPosY = charminfo->GetStayPosY(); float stayPosZ = charminfo->GetStayPosZ(); if (m_unit->GetPositionX() == stayPosX && m_unit->GetPositionY() == stayPosY && m_unit->GetPositionZ() == stayPosZ) { float StayPosO = charminfo->GetStayPosO(); if (m_unit->hasUnitState(UNIT_STAT_MOVING)) { m_unit->GetMotionMaster()->Clear(false); m_unit->GetMotionMaster()->MoveIdle(); } else if (m_unit->GetOrientation() != StayPosO) m_unit->SetOrientation(StayPosO); } else m_unit->GetMotionMaster()->MovePoint(0, stayPosX, stayPosY, stayPosZ, false); } else if (m_unit->hasUnitState(UNIT_STAT_FOLLOW)) { if (owner->IsWithinDistInMap(m_unit, PET_FOLLOW_DIST)) { m_unit->GetMotionMaster()->Clear(false); m_unit->GetMotionMaster()->MoveIdle(); } } else if (charmInfo && charmInfo->HasCommandState(COMMAND_FOLLOW) && !owner->IsWithinDistInMap(m_unit, (PET_FOLLOW_DIST * 2))) m_unit->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); } } }
void WorldSession::HandlePetAction(WorldPacket& recv_data) { ObjectGuid petGuid; uint32 data; ObjectGuid targetGuid; recv_data >> petGuid; recv_data >> data; recv_data >> targetGuid; uint32 spellid = UNIT_ACTION_BUTTON_ACTION(data); uint8 flag = UNIT_ACTION_BUTTON_TYPE(data); // delete = 0x07 CastSpell = C1 DETAIL_LOG("HandlePetAction: %s flag is %u, spellid is %u, target %s.", petGuid.GetString().c_str(), uint32(flag), spellid, targetGuid.GetString().c_str()); // used also for charmed creature/player Unit* petUnit = _player->GetMap()->GetUnit(petGuid); if (!petUnit) { sLog.outError("HandlePetAction: %s not exist.", petGuid.GetString().c_str()); return; } if (_player->GetObjectGuid() != petUnit->GetCharmerOrOwnerGuid()) { sLog.outError("HandlePetAction: %s isn't controlled by %s.", petGuid.GetString().c_str(), _player->GetGuidStr().c_str()); return; } if (!petUnit->isAlive()) return; CharmInfo* charmInfo = petUnit->GetCharmInfo(); if (!charmInfo) { sLog.outError("WorldSession::HandlePetAction: object (GUID: %u TypeId: %u) is considered pet-like but doesn't have a charminfo!", petUnit->GetGUIDLow(), petUnit->GetTypeId()); return; } Pet* pet = nullptr; Creature* creature = nullptr; if (petUnit->GetTypeId() == TYPEID_UNIT) { creature = static_cast<Creature*>(petUnit); if (creature->IsPet()) { pet = static_cast<Pet*>(petUnit); if (pet->GetModeFlags() & PET_MODE_DISABLE_ACTIONS) return; } } if (!pet) { if (petUnit->hasUnitState(UNIT_STAT_CONTROLLED)) { // possess case if (flag != uint8(ACT_COMMAND)) { sLog.outError("PetHAndler: unknown PET flag Action %i and spellid %i. For possessed %s", uint32(flag), spellid, petUnit->GetGuidStr().c_str()); return; } switch (spellid) { case COMMAND_STAY: case COMMAND_FOLLOW: charmInfo->SetCommandState(CommandStates(spellid)); break; case COMMAND_ATTACK: { Unit* targetUnit = targetGuid ? _player->GetMap()->GetUnit(targetGuid) : nullptr; if (targetUnit && targetUnit != petUnit && targetUnit->isTargetableForAttack()) { // This is true if pet has no target or has target but targets differs. if (petUnit->getVictim() != targetUnit) petUnit->Attack(targetUnit, true); } break; } case COMMAND_ABANDON: _player->Uncharm(); break; default: sLog.outError("PetHandler: Not allowed action %i and spellid %i. Pet %s owner is %s", uint32(flag), spellid, petUnit->GetGuidStr().c_str(), _player->GetGuidStr().c_str()); break; } } if (!petUnit->GetCharmerGuid()) return; } switch (flag) { case ACT_COMMAND: // 0x07 switch (spellid) { case COMMAND_STAY: // flat=1792 // STAY { if (!petUnit->hasUnitState(UNIT_STAT_CONTROLLED)) { petUnit->StopMoving(); petUnit->GetMotionMaster()->Clear(); } petUnit->AttackStop(true, true); charmInfo->SetCommandState(COMMAND_STAY); break; } case COMMAND_FOLLOW: // spellid=1792 // FOLLOW { if (!petUnit->hasUnitState(UNIT_STAT_CONTROLLED)) { petUnit->StopMoving(); petUnit->GetMotionMaster()->Clear(); charmInfo->SetIsRetreating(true); } petUnit->AttackStop(true, true); charmInfo->SetCommandState(COMMAND_FOLLOW); break; } case COMMAND_ATTACK: // spellid=1792 // ATTACK { charmInfo->SetIsRetreating(); charmInfo->SetSpellOpener(); Unit* targetUnit = targetGuid ? _player->GetMap()->GetUnit(targetGuid) : nullptr; if (targetUnit && targetUnit != petUnit && targetUnit->isTargetableForAttack() && targetUnit->isInAccessablePlaceFor((Creature*)petUnit)) { // This is true if pet has no target or has target but targets differs. if (petUnit->getVictim() != targetUnit) { petUnit->AttackStop(); if (!petUnit->hasUnitState(UNIT_STAT_CONTROLLED)) { petUnit->GetMotionMaster()->Clear(); petUnit->AI()->AttackStart(targetUnit); if (pet) { // 10% chance to play special warlock pet attack talk, else growl if (pet->getPetType() == SUMMON_PET && roll_chance_i(10)) pet->SendPetTalk((uint32)PET_TALK_ATTACK); pet->SendPetAIReaction(); } } else pet->Attack(targetUnit, true); } } break; } case COMMAND_ABANDON: // abandon (hunter pet) or dismiss (summoned pet) { if (pet && pet->getPetType() == HUNTER_PET) pet->Unsummon(PET_SAVE_AS_DELETED, _player); else { // dismissing a summoned pet is like killing them (this prevents returning a soulshard...) if (creature && creature->IsTemporarySummon()) petUnit->SetDeathState(CORPSE); else _player->Uncharm(); } charmInfo->SetStayPosition(); break; } default: sLog.outError("WORLD: unknown PET flag Action %i and spellid %i.", uint32(flag), spellid); } break; case ACT_REACTION: // 0x6 switch (spellid) { case REACT_PASSIVE: // passive { petUnit->AttackStop(true, true); charmInfo->SetSpellOpener(); } case REACT_DEFENSIVE: // recovery case REACT_AGGRESSIVE: // activete { charmInfo->SetReactState(ReactStates(spellid)); break; } } break; case ACT_DISABLED: // 0x81 spell (disabled), ignore case ACT_PASSIVE: // 0x01 case ACT_ENABLED: // 0xC1 spell { charmInfo->SetIsRetreating(); charmInfo->SetSpellOpener(); Unit* unit_target = targetGuid ? _player->GetMap()->GetUnit(targetGuid) : nullptr; // do not cast unknown spells SpellEntry const* spellInfo = sSpellTemplate.LookupEntry<SpellEntry>(spellid); if (!spellInfo) { sLog.outError("WORLD: unknown PET spell id %i", spellid); return; } if (petUnit->GetCharmInfo() && petUnit->GetCharmInfo()->GetGlobalCooldownMgr().HasGlobalCooldown(spellInfo)) return; for (int i = 0; i < MAX_EFFECT_INDEX; ++i) { if (spellInfo->EffectImplicitTargetA[i] == TARGET_ALL_ENEMY_IN_AREA || spellInfo->EffectImplicitTargetA[i] == TARGET_ALL_ENEMY_IN_AREA_INSTANT || spellInfo->EffectImplicitTargetA[i] == TARGET_ALL_ENEMY_IN_AREA_CHANNELED) return; } // do not cast not learned spells if (!petUnit->HasSpell(spellid) || IsPassiveSpell(spellInfo)) return; _player->SetInCombatState(true, unit_target); petUnit->clearUnitState(UNIT_STAT_MOVING); Spell* spell = new Spell(petUnit, spellInfo, false); SpellCastResult result = spell->CheckPetCast(unit_target); const SpellRangeEntry* sRange = sSpellRangeStore.LookupEntry(spellInfo->rangeIndex); if (unit_target && !(petUnit->IsWithinDistInMap(unit_target, sRange->maxRange) && petUnit->IsWithinLOSInMap(unit_target)) && !(GetPlayer()->IsFriendlyTo(unit_target) || petUnit->HasAuraType(SPELL_AURA_MOD_POSSESS))) { charmInfo->SetSpellOpener(spellid, sRange->minRange, sRange->maxRange); spell->finish(false); delete spell; petUnit->AttackStop(); if (!petUnit->hasUnitState(UNIT_STAT_CONTROLLED)) { petUnit->GetMotionMaster()->Clear(); petUnit->AI()->AttackStart(unit_target); // 10% chance to play special warlock pet attack talk, else growl if (pet && pet->getPetType() == SUMMON_PET && pet != unit_target && roll_chance_i(10)) petUnit->SendPetTalk((uint32)PET_TALK_ATTACK); petUnit->SendPetAIReaction(); } else petUnit->Attack(unit_target, true); return; } // auto turn to target unless possessed if (result == SPELL_FAILED_UNIT_NOT_INFRONT && !petUnit->hasUnitState(UNIT_STAT_CONTROLLED)) { if (unit_target) { petUnit->SetInFront(unit_target); if (unit_target->GetTypeId() == TYPEID_PLAYER) petUnit->SendCreateUpdateToPlayer((Player*)unit_target); } else if (Unit* unit_target2 = spell->m_targets.getUnitTarget()) { petUnit->SetInFront(unit_target2); if (unit_target2->GetTypeId() == TYPEID_PLAYER) petUnit->SendCreateUpdateToPlayer((Player*)unit_target2); } if (Unit* powner = petUnit->GetCharmerOrOwner()) if (powner->GetTypeId() == TYPEID_PLAYER) petUnit->SendCreateUpdateToPlayer((Player*)powner); result = SPELL_CAST_OK; } if (result == SPELL_CAST_OK) { if (creature) creature->AddCreatureSpellCooldown(spellid); unit_target = spell->m_targets.getUnitTarget(); charmInfo->SetSpellOpener(); spell->SpellStart(&(spell->m_targets)); } else { if (petUnit->hasUnitState(UNIT_STAT_CONTROLLED)) Spell::SendCastResult(GetPlayer(), spellInfo, 0, result); else { Unit* owner = petUnit->GetCharmerOrOwner(); if (owner && owner->GetTypeId() == TYPEID_PLAYER) Spell::SendCastResult((Player*)owner, spellInfo, 0, result, true); } if (creature && !creature->HasSpellCooldown(spellid)) GetPlayer()->SendClearCooldown(spellid, petUnit); charmInfo->SetSpellOpener(); spell->finish(false); delete spell; } break; } default: sLog.outError("WORLD: unknown PET flag Action %i and spellid %i.", uint32(flag), spellid); } }