diff --git a/data/sql/updates/db_world/2024_10_04_00.sql b/data/sql/updates/db_world/2024_10_04_00.sql new file mode 100644 index 000000000..028045138 --- /dev/null +++ b/data/sql/updates/db_world/2024_10_04_00.sql @@ -0,0 +1,18 @@ +-- DB update 2024_10_03_00 -> 2024_10_04_00 +DELETE FROM `spell_linked_spell` WHERE `spell_trigger` IN (-41608, -41609, -41610, -41611, -46837, -46839, -45373); +INSERT INTO `spell_linked_spell` (`spell_trigger`, `spell_effect`, `type`, `comment`) VALUES +(-41609, -41607, 0, 'Shattrath Flask of Fortification'), +(-41610, -41605, 0, 'Shattrath Flask of Mighty Restoration'), +(-41611, -41604, 0, 'Shattrath Flask of Supreme Power'), +(-41608, -41606, 0, 'Shattrath Flask of Relentless Assault'), +(-46837, -46838, 0, 'Shattrath Flask of Pure Death'), +(-46839, -46840, 0, 'Shattrath Flask of Blinding Light'), +(-45373, -45374, 0, 'Bloodberry Elixir'); + +UPDATE `spell_area` SET `spell` = 41607, `aura_spell` = 41609, `autocast` = 1 WHERE `spell` = 41609; +UPDATE `spell_area` SET `spell` = 41605, `aura_spell` = 41610, `autocast` = 1 WHERE `spell` = 41610; +UPDATE `spell_area` SET `spell` = 41604, `aura_spell` = 41611, `autocast` = 1 WHERE `spell` = 41611; +UPDATE `spell_area` SET `spell` = 41606, `aura_spell` = 41608, `autocast` = 1 WHERE `spell` = 41608; +UPDATE `spell_area` SET `spell` = 46838, `aura_spell` = 46837, `autocast` = 1 WHERE `spell` = 46837; +UPDATE `spell_area` SET `spell` = 46840, `aura_spell` = 46839, `autocast` = 1 WHERE `spell` = 46839; +UPDATE `spell_area` SET `spell` = 45374, `aura_spell` = 45373, `autocast` = 1 WHERE `spell` = 45373; diff --git a/data/sql/updates/db_world/2024_10_08_00.sql b/data/sql/updates/db_world/2024_10_08_00.sql new file mode 100644 index 000000000..1e87d9351 --- /dev/null +++ b/data/sql/updates/db_world/2024_10_08_00.sql @@ -0,0 +1,3 @@ +-- DB update 2024_10_04_00 -> 2024_10_08_00 +DELETE FROM `spell_custom_attr` WHERE `spell_id` = 41342; +INSERT INTO `spell_custom_attr` (`spell_id`, `attributes`) VALUES (41342, 32768); diff --git a/data/sql/updates/db_world/2024_10_09_00.sql b/data/sql/updates/db_world/2024_10_09_00.sql new file mode 100644 index 000000000..1ee95eede --- /dev/null +++ b/data/sql/updates/db_world/2024_10_09_00.sql @@ -0,0 +1,2 @@ +-- DB update 2024_10_08_00 -> 2024_10_09_00 +DELETE FROM `spell_dbc` WHERE `ID` IN (5143, 5144, 5145, 8416, 8417, 10211, 10212, 25345, 27075, 38699, 38704, 42843, 42846); diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp index fb7b01fec..5c6387d06 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp @@ -584,7 +584,8 @@ Player* ScriptedAI::SelectTargetFromPlayerList(float maxdist, uint32 excludeAura BossAI::BossAI(Creature* creature, uint32 bossId) : ScriptedAI(creature), instance(creature->GetInstanceScript()), summons(creature), - _bossId(bossId) + _bossId(bossId), + _nextHealthCheck(0, { }, false) { callForHelpRange = 0.0f; if (instance) @@ -733,22 +734,20 @@ void BossAI::UpdateAI(uint32 diff) void BossAI::DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*damagetype*/, SpellSchoolMask /*damageSchoolMask*/) { - if (!_healthCheckEvents.empty()) - { - for (auto& check : _healthCheckEvents) + if (_nextHealthCheck._valid) + if (me->HealthBelowPctDamaged(_nextHealthCheck._healthPct, damage)) { - if (check._valid && me->HealthBelowPctDamaged(check._healthPct, damage)) - { - check._exec(); - check._valid = false; - } - } + _nextHealthCheck._exec(); + _nextHealthCheck._valid = false; - _healthCheckEvents.remove_if([&](HealthCheckEventData data) -> bool - { - return !data._valid; - }); - } + _healthCheckEvents.remove_if([&](HealthCheckEventData data) -> bool + { + return data._healthPct == _nextHealthCheck._healthPct; + }); + + if (!_healthCheckEvents.empty()) + _nextHealthCheck = _healthCheckEvents.front(); + } } /** @@ -760,6 +759,7 @@ void BossAI::DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /* void BossAI::ScheduleHealthCheckEvent(uint32 healthPct, std::function exec) { _healthCheckEvents.push_back(HealthCheckEventData(healthPct, exec)); + _nextHealthCheck = _healthCheckEvents.front(); }; void BossAI::ScheduleHealthCheckEvent(std::initializer_list healthPct, std::function exec) @@ -768,6 +768,8 @@ void BossAI::ScheduleHealthCheckEvent(std::initializer_list healthPct, st { _healthCheckEvents.push_back(HealthCheckEventData(checks, exec)); } + + _nextHealthCheck = _healthCheckEvents.front(); } // WorldBossAI - for non-instanced bosses diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.h b/src/server/game/AI/ScriptedAI/ScriptedCreature.h index 69830d815..f7e55f32e 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.h +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.h @@ -503,6 +503,7 @@ protected: private: uint32 const _bossId; std::list _healthCheckEvents; + HealthCheckEventData _nextHealthCheck; }; class WorldBossAI : public ScriptedAI diff --git a/src/server/game/Combat/ThreatMgr.cpp b/src/server/game/Combat/ThreatMgr.cpp index 185ad3f9d..620556c5f 100644 --- a/src/server/game/Combat/ThreatMgr.cpp +++ b/src/server/game/Combat/ThreatMgr.cpp @@ -416,7 +416,7 @@ ThreatMgr::ThreatMgr(Unit* owner) : iCurrentVictim(nullptr), iOwner(owner), iUpd void ThreatMgr::ClearAllThreat() { - if (iOwner->CanHaveThreatList() && !isThreatListEmpty()) + if (iOwner->CanHaveThreatList(true) && !isThreatListEmpty()) iOwner->SendClearThreatListOpcode(); clearReferences(); } diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 51bd3bef8..589a132e4 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -1876,7 +1876,7 @@ bool Creature::IsInvisibleDueToDespawn() const if (Unit::IsInvisibleDueToDespawn()) return true; - if (IsAlive() || m_corpseRemoveTime > GameTime::GetGameTime().count()) + if (IsAlive() || isDying() || m_corpseRemoveTime > GameTime::GetGameTime().count()) return false; return true; diff --git a/src/server/game/Entities/Unit/CharmInfo.cpp b/src/server/game/Entities/Unit/CharmInfo.cpp index c278bd366..425d15579 100644 --- a/src/server/game/Entities/Unit/CharmInfo.cpp +++ b/src/server/game/Entities/Unit/CharmInfo.cpp @@ -87,6 +87,7 @@ void CharmInfo::InitPossessCreateSpells() case 27664: // Crashin' Thrashin' Racer case 40281: // Crashin' Thrashin' Racer case 23109: // Vengeful Spirit + case 25653: // Power of the Blue Flight break; default: InitEmptyActionBar(); diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 1854dec24..ce0da4e6e 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -1893,6 +1893,23 @@ void Unit::DealMeleeDamage(CalcDamageInfo* damageInfo, bool durabilityLoss) Unit::DealDamage(this, victim, damageInfo->damages[i].damage, &cleanDamage, DIRECT_DAMAGE, SpellSchoolMask(damageInfo->damages[i].damageSchoolMask), nullptr, durabilityLoss); } + // gain rage if attack is fully blocked, dodged or parried + if (HasActivePowerType(POWER_RAGE) && (damageInfo->TargetState == VICTIMSTATE_BLOCKS || damageInfo->TargetState == VICTIMSTATE_DODGE || damageInfo->TargetState == VICTIMSTATE_PARRY)) + { + switch (damageInfo->attackType) + { + case BASE_ATTACK: + case OFF_ATTACK: + { + uint32 weaponSpeedHitFactor = uint32(GetAttackTime(damageInfo->attackType) / 1000.0f * (damageInfo->attackType == BASE_ATTACK ? 3.5f : 1.75f)); + RewardRage(damageInfo->cleanDamage, weaponSpeedHitFactor, true); + break; + } + default: + break; + } + } + // If this is a creature and it attacks from behind it has a probability to daze it's victim if ((damageInfo->damages[0].damage + damageInfo->damages[1].damage) && ((damageInfo->hitOutCome == MELEE_HIT_CRIT || damageInfo->hitOutCome == MELEE_HIT_CRUSHING || damageInfo->hitOutCome == MELEE_HIT_NORMAL || damageInfo->hitOutCome == MELEE_HIT_GLANCING) && !IsPlayer() && !ToCreature()->IsControlledByPlayer() && !victim->HasInArc(M_PI, this) @@ -8970,9 +8987,9 @@ bool Unit::HandleProcTriggerSpell(Unit* victim, uint32 damage, AuraEffect* trigg if (GetStat(STAT_AGILITY) > stat) { trigger_spell_id = 67772; } break; } - // Mana Drain Trigger - case 27522: - case 40336: + case 27522: // Mana Drain Trigger + case 40336: // Mana Drain Trigger + case 46939: // Black Bow of the Betrayer { // On successful melee or ranged attack gain $29471s1 mana and if possible drain $27526s1 mana from the target. if (IsAlive()) @@ -14535,6 +14552,7 @@ void Unit::setDeathState(DeathState s, bool despawn) { // death state needs to be updated before RemoveAllAurasOnDeath() calls HandleChannelDeathItem(..) so that // it can be used to check creation of death items (such as soul shards). + m_deathState = s; if (s != DeathState::Alive && s != DeathState::JustRespawned) { @@ -14584,8 +14602,6 @@ void Unit::setDeathState(DeathState s, bool despawn) { RemoveFlag (UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE); // clear skinnable for creature and player (at battleground) } - - m_deathState = s; } /*######################################## @@ -14593,14 +14609,14 @@ void Unit::setDeathState(DeathState s, bool despawn) ######## AGGRO SYSTEM ######## ######## ######## ########################################*/ -bool Unit::CanHaveThreatList() const +bool Unit::CanHaveThreatList(bool skipAliveCheck) const { // only creatures can have threat list if (!IsCreature()) return false; // only alive units can have threat list - if (!IsAlive() || isDying()) + if (!skipAliveCheck && !IsAlive()) return false; // totems can not have threat list @@ -20144,6 +20160,7 @@ void Unit::SendRemoveFromThreatListOpcode(HostileReference* pHostileReference) void Unit::RewardRage(uint32 damage, uint32 weaponSpeedHitFactor, bool attacker) { + // Rage formulae https://wowwiki-archive.fandom.com/wiki/Rage#Formulae float addRage; float rageconversion = ((0.0091107836f * GetLevel() * GetLevel()) + 3.225598133f * GetLevel()) + 4.2652911f; @@ -20154,9 +20171,10 @@ void Unit::RewardRage(uint32 damage, uint32 weaponSpeedHitFactor, bool attacker) if (attacker) { - addRage = (damage / rageconversion * 7.5f + weaponSpeedHitFactor) / 2; - - // talent who gave more rage on attack + // see Bornak's bluepost explanation (05/29/2009) + float rageFromDamageDealt = damage / rageconversion * 7.5f; + addRage = (rageFromDamageDealt + weaponSpeedHitFactor) / 2.0f; + addRage = std::min(addRage, rageFromDamageDealt * 2.0f); AddPct(addRage, GetTotalAuraModifier(SPELL_AURA_MOD_RAGE_FROM_DAMAGE_DEALT)); } else diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 78b406e0f..35dba10f6 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -831,7 +831,7 @@ public: void SetCombatTimer(uint32 timer) { m_CombatTimer = timer; } // Threat related methods - [[nodiscard]] bool CanHaveThreatList() const; + [[nodiscard]] bool CanHaveThreatList(bool skipAliveCheck = false) const; void AddThreat(Unit* victim, float fThreat, SpellSchoolMask schoolMask = SPELL_SCHOOL_MASK_NORMAL, SpellInfo const* threatSpell = nullptr); float ApplyTotalThreatModifier(float fThreat, SpellSchoolMask schoolMask = SPELL_SCHOOL_MASK_NORMAL); void TauntApply(Unit* victim); diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index d6b6cb511..e69bd401c 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -2929,7 +2929,7 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target) { if (missInfo != SPELL_MISS_EVADE && !m_caster->IsFriendlyTo(effectUnit) && (!m_spellInfo->IsPositive() || m_spellInfo->HasEffect(SPELL_EFFECT_DISPEL))) { - if (!m_triggeredByAuraSpell.spellInfo || (!(m_triggeredByAuraSpell.spellInfo->Effects[m_triggeredByAuraSpell.effectIndex].TriggerSpell == m_spellInfo->Id) && !(m_triggeredByAuraSpell.spellInfo->IsAuraEffectEqual(m_spellInfo)))) + if (!m_triggeredByAuraSpell.spellInfo || m_damage || (!(m_triggeredByAuraSpell.spellInfo->Effects[m_triggeredByAuraSpell.effectIndex].TriggerSpell == m_spellInfo->Id) && !(m_triggeredByAuraSpell.spellInfo->IsAuraEffectEqual(m_spellInfo)))) m_caster->CombatStart(effectUnit, !(m_spellInfo->AttributesEx3 & SPELL_ATTR3_SUPRESS_TARGET_PROCS)); // Patch 3.0.8: All player spells which cause a creature to become aggressive to you will now also immediately cause the creature to be tapped. diff --git a/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter1.cpp b/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter1.cpp index 5b8d272f6..b983b0b30 100644 --- a/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter1.cpp +++ b/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter1.cpp @@ -289,7 +289,7 @@ public: _duelInProgress = true; timer = 600000; // clear playerGUIDs after 10 minutes if no one initiates a duel - me->GetMotionMaster()->MoveFollow(caster, 2.0f, 0.0f); + me->SetFacingToObject(caster); events.ScheduleEvent(EVENT_SPEAK, 3s); events.ScheduleEvent(EVENT_SPEAK + 1, 7s); diff --git a/src/server/scripts/Northrend/zone_dragonblight.cpp b/src/server/scripts/Northrend/zone_dragonblight.cpp index 4f7d18fd9..3cf2d71ef 100644 --- a/src/server/scripts/Northrend/zone_dragonblight.cpp +++ b/src/server/scripts/Northrend/zone_dragonblight.cpp @@ -2137,6 +2137,7 @@ public: { _textCounter = 1; _playerGUID.Clear(); + _events.Reset(); } void JustEngagedWith(Unit* who) override diff --git a/src/server/scripts/Outland/BlackTemple/boss_illidan.cpp b/src/server/scripts/Outland/BlackTemple/boss_illidan.cpp index 067970420..80a152a4f 100644 --- a/src/server/scripts/Outland/BlackTemple/boss_illidan.cpp +++ b/src/server/scripts/Outland/BlackTemple/boss_illidan.cpp @@ -1317,8 +1317,18 @@ struct npc_flame_of_azzinoth : public ScriptedAI { ScheduleTimedEvent(10s, [&] { if (Creature* _blade = ObjectAccessor::GetCreature(*me, _bladeGUID)) + { + Unit* offTank = nullptr; + + if (Creature* secondBlaze = me->FindNearestCreature(NPC_BLAZE, 100.0f, true)) + offTank = secondBlaze->GetVictim(); + if (Unit* target = _blade->AI()->SelectTarget(SelectTargetMethod::Random, 0, -40.0f, true)) - DoCast(target, SPELL_CHARGE); + { + if (!offTank || offTank != target) + DoCast(target, SPELL_CHARGE); + } + } }, 5s, 20s); ScheduleTimedEvent(10s, 20s, [&] { diff --git a/src/server/scripts/Outland/BlackTemple/boss_mother_shahraz.cpp b/src/server/scripts/Outland/BlackTemple/boss_mother_shahraz.cpp index d0028399a..1149c7b69 100644 --- a/src/server/scripts/Outland/BlackTemple/boss_mother_shahraz.cpp +++ b/src/server/scripts/Outland/BlackTemple/boss_mother_shahraz.cpp @@ -196,12 +196,22 @@ class spell_mother_shahraz_saber_lash_aura : public AuraScript } }; -const Position validTeleportStairsPos[4] = +const Position validTeleportStairsPos[9] = { + //Platform teleports + + {945.00f, 149.17f, 197.07483}, + {956.92f, 153.20f, 197.07483}, + {933.69f, 154.15f, 197.07483}, + + //Floor teleports + {966.87f, 184.45f, 192.84f}, {927.22f, 187.04f, 192.84f}, {922.54f, 110.09f, 192.84f}, - {958.01f, 110.47f, 192.84f} + {958.01f, 110.47f, 192.84f}, + {939.95f, 108.29f, 192.84f}, + {945.68f, 205.74f, 192.84f} }; constexpr float minTeleportDist = 30.f; @@ -227,7 +237,7 @@ class spell_mother_shahraz_fatal_attraction : public SpellScript // Check if the boss is near stairs to avoid players falling through the platform with random teleports. if (GetCaster()->GetPositionY() < 194.f) - finalDest = validTeleportStairsPos[urand(0, 3)]; + finalDest = validTeleportStairsPos[urand(0, 8)]; else { finalDest = GetCaster()->GetNearPosition(frand(minTeleportDist, maxTeleportDist), static_cast(rand_norm()) * static_cast(2 * M_PI), true);