diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 1ea1075b8..f8ab59522 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -5271,6 +5271,19 @@ bool Unit::HasAuraTypeWithValue(AuraType auratype, int32 value) const return false; } +bool Unit::HasAuraTypeWithTriggerSpell(AuraType auratype, uint32 triggerSpell) const +{ + for (AuraEffect const* aura : GetAuraEffectsByType(auratype)) + { + if (aura->GetSpellInfo()->Effects[aura->GetEffIndex()].TriggerSpell == triggerSpell) + { + return true; + } + } + + return false; +} + bool Unit::HasNegativeAuraWithInterruptFlag(uint32 flag, ObjectGuid guid) { if (!(m_interruptMask & flag)) diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 375404e12..aa3ccf16d 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -2071,6 +2071,7 @@ public: [[nodiscard]] bool HasAuraTypeWithMiscvalue(AuraType auratype, int32 miscvalue) const; bool HasAuraTypeWithAffectMask(AuraType auratype, SpellInfo const* affectedSpell) const; [[nodiscard]] bool HasAuraTypeWithValue(AuraType auratype, int32 value) const; + [[nodiscard]] bool HasAuraTypeWithTriggerSpell(AuraType auratype, uint32 triggerSpell) const; bool HasNegativeAuraWithInterruptFlag(uint32 flag, ObjectGuid guid = ObjectGuid::Empty); [[nodiscard]] bool HasVisibleAuraType(AuraType auraType) const; bool HasNegativeAuraWithAttribute(uint32 flag, ObjectGuid guid = ObjectGuid::Empty); diff --git a/src/server/game/Handlers/PetHandler.cpp b/src/server/game/Handlers/PetHandler.cpp index e1117ad3a..e7096282a 100644 --- a/src/server/game/Handlers/PetHandler.cpp +++ b/src/server/game/Handlers/PetHandler.cpp @@ -416,12 +416,12 @@ void WorldSession::HandlePetAction(WorldPacket& recvData) recvData >> data; recvData >> guid2; //tag guid - uint32 spellid = UNIT_ACTION_BUTTON_ACTION(data); + 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 %s - flag: %u, spellid: %u, target: %s.", guid1.ToString().c_str(), uint32(flag), spellid, guid2.ToString().c_str()); + LOG_DEBUG("network.opcode", "HandlePetAction: Pet %s - flag: %u, spellId: %u, target: %s.", guid1.ToString().c_str(), uint32(flag), spellId, guid2.ToString().c_str()); if (!pet) { @@ -438,8 +438,8 @@ void WorldSession::HandlePetAction(WorldPacket& recvData) 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))) + 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; } @@ -448,13 +448,13 @@ void WorldSession::HandlePetAction(WorldPacket& recvData) return; // Do not follow itself vehicle - if (spellid == COMMAND_FOLLOW && _player->IsOnVehicle(pet)) + if (spellId == COMMAND_FOLLOW && _player->IsOnVehicle(pet)) { return; } if (GetPlayer()->m_Controlled.size() == 1) - HandlePetActionHelper(pet, guid1, spellid, flag, guid2); + HandlePetActionHelper(pet, guid1, spellId, flag, guid2); else { //If a pet is dismissed, m_Controlled will change @@ -462,10 +462,10 @@ void WorldSession::HandlePetAction(WorldPacket& recvData) 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))) + if ((*itr)->GetEntry() == pet->GetEntry() && ((*itr)->IsAlive() || (flag == ACT_COMMAND && spellId == COMMAND_ABANDON))) controlled.push_back(*itr); // xinef: mirror image blizzard crappness - else if ((*itr)->GetEntry() == NPC_MIRROR_IMAGE && flag == ACT_COMMAND && spellid == COMMAND_FOLLOW) + else if ((*itr)->GetEntry() == NPC_MIRROR_IMAGE && flag == ACT_COMMAND && spellId == COMMAND_FOLLOW) { (*itr)->InterruptNonMeleeSpells(false); } @@ -473,7 +473,7 @@ void WorldSession::HandlePetAction(WorldPacket& recvData) for (Unit* pet : controlled) if (pet && pet->IsInWorld() && pet->GetMap() == _player->GetMap()) - HandlePetActionHelper(pet, guid1, spellid, flag, guid2); + HandlePetActionHelper(pet, guid1, spellId, flag, guid2); } } @@ -505,20 +505,20 @@ void WorldSession::HandlePetStopAttack(WorldPacket& recvData) pet->ClearInPetCombat(); } -void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spellid, uint16 flag, ObjectGuid guid2) +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: %s, tagGuid: %s, spellId: %u, flag: %u): object (%s) is considered pet-like but doesn't have a charminfo!", - guid1.ToString().c_str(), guid2.ToString().c_str(), spellid, flag, pet->GetGUID().ToString().c_str()); + guid1.ToString().c_str(), guid2.ToString().c_str(), spellId, flag, pet->GetGUID().ToString().c_str()); return; } switch (flag) { case ACT_COMMAND: //0x07 - switch (spellid) + switch (spellId) { case COMMAND_STAY: //flat=1792 //STAY { @@ -544,7 +544,7 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe charmInfo->SetForcedTargetGUID(); break; } - case COMMAND_FOLLOW: //spellid=1792 //FOLLOW + case COMMAND_FOLLOW: //spellId=1792 //FOLLOW { pet->AttackStop(); pet->InterruptNonMeleeSpells(false); @@ -564,12 +564,12 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe charmInfo->SetForcedTargetGUID(); break; } - case COMMAND_ATTACK: //spellid=1792 //ATTACK + case COMMAND_ATTACK: //spellId=1792 //ATTACK { // Can't attack if owner is pacified if (_player->HasAuraType(SPELL_AURA_MOD_PACIFY)) { - //pet->SendPetCastFail(spellid, SPELL_FAILED_PACIFIED); + //pet->SendPetCastFail(spellId, SPELL_FAILED_PACIFIED); //TODO: Send proper error message to client return; } @@ -668,11 +668,11 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe } break; default: - LOG_ERROR("network.opcode", "WORLD: unknown PET flag Action %i and spellid %i.", uint32(flag), spellid); + LOG_ERROR("network.opcode", "WORLD: unknown PET flag Action %i and spellId %i.", uint32(flag), spellId); } break; case ACT_REACTION: // 0x6 - switch (spellid) + switch (spellId) { case REACT_PASSIVE: //passive pet->AttackStop(); @@ -684,9 +684,9 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe case REACT_DEFENSIVE: //recovery case REACT_AGGRESSIVE: //activete if (pet->GetTypeId() == TYPEID_UNIT) - pet->ToCreature()->SetReactState(ReactStates(spellid)); + pet->ToCreature()->SetReactState(ReactStates(spellId)); else - charmInfo->SetPlayerReactState(ReactStates(spellid)); + charmInfo->SetPlayerReactState(ReactStates(spellId)); break; } break; @@ -697,10 +697,10 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe Unit* unit_target = nullptr; // do not cast unknown spells - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellid); + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) { - LOG_ERROR("network.opcode", "WORLD: unknown PET spell id %i", spellid); + LOG_ERROR("network.opcode", "WORLD: unknown PET spell id %i", spellId); return; } @@ -715,10 +715,6 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe return; } - // do not cast not learned spells - if (!pet->HasSpell(spellid) || spellInfo->IsPassive()) - return; - // Clear the flags as if owner clicked 'attack'. AI will reset them // after AttackStart, even if spell failed charmInfo->SetIsAtStay(false); @@ -726,7 +722,28 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe charmInfo->SetIsReturning(false); charmInfo->SetIsFollowing(false); - Spell* spell = new Spell(pet, spellInfo, TRIGGERED_NONE); + 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); @@ -757,7 +774,7 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe { if (!spellInfo->IsCooldownStartedOnEvent()) { - pet->ToCreature()->AddSpellCooldown(spellid, 0, 0); + pet->ToCreature()->AddSpellCooldown(spellId, 0, 0); } unit_target = spell->m_targets.GetUnitTarget(); @@ -811,9 +828,9 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe else spell->SendPetCastResult(SPELL_FAILED_DONT_REPORT); - if (!pet->HasSpellCooldown(spellid)) + if (!pet->HasSpellCooldown(spellId)) if(pet->ToPet()) - pet->ToPet()->RemoveSpellCooldown(spellid, true); + pet->ToPet()->RemoveSpellCooldown(spellId, true); spell->finish(false); delete spell; @@ -871,7 +888,7 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe pet->SendPetAIReaction(guid1); } - pet->ToPet()->CastWhenWillAvailable(spellid, unit_target, nullptr, tempspellIsPositive); + pet->ToPet()->CastWhenWillAvailable(spellId, unit_target, nullptr, tempspellIsPositive); } } else if (haspositiveeffect) @@ -907,7 +924,7 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe pet->SendPetAIReaction(guid1); } - pet->ToPet()->CastWhenWillAvailable(spellid, unit_target, victim, tmpSpellIsPositive); + pet->ToPet()->CastWhenWillAvailable(spellId, unit_target, victim, tmpSpellIsPositive); } } } @@ -922,8 +939,8 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe spell->SendPetCastResult(result); } - if (!pet->ToCreature()->HasSpellCooldown(spellid)) - GetPlayer()->SendClearCooldown(spellid, pet); + if (!pet->ToCreature()->HasSpellCooldown(spellId)) + GetPlayer()->SendClearCooldown(spellId, pet); spell->finish(false); delete spell; @@ -934,7 +951,7 @@ void WorldSession::HandlePetActionHelper(Unit* pet, ObjectGuid guid1, uint32 spe break; } default: - LOG_ERROR("network.opcode", "WORLD: unknown PET flag Action %i and spellid %i.", uint32(flag), spellid); + LOG_ERROR("network.opcode", "WORLD: unknown PET flag Action %i and spellId %i.", uint32(flag), spellId); } } @@ -1282,9 +1299,9 @@ void WorldSession::HandlePetSpellAutocastOpcode(WorldPacket& recvPacket) { LOG_DEBUG("network.opcode", "CMSG_PET_SPELL_AUTOCAST"); ObjectGuid guid; - uint32 spellid; + uint32 spellId; uint8 state; //1 for on, 0 for off - recvPacket >> guid >> spellid >> state; + recvPacket >> guid >> spellId >> state; if (!_player->GetGuardianPet() && !_player->GetCharm()) return; @@ -1292,7 +1309,7 @@ void WorldSession::HandlePetSpellAutocastOpcode(WorldPacket& recvPacket) if (guid.IsPlayer()) return; - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellid); + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId); if (!spellInfo) return; @@ -1317,7 +1334,7 @@ void WorldSession::HandlePetSpellAutocastOpcode(WorldPacket& recvPacket) continue; // do not add not learned spells/ passive spells - if (!pet->HasSpell(spellid) || !spellInfo->IsAutocastable()) + if (!pet->HasSpell(spellId) || !spellInfo->IsAutocastable()) continue; CharmInfo* charmInfo = pet->GetCharmInfo(); diff --git a/src/server/game/Spells/Auras/SpellAuraDefines.h b/src/server/game/Spells/Auras/SpellAuraDefines.h index 20effd0a8..9092b5ade 100644 --- a/src/server/game/Spells/Auras/SpellAuraDefines.h +++ b/src/server/game/Spells/Auras/SpellAuraDefines.h @@ -108,7 +108,7 @@ enum AuraType SPELL_AURA_TRACK_RESOURCES = 45, SPELL_AURA_46 = 46, // Ignore all Gear test spells SPELL_AURA_MOD_PARRY_PERCENT = 47, - SPELL_AURA_48 = 48, // One periodic spell + SPELL_AURA_PERIODIC_TRIGGER_SPELL_FROM_CLIENT = 48, // One periodic spell SPELL_AURA_MOD_DODGE_PERCENT = 49, SPELL_AURA_MOD_CRITICAL_HEALING_AMOUNT = 50, SPELL_AURA_MOD_BLOCK_PERCENT = 51, diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp index 0544af44d..4e1f59199 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp +++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp @@ -104,7 +104,7 @@ pAuraEffectHandler AuraEffectHandler[TOTAL_AURAS] = &AuraEffect::HandleAuraTrackResources, // 45 SPELL_AURA_TRACK_RESOURCES &AuraEffect::HandleNULL, // 46 SPELL_AURA_46 (used in test spells 54054 and 54058, and spell 48050) (3.0.8a) &AuraEffect::HandleAuraModParryPercent, // 47 SPELL_AURA_MOD_PARRY_PERCENT - &AuraEffect::HandleNULL, // 48 SPELL_AURA_48 spell Napalm (area damage spell with additional delayed damage effect) + &AuraEffect::HandleNoImmediateEffect, // 48 SPELL_AURA_PERIODIC_TRIGGER_SPELL_FROM_CLIENT &AuraEffect::HandleAuraModDodgePercent, // 49 SPELL_AURA_MOD_DODGE_PERCENT &AuraEffect::HandleNoImmediateEffect, // 50 SPELL_AURA_MOD_CRITICAL_HEALING_AMOUNT implemented in Unit::SpellCriticalHealingBonus &AuraEffect::HandleAuraModBlockPercent, // 51 SPELL_AURA_MOD_BLOCK_PERCENT @@ -595,6 +595,7 @@ void AuraEffect::CalculatePeriodic(Unit* caster, bool create, bool load) case SPELL_AURA_PERIODIC_HEAL: case SPELL_AURA_OBS_MOD_HEALTH: case SPELL_AURA_PERIODIC_TRIGGER_SPELL: + case SPELL_AURA_PERIODIC_TRIGGER_SPELL_FROM_CLIENT: case SPELL_AURA_PERIODIC_ENERGIZE: case SPELL_AURA_PERIODIC_LEECH: case SPELL_AURA_PERIODIC_HEALTH_FUNNEL: @@ -1096,6 +1097,9 @@ void AuraEffect::PeriodicTick(AuraApplication* aurApp, Unit* caster) const case SPELL_AURA_PERIODIC_TRIGGER_SPELL: HandlePeriodicTriggerSpellAuraTick(target, caster); break; + case SPELL_AURA_PERIODIC_TRIGGER_SPELL_FROM_CLIENT: + // Don't actually do anything - client will trigger casts of these spells by itself + break; case SPELL_AURA_PERIODIC_TRIGGER_SPELL_WITH_VALUE: HandlePeriodicTriggerSpellWithValueAuraTick(target, caster); break; diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp index 3dceda35b..3bdee353c 100644 --- a/src/server/game/Spells/SpellInfo.cpp +++ b/src/server/game/Spells/SpellInfo.cpp @@ -2325,6 +2325,7 @@ uint32 SpellInfo::GetMaxTicks() const case SPELL_AURA_PERIODIC_DAMAGE: case SPELL_AURA_PERIODIC_HEAL: case SPELL_AURA_PERIODIC_LEECH: + case SPELL_AURA_PERIODIC_TRIGGER_SPELL_FROM_CLIENT: if (Effects[x].Amplitude != 0) return DotDuration / Effects[x].Amplitude; break; @@ -2656,6 +2657,7 @@ bool SpellInfo::_IsPositiveEffect(uint8 effIndex, bool deep) const case SPELL_AURA_ADD_TARGET_TRIGGER: return true; case SPELL_AURA_PERIODIC_TRIGGER_SPELL_WITH_VALUE: + case SPELL_AURA_PERIODIC_TRIGGER_SPELL_FROM_CLIENT: case SPELL_AURA_PERIODIC_TRIGGER_SPELL: if (!deep) { diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index 5d5945af1..bd0cc860b 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -3403,6 +3403,7 @@ void SpellMgr::LoadSpellCustomAttr() switch(spellInfo->Effects[j].ApplyAuraName) { case SPELL_AURA_PERIODIC_TRIGGER_SPELL: + case SPELL_AURA_PERIODIC_TRIGGER_SPELL_FROM_CLIENT: case SPELL_AURA_PERIODIC_TRIGGER_SPELL_WITH_VALUE: if (SpellInfo const* triggerSpell = sSpellMgr->GetSpellInfo(spellInfo->Effects[j].TriggerSpell)) {