diff --git a/src/server/game/AI/CoreAI/TotemAI.cpp b/src/server/game/AI/CoreAI/TotemAI.cpp index 3954a92916..66cd41c3e6 100644 --- a/src/server/game/AI/CoreAI/TotemAI.cpp +++ b/src/server/game/AI/CoreAI/TotemAI.cpp @@ -24,6 +24,8 @@ #include "GridNotifiersImpl.h" #include "CellImpl.h" +static constexpr uint32 TargetSearchInterval = 400; // let's mimic one retail AI tick + int32 TotemAI::Permissible(Creature const* creature) { if (creature->IsSummon() && creature->IsTotem()) @@ -32,7 +34,7 @@ int32 TotemAI::Permissible(Creature const* creature) return PERMIT_BASE_NO; } -TotemAI::TotemAI(Creature* creature) : NullCreatureAI(creature), _spellInfo(nullptr), _spellRange(0.f), _victimGUID() { } +TotemAI::TotemAI(Creature* creature) : NullCreatureAI(creature), _spellInfo(nullptr), _spellRange(0.f), _targetSearchTimer(0) { } void TotemAI::JustAppeared() { @@ -48,7 +50,7 @@ void TotemAI::JustAppeared() _spellRange = _spellInfo->GetMaxRange(false); } -void TotemAI::UpdateAI(uint32 /*diff*/) +void TotemAI::UpdateAI(uint32 diff) { // Passive aura totems do not need any further casting if (!_spellInfo || _spellInfo->IsPassive()) @@ -57,12 +59,21 @@ void TotemAI::UpdateAI(uint32 /*diff*/) if (!me->IsAlive() || me->IsNonMeleeSpellCast(false)) return; - Unit* target = SelectTotemTarget(); + if (_targetSearchTimer <= diff) + { + Unit* target = SelectTotemTarget(); - if ((!target && !_spellInfo->NeedsExplicitUnitTarget()) || target) - me->CastSpell(target, _spellInfo->Id); - if (target) - _victimGUID = target->GetGUID(); + if ((!target && !_spellInfo->NeedsExplicitUnitTarget()) || target) + { + me->CastSpell(target, _spellInfo->Id); + if (target) + _victimGUID = target->GetGUID(); + } + else + _targetSearchTimer = TargetSearchInterval; + } + else + _targetSearchTimer -= diff; } Unit* TotemAI::SelectTotemTarget() diff --git a/src/server/game/AI/CoreAI/TotemAI.h b/src/server/game/AI/CoreAI/TotemAI.h index c1c8ef07d1..4b8988ca7f 100644 --- a/src/server/game/AI/CoreAI/TotemAI.h +++ b/src/server/game/AI/CoreAI/TotemAI.h @@ -29,7 +29,6 @@ class Totem; class TC_GAME_API TotemAI : public NullCreatureAI { public: - explicit TotemAI(Creature* creature); void AttackStart(Unit* /*victim*/) override {} @@ -40,9 +39,11 @@ class TC_GAME_API TotemAI : public NullCreatureAI static int32 Permissible(Creature const* creature); - private: + protected: SpellInfo const* _spellInfo; float _spellRange; + private: ObjectGuid _victimGUID; + uint32 _targetSearchTimer; }; #endif diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 0a09f063e9..9fedcba0ca 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -2428,11 +2428,14 @@ float Unit::GetUnitDodgeChance(WeaponAttackType attType, Unit const* victim) con } else { - chance += victim->GetTotalAuraModifier(SPELL_AURA_MOD_DODGE_PERCENT); - if (skillDifference <= 10) - chance = 5.f + skillDifference * 0.1f; - else - chance = 6.f + (skillDifference - 10) * 0.1f; + if (!victim->IsTotem()) + { + chance += victim->GetTotalAuraModifier(SPELL_AURA_MOD_DODGE_PERCENT); + if (skillDifference <= 10) + chance = 5.f + skillDifference * 0.1f; + else + chance = 6.f + (skillDifference - 10) * 0.1f; + } } // Reduce enemy dodge chance by SPELL_AURA_MOD_COMBAT_RESULT_CHANCE @@ -2474,7 +2477,7 @@ float Unit::GetUnitParryChance(WeaponAttackType attType, Unit const* victim) con else { // Allow parries for creatures only if it's not a totem, does have a virtual item equipped and does not have CREATURE_FLAG_EXTRA_NO_PARRY - if (victim->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 0) || victim->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 1) && + if (!victim->IsTotem() && (victim->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 0) || victim->GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + 1)) && !(victim->ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_PARRY)) { chance += victim->GetTotalAuraModifier(SPELL_AURA_MOD_PARRY_PERCENT); @@ -2536,7 +2539,7 @@ float Unit::GetUnitBlockChance(Unit const* victim) const } else { - if (!(victim->ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_BLOCK)) + if (!victim->IsTotem() && !(victim->ToCreature()->GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_BLOCK)) { chance = 5.0f; chance += victim->GetTotalAuraModifier(SPELL_AURA_MOD_BLOCK_PERCENT); @@ -3236,6 +3239,13 @@ void Unit::_UnapplyAura(AuraApplicationMap::iterator& i, AuraRemoveFlags removeM // all effect mustn't be applied ASSERT(!aurApp->GetEffectMask()); + // Remove totem at next update if totem loses its aura + if (aurApp->GetRemoveMode().HasFlag(AuraRemoveFlags::Expired) && GetTypeId() == TYPEID_UNIT && IsTotem()) + { + if (ToTotem()->GetSpell() == aura->GetId() && ToTotem()->GetTotemType() == TOTEM_PASSIVE) + ToTotem()->setDeathState(JUST_DIED); + } + // Remove aurastates only if were not found if (!auraStateFound) ModifyAuraState(auraState, false); @@ -5731,9 +5741,13 @@ void Unit::SetCharm(Unit* charm, bool apply) // Hook for OnHeal Event sScriptMgr->OnHeal(healer, victim, (uint32&)gain); - if (healer) + Unit* unit = healer; + if (healer && healer->GetTypeId() == TYPEID_UNIT && healer->IsTotem()) + unit = healer->GetOwner(); + + if (unit) { - if (Player* player = healer->ToPlayer()) + if (Player* player = unit->ToPlayer()) { if (!healInfo.GetSpellInfo() || !healInfo.GetSpellInfo()->HasAttribute(SPELL_ATTR7_DO_NOT_COUNT_FOR_PVP_SCOREBOARD)) if (Battleground* bg = player->GetBattleground()) @@ -5917,6 +5931,11 @@ int32 Unit::SpellDamageBonusDone(Unit* victim, SpellInfo const* spellProto, int3 return int32(std::max(float(pdamage + DoneTotal) * DoneTotalMod, 0.0f)); } + // Totems get their bonus damage from their owner + if (IsCreature() && IsTotem()) + if (Unit* creator = ObjectAccessor::GetUnit(*this, GetCreatorGUID())) + return creator->SpellDamageBonusDone(victim, spellProto, pdamage, damagetype, effIndex, stack, spell, aurEff); + DoneTotalMod = SpellDamagePctDone(victim, spellProto, damagetype); // done scripted mod (take it from owner) @@ -6067,6 +6086,10 @@ float Unit::SpellDamagePctDone(Unit* victim, SpellInfo const* spellProto, Damage if (spellProto->HasAttribute(SPELL_ATTR6_IGNORE_CASTER_DAMAGE_MODIFIERS)) return 1.0f; + // For totems pct done mods are calculated when its calculation is run on the player in SpellDamageBonusDone. + if (GetTypeId() == TYPEID_UNIT && IsTotem()) + return 1.0f; + // Done total percent damage auras float DoneTotalMod = 1.0f; @@ -6664,6 +6687,11 @@ float Unit::SpellCritChanceTaken(Unit const* caster, SpellInfo const* spellInfo, int32 Unit::SpellHealingBonusDone(Unit* victim, SpellInfo const* spellProto, int32 healamount, DamageEffectType damagetype, uint8 effIndex, uint32 stack /*= 1*/, Spell* spell /*= nullptr*/, AuraEffect const* aurEff /*= nullptr*/) const { + // For totems get healing bonus from owner (statue isn't totem in fact) + if (GetTypeId() == TYPEID_UNIT && IsTotem()) + if (Unit* creator = ObjectAccessor::GetUnit(*this, GetCreatorGUID())) + return creator->SpellHealingBonusDone(victim, spellProto, healamount, damagetype, effIndex, stack, spell, aurEff); + // Some spells don't benefit from done mods if (spellProto->HasAttribute(SPELL_ATTR3_IGNORE_CASTER_MODIFIERS)) return healamount; @@ -6774,6 +6802,10 @@ int32 Unit::SpellHealingBonusDone(Unit* victim, SpellInfo const* spellProto, int float Unit::SpellHealingPctDone(Unit* victim, SpellInfo const* spellProto) const { + // For totems pct done mods are calculated when its calculation is run on the player in SpellHealingBonusDone. + if (GetTypeId() == TYPEID_UNIT && IsTotem()) + return 1.0f; + // No bonus healing for potion spells if (spellProto->SpellFamilyName == SPELLFAMILY_POTION) return 1.0f; @@ -9983,7 +10015,7 @@ Unit* Unit::SelectNearbyTarget(Unit* exclude, float dist) const // remove not LoS targets for (std::list::iterator tIter = targets.begin(); tIter != targets.end();) { - if (!IsWithinLOSInMap(*tIter) || (*tIter)->IsSpiritService() || (*tIter)->IsCritter()) + if (!IsWithinLOSInMap(*tIter) || (*tIter)->IsTotem() || (*tIter)->IsSpiritService() || (*tIter)->IsCritter()) targets.erase(tIter++); else ++tIter; @@ -10471,7 +10503,7 @@ void Unit::PlayOneShotAnimKitId(uint16 animKitId) } // Do KILL and KILLED procs. KILL proc is called only for the unit who landed the killing blow (and its owner - for pets and totems) regardless of who tapped the victim - if (attacker && attacker->IsGuardian()) + if (attacker && (attacker->IsPet() || attacker->IsTotem())) { // proc only once for victim if (Unit* owner = attacker->GetOwner()) diff --git a/src/server/scripts/World/npcs_special.cpp b/src/server/scripts/World/npcs_special.cpp index 2684b9eb30..3e6970c9cc 100644 --- a/src/server/scripts/World/npcs_special.cpp +++ b/src/server/scripts/World/npcs_special.cpp @@ -3084,9 +3084,9 @@ struct npc_shaman_searing_totem : public TotemAI { // The Searing Totem prefers targets afflicted by its creator's Flameshock and Stormstrike ability std::list targets; - Trinity::NearestAttackableUnitInObjectRangeCheck u_check(me, Coalesce(me->GetCreator(), me), 15.f); + Trinity::NearestAttackableUnitInObjectRangeCheck u_check(me, Coalesce(me->GetCreator(), me), _spellRange); Trinity::UnitListSearcher searcher(me, targets, u_check); - Cell::VisitAllObjects(me, searcher, 15.f); + Cell::VisitAllObjects(me, searcher, _spellRange); if (!targets.empty()) {