void WorldSession::HandleUseItemOpcode(WorldPacket& recvPacket) { // TODO: add targets.read() check Player* pUser = _player; // ignore for remote control state if(pUser->m_mover != pUser) return; uint8 bagIndex, slot; uint8 unk_flags; // flags (if 0x02 - some additional data are received) uint8 cast_count; // next cast if exists (single or not) uint64 item_guid; uint32 glyphIndex; // something to do with glyphs? uint32 spellid; // casted spell id recvPacket >> bagIndex >> slot >> cast_count >> spellid >> item_guid >> glyphIndex >> unk_flags; // reject fake data if (glyphIndex >= MAX_GLYPH_SLOT_INDEX) { pUser->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, NULL, NULL ); return; } Item *pItem = pUser->GetItemByPos(bagIndex, slot); if (!pItem) { pUser->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, NULL, NULL ); return; } if (pItem->GetGUID() != item_guid) { pUser->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, NULL, NULL ); return; } sLog.outDetail("WORLD: CMSG_USE_ITEM packet, bagIndex: %u, slot: %u, cast_count: %u, spellid: %u, Item: %u, glyphIndex: %u, unk_flags: %u, data length = %i", bagIndex, slot, cast_count, spellid, pItem->GetEntry(), glyphIndex, unk_flags, (uint32)recvPacket.size()); ItemPrototype const *proto = pItem->GetProto(); if (!proto) { pUser->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, pItem, NULL ); return; } // some item classes can be used only in equipped state if (proto->InventoryType != INVTYPE_NON_EQUIP && !pItem->IsEquipped()) { pUser->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, pItem, NULL ); return; } uint8 msg = pUser->CanUseItem(pItem); if (msg != EQUIP_ERR_OK) { pUser->SendEquipError( msg, pItem, NULL ); return; } // only allow conjured consumable, bandage, poisons (all should have the 2^21 item flag set in DB) if (proto->Class == ITEM_CLASS_CONSUMABLE && !(proto->Flags & ITEM_FLAGS_USEABLE_IN_ARENA) && pUser->InArena()) { pUser->SendEquipError(EQUIP_ERR_NOT_DURING_ARENA_MATCH,pItem,NULL); return; } if (pUser->isInCombat()) { for(int i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) { if (SpellEntry const *spellInfo = sSpellStore.LookupEntry(proto->Spells[i].SpellId)) { if (IsNonCombatSpell(spellInfo)) { pUser->SendEquipError(EQUIP_ERR_NOT_IN_COMBAT,pItem,NULL); return; } } } } // check also BIND_WHEN_PICKED_UP and BIND_QUEST_ITEM for .additem or .additemset case by GM (not binded at adding to inventory) if( pItem->GetProto()->Bonding == BIND_WHEN_USE || pItem->GetProto()->Bonding == BIND_WHEN_PICKED_UP || pItem->GetProto()->Bonding == BIND_QUEST_ITEM ) { if (!pItem->IsSoulBound()) { pItem->SetState(ITEM_CHANGED, pUser); pItem->SetBinding( true ); } } SpellCastTargets targets; recvPacket >> targets.ReadForCaster(pUser); targets.Update(pUser); if (!pItem->IsTargetValidForItemUse(targets.getUnitTarget())) { // free gray item after use fail pUser->SendEquipError(EQUIP_ERR_NONE, pItem, NULL); // send spell error if (SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellid)) { // for implicit area/coord target spells if (IsPointEffectTarget(Targets(spellInfo->EffectImplicitTargetA[EFFECT_INDEX_0])) || IsAreaEffectTarget(Targets(spellInfo->EffectImplicitTargetA[EFFECT_INDEX_0]))) Spell::SendCastResult(_player,spellInfo,cast_count,SPELL_FAILED_NO_VALID_TARGETS); // for explicit target spells else Spell::SendCastResult(_player,spellInfo,cast_count,SPELL_FAILED_BAD_TARGETS); } return; } //Note: If script stop casting it must send appropriate data to client to prevent stuck item in gray state. if(!Script->ItemUse(pUser,pItem,targets)) { // no script or script not process request by self pUser->CastItemUseSpell(pItem,targets,cast_count,glyphIndex); } }
void WorldSession::HandleUseItemOpcode(WorldPacket& recvPacket) { uint8 bagIndex, slot; uint8 spell_count; // number of spells at item, not used recvPacket >> bagIndex >> slot >> spell_count; // TODO: add targets.read() check Player* pUser = _player; // ignore for remote control state if (!pUser->IsSelfMover()) { recvPacket.rpos(recvPacket.wpos()); // prevent spam at not read packet tail return; } Item* pItem = pUser->GetItemByPos(bagIndex, slot); if (!pItem) { recvPacket.rpos(recvPacket.wpos()); // prevent spam at not read packet tail pUser->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, nullptr, nullptr); return; } DETAIL_LOG("WORLD: CMSG_USE_ITEM packet, bagIndex: %u, slot: %u, spell_count: %u , Item: %u, data length = %u", bagIndex, slot, spell_count, pItem->GetEntry(), (uint32)recvPacket.size()); ItemPrototype const* proto = pItem->GetProto(); if (!proto) { recvPacket.rpos(recvPacket.wpos()); // prevent spam at not read packet tail pUser->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, pItem, nullptr); return; } // some item classes can be used only in equipped state if (proto->InventoryType != INVTYPE_NON_EQUIP && !pItem->IsEquipped()) { recvPacket.rpos(recvPacket.wpos()); // prevent spam at not read packet tail pUser->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, pItem, nullptr); return; } InventoryResult msg = pUser->CanUseItem(pItem); if (msg != EQUIP_ERR_OK) { recvPacket.rpos(recvPacket.wpos()); // prevent spam at not read packet tail pUser->SendEquipError(msg, pItem, nullptr); return; } // not allow use item from trade (cheat way only) if (pItem->IsInTrade()) { recvPacket.rpos(recvPacket.wpos()); // prevent spam at not read packet tail pUser->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, pItem, nullptr); return; } if (pUser->isInCombat()) { for (int i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) { if (SpellEntry const* spellInfo = sSpellStore.LookupEntry(proto->Spells[i].SpellId)) { if (IsNonCombatSpell(spellInfo)) { recvPacket.rpos(recvPacket.wpos()); // prevent spam at not read packet tail pUser->SendEquipError(EQUIP_ERR_NOT_IN_COMBAT, pItem, nullptr); return; } } } } // check also BIND_WHEN_PICKED_UP and BIND_QUEST_ITEM for .additem or .additemset case by GM (not binded at adding to inventory) if (pItem->GetProto()->Bonding == BIND_WHEN_USE || pItem->GetProto()->Bonding == BIND_WHEN_PICKED_UP || pItem->GetProto()->Bonding == BIND_QUEST_ITEM) { if (!pItem->IsSoulBound()) { pItem->SetState(ITEM_CHANGED, pUser); pItem->SetBinding(true); } } SpellCastTargets targets; recvPacket >> targets.ReadForCaster(pUser); targets.Update(pUser); if (!pItem->IsTargetValidForItemUse(targets.getUnitTarget())) { // free gray item after use fail pUser->SendEquipError(EQUIP_ERR_NONE, pItem, nullptr); // search spell for spell error uint32 spellid = 0; for (int i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) { if (proto->Spells[i].SpellTrigger == ITEM_SPELLTRIGGER_ON_USE || proto->Spells[i].SpellTrigger == ITEM_SPELLTRIGGER_ON_NO_DELAY_USE) { spellid = proto->Spells[i].SpellId; break; } } // send spell error if (SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellid)) Spell::SendCastResult(_player, spellInfo, SPELL_FAILED_BAD_TARGETS); return; } // Note: If script stop casting it must send appropriate data to client to prevent stuck item in gray state. if (!sScriptMgr.OnItemUse(pUser, pItem, targets)) { // no script or script not process request by self pUser->CastItemUseSpell(pItem, targets); } }
void WorldSession::HandleUseItemOpcode(WorldPacket& recvPacket) { uint8 bagIndex, slot; uint8 cast_flags; // flags (if 0x02 - some additional data are received) uint8 cast_count; // next cast if exists (single or not) ObjectGuid itemGuid; uint32 glyphIndex; // something to do with glyphs? uint32 spellid; // casted spell id recvPacket >> bagIndex >> slot >> cast_count >> spellid >> itemGuid >> glyphIndex >> cast_flags; // TODO: add targets.read() check Player* pUser = _player; // ignore for remote control state if (!pUser->IsSelfMover()) { recvPacket.rpos(recvPacket.wpos()); // prevent spam at not read packet tail return; } // reject fake data if (glyphIndex >= MAX_GLYPH_SLOT_INDEX) { recvPacket.rpos(recvPacket.wpos()); // prevent spam at not read packet tail pUser->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, NULL, NULL ); return; } Item *pItem = pUser->GetItemByPos(bagIndex, slot); if (!pItem) { recvPacket.rpos(recvPacket.wpos()); // prevent spam at not read packet tail pUser->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, NULL, NULL ); return; } if (pItem->GetObjectGuid() != itemGuid) { recvPacket.rpos(recvPacket.wpos()); // prevent spam at not read packet tail pUser->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, NULL, NULL ); return; } DETAIL_LOG("WORLD: CMSG_USE_ITEM packet, bagIndex: %u, slot: %u, cast_count: %u, spellid: %u, Item: %u, glyphIndex: %u, unk_flags: %u, data length = " SIZEFMTD, bagIndex, slot, cast_count, spellid, pItem->GetEntry(), glyphIndex, cast_flags, recvPacket.size()); ItemPrototype const *proto = pItem->GetProto(); if (!proto) { recvPacket.rpos(recvPacket.wpos()); // prevent spam at not read packet tail pUser->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, pItem, NULL ); return; } // some item classes can be used only in equipped state if (proto->InventoryType != INVTYPE_NON_EQUIP && !pItem->IsEquipped()) { recvPacket.rpos(recvPacket.wpos()); // prevent spam at not read packet tail pUser->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, pItem, NULL ); return; } InventoryResult msg = pUser->CanUseItem(pItem); if (msg != EQUIP_ERR_OK) { recvPacket.rpos(recvPacket.wpos()); // prevent spam at not read packet tail pUser->SendEquipError( msg, pItem, NULL ); return; } // not allow use item from trade (cheat way only) if (pItem->IsInTrade()) { recvPacket.rpos(recvPacket.wpos()); // prevent spam at not read packet tail pUser->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, pItem, NULL ); return; } // only allow conjured consumable, bandage, poisons (all should have the 2^21 item flag set in DB) if (proto->Class == ITEM_CLASS_CONSUMABLE && !(proto->Flags & ITEM_FLAG_USEABLE_IN_ARENA) && pUser->InArena()) { recvPacket.rpos(recvPacket.wpos()); // prevent spam at not read packet tail pUser->SendEquipError(EQUIP_ERR_NOT_DURING_ARENA_MATCH,pItem,NULL); return; } if ((proto->Area && proto->Area != pUser->GetAreaId()) || (proto->Map && proto->Map != pUser->GetMapId())) { if (SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellid)) Spell::SendCastResult(pUser, spellInfo, cast_count, SPELL_FAILED_INCORRECT_AREA); return; } if (pUser->isInCombat()) { for(int i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) { if (SpellEntry const *spellInfo = sSpellStore.LookupEntry(proto->Spells[i].SpellId)) { if (IsNonCombatSpell(spellInfo)) { recvPacket.rpos(recvPacket.wpos()); // prevent spam at not read packet tail pUser->SendEquipError(EQUIP_ERR_NOT_IN_COMBAT,pItem,NULL); return; } } } // Prevent potion drink if another potion in processing (client have potions disabled in like case) if (pItem->IsPotion() && pUser->GetLastPotionId()) { recvPacket.rpos(recvPacket.wpos()); // prevent spam at not read packet tail pUser->SendEquipError(EQUIP_ERR_OBJECT_IS_BUSY,pItem,NULL); return; } } // check also BIND_WHEN_PICKED_UP and BIND_QUEST_ITEM for .additem or .additemset case by GM (not binded at adding to inventory) if( pItem->GetProto()->Bonding == BIND_WHEN_USE || pItem->GetProto()->Bonding == BIND_WHEN_PICKED_UP || pItem->GetProto()->Bonding == BIND_QUEST_ITEM ) { if (!pItem->IsSoulBound()) { pItem->SetState(ITEM_CHANGED, pUser); pItem->SetBinding( true ); } } SpellCastTargets targets; recvPacket >> targets.ReadForCaster(pUser); // some spell cast packet including more data (for projectiles?) targets.ReadAdditionalData(recvPacket, cast_flags); targets.Update(pUser); Unit* pTarget = pUser->GetMap()->GetUnit(pUser->GetTargetGuid()); if (!pItem->IsTargetValidForItemUse(pTarget)) { // free gray item after use fail pUser->SendEquipError(EQUIP_ERR_NONE, pItem, NULL); // send spell error if (SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellid)) { SpellEffectEntry const* spellEffect = spellInfo->GetSpellEffect(EFFECT_INDEX_0); // for implicit area/coord target spells if (spellEffect && (IsPointEffectTarget(Targets(spellEffect->EffectImplicitTargetA)) || IsAreaEffectTarget(Targets(spellEffect->EffectImplicitTargetA)))) Spell::SendCastResult(_player,spellInfo,cast_count,SPELL_FAILED_NO_VALID_TARGETS); // for explicit target spells else Spell::SendCastResult(_player,spellInfo,cast_count,SPELL_FAILED_BAD_TARGETS); } return; } //Note: If script stop casting it must send appropriate data to client to prevent stuck item in gray state. if (!sScriptMgr.OnItemUse(pUser, pItem, targets)) { // no script or script not process request by self pUser->CastItemUseSpell(pItem,targets,cast_count,glyphIndex); } }
void WorldSession::HandleUseItemOpcode(WorldPacket& recvPacket) { // TODO: add targets.read() check Player* pUser = _player; uint8 bagIndex, slot; uint8 spell_count; // number of spells at item, not used recvPacket >> bagIndex >> slot >> spell_count; Item *pItem = pUser->GetItemByPos(bagIndex, slot); if(!pItem) { pUser->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, NULL, NULL ); return; } sLog.outDetail("WORLD: CMSG_USE_ITEM packet, bagIndex: %u, slot: %u, spell_count: %u , Item: %u, data length = %i", bagIndex, slot, spell_count, pItem->GetEntry(), (uint32)recvPacket.size()); ItemPrototype const *proto = pItem->GetProto(); if(!proto) { pUser->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, pItem, NULL ); return; } // some item classes can be used only in equipped state if(proto->InventoryType != INVTYPE_NON_EQUIP && !pItem->IsEquipped()) { pUser->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, pItem, NULL ); return; } uint8 msg = pUser->CanUseItem(pItem); if( msg != EQUIP_ERR_OK ) { pUser->SendEquipError( msg, pItem, NULL ); return; } if (pUser->isInCombat()) { for(int i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) { if (SpellEntry const *spellInfo = sSpellStore.LookupEntry(proto->Spells[i].SpellId)) { if (IsNonCombatSpell(spellInfo)) { pUser->SendEquipError(EQUIP_ERR_NOT_IN_COMBAT,pItem,NULL); return; } } } } // check also BIND_WHEN_PICKED_UP and BIND_QUEST_ITEM for .additem or .additemset case by GM (not binded at adding to inventory) if( pItem->GetProto()->Bonding == BIND_WHEN_USE || pItem->GetProto()->Bonding == BIND_WHEN_PICKED_UP || pItem->GetProto()->Bonding == BIND_QUEST_ITEM ) { if (!pItem->IsSoulBound()) { pItem->SetState(ITEM_CHANGED, pUser); pItem->SetBinding( true ); } } SpellCastTargets targets; recvPacket >> targets.ReadForCaster(pUser); targets.Update(pUser); if (!pItem->IsTargetValidForItemUse(targets.getUnitTarget())) { // free gray item after use fail pUser->SendEquipError(EQUIP_ERR_NONE, pItem, NULL); // search spell for spell error uint32 spellid = 0; for(int i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) { if( proto->Spells[i].SpellTrigger == ITEM_SPELLTRIGGER_ON_USE || proto->Spells[i].SpellTrigger == ITEM_SPELLTRIGGER_ON_NO_DELAY_USE) { spellid = proto->Spells[i].SpellId; break; } } // send spell error if (SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellid)) Spell::SendCastResult(_player,spellInfo,SPELL_FAILED_BAD_TARGETS); return; } //Note: If script stop casting it must send appropriate data to client to prevent stuck item in gray state. if(!Script->ItemUse(pUser,pItem,targets)) { // no script or script not process request by self // special learning case if(pItem->GetProto()->Spells[0].SpellId==SPELL_ID_GENERIC_LEARN) { uint32 learning_spell_id = pItem->GetProto()->Spells[1].SpellId; SpellEntry const *spellInfo = sSpellStore.LookupEntry(SPELL_ID_GENERIC_LEARN); if(!spellInfo) { sLog.outError("Item (Entry: %u) in have wrong spell id %u, ignoring ",proto->ItemId, SPELL_ID_GENERIC_LEARN); pUser->SendEquipError(EQUIP_ERR_NONE,pItem,NULL); return; } Spell *spell = new Spell(pUser, spellInfo, false); spell->m_CastItem = pItem; spell->m_currentBasePoints[0] = learning_spell_id; spell->prepare(&targets); return; } // use triggered flag only for items with many spell casts and for not first cast int count = 0; for(int i = 0; i < MAX_ITEM_PROTO_SPELLS; ++i) { _Spell const& spellData = pItem->GetProto()->Spells[i]; // no spell if(!spellData.SpellId) continue; // wrong triggering type if( spellData.SpellTrigger != ITEM_SPELLTRIGGER_ON_USE && spellData.SpellTrigger != ITEM_SPELLTRIGGER_ON_NO_DELAY_USE) continue; SpellEntry const *spellInfo = sSpellStore.LookupEntry(spellData.SpellId); if(!spellInfo) { sLog.outError("Item (Entry: %u) in have wrong spell id %u, ignoring ",proto->ItemId, spellData.SpellId); continue; } Spell *spell = new Spell(pUser, spellInfo, (count > 0)); spell->m_CastItem = pItem; spell->prepare(&targets); ++count; } } }
void WorldSession::HandlePetCastSpellOpcode(WorldPacket& recvPacket) { TC_LOG_DEBUG("network", "WORLD: CMSG_PET_CAST_SPELL"); ObjectGuid casterGUID; ObjectGuid unkGUID1; ObjectGuid transportDstGUID; ObjectGuid transportSrcGUID; ObjectGuid targetGUID; ObjectGuid unkGUID2; bool hasDestPos; bool hasSrcPos; bool hasSpeed; bool hasSpell; bool hasGlyphIndex; bool hasTargetFlags; bool hasElevation; bool hasString; bool hasCastCount; bool hasUnk5bits; uint32 archeologyCounter = 0; WorldLocation dstLoc, srcLoc; float speed = 0.0f; float elevation = 0.0f; uint32 targetFlags = 0; uint32 spellID = 0; uint32 stringLenght = 0; uint8 castCount = 0; recvPacket.ReadBitSeq<3, 5>(casterGUID); hasDestPos = recvPacket.ReadBit(); recvPacket.ReadBit(); // unk bit hasSpeed = !recvPacket.ReadBit(); hasSrcPos = recvPacket.ReadBit(); hasSpell = !recvPacket.ReadBit(); recvPacket.ReadBitSeq<0>(casterGUID); hasGlyphIndex = !recvPacket.ReadBit(); recvPacket.ReadBitSeq<7>(casterGUID); hasTargetFlags = !recvPacket.ReadBit(); hasElevation = !recvPacket.ReadBit(); recvPacket.ReadBit(); // has movement info hasString = !recvPacket.ReadBit(); recvPacket.ReadBit(); // !inverse bit, unk hasCastCount = !recvPacket.ReadBit(); recvPacket.ReadBitSeq<2, 4>(casterGUID); archeologyCounter = recvPacket.ReadBits(2); recvPacket.ReadBitSeq<1>(casterGUID); hasUnk5bits = !recvPacket.ReadBit(); for (uint32 i = 0; i < archeologyCounter; i++) recvPacket.ReadBits(2); // archeology type recvPacket.ReadBitSeq<6>(casterGUID); if (hasDestPos) recvPacket.ReadBitSeq<2, 7, 4, 0, 1, 6, 5, 3>(transportDstGUID); // movement block (disabled by patch client-side) if (hasSrcPos) recvPacket.ReadBitSeq<6, 2, 3, 1, 5, 4, 0, 7>(transportSrcGUID); if (hasUnk5bits) recvPacket.ReadBits(5); // unk 5 bits // Target GUID recvPacket.ReadBitSeq<3, 5, 6, 2, 4, 1, 7, 0>(targetGUID); // unkGUID1 recvPacket.ReadBitSeq<3, 1, 5, 2, 4, 7, 0, 6>(unkGUID1); if (hasTargetFlags) targetFlags = recvPacket.ReadBits(20); if (hasString) stringLenght = recvPacket.ReadBits(7); recvPacket.ReadByteSeq<0, 4, 5, 1, 2, 3, 7>(casterGUID); for (uint32 i = 0; i < archeologyCounter; i++) { recvPacket.read_skip<uint32>(); // entry recvPacket.read_skip<uint32>(); // counter } recvPacket.ReadByteSeq<6>(casterGUID); recvPacket.ReadByteSeq<1, 5, 4, 2, 7, 3, 0>(unkGUID1); if (hasSrcPos) { recvPacket.ReadByteSeq<4>(transportSrcGUID); srcLoc.m_positionY = recvPacket.read<float>(); recvPacket.ReadByteSeq<2, 6>(transportSrcGUID); srcLoc.m_positionZ = recvPacket.read<float>(); srcLoc.m_positionX = recvPacket.read<float>(); recvPacket.ReadByteSeq<1, 3, 5, 7, 0>(transportSrcGUID); } // Target GUID recvPacket.ReadByteSeq<7, 4, 2, 6, 3, 0, 5, 1>(targetGUID); if (hasDestPos) { dstLoc.m_positionX = recvPacket.read<float>(); recvPacket.ReadByteSeq<5, 7, 2, 0, 1, 3, 6>(transportDstGUID); dstLoc.m_positionZ = recvPacket.read<float>(); dstLoc.m_positionY = recvPacket.read<float>(); recvPacket.ReadByteSeq<4>(transportDstGUID); } if (hasGlyphIndex) recvPacket.read_skip<uint32>(); // glyph index if (hasElevation) elevation = recvPacket.read<float>(); if (hasSpell) spellID = recvPacket.read<uint32>(); if (hasCastCount) castCount = recvPacket.read<uint8>(); if (hasString) recvPacket.ReadString(stringLenght); if (hasSpeed) speed = recvPacket.read<float>(); TC_LOG_DEBUG("network", "WORLD: CMSG_PET_CAST_SPELL, castCount: %u, spellId %u, targetFlags %u", castCount, spellID, targetFlags); // This opcode is also sent from charmed and possessed units (players and creatures) if (!_player->GetGuardianPet() && !_player->GetCharm()) return; Unit* caster = ObjectAccessor::GetUnit(*_player, casterGUID); if (!caster || (caster != _player->GetGuardianPet() && caster != _player->GetCharm())) { TC_LOG_ERROR("network", "HandlePetCastSpellOpcode: Pet %u isn't pet of player %s .", uint32(GUID_LOPART(casterGUID)), GetPlayer()->GetName().c_str()); return; } SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellID); if (!spellInfo) { TC_LOG_ERROR("network", "WORLD: unknown PET spell id %i", spellID); return; } if (spellInfo->StartRecoveryCategory > 0) // Check if spell is affected by GCD if (caster->GetTypeId() == TYPEID_UNIT && caster->GetCharmInfo() && caster->GetCharmInfo()->GetGlobalCooldownMgr().HasGlobalCooldown(caster, spellInfo)) { caster->SendPetCastFail(spellID, SPELL_FAILED_NOT_READY); return; } // do not cast not learned spells if (!caster->HasSpell(spellID) || spellInfo->IsPassive()) return; SpellCastTargets targets; targets.Initialize(targetFlags, targetGUID, unkGUID1, transportDstGUID, dstLoc, transportSrcGUID, srcLoc); targets.SetElevation(elevation); targets.SetSpeed(speed); targets.Update(caster); // Interrupt auto-cast if other spell is forced by player if (caster->IsNonMeleeSpellCasted(false, true, true, false, true)) caster->InterruptNonMeleeSpells(false, 0, false); caster->ClearUnitState(UNIT_STATE_FOLLOW); Spell* spell = new Spell(caster, spellInfo, TRIGGERED_NONE, 0, false, true); spell->m_cast_count = castCount; // probably pending spell cast spell->m_targets = targets; // TODO: need to check victim? SpellCastResult result; if (caster->m_movedPlayer) result = spell->CheckPetCast(caster->m_movedPlayer->GetSelectedUnit()); else result = spell->CheckPetCast(NULL); if (result == SPELL_CAST_OK) { if (caster->GetTypeId() == TYPEID_UNIT) { Creature* pet = caster->ToCreature(); pet->AddCreatureSpellCooldown(spellID); if (pet->isPet()) { Pet* p = (Pet*)pet; // 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 (p->getPetType() == SUMMON_PET && (urand(0, 100) < 10)) pet->SendPetTalk((uint32)PET_TALK_SPECIAL_SPELL); else pet->SendPetAIReaction(spellID); } } spell->prepare(&(spell->m_targets)); } else { caster->SendPetCastFail(spellID, result); if (caster->GetTypeId() == TYPEID_PLAYER) { if (!caster->ToPlayer()->HasSpellCooldown(spellID)) GetPlayer()->SendClearCooldown(spellID, caster); } else { if (!caster->ToCreature()->HasSpellCooldown(spellID)) GetPlayer()->SendClearCooldown(spellID, caster); } spell->finish(false); delete spell; } }