diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index cbee7da6fd..e41239024c 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -1788,18 +1788,27 @@ void Player::RegenerateAll(uint32 diff) for (uint8 i = 0; i < MAX_RUNES; i += 2) { uint8 runeToRegen = i; - uint32 cd = GetRuneCooldown(i); - uint32 secondRuneCd = GetRuneCooldown(i + 1); - float cdmod = GetFloatValue(PLAYER_RUNE_REGEN_1) * 10.0f; + RuneType runeType = GetCurrentRune(runeToRegen); + float runeCooldown = GetRuneCooldown(i); + float secondRuneCd = GetRuneCooldown(i + 1); + // Regenerate second rune of the same type only after first rune is off the cooldown - if (secondRuneCd && (cd > secondRuneCd || !cd)) + if (G3D::fuzzyNe(secondRuneCd, 0.0f) && (runeCooldown > secondRuneCd || G3D::fuzzyEq(runeCooldown, 0.0f))) { runeToRegen = i + 1; - cd = secondRuneCd; + runeType = GetCurrentRune(runeToRegen); + runeCooldown = secondRuneCd; } - if (cd) - SetRuneCooldown(runeToRegen, (cd > (diff * cdmod)) ? cd - (diff * cdmod) : 0); + // No cooldown, nothing to do + if (G3D::fuzzyEq(runeCooldown, 0.0f)) + continue; + + float cdDiff = GetFloatValue(PLAYER_RUNE_REGEN_1 + AsUnderlyingType(runeType)) * 0.001f * diff; + if (runeCooldown > cdDiff) + SetRuneCooldown(runeToRegen, runeCooldown - cdDiff); + else + SetRuneCooldown(runeToRegen, 0.0f); } } } @@ -22807,9 +22816,6 @@ void Player::SendInitialPacketsBeforeAddToMap(bool firstLogin /*= false*/) { // Initialize rune cooldowns ResyncRunes(); - - // Send already converted runes - SendConvertedRunes(); } // Spell modifiers @@ -22884,6 +22890,10 @@ void Player::SendInitialPacketsAfterAddToMap() if (HasAuraType(SPELL_AURA_MOD_ROOT)) SetRooted(true, true); + // only send the packet for converted runes instead of converting them again + if (HasAuraType(SPELL_AURA_CONVERT_RUNE)) + SendConvertedRunes(); + SendAurasForTarget(this); SendEnchantmentDurations(); // must be after add to map SendItemDurations(); // must be after add to map @@ -24814,11 +24824,95 @@ bool Player::isTotalImmunity() const return false; } +void Runes::SetRuneState(uint8 index, bool set /*= true*/) +{ + if (set) + RuneState |= (1 << index); // usable + else + RuneState &= ~(1 << index); // on cooldown +} + +void Player::ResyncRunes() const +{ + WorldPackets::Spells::ResyncRunes packet; + for (uint8 i = 0; i < MAX_RUNES; ++i) + { + WorldPackets::Spells::ResyncRune resyncRune; + resyncRune.RuneType = AsUnderlyingType(GetCurrentRune(i)); + resyncRune.Cooldown = uint8((1.0f - GetRuneCooldown(i)) * uint32(255)); + packet.Runes.emplace_back(resyncRune); + } + + SendDirectMessage(packet.Write()); +} + +void Player::AddRunePower(uint8 mask) const +{ + WorldPackets::Spells::AddRunePower packet; + packet.AddedRunesMask = mask; // mask (0x00-0x3F probably) + SendDirectMessage(packet.Write()); +} + +static constexpr RuneType runeSlotTypes[MAX_RUNES] = +{ + /*0*/ RuneType::Blood, + /*1*/ RuneType::Blood, + /*2*/ RuneType::Unholy, + /*3*/ RuneType::Unholy, + /*4*/ RuneType::Frost, + /*5*/ RuneType::Frost +}; + +void Player::InitRunes() +{ + if (getClass() != CLASS_DEATH_KNIGHT) + return; + + m_runes = std::make_unique(); + m_runes->RuneState = 0; + m_runes->LastUsedRune = RuneType::Blood; + m_runes->LastUsedRuneMask = 0; + + for (uint8 i = 0; i < MAX_RUNES; ++i) + { + SetBaseRune(i, runeSlotTypes[i]); // init base types + SetCurrentRune(i, runeSlotTypes[i]); // init current types + SetRuneCooldown(i, 0.0f); // reset cooldowns + SetRuneConvertAura(i, nullptr, SPELL_AURA_NONE, nullptr); + m_runes->SetRuneState(i); + } + + for (uint8 i = 0; i < AsUnderlyingType(RuneType::Max); ++i) + SetFloatValue(PLAYER_RUNE_REGEN_1 + i, 0.1f); // set a base regen timer equal to 10 sec +} + +bool Player::IsRuneFullyDepleted(uint8 index) const +{ + return G3D::fuzzyEq(GetRuneCooldown(index), RUNE_BASE_COOLDOWN); +} + +bool Player::HasFullyDepletedRune(RuneType runeType) const +{ + if (!m_runes) + return false; + + for (uint8 i = 0; i < MAX_RUNES; ++i) + { + if (GetBaseRune(i) != runeType) + continue; + + if (IsRuneFullyDepleted(i)) + return true; + } + + return false; +} + void Player::SetRuneConvertAura(uint8 index, AuraEffect const* aura, AuraType auraType, SpellInfo const* spellInfo) { - m_runes->runes[index].ConvertAura = aura; - m_runes->runes[index].ConvertAuraType = auraType; - m_runes->runes[index].ConvertAuraInfo = spellInfo; + m_runes->_Runes[index].ConvertAura = aura; + m_runes->_Runes[index].ConvertAuraType = auraType; + m_runes->_Runes[index].ConvertAuraInfo = spellInfo; } void Player::AddRuneByAuraEffect(uint8 index, RuneType newType, AuraEffect const* aura, AuraType auraType, SpellInfo const* spellInfo) @@ -24827,12 +24921,9 @@ void Player::AddRuneByAuraEffect(uint8 index, RuneType newType, AuraEffect const ConvertRune(index, newType); } -void Player::SetRuneCooldown(uint8 index, uint32 cooldown) +void Player::SetRuneCooldown(uint8 index, float cooldown) { - if (AuraEffect* aurEff = GetAuraEffect(SPELL_AURA_DUMMY, SPELLFAMILY_GENERIC, 2636, EFFECT_2)) - cooldown *= 1.0 - (aurEff->GetAmount() / 100); - - m_runes->runes[index].Cooldown = cooldown; + m_runes->_Runes[index].Cooldown = cooldown; m_runes->SetRuneState(index, (cooldown == 0) ? true : false); } @@ -24840,7 +24931,7 @@ void Player::RemoveRunesByAuraEffect(AuraEffect const* aura) { for (uint8 i = 0; i < MAX_RUNES; ++i) { - if (m_runes->runes[i].ConvertAura == aura) + if (m_runes->_Runes[i].ConvertAura == aura) { ConvertRune(i, GetBaseRune(i)); SetRuneConvertAura(i, nullptr, SPELL_AURA_NONE, nullptr); @@ -24850,17 +24941,17 @@ void Player::RemoveRunesByAuraEffect(AuraEffect const* aura) void Player::RestoreBaseRune(uint8 index) { - SpellInfo const* spellInfo = m_runes->runes[index].ConvertAuraInfo; - AuraType type = m_runes->runes[index].ConvertAuraType; + SpellInfo const* spellInfo = m_runes->_Runes[index].ConvertAuraInfo; + AuraType type = m_runes->_Runes[index].ConvertAuraType; // If rune was converted by a non-pasive aura that still active we should keep it converted if (spellInfo) { - if (!(spellInfo->Attributes & SPELL_ATTR0_PASSIVE)) - return; + if (!(spellInfo->Attributes & SPELL_ATTR0_PASSIVE)) + return; - // Don't even convert aura for passive convertion - if (spellInfo->IsPassive() && type == SPELL_AURA_CONVERT_RUNE) - return; + // Don't even convert aura for passive convertion + if (spellInfo->IsPassive() && type == SPELL_AURA_CONVERT_RUNE) + return; } ConvertRune(index, GetBaseRune(index)); @@ -24873,89 +24964,28 @@ void Player::ConvertRune(uint8 index, RuneType newType) WorldPackets::Spells::ConvertRune packet; packet.Index = index; - packet.Rune = newType; - - SendDirectMessage(packet.Write()); -} - -void Player::ResyncRunes() -{ - WorldPackets::Spells::ResyncRunes packet; - for (uint8 i = 0; i < MAX_RUNES; ++i) - { - WorldPackets::Spells::ResyncRune resyncRune; - resyncRune.RuneType = GetCurrentRune(i); - resyncRune.Cooldown = uint8(GetRuneCooldown(i) * uint32(255) / uint32(RUNE_BASE_COOLDOWN)); - packet.Runes.emplace_back(resyncRune); - } + packet.Rune = AsUnderlyingType(newType); SendDirectMessage(packet.Write()); } void Player::SendConvertedRunes() { + if (!m_runes) + return; + for (uint8 i = 0; i < MAX_RUNES; ++i) { if (GetBaseRune(i) != GetCurrentRune(i)) { WorldPackets::Spells::ConvertRune convertRune; convertRune.Index = i; - convertRune.Rune = GetCurrentRune(i); + convertRune.Rune = AsUnderlyingType(GetCurrentRune(i)); SendDirectMessage(convertRune.Write()); } } } -void Player::AddRunePower(uint8 mask) -{ - WorldPackets::Spells::AddRunePower packet; - packet.AddedRunesMask = mask; // mask (0x00-0x3F probably) - SendDirectMessage(packet.Write()); -} - -static RuneType runeSlotTypes[MAX_RUNES] = -{ - /*0*/ RUNE_BLOOD, - /*1*/ RUNE_BLOOD, - /*2*/ RUNE_UNHOLY, - /*3*/ RUNE_UNHOLY, - /*4*/ RUNE_FROST, - /*5*/ RUNE_FROST -}; - -void Player::InitRunes() -{ - if (getClass() != CLASS_DEATH_KNIGHT) - return; - - m_runes = std::make_unique(); - - m_runes->runeState = 0; - m_runes->lastUsedRune = RUNE_BLOOD; - m_runes->lastUsedRuneMask = 0; - - for (uint8 i = 0; i < MAX_RUNES; ++i) - { - SetBaseRune(i, runeSlotTypes[i]); // init base types - SetCurrentRune(i, runeSlotTypes[i]); // init current types - SetRuneCooldown(i, 0); // reset cooldowns - SetRuneConvertAura(i, nullptr, SPELL_AURA_NONE, nullptr); - m_runes->SetRuneState(i); - } - - for (uint8 i = 0; i < NUM_RUNE_TYPES; ++i) - SetFloatValue(PLAYER_RUNE_REGEN_1 + i, 0.1f); // set a base regen timer equal to 10 sec -} - -bool Player::IsBaseRuneSlotsOnCooldown(RuneType runeType) const -{ - for (uint8 i = 0; i < MAX_RUNES; ++i) - if (GetBaseRune(i) == runeType && GetRuneCooldown(i) == 0) - return false; - - return true; -} - void Player::AutoStoreLoot(uint8 bag, uint8 slot, uint32 loot_id, LootStore const& store, bool broadcast) { Loot loot; diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index e9f87d35c1..df3b73f676 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -340,28 +340,24 @@ struct Areas float y2; }; -#define MAX_RUNES 6 +constexpr uint8 MAX_RUNES = 6; +constexpr float RUNE_BASE_COOLDOWN = 1.0f; -enum RuneCooldowns +enum class RuneType : uint8 { - RUNE_BASE_COOLDOWN = 10000, - RUNE_MISS_COOLDOWN = 1500 // cooldown applied on runes when the spell misses -}; -enum RuneType : uint8 -{ - RUNE_BLOOD = 0, - RUNE_UNHOLY = 1, - RUNE_FROST = 2, - RUNE_DEATH = 3, - NUM_RUNE_TYPES = 4 + Blood = 0, + Unholy = 1, + Frost = 2, + Death = 3, + Max = 4 }; struct RuneInfo { - uint8 BaseRune; - uint8 CurrentRune; - uint32 Cooldown; + RuneType BaseRune; + RuneType CurrentRune; + float Cooldown; AuraEffect const* ConvertAura; AuraType ConvertAuraType; SpellInfo const* ConvertAuraInfo; @@ -369,18 +365,12 @@ struct RuneInfo struct Runes { - RuneInfo runes[MAX_RUNES]; - uint8 runeState; // mask of available runes - RuneType lastUsedRune; - uint8 lastUsedRuneMask; + RuneInfo _Runes[MAX_RUNES]; + uint8 RuneState; // mask of available runes + RuneType LastUsedRune; + uint8 LastUsedRuneMask; - void SetRuneState(uint8 index, bool set = true) - { - if (set) - runeState |= (1 << index); // usable - else - runeState &= ~(1 << index); // on cooldown - } + void SetRuneState(uint8 index, bool set = true); }; struct EnchantDuration @@ -2328,27 +2318,34 @@ class TC_GAME_API Player : public Unit, public GridObject DeclinedName const* GetDeclinedNames() const { return m_declinedname.get(); } - uint8 GetRunesState() const { return m_runes->runeState; } - RuneType GetBaseRune(uint8 index) const { return RuneType(m_runes->runes[index].BaseRune); } - RuneType GetCurrentRune(uint8 index) const { return RuneType(m_runes->runes[index].CurrentRune); } - uint32 GetRuneCooldown(uint8 index) const { return m_runes->runes[index].Cooldown; } - bool IsBaseRuneSlotsOnCooldown(RuneType runeType) const; - RuneType GetLastUsedRune() { return m_runes->lastUsedRune; } - uint8 GetLastUsedRuneMask() { return m_runes->lastUsedRuneMask; } - void ClearLastUsedRuneMask() { m_runes->lastUsedRuneMask = 0; } - void SetLastUsedRune(RuneType type) { m_runes->lastUsedRune = type; } - void SetLastUsedRuneIndex(uint8 index) { m_runes->lastUsedRuneMask |= (1 << index); } - void SetBaseRune(uint8 index, RuneType baseRune) { m_runes->runes[index].BaseRune = baseRune; } - void SetCurrentRune(uint8 index, RuneType currentRune) { m_runes->runes[index].CurrentRune = currentRune; } + // Runes + uint8 GetRunesState() const { return m_runes->RuneState; } + RuneType GetBaseRune(uint8 index) const { return RuneType(m_runes->_Runes[index].BaseRune); } + RuneType GetCurrentRune(uint8 index) const { return RuneType(m_runes->_Runes[index].CurrentRune); } + float GetRuneCooldown(uint8 index) const { return m_runes->_Runes[index].Cooldown; } + bool IsRuneFullyDepleted(uint8 index) const; + /// + /// Checks if there is a fully depleted rune. A rune is considered fully depleted when it's on cooldown and isn't recovering yet ('waiting' for another rune to finish regenerating) + /// + /// The kind of base rune that is being checked (Blood, Unholy, Frost). Death Runes are no base rune and can't be checked with this method. Use the underlying base rune instead. + /// true when there is a fully depleted rune + bool HasFullyDepletedRune(RuneType runeType) const; + RuneType GetLastUsedRune() { return m_runes->LastUsedRune; } + uint8 GetLastUsedRuneMask() { return m_runes->LastUsedRuneMask; } + void ClearLastUsedRuneMask() { m_runes->LastUsedRuneMask = 0; } + void SetLastUsedRune(RuneType type) { m_runes->LastUsedRune = type; } + void SetLastUsedRuneIndex(uint8 index) { m_runes->LastUsedRuneMask |= (1 << index); } + void SetBaseRune(uint8 index, RuneType baseRune) { m_runes->_Runes[index].BaseRune = baseRune; } + void SetCurrentRune(uint8 index, RuneType currentRune) { m_runes->_Runes[index].CurrentRune = currentRune; } void SetRuneConvertAura(uint8 index, AuraEffect const* aura, AuraType auraType, SpellInfo const* spellInfo); void AddRuneByAuraEffect(uint8 index, RuneType newType, AuraEffect const* aura, AuraType auraType, SpellInfo const* spellInfo); - void SetRuneCooldown(uint8 index, uint32 cooldown); + void SetRuneCooldown(uint8 index, float cooldown); void RemoveRunesByAuraEffect(AuraEffect const* aura); void RestoreBaseRune(uint8 index); void ConvertRune(uint8 index, RuneType newType); - void ResyncRunes(); void SendConvertedRunes(); - void AddRunePower(uint8 mask); + void ResyncRunes() const; + void AddRunePower(uint8 mask) const; void InitRunes(); void SendRespondInspectAchievements(Player* player) const; diff --git a/src/server/game/Entities/Unit/StatSystem.cpp b/src/server/game/Entities/Unit/StatSystem.cpp index 9873df8ff6..3d5b221fac 100644 --- a/src/server/game/Entities/Unit/StatSystem.cpp +++ b/src/server/game/Entities/Unit/StatSystem.cpp @@ -180,7 +180,7 @@ void Unit::UpdatePowerRegeneration(Powers powerType) baseRegen += powerRegenMod; if (IsPlayer()) - for (uint8 i = 0; i < NUM_RUNE_TYPES; ++i) + for (uint8 i = 0; i < AsUnderlyingType(RuneType::Max); ++i) SetFloatValue(PLAYER_RUNE_REGEN_1 + i, baseRegen); break; } diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp index bdc19ea62a..0d5d0eded0 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp +++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp @@ -5071,13 +5071,8 @@ void AuraEffect::HandleAuraConvertRune(AuraApplication const* aurApp, uint8 mode if (!(mode & AURA_EFFECT_HANDLE_REAL)) return; - Unit* target = aurApp->GetTarget(); - - Player* player = target->ToPlayer(); - if (!player) - return; - - if (player->getClass() != CLASS_DEATH_KNIGHT) + Player* playerTarget = aurApp->GetTarget()->ToPlayer(); + if (!playerTarget || playerTarget->getClass() != CLASS_DEATH_KNIGHT) return; /* @@ -5087,24 +5082,20 @@ void AuraEffect::HandleAuraConvertRune(AuraApplication const* aurApp, uint8 mode * Amount is the number of runes that will be converted if available */ - uint32 runes = GetAmount(); - + uint32 runesToConvert = GetAmount(); if (apply) { - for (uint32 i = 0; i < MAX_RUNES && runes; ++i) + for (uint8 i = 0; i < MAX_RUNES && runesToConvert > 0; ++i) { - if (RuneType(GetMiscValue()) != player->GetCurrentRune(i)) + if (static_cast(GetMiscValue()) != playerTarget->GetCurrentRune(i)) continue; - if (!player->GetRuneCooldown(i)) - { - player->AddRuneByAuraEffect(i, RuneType(GetMiscValueB()), this, GetAuraType(), GetSpellInfo()); - --runes; - } + playerTarget->AddRuneByAuraEffect(i, static_cast(GetMiscValueB()), this, GetAuraType(), GetSpellInfo()); + --runesToConvert; } } else - player->RemoveRunesByAuraEffect(this); + playerTarget->RemoveRunesByAuraEffect(this); } void AuraEffect::HandleAuraLinked(AuraApplication const* aurApp, uint8 mode, bool apply) const diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index a0be7b3452..ad52caf80f 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -4569,11 +4569,7 @@ void Spell::SendSpellStart() castData.RemainingRunes->Start = m_runesState; // runes state before castData.RemainingRunes->Count = player->GetRunesState(); // runes state after for (uint8 i = 0; i < MAX_RUNES; ++i) - { - // float casts ensure the division is performed on floats as we need float result - float baseCd = float(uint32(RUNE_BASE_COOLDOWN)); - castData.RemainingRunes->Cooldowns.push_back((baseCd - float(player->GetRuneCooldown(i))) / baseCd * 255); // rune cooldown passed - } + castData.RemainingRunes->Cooldowns.push_back((1.0f - player->GetRuneCooldown(i)) * uint32(255)); // rune cooldown passed } else { @@ -4693,11 +4689,7 @@ void Spell::SendSpellGo() castData.RemainingRunes->Start = m_runesState; // runes state before castData.RemainingRunes->Count = player->GetRunesState(); // runes state after for (uint8 i = 0; i < MAX_RUNES; ++i) - { - // float casts ensure the division is performed on floats as we need float result - float baseCd = float(uint32(RUNE_BASE_COOLDOWN)); - castData.RemainingRunes->Cooldowns.push_back((baseCd - float(player->GetRuneCooldown(i))) / baseCd * 255); // rune cooldown passed - } + castData.RemainingRunes->Cooldowns.push_back((1.0f - player->GetRuneCooldown(i)) * uint32(255)); // rune cooldown passed } else { @@ -5347,29 +5339,29 @@ SpellCastResult Spell::CheckRuneCost(uint32 runeCostID) const if (src->NoRuneCost()) return SPELL_CAST_OK; - int32 runeCost[NUM_RUNE_TYPES]; // blood, frost, unholy, death + std::array runeCost = { }; // blood, frost, unholy, death - for (uint32 i = 0; i < RUNE_DEATH; ++i) + for (uint8 i = 0; i < AsUnderlyingType(RuneType::Death); ++i) { runeCost[i] = src->RuneCost[i]; if (Player* modOwner = m_caster->GetSpellModOwner()) modOwner->ApplySpellMod(m_spellInfo, SpellModOp::PowerCost0, runeCost[i], const_cast(this)); } - runeCost[RUNE_DEATH] = MAX_RUNES; // calculated later + runeCost[AsUnderlyingType(RuneType::Death)] = MAX_RUNES; // calculated later - for (uint32 i = 0; i < MAX_RUNES; ++i) + for (uint8 i = 0; i < MAX_RUNES; ++i) { RuneType rune = player->GetCurrentRune(i); - if ((player->GetRuneCooldown(i) == 0) && (runeCost[rune] > 0)) - runeCost[rune]--; + if (G3D::fuzzyEq(player->GetRuneCooldown(i), 0.0f) && (runeCost[AsUnderlyingType(rune)] > 0)) + runeCost[AsUnderlyingType(rune)]--; } - for (uint32 i = 0; i < RUNE_DEATH; ++i) + for (uint8 i = 0; i < AsUnderlyingType(RuneType::Death); ++i) if (runeCost[i] > 0) - runeCost[RUNE_DEATH] += runeCost[i]; + runeCost[AsUnderlyingType(RuneType::Death)] += runeCost[i]; - if (runeCost[RUNE_DEATH] > MAX_RUNES) + if (runeCost[AsUnderlyingType(RuneType::Death)] > MAX_RUNES) return SPELL_FAILED_NO_POWER; // not sure if result code is correct return SPELL_CAST_OK; @@ -5386,102 +5378,95 @@ void Spell::TakeRunePower(SpellMissInfo hitInfo) Player* player = m_caster->ToPlayer(); m_runesState = player->GetRunesState(); // store previous state - player->ClearLastUsedRuneMask(); - std::array runeCost = { }; // blood, frost, unholy, death + std::array runeCost = { }; // blood, frost, unholy, death // Apply rune cost modifiers - for (uint8 i = 0; i < RUNE_DEATH; ++i) + for (uint8 i = 0; i < AsUnderlyingType(RuneType::Death); ++i) { runeCost[i] = runeCostData->RuneCost[i]; if (Player* modOwner = m_caster->GetSpellModOwner()) modOwner->ApplySpellMod(m_spellInfo, SpellModOp::PowerCost0, runeCost[i], const_cast(this)); } - bool consumeRunes = false; - for (uint8 i = 0; i < RUNE_DEATH; ++i) + bool hasRuneCost = false; + for (uint8 i = 0; i < AsUnderlyingType(RuneType::Death); ++i) if (runeCost[i] != 0) - consumeRunes = true; + hasRuneCost = true; - // If the spell does not require any rune because of power cost spell mods, the Death Knight wont gain any Runic Power (Freezing Fog for example) - if (!consumeRunes && !runeCostData->NoRuneCost()) + // If the spell does not require any rune because of power cost spell mods, the Death Knight won't gain any Runic Power (Freezing Fog for example) + if (!hasRuneCost && !runeCostData->NoRuneCost()) return; - consumeRunes = hitInfo == SPELL_MISS_NONE || hitInfo == SPELL_MISS_MISS; - // Death Strike is special - it consumes runes on dodge and parry as well, because the healing component still goes through. - // There is no flag to control this and it's probably tied to how Blizzard applies spell effects and auras, which we KNOW we do wrong. - if (m_spellInfo->Id == 49998) - consumeRunes |= hitInfo == SPELL_MISS_DODGE || hitInfo == SPELL_MISS_PARRY; + // The attack refunds its runes when missed + if (m_spellInfo->HasAttribute(SPELL_ATTR1_DISCOUNT_POWER_ON_MISS) && hitInfo == SPELL_MISS_MISS) + return; + + player->ClearLastUsedRuneMask(); // Let's say we use a skill that requires a Frost rune. This is the order: // - Frost rune // - Death rune, originally a Frost rune // - Death rune, any kind - runeCost[RUNE_DEATH] = 0; // calculated later + runeCost[AsUnderlyingType(RuneType::Death)] = 0; // calculated later - for (uint32 i = 0; i < MAX_RUNES; ++i) + for (uint8 i = 0; i < MAX_RUNES; ++i) { RuneType rune = player->GetCurrentRune(i); - if (!player->GetRuneCooldown(i) && runeCost[rune] > 0) + if (G3D::fuzzyEq(player->GetRuneCooldown(i), 0.0f) && runeCost[AsUnderlyingType(rune)] > 0) { - player->SetRuneCooldown(i, consumeRunes ? uint32(RUNE_BASE_COOLDOWN) : uint32(RUNE_MISS_COOLDOWN)); + player->SetRuneCooldown(i, RUNE_BASE_COOLDOWN); player->SetLastUsedRune(rune); player->SetLastUsedRuneIndex(i); - --runeCost[rune]; + --runeCost[AsUnderlyingType(rune)]; } } // Find a Death rune where the base rune matches the one we need - runeCost[RUNE_DEATH] = runeCost[RUNE_BLOOD] + runeCost[RUNE_UNHOLY] + runeCost[RUNE_FROST]; + runeCost[AsUnderlyingType(RuneType::Death)] = runeCost[AsUnderlyingType(RuneType::Blood)] + runeCost[AsUnderlyingType(RuneType::Unholy)] + runeCost[AsUnderlyingType(RuneType::Frost)]; - if (runeCost[RUNE_DEATH] > 0) + if (runeCost[AsUnderlyingType(RuneType::Death)] > 0) { for (uint8 i = 0; i < MAX_RUNES; ++i) { RuneType rune = player->GetCurrentRune(i); - if (!player->GetRuneCooldown(i) && rune == RUNE_DEATH) + if (G3D::fuzzyEq(player->GetRuneCooldown(i), 0.0f) && rune == RuneType::Death) { - player->SetRuneCooldown(i, consumeRunes ? uint32(RUNE_BASE_COOLDOWN) : uint32(RUNE_MISS_COOLDOWN)); + player->SetRuneCooldown(i, RUNE_BASE_COOLDOWN); player->SetLastUsedRune(rune); player->SetLastUsedRuneIndex(i); - runeCost[rune]--; + runeCost[AsUnderlyingType(rune)]--; + player->RestoreBaseRune(i); - // keep Death Rune type if missed - if (consumeRunes) - player->RestoreBaseRune(i); - - if (runeCost[RUNE_DEATH] == 0) + if (runeCost[AsUnderlyingType(RuneType::Death)] == 0) break; } } } - // If the spell has consumed runes, runic power will be generated - if (consumeRunes) + // Generate Runic Power + if (int32 runicPowerGain = runeCostData->RunicPower * sWorld->getRate(RATE_POWER_RUNICPOWER_INCOME)) { - if (int32 runicPowerGain = runeCostData->RunicPower * sWorld->getRate(RATE_POWER_RUNICPOWER_INCOME)) + Unit::AuraEffectList const& bonusPct = player->GetAuraEffectsByType(SPELL_AURA_MOD_RUNE_REGEN_SPEED); + for (auto i = bonusPct.begin(); i != bonusPct.end(); ++i) { - Unit::AuraEffectList const& bonusPct = player->GetAuraEffectsByType(SPELL_AURA_MOD_RUNE_REGEN_SPEED); - for (auto i = bonusPct.begin(); i != bonusPct.end(); ++i) + // Improved Frost Presence + if ((*i)->GetId() == 63621) { - // Improved Frost Presence - if ((*i)->GetId() == 63621) - { - // Apply bonus when in Unholy or Blood Presence - if (player->HasAura(48265) || player->HasAura(48263)) - AddPct(runicPowerGain, (*i)->GetAmount()); - continue; - } - else + // Apply bonus when in Unholy or Blood Presence + if (player->HasAura(48265) || player->HasAura(48263)) AddPct(runicPowerGain, (*i)->GetAmount()); + continue; } - - // Apply runic power gain modifiers - if (Player* modOwner = m_caster->GetSpellModOwner()) - modOwner->ApplySpellMod(m_spellInfo, SpellModOp::PowerCost0, runicPowerGain, const_cast(this)); - - player->ModifyPower(POWER_RUNIC_POWER, std::max(0, runicPowerGain)); + else + AddPct(runicPowerGain, (*i)->GetAmount()); } + + // Apply runic power gain modifiers + if (Player* modOwner = m_caster->GetSpellModOwner()) + modOwner->ApplySpellMod(m_spellInfo, SpellModOp::PowerCost0, runicPowerGain, const_cast(this)); + + player->ModifyPower(POWER_RUNIC_POWER, std::max(0, runicPowerGain)); } } diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index cabdae3bde..787f578871 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -5235,17 +5235,26 @@ void Spell::EffectActivateRune(SpellEffIndex effIndex) m_runesState = player->GetRunesState(); uint32 count = damage; - int32 miscValue = (1 << m_spellInfo->Effects[effIndex].MiscValue); + RuneType runeType = static_cast(m_spellInfo->Effects[effIndex].MiscValue); + Optional baseRuneType; + // Bloot Tap can activate both - Blood runes and Death runes which have been converted + if (runeType == RuneType::Death) + baseRuneType = RuneType::Blood; - // Death Runes may also activate Blood Runes (Blood Tap) - if (miscValue & (1 << RUNE_DEATH)) - miscValue |= (1 << RUNE_BLOOD); - - for (uint32 i = 0; i < MAX_RUNES && count > 0; ++i) + for (uint8 i = 0; i < MAX_RUNES && count > 0; ++i) { - if ((1 << player->GetCurrentRune(i)) & miscValue && player->GetRuneCooldown(i)) + if (player->GetCurrentRune(i) != runeType) { - player->SetRuneCooldown(i, 0); + if (!baseRuneType.has_value()) + continue; + + if (player->GetBaseRune(i) != *baseRuneType) + continue; + } + + if (G3D::fuzzyNe(player->GetRuneCooldown(i), 0.0f)) + { + player->SetRuneCooldown(i, 0.0f); --count; } } diff --git a/src/server/scripts/Spells/spell_dk.cpp b/src/server/scripts/Spells/spell_dk.cpp index 04942bdcf6..4cbf732ab1 100644 --- a/src/server/scripts/Spells/spell_dk.cpp +++ b/src/server/scripts/Spells/spell_dk.cpp @@ -1136,17 +1136,26 @@ class spell_dk_dark_transformation_aura : public AuraScript // -51459 - Runic Corruption class spell_dk_runic_empowerment : public AuraScript { - bool Validate(SpellInfo const* /*spellInfo*/) override + bool Load() override { - return ValidateSpellInfo({ SPELL_DK_RUNIC_CORRUPTION_TRIGGERED }); + if (!GetCaster()->IsPlayer() || GetCaster()->ToPlayer()->getClass() != CLASS_DEATH_KNIGHT) + return false; + + return true; } void HandleProc(AuraEffect const* aurEff, ProcEventInfo& /*eventInfo*/) { - PreventDefaultAction(); - + std::vector fullyDepletedRuneIndexes; Player* player = GetTarget()->ToPlayer(); - if (!player) + + for (uint i = 0; i < MAX_RUNES; ++i) + { + if (player->IsRuneFullyDepleted(i)) + fullyDepletedRuneIndexes.push_back(i); + } + + if (fullyDepletedRuneIndexes.empty()) return; // Runic corruption replaces the rune activation with a rune regeneration speed buff @@ -1156,34 +1165,20 @@ class spell_dk_runic_empowerment : public AuraScript return; } - std::vector fullyDepletedRuneIndexes; - for (uint8 i = 0; i < MAX_RUNES; ++i) - { - // Only activate fully depleted runes, which are being on hold until - if (player->GetRuneCooldown(i) != RUNE_BASE_COOLDOWN) - continue; - - fullyDepletedRuneIndexes.push_back(i); - } - - if (!fullyDepletedRuneIndexes.empty()) - { - uint8 runeIndex = Trinity::Containers::SelectRandomContainerElement(fullyDepletedRuneIndexes); - ActivateRune(player, runeIndex); - } + uint8 runeIndex = Trinity::Containers::SelectRandomContainerElement(fullyDepletedRuneIndexes); + ActivateRune(player, runeIndex); } void Register() override { OnEffectProc.Register(&spell_dk_runic_empowerment::HandleProc, EFFECT_0, SPELL_AURA_DUMMY); } - private: // Sniffs do not show any spell cast to activate runes. We just copy the code part from Spell::EffectActivateRune in this case with some small tweaks void ActivateRune(Player* player, uint8 runeIndex) { uint8 currentRuneState = player->GetRunesState(); - player->SetRuneCooldown(runeIndex, 0); + player->SetRuneCooldown(runeIndex, 0.0f); uint8 runesState = player->GetRunesState() & ~currentRuneState; player->AddRunePower(runesState); } @@ -1301,8 +1296,8 @@ class spell_dk_reaping : public AuraScript for (uint8 i = 0; i < MAX_RUNES; i++) { - if (runeMask & (1 << i) && player->GetCurrentRune(i) == RUNE_BLOOD) - player->AddRuneByAuraEffect(i, RUNE_DEATH, aurEff, SPELL_AURA_PERIODIC_DUMMY, GetSpellInfo()); + if (runeMask & (1 << i) && player->GetCurrentRune(i) == RuneType::Blood) + player->AddRuneByAuraEffect(i, RuneType::Death, aurEff, SPELL_AURA_PERIODIC_DUMMY, GetSpellInfo()); } GetEffect(EFFECT_0)->ResetPeriodic(true); @@ -1349,8 +1344,8 @@ class spell_dk_blood_rites : public AuraScript for (uint8 i = 0; i < MAX_RUNES; i++) { - if (runeMask & (1 << i) && player->GetCurrentRune(i) != RUNE_DEATH) - player->AddRuneByAuraEffect(i, RUNE_DEATH, aurEff, SPELL_AURA_PERIODIC_DUMMY, GetSpellInfo()); + if (runeMask & (1 << i) && player->GetCurrentRune(i) != RuneType::Death) + player->AddRuneByAuraEffect(i, RuneType::Death, aurEff, SPELL_AURA_PERIODIC_DUMMY, GetSpellInfo()); } GetEffect(EFFECT_0)->ResetPeriodic(true); @@ -1502,7 +1497,7 @@ class spell_dk_deaths_advance : public AuraScript uint8 unholyRunesOnCooldownCount = 0; for (uint8 i = 0; i < MAX_RUNES; ++i) { - if (player->GetCurrentRune(i) == RUNE_UNHOLY && player->GetRuneCooldown(i)) + if (player->GetCurrentRune(i) == RuneType::Unholy && player->GetRuneCooldown(i)) unholyRunesOnCooldownCount++; } @@ -1530,7 +1525,7 @@ class spell_dk_deaths_advance_aura : public AuraScript for (uint8 i = 0; i < MAX_RUNES; ++i) { - if (player->GetCurrentRune(i) == RUNE_UNHOLY && player->GetRuneCooldown(i)) + if (player->GetCurrentRune(i) == RuneType::Unholy && player->GetRuneCooldown(i)) unholyRunesOnCooldownCount++; }