diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 82b4b18c1..980c1e2c2 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -7121,10 +7121,6 @@ void Player::CastItemCombatSpell(Unit* target, WeaponAttackType attType, uint32 continue; } - // not allow proc extra attack spell at extra attack - if (m_extraAttacks && spellInfo->HasEffect(SPELL_EFFECT_ADD_EXTRA_ATTACKS)) - return; - float chance = (float)spellInfo->ProcChance; if (spellData.SpellPPMRate) diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index f81c04b5e..1886709c7 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -230,7 +230,6 @@ Unit::Unit(bool isWorldObject) : WorldObject(isWorldObject), m_modAttackSpeedPct[OFF_ATTACK] = 1.0f; m_modAttackSpeedPct[RANGED_ATTACK] = 1.0f; - m_extraAttacks = 0; m_canDualWield = false; m_rootTimes = 0; @@ -315,6 +314,8 @@ Unit::Unit(bool isWorldObject) : WorldObject(isWorldObject), _lastLiquid = nullptr; _oldFactionId = 0; + + _lastExtraAttackSpell = 0; } //////////////////////////////////////////////////////////// @@ -429,6 +430,23 @@ void Unit::Update(uint32 p_time) } } + _lastDamagedTargetGuid = ObjectGuid::Empty; + if (_lastExtraAttackSpell) + { + while (!extraAttacksTargets.empty()) + { + auto itr = extraAttacksTargets.begin(); + ObjectGuid targetGuid = itr->first; + uint32 count = itr->second; + extraAttacksTargets.erase(itr); + if (Unit* victim = ObjectAccessor::GetUnit(*this, targetGuid)) + { + HandleProcExtraAttackFor(victim, count); + } + } + _lastExtraAttackSpell = 0; + } + // not implemented before 3.0.2 // xinef: if attack time > 0, reduce by diff // if on next update, attack time < 0 assume player didnt attack - set to 0 @@ -2285,8 +2303,15 @@ void Unit::CalcHealAbsorb(HealInfo& healInfo) void Unit::AttackerStateUpdate(Unit* victim, WeaponAttackType attType, bool extra) { - if (HasUnitState(UNIT_STATE_CANNOT_AUTOATTACK) || HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED)) + if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED)) + { return; + } + + if (HasUnitState(UNIT_STATE_CANNOT_AUTOATTACK) && !extra) + { + return; + } if (!victim->IsAlive()) return; @@ -2303,6 +2328,11 @@ void Unit::AttackerStateUpdate(Unit* victim, WeaponAttackType attType, bool extr if (attType != BASE_ATTACK && attType != OFF_ATTACK) return; // ignore ranged case + if (!extra && _lastExtraAttackSpell) + { + _lastExtraAttackSpell = 0; + } + bool meleeAttack = true; // melee attack spell casted at main hand attack only - no normal melee dmg dealt @@ -2344,6 +2374,8 @@ void Unit::AttackerStateUpdate(Unit* victim, WeaponAttackType attType, bool extr //TriggerAurasProcOnEvent(damageInfo); + _lastDamagedTargetGuid = victim->GetGUID(); + DealMeleeDamage(&damageInfo, true); DamageInfo dmgInfo(damageInfo); @@ -2456,15 +2488,31 @@ bool Unit::GetMeleeAttackPoint(Unit* attacker, Position& pos) return true; } -void Unit::HandleProcExtraAttackFor(Unit* victim) +void Unit::HandleProcExtraAttackFor(Unit* victim, uint32 count) { - while (m_extraAttacks) + while (count) { + --count; AttackerStateUpdate(victim, BASE_ATTACK, true); - --m_extraAttacks; } } +void Unit::AddExtraAttacks(uint32 count) +{ + ObjectGuid targetGUID = _lastDamagedTargetGuid; + if (!targetGUID) + { + if (ObjectGuid selection = GetTarget()) + { + targetGUID = selection; // Spell was cast directly (not triggered by aura) + } + else + return; + } + + extraAttacksTargets[targetGUID] += count; +} + MeleeHitOutcome Unit::RollMeleeOutcomeAgainst(Unit const* victim, WeaponAttackType attType) const { // This is only wrapper @@ -8417,7 +8465,7 @@ bool Unit::HandleAuraProc(Unit* victim, uint32 damage, Aura* triggeredByAura, Sp return false; } -bool Unit::HandleProcTriggerSpell(Unit* victim, uint32 damage, AuraEffect* triggeredByAura, SpellInfo const* procSpell, uint32 procFlags, uint32 procEx, uint32 cooldown, uint32 procPhase) +bool Unit::HandleProcTriggerSpell(Unit* victim, uint32 damage, AuraEffect* triggeredByAura, SpellInfo const* procSpell, uint32 procFlags, uint32 procEx, uint32 cooldown, uint32 procPhase, ProcEventInfo& eventInfo) { // Get triggered aura spell info SpellInfo const* auraSpellInfo = triggeredByAura->GetSpellInfo(); @@ -8903,8 +8951,23 @@ bool Unit::HandleProcTriggerSpell(Unit* victim, uint32 damage, AuraEffect* trigg } // not allow proc extra attack spell at extra attack - if (m_extraAttacks && triggerEntry->HasEffect(SPELL_EFFECT_ADD_EXTRA_ATTACKS)) - return false; + if (triggerEntry->HasEffect(SPELL_EFFECT_ADD_EXTRA_ATTACKS)) + { + uint32 lastExtraAttackSpell = eventInfo.GetActor()->GetLastExtraAttackSpell(); + + // Patch 1.12.0(?) extra attack abilities can no longer chain proc themselves + if (lastExtraAttackSpell == trigger_spell_id) + { + return false; + } + + // Patch 2.2.0 Sword Specialization (Warrior, Rogue) extra attack can no longer proc additional extra attacks + // 3.3.5 Sword Specialization (Warrior), Hack and Slash (Rogue) + if (lastExtraAttackSpell == 16459 || lastExtraAttackSpell == 66923) + { + return false; + } + } // Custom requirements (not listed in procEx) Warning! damage dealing after this // Custom triggered spells @@ -15826,7 +15889,7 @@ void Unit::ProcDamageAndSpellFor(bool isVictim, Unit* target, uint32 procFlag, u { LOG_DEBUG("spells.aura", "ProcDamageAndSpell: casting spell {} (triggered by {} aura of spell {})", spellInfo->Id, (isVictim ? "a victim's" : "an attacker's"), triggeredByAura->GetId()); // Don`t drop charge or add cooldown for not started trigger - if (HandleProcTriggerSpell(target, damage, triggeredByAura, procSpellInfo, procFlag, procExtra, cooldown, procPhase)) + if (HandleProcTriggerSpell(target, damage, triggeredByAura, procSpellInfo, procFlag, procExtra, cooldown, procPhase, eventInfo)) takeCharges = true; break; } @@ -15885,7 +15948,7 @@ void Unit::ProcDamageAndSpellFor(bool isVictim, Unit* target, uint32 procFlag, u { LOG_DEBUG("spells.aura", "ProcDamageAndSpell: casting spell {} (triggered with value by {} aura of spell {})", spellInfo->Id, (isVictim ? "a victim's" : "an attacker's"), triggeredByAura->GetId()); - if (HandleProcTriggerSpell(target, damage, triggeredByAura, procSpellInfo, procFlag, procExtra, cooldown, procPhase)) + if (HandleProcTriggerSpell(target, damage, triggeredByAura, procSpellInfo, procFlag, procExtra, cooldown, procPhase, eventInfo)) takeCharges = true; break; } diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 6b100ceda..e97d21ee3 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -1506,7 +1506,13 @@ public: void CalculateMeleeDamage(Unit* victim, uint32 damage, CalcDamageInfo* damageInfo, WeaponAttackType attackType = BASE_ATTACK, const bool sittingVictim = false); void DealMeleeDamage(CalcDamageInfo* damageInfo, bool durabilityLoss); - void HandleProcExtraAttackFor(Unit* victim); + + void HandleProcExtraAttackFor(Unit* victim, uint32 count); + void SetLastExtraAttackSpell(uint32 spellId) { _lastExtraAttackSpell = spellId; } + [[nodiscard]] uint32 GetLastExtraAttackSpell() const { return _lastExtraAttackSpell; } + void AddExtraAttacks(uint32 count); + void SetLastDamagedTargetGuid(ObjectGuid const& guid) { _lastDamagedTargetGuid = guid; } + [[nodiscard]] ObjectGuid const& GetLastDamagedTargetGuid() const { return _lastDamagedTargetGuid; } void CalculateSpellDamageTaken(SpellNonMeleeDamage* damageInfo, int32 damage, SpellInfo const* spellInfo, WeaponAttackType attackType = BASE_ATTACK, bool crit = false); void DealSpellDamage(SpellNonMeleeDamage* damageInfo, bool durabilityLoss, Spell const* spell = nullptr); @@ -2467,7 +2473,7 @@ private: bool IsTriggeredAtSpellProcEvent(Unit* victim, Aura* aura, WeaponAttackType attType, bool isVictim, bool active, SpellProcEventEntry const*& spellProcEvent, ProcEventInfo const& eventInfo); bool HandleDummyAuraProc(Unit* victim, uint32 damage, AuraEffect* triggeredByAura, SpellInfo const* procSpell, uint32 procFlag, uint32 procEx, uint32 cooldown); bool HandleAuraProc(Unit* victim, uint32 damage, Aura* triggeredByAura, SpellInfo const* procSpell, uint32 procFlag, uint32 procEx, uint32 cooldown, bool* handled); - bool HandleProcTriggerSpell(Unit* victim, uint32 damage, AuraEffect* triggeredByAura, SpellInfo const* procSpell, uint32 procFlag, uint32 procEx, uint32 cooldown, uint32 procPhase); + bool HandleProcTriggerSpell(Unit* victim, uint32 damage, AuraEffect* triggeredByAura, SpellInfo const* procSpell, uint32 procFlag, uint32 procEx, uint32 cooldown, uint32 procPhase, ProcEventInfo& eventInfo); bool HandleOverrideClassScriptAuraProc(Unit* victim, uint32 damage, AuraEffect* triggeredByAura, SpellInfo const* procSpell, uint32 cooldown); bool HandleAuraRaidProcFromChargeWithValue(AuraEffect* triggeredByAura); bool HandleAuraRaidProcFromCharge(AuraEffect* triggeredByAura); @@ -2511,6 +2517,10 @@ private: uint32 _oldFactionId; ///< faction before charm [[nodiscard]] float processDummyAuras(float TakenTotalMod) const; + + uint32 _lastExtraAttackSpell; + std::unordered_map extraAttacksTargets; + ObjectGuid _lastDamagedTargetGuid; }; namespace Acore diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 86c876c0a..dddaf542e 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -2605,6 +2605,8 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target) // Do damage and triggers else if (m_damage > 0) { + caster->SetLastDamagedTargetGuid(unitTarget->GetGUID()); + // Fill base damage struct (unitTarget - is real spell target) SpellNonMeleeDamage damageInfo(caster, unitTarget, m_spellInfo, m_spellSchoolMask); @@ -4049,8 +4051,10 @@ void Spell::_handle_finish_phase() m_caster->AddComboPoints(m_comboTarget, m_comboPointGain); } - if (m_caster->m_extraAttacks && GetSpellInfo()->HasEffect(SPELL_EFFECT_ADD_EXTRA_ATTACKS)) - m_caster->HandleProcExtraAttackFor(m_caster->GetVictim()); + if (m_spellInfo->HasEffect(SPELL_EFFECT_ADD_EXTRA_ATTACKS)) + { + m_caster->SetLastExtraAttackSpell(m_spellInfo->Id); + } if (!IsAutoRepeat() && !IsNextMeleeSwingSpell()) if (m_caster->GetCharmerOrOwnerPlayerOrPlayerItself()) diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index ef5dd6579..300f4c466 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -4664,17 +4664,18 @@ void Spell::EffectResurrect(SpellEffIndex effIndex) void Spell::EffectAddExtraAttacks(SpellEffIndex effIndex) { if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET) + { return; + } - if (!unitTarget || !unitTarget->IsAlive() || !unitTarget->GetVictim()) + if (!unitTarget || !unitTarget->IsAlive()) + { return; + } - if (unitTarget->m_extraAttacks) - return; + unitTarget->AddExtraAttacks(damage); - unitTarget->m_extraAttacks = damage; - - ExecuteLogEffectExtraAttacks(effIndex, unitTarget->GetVictim(), damage); + ExecuteLogEffectExtraAttacks(effIndex, unitTarget, damage); } void Spell::EffectParry(SpellEffIndex /*effIndex*/)