diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/server/game/Entities/Object/Object.cpp | 13 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.cpp | 218 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.h | 53 | ||||
-rw-r--r-- | src/server/game/Entities/Unit/StatSystem.cpp | 45 | ||||
-rw-r--r-- | src/server/game/Spells/Spell.cpp | 119 |
5 files changed, 290 insertions, 158 deletions
diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index 24f690989d9..5b923fc5a3f 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -758,7 +758,7 @@ void Object::BuildMovementUpdate(ByteBuffer* data, CreateObjectBits flags, Playe Player const* player = ToPlayer(); bool HasSceneInstanceIDs = !player->GetSceneMgr().GetSceneTemplateByInstanceMap().empty(); - bool HasRuneState = ToUnit()->GetPowerIndex(POWER_RUNES) != MAX_POWERS; + bool HasRuneState = ToUnit()->GetPowerIndex(POWER_RUNE_BLOOD) != MAX_POWERS; bool HasActionButtons = true; data->WriteBit(HasSceneInstanceIDs); @@ -773,14 +773,11 @@ void Object::BuildMovementUpdate(ByteBuffer* data, CreateObjectBits flags, Playe } if (HasRuneState) { - float baseCd = float(player->GetRuneBaseCooldown()); - uint32 maxRunes = uint32(player->GetMaxPower(POWER_RUNES)); - - *data << uint8((1 << maxRunes) - 1); + *data << uint8((1 << MAX_RUNES) - 1); *data << uint8(player->GetRunesState()); - *data << uint32(maxRunes); - for (uint32 i = 0; i < maxRunes; ++i) - *data << uint8((baseCd - float(player->GetRuneCooldown(i))) / baseCd * 255); + *data << uint32(MAX_RUNES); + for (uint32 i = 0; i < MAX_RUNES; ++i) + *data << uint8(player->GetRuneCooldown(i) * uint32(255) / uint32(RUNE_BASE_COOLDOWN)); } if (HasActionButtons) { diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index dcd0e6afcbc..3f74950042a 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -1622,27 +1622,30 @@ void Player::RegenerateAll() m_regenTimerCount += m_regenTimer; for (Powers power = POWER_MANA; power < MAX_POWERS; power = Powers(power + 1)) - if (power != POWER_RUNES) + if (power != POWER_RUNE_BLOOD && power != POWER_RUNE_FROST && power != POWER_RUNE_UNHOLY) Regenerate(power); // Runes act as cooldowns, and they don't need to send any data if (GetClass() == CLASS_DEATH_KNIGHT) { - uint32 regeneratedRunes = 0; - uint32 regenIndex = 0; - while (regeneratedRunes < MAX_RECHARGING_RUNES && m_runes->CooldownOrder.size() > regenIndex) + for (uint8 i = 0; i < MAX_RUNES; i += 2) { - uint8 runeToRegen = m_runes->CooldownOrder[regenIndex]; - uint32 runeCooldown = GetRuneCooldown(runeToRegen); - if (runeCooldown > m_regenTimer) + uint8 runeToRegen = i; + uint32 runeCooldown = GetRuneCooldown(i); + uint32 secondRuneCd = GetRuneCooldown(i + 1); + + //float cdmod = m_unitData->PowerRegenFlatModifier[GetPowerIndex(GetPowerTypeForBaseRune(runeToRegen))] * 10.0f; + // Regenerate second rune of the same type only after first rune is off the cooldown + if (secondRuneCd && (runeCooldown > secondRuneCd || !runeCooldown)) { - SetRuneCooldown(runeToRegen, runeCooldown - m_regenTimer); - ++regenIndex; + runeToRegen = i + 1; + runeCooldown = secondRuneCd; } + + if (runeCooldown > m_regenTimer) + SetRuneCooldown(runeToRegen, runeCooldown - m_regenTimer); else SetRuneCooldown(runeToRegen, 0); - - ++regeneratedRunes; } } @@ -5332,8 +5335,6 @@ void Player::UpdateRating(CombatRating cr) ApplyAttackTimePercentMod(OFF_ATTACK, oldVal, false); ApplyAttackTimePercentMod(BASE_ATTACK, newVal, true); ApplyAttackTimePercentMod(OFF_ATTACK, newVal, true); - if (GetClass() == CLASS_DEATH_KNIGHT) - UpdateAllRunesRegen(); break; case CR_HASTE_RANGED: ApplyAttackTimePercentMod(RANGED_ATTACK, oldVal, false); @@ -17734,18 +17735,6 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::Power, loadedPowers), 0); SetPower(POWER_LUNAR_POWER, 0); - // Init rune recharge - if (GetPowerIndex(POWER_RUNES) != MAX_POWERS) - { - int32 runes = GetPower(POWER_RUNES); - int32 maxRunes = GetMaxPower(POWER_RUNES); - uint32 runeCooldown = GetRuneBaseCooldown(); - while (runes < maxRunes) - { - SetRuneCooldown(runes, runeCooldown); - ++runes; - } - } SetPersonalTabard(fields.personalTabardEmblemStyle, fields.personalTabardEmblemColor, fields.personalTabardBorderStyle, fields.personalTabardBorderColor, fields.personalTabardBackgroundColor); @@ -25647,91 +25636,162 @@ void Player::SetTitle(CharTitlesEntry const* title, bool lost) SendDirectMessage(packet.Write()); } -uint8 Player::GetRunesState() const +void Runes::SetRuneState(uint8 index, bool set /*= true*/) { - return uint8(m_runes->RuneState & ((1 << GetMaxPower(POWER_RUNES)) - 1)); + if (set) + RuneState |= (1 << index); // usable + else + RuneState &= ~(1 << index); // on cooldown } -uint32 Player::GetRuneBaseCooldown() const +void Player::ResyncRunes() const { - float cooldown = RUNE_BASE_COOLDOWN; - - AuraEffectList const& regenAura = GetAuraEffectsByType(SPELL_AURA_MOD_POWER_REGEN_PERCENT); - for (AuraEffectList::const_iterator i = regenAura.begin();i != regenAura.end(); ++i) - if ((*i)->GetMiscValue() == POWER_RUNES) - cooldown *= 1.0f - (*i)->GetAmount() / 100.0f; + WorldPackets::Spells::ResyncRunes data(MAX_RUNES); + data.Runes.Start = uint8((1 << MAX_RUNES) - 1); + data.Runes.Count = GetRunesState(); - // Runes cooldown are now affected by player's haste from equipment ... - float hastePct = GetRatingBonusValue(CR_HASTE_MELEE); + data.Runes.Cooldowns.reserve(MAX_RUNES); + for (uint8 i = 0; i < MAX_RUNES; ++i) + data.Runes.Cooldowns.push_back(uint8(GetRuneCooldown(i) * uint32(255) / uint32(RUNE_BASE_COOLDOWN))); - // ... and some auras. - hastePct += GetTotalAuraModifier(SPELL_AURA_MOD_MELEE_HASTE); - hastePct += GetTotalAuraModifier(SPELL_AURA_MOD_MELEE_HASTE_2); - hastePct += GetTotalAuraModifier(SPELL_AURA_MOD_MELEE_HASTE_3); + SendDirectMessage(data.Write()); +} - cooldown *= 1.0f - (hastePct / 100.0f); +void Player::SendConvertedRunes() const +{ - return cooldown; } -void Player::SetRuneCooldown(uint8 index, uint32 cooldown) +void Player::AddRunePower(uint8 mask) const { - m_runes->Cooldown[index] = cooldown; - m_runes->SetRuneState(index, (cooldown == 0) ? true : false); - int32 activeRunes = std::count(std::begin(m_runes->Cooldown), &m_runes->Cooldown[std::min(GetMaxPower(POWER_RUNES), MAX_RUNES)], 0u); - if (activeRunes != GetPower(POWER_RUNES)) - SetPower(POWER_RUNES, activeRunes); + WorldPackets::Spells::AddRunePower packet; + packet.AddedRunesMask = mask; // mask (0x00-0x3F probably) + SendDirectMessage(packet.Write()); } -void Runes::SetRuneState(uint8 index, bool set /*= true*/) +static RuneType runeSlotTypes[MAX_RUNES] = { - auto itr = std::find(CooldownOrder.begin(), CooldownOrder.end(), index); - if (set) + /*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<Runes>(); + m_runes->RuneState = 0; + m_runes->LastUsedRune = RuneType::Blood; + m_runes->LastUsedRuneMask = 0; + + for (uint8 i = 0; i < MAX_RUNES; ++i) { - RuneState |= (1 << index); // usable - if (itr != CooldownOrder.end()) - CooldownOrder.erase(itr); + 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); } - else + + for (uint8 i = 0; i < MAX_RUNES; ++i) + SetRuneCooldown(i, 0); // reset cooldowns + + for (Powers rune : { POWER_RUNE_BLOOD, POWER_RUNE_FROST, POWER_RUNE_UNHOLY }) { - RuneState &= ~(1 << index); // on cooldown - if (itr == CooldownOrder.end()) - CooldownOrder.push_back(index); + int32 powerIndex = GetPowerIndex(rune); + if (powerIndex == MAX_POWERS) + continue; + + SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::PowerRegenFlatModifier, powerIndex), 0.0f); + SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::PowerRegenInterruptedFlatModifier, powerIndex), 0.0f); } } -void Player::ResyncRunes() const +Powers Player::GetPowerTypeForBaseRune(uint8 index) const { - uint32 maxRunes = uint32(GetMaxPower(POWER_RUNES)); - - WorldPackets::Spells::ResyncRunes data(maxRunes); - data.Runes.Start = uint8((1 << maxRunes) - 1); - data.Runes.Count = GetRunesState(); + switch (GetBaseRune(index)) + { + case RuneType::Blood: return POWER_RUNE_BLOOD; + case RuneType::Frost: return POWER_RUNE_FROST; + case RuneType::Unholy: return POWER_RUNE_UNHOLY; + default: + return MAX_POWERS; + } +} - float baseCd = float(GetRuneBaseCooldown()); - for (uint32 i = 0; i < maxRunes; ++i) - data.Runes.Cooldowns.push_back(uint8((baseCd - float(GetRuneCooldown(i))) / baseCd * 255)); +bool Player::IsBaseRuneSlotsOnCooldown(RuneType runeType) const +{ + for (uint8 i = 0; i < MAX_RUNES; ++i) + if (GetBaseRune(i) == runeType && GetRuneCooldown(i) == 0) + return false; - SendDirectMessage(data.Write()); + return true; } -void Player::InitRunes() +void Player::SetRuneConvertAura(uint8 index, AuraEffect const* aura, AuraType auraType, SpellInfo const* spellInfo) { - if (GetClass() != CLASS_DEATH_KNIGHT) - return; + m_runes->_Runes[index].ConvertAura = aura; + m_runes->_Runes[index].ConvertAuraType = auraType; + m_runes->_Runes[index].ConvertAuraInfo = spellInfo; +} - uint32 runeIndex = GetPowerIndex(POWER_RUNES); - if (runeIndex == MAX_POWERS) - return; +void Player::AddRuneByAuraEffect(uint8 index, RuneType newType, AuraEffect const* aura, AuraType auraType, SpellInfo const* spellInfo) +{ + SetRuneConvertAura(index, aura, auraType, spellInfo); + ConvertRune(index, newType); +} - m_runes = std::make_unique<Runes>(); - m_runes->RuneState = 0; +void Player::SetRuneCooldown(uint8 index, uint32 cooldown) +{ + m_runes->_Runes[index].Cooldown = cooldown; + m_runes->SetRuneState(index, (cooldown == 0) ? true : false); +} +void Player::RemoveRunesByAuraEffect(AuraEffect const* aura) +{ for (uint8 i = 0; i < MAX_RUNES; ++i) - SetRuneCooldown(i, 0); // reset cooldowns + { + if (m_runes->_Runes[i].ConvertAura == aura) + { + ConvertRune(i, GetBaseRune(i)); + SetRuneConvertAura(i, nullptr, SPELL_AURA_NONE, nullptr); + } + } +} - SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::PowerRegenFlatModifier, runeIndex), 0.0f); - SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::PowerRegenInterruptedFlatModifier, runeIndex), 0.0f); +void Player::RestoreBaseRune(uint8 index) +{ + 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; + + // Don't even convert aura for passive convertion + if (spellInfo->IsPassive() && type == SPELL_AURA_CONVERT_RUNE) + return; + } + + ConvertRune(index, GetBaseRune(index)); + SetRuneConvertAura(index, nullptr, SPELL_AURA_NONE, nullptr); +} + +void Player::ConvertRune(uint8 index, RuneType newType) +{ + SetCurrentRune(index, newType); + + WorldPackets::Spells::ConvertRune packet(MAX_RUNES); + packet.Index = index; + packet.Rune = AsUnderlyingType(newType); + + SendDirectMessage(packet.Write()); } void Player::AutoStoreLoot(uint8 bag, uint8 slot, uint32 loot_id, LootStore const& store, ItemContext context, bool broadcast, bool createdByPlayer) diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index e7b7cd939d6..e0239cc08d2 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -396,8 +396,7 @@ struct Areas float y2; }; -#define MAX_RUNES 7 -#define MAX_RECHARGING_RUNES 3 +constexpr uint8 MAX_RUNES = 6; enum RuneCooldowns { @@ -405,11 +404,31 @@ enum RuneCooldowns RUNE_MISS_COOLDOWN = 1500 // cooldown applied on runes when the spell misses }; +enum class RuneType : uint8 +{ + Blood = 0, + Unholy = 1, + Frost = 2, + Death = 3, + Max = 4 +}; + +struct RuneInfo +{ + RuneType BaseRune; + RuneType CurrentRune; + uint32 Cooldown; + AuraEffect const* ConvertAura; + AuraType ConvertAuraType; + SpellInfo const* ConvertAuraInfo; +}; + struct Runes { - std::deque<uint8> CooldownOrder; - uint32 Cooldown[MAX_RUNES]; + RuneInfo _Runes[MAX_RUNES]; uint8 RuneState; // mask of available runes + RuneType LastUsedRune; + uint8 LastUsedRuneMask; void SetRuneState(uint8 index, bool set = true); }; @@ -2086,7 +2105,6 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player> void ApplyManaRegenBonus(int32 amount, bool apply); void ApplyHealthRegenBonus(int32 amount, bool apply); void UpdatePowerRegen(Powers powerType); - void UpdateAllRunesRegen(); void SetPetSpellPower(uint32 spellPower) { SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::PetSpellPower), spellPower); } @@ -2633,11 +2651,30 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player> bool isAllowedToLoot(Creature const* creature) const; UF::DeclinedNames const* GetDeclinedNames() const { return m_playerData->DeclinedNames.has_value() ? &*m_playerData->DeclinedNames : nullptr; } - uint8 GetRunesState() const; - uint32 GetRuneCooldown(uint8 index) const { return m_runes->Cooldown[index]; } - uint32 GetRuneBaseCooldown() const; + + // 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); } + Powers GetPowerTypeForBaseRune(uint8 index) const; + 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; } + 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 RemoveRunesByAuraEffect(AuraEffect const* aura); + void RestoreBaseRune(uint8 index); + void ConvertRune(uint8 index, RuneType newType); void ResyncRunes() const; + void SendConvertedRunes() 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 43bb0689481..aab089781e6 100644 --- a/src/server/game/Entities/Unit/StatSystem.cpp +++ b/src/server/game/Entities/Unit/StatSystem.cpp @@ -798,7 +798,7 @@ void Player::ApplyHealthRegenBonus(int32 amount, bool apply) void Player::UpdatePowerRegen(Powers powerType) { uint32 powerIndex = GetPowerIndex(powerType); - if (powerIndex == MAX_POWERS && powerType != POWER_RUNES) + if (powerIndex == MAX_POWERS) return; float powerRegenMod = GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_POWER_REGEN, powerType) / 5.f; @@ -852,51 +852,20 @@ void Player::UpdatePowerRegen(Powers powerType) // Butchery and Anger Management SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::PowerRegenInterruptedFlatModifier, powerIndex), powerRegenMod); break; - case POWER_RUNES: + case POWER_RUNE_BLOOD: + case POWER_RUNE_FROST: + case POWER_RUNE_UNHOLY: { - UpdateAllRunesRegen(); // @todo: replace this with the code below once runes have been downgraded for Cataclysm Classic - break; - - /* - // Formular: base cooldown / (1 - haste) - float regeneration = 0.1f; - float haste = m_unitData->ModHasteRegen; - if (haste != 0.f) - regeneration /= haste; - - for (int8 i = 0; i < NUM_RUNE_TYPES; i++) - { - float mod = 0.f; - for (AuraEffect const* effect : GetAuraEffectsByType(SPELL_AURA_MOD_POWER_REGEN)) - if (effect->GetMiscValue() == int32(powerType) && effect->GetMiscValueB() == i) - mod += effect->GetAmount(); - - SetFloatValue(PLAYER_RUNE_REGEN_1 + i, regeneration + mod); - } + PowerTypeEntry const* powerTypeEntry = sDB2Manager.GetPowerTypeEntry(powerType); + SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::PowerRegenFlatModifier, powerIndex), float(1 * IN_MILLISECONDS) / float(RUNE_BASE_COOLDOWN) - powerTypeEntry->RegenPeace); + SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::PowerRegenInterruptedFlatModifier, powerIndex), float(1 * IN_MILLISECONDS) / float(RUNE_BASE_COOLDOWN) - powerTypeEntry->RegenCombat); break; - */ } default: break; } } -void Player::UpdateAllRunesRegen() -{ - if (GetClass() != CLASS_DEATH_KNIGHT) - return; - - uint32 runeIndex = GetPowerIndex(POWER_RUNES); - if (runeIndex == MAX_POWERS) - return; - - PowerTypeEntry const* runeEntry = sDB2Manager.GetPowerTypeEntry(POWER_RUNES); - - uint32 cooldown = GetRuneBaseCooldown(); - SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::PowerRegenFlatModifier, runeIndex), float(1 * IN_MILLISECONDS) / float(cooldown) - runeEntry->RegenPeace); - SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::PowerRegenInterruptedFlatModifier, runeIndex), float(1 * IN_MILLISECONDS) / float(cooldown) - runeEntry->RegenCombat); -} - void Player::_ApplyAllStatBonuses() { SetCanModifyStats(false); diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index 29a652721f3..370acf14178 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -4729,7 +4729,7 @@ void Spell::SendSpellStart() && std::find_if(m_powerCost.begin(), m_powerCost.end(), [](SpellPowerCost const& cost) { return cost.Power != POWER_HEALTH; }) != m_powerCost.end()) castFlags |= CAST_FLAG_POWER_LEFT_SELF; - if (HasPowerTypeCost(POWER_RUNES)) + if (HasPowerTypeCost(POWER_RUNE_BLOOD) || HasPowerTypeCost(POWER_RUNE_FROST) || HasPowerTypeCost(POWER_RUNE_UNHOLY)) castFlags |= CAST_FLAG_NO_GCD; // not needed, but Blizzard sends it if (m_spellInfo->HasAttribute(SPELL_ATTR8_HEAL_PREDICTION) && m_casttime && m_caster->IsUnit()) @@ -4775,10 +4775,10 @@ void Spell::SendSpellStart() { castData.RemainingRunes->Start = m_runesState; // runes state before castData.RemainingRunes->Count = player->GetRunesState(); // runes state after - for (uint8 i = 0; i < player->GetMaxPower(POWER_RUNES); ++i) + 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(player->GetRuneBaseCooldown()); + float baseCd = float(RUNE_BASE_COOLDOWN); castData.RemainingRunes->Cooldowns.push_back((baseCd - float(player->GetRuneCooldown(i))) / baseCd * 255); // rune cooldown passed } } @@ -4786,7 +4786,7 @@ void Spell::SendSpellStart() { castData.RemainingRunes->Start = 0; castData.RemainingRunes->Count = 0; - for (uint8 i = 0; i < player->GetMaxPower(POWER_RUNES); ++i) + for (uint8 i = 0; i < MAX_RUNES; ++i) castData.RemainingRunes->Cooldowns.push_back(0); } } @@ -4830,7 +4830,7 @@ void Spell::SendSpellGo() if ((m_caster->GetTypeId() == TYPEID_PLAYER) && (m_caster->ToPlayer()->GetClass() == CLASS_DEATH_KNIGHT) - && HasPowerTypeCost(POWER_RUNES) + && (HasPowerTypeCost(POWER_RUNE_BLOOD) || HasPowerTypeCost(POWER_RUNE_FROST) || HasPowerTypeCost(POWER_RUNE_UNHOLY)) && !(_triggeredCastFlags & TRIGGERED_IGNORE_POWER_AND_REAGENT_COST)) { castFlags |= CAST_FLAG_NO_GCD; // not needed, but Blizzard sends it @@ -4881,10 +4881,10 @@ void Spell::SendSpellGo() Player* player = ASSERT_NOTNULL(m_caster->ToPlayer()); castData.RemainingRunes->Start = m_runesState; // runes state before castData.RemainingRunes->Count = player->GetRunesState(); // runes state after - for (uint8 i = 0; i < player->GetMaxPower(POWER_RUNES); ++i) + 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(player->GetRuneBaseCooldown()); + float baseCd = float(RUNE_BASE_COOLDOWN); castData.RemainingRunes->Cooldowns.push_back((baseCd - float(player->GetRuneCooldown(i))) / baseCd * 255); // rune cooldown passed } } @@ -5455,7 +5455,7 @@ void Spell::TakePower() } } - if (powerType == POWER_RUNES) + if (powerType == POWER_RUNE_BLOOD || powerType == POWER_RUNE_FROST || powerType == POWER_RUNE_UNHOLY) { TakeRunePower(hit); continue; @@ -5483,12 +5483,12 @@ void Spell::TakePower() SpellCastResult Spell::CheckRuneCost() const { - int32 runeCost = std::accumulate(m_powerCost.begin(), m_powerCost.end(), 0, [](int32 totalCost, SpellPowerCost const& cost) + int32 totalRuneCost = std::accumulate(m_powerCost.begin(), m_powerCost.end(), 0, [](int32 totalCost, SpellPowerCost const& cost) { - return totalCost + (cost.Power == POWER_RUNES ? cost.Amount : 0); + return totalCost + ((cost.Power == POWER_RUNE_BLOOD || cost.Power == POWER_RUNE_FROST || cost.Power == POWER_RUNE_UNHOLY) ? cost.Amount : 0); }); - if (!runeCost) + if (!totalRuneCost) return SPELL_CAST_OK; Player* player = m_caster->ToPlayer(); @@ -5498,12 +5498,31 @@ SpellCastResult Spell::CheckRuneCost() const if (player->GetClass() != CLASS_DEATH_KNIGHT) return SPELL_CAST_OK; - int32 readyRunes = 0; - for (int32 i = 0; i < player->GetMaxPower(POWER_RUNES); ++i) - if (player->GetRuneCooldown(i) == 0) - ++readyRunes; + std::array<int32, AsUnderlyingType(RuneType::Max)> runeCost = { }; // blood, frost, unholy, death + for (SpellPowerCost const& cost : m_powerCost) + { + switch (cost.Power) + { + case POWER_RUNE_BLOOD: runeCost[AsUnderlyingType(RuneType::Blood)] = cost.Amount; break; + case POWER_RUNE_FROST: runeCost[AsUnderlyingType(RuneType::Frost)] = cost.Amount; break; + case POWER_RUNE_UNHOLY: runeCost[AsUnderlyingType(RuneType::Unholy)] = cost.Amount; break; + default: + break; + } + } - if (readyRunes < runeCost) + for (uint32 i = 0; i < MAX_RUNES; ++i) + { + RuneType rune = player->GetCurrentRune(i); + if ((player->GetRuneCooldown(i) == 0) && (runeCost[AsUnderlyingType(rune)] > 0)) + runeCost[AsUnderlyingType(rune)]--; + } + + for (uint8 i = 0; i < AsUnderlyingType(RuneType::Death); ++i) + if (runeCost[i] > 0) + runeCost[AsUnderlyingType(RuneType::Death)] += runeCost[i]; + + if (runeCost[AsUnderlyingType(RuneType::Death)] > MAX_RUNES) return SPELL_FAILED_NO_POWER; // not sure if result code is correct return SPELL_CAST_OK; @@ -5514,20 +5533,70 @@ void Spell::TakeRunePower(bool didHit) if (m_caster->GetTypeId() != TYPEID_PLAYER || m_caster->ToPlayer()->GetClass() != CLASS_DEATH_KNIGHT) return; + int32 totalRuneCost = std::accumulate(m_powerCost.begin(), m_powerCost.end(), 0, [](int32 totalCost, SpellPowerCost const& cost) + { + return totalCost + ((cost.Power == POWER_RUNE_BLOOD || cost.Power == POWER_RUNE_FROST || cost.Power == POWER_RUNE_UNHOLY) ? cost.Amount : 0); + }); + + // Spells that do not consume any runes, do not grant any Runic Power + if (!totalRuneCost) + return; + Player* player = m_caster->ToPlayer(); m_runesState = player->GetRunesState(); // store previous state + player->ClearLastUsedRuneMask(); + + std::array<int32, AsUnderlyingType(RuneType::Max)> runeCost = { }; // blood, frost, unholy, death + for (SpellPowerCost const& cost : m_powerCost) + { + switch (cost.Power) + { + case POWER_RUNE_BLOOD: runeCost[AsUnderlyingType(RuneType::Blood)] = cost.Amount; break; + case POWER_RUNE_FROST: runeCost[AsUnderlyingType(RuneType::Frost)] = cost.Amount; break; + case POWER_RUNE_UNHOLY: runeCost[AsUnderlyingType(RuneType::Unholy)] = cost.Amount; break; + default: + break; + } + } - int32 runeCost = std::accumulate(m_powerCost.begin(), m_powerCost.end(), 0, [](int32 totalCost, SpellPowerCost const& cost) + // 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 + for (uint32 i = 0; i < MAX_RUNES; ++i) { - return totalCost + (cost.Power == POWER_RUNES ? cost.Amount : 0); - }); + RuneType rune = player->GetCurrentRune(i); + if (!player->GetRuneCooldown(i) && runeCost[AsUnderlyingType(rune)] > 0) + { + player->SetRuneCooldown(i, didHit ? uint32(RUNE_BASE_COOLDOWN) : uint32(RUNE_MISS_COOLDOWN)); + player->SetLastUsedRune(rune); + player->SetLastUsedRuneIndex(i); + --runeCost[AsUnderlyingType(rune)]; + } + } - for (int32 i = 0; i < player->GetMaxPower(POWER_RUNES); ++i) + // Find a Death rune where the base rune matches the one we need + runeCost[AsUnderlyingType(RuneType::Death)] = runeCost[AsUnderlyingType(RuneType::Blood)] + runeCost[AsUnderlyingType(RuneType::Unholy)] + runeCost[AsUnderlyingType(RuneType::Frost)]; + + if (runeCost[AsUnderlyingType(RuneType::Death)] > 0) { - if (!player->GetRuneCooldown(i) && runeCost > 0) + for (uint8 i = 0; i < MAX_RUNES; ++i) { - player->SetRuneCooldown(i, didHit ? player->GetRuneBaseCooldown() : uint32(RUNE_MISS_COOLDOWN)); - --runeCost; + RuneType rune = player->GetCurrentRune(i); + if (!player->GetRuneCooldown(i) && rune == RuneType::Death) + { + player->SetRuneCooldown(i, didHit ? uint32(RUNE_BASE_COOLDOWN) : uint32(RUNE_MISS_COOLDOWN)); + player->SetLastUsedRune(rune); + player->SetLastUsedRuneIndex(i); + runeCost[AsUnderlyingType(rune)]--; + + // keep Death Rune type if missed + if (didHit) + player->RestoreBaseRune(i); + + if (runeCost[AsUnderlyingType(RuneType::Death)] == 0) + break; + } } } } @@ -7260,8 +7329,8 @@ SpellCastResult Spell::CheckPower() const return SPELL_FAILED_UNKNOWN; } - //check rune cost only if a spell has PowerType == POWER_RUNES - if (cost.Power == POWER_RUNES) + // check rune cost only if a spell has PowerType == POWER_RUNE_BLOOD or POWER_RUNE_FROST or POWER_RUNE_UNHOLY + if (cost.Power == POWER_RUNE_BLOOD || cost.Power == POWER_RUNE_FROST || cost.Power == POWER_RUNE_UNHOLY) { SpellCastResult failReason = CheckRuneCost(); if (failReason != SPELL_CAST_OK) |