diff options
author | Ovahlord <dreadkiller@gmx.de> | 2024-07-22 23:45:44 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-22 23:45:44 +0200 |
commit | 17c35e585736e13ac7a4a7758a50ccc54442a6af (patch) | |
tree | abfb2042ff3407b8630df340bfdccfb493e9971b /src | |
parent | 0aa82cde51c08fff64ca904c07ee3a28414bdbee (diff) |
Core/Units: unify power regeneration handling (#30088)
* Core/Units: unify power regeneration handling
- Creatures, Players and Pets now all share the same power update and regeneration handling
- Removed all false power regeneration handlings for pets and creatures
- Removed a nowhere confirmed hack for delaying health regeneration after polymorphing a target
Diffstat (limited to 'src')
-rw-r--r-- | src/server/game/Entities/Creature/Creature.cpp | 111 | ||||
-rw-r--r-- | src/server/game/Entities/Creature/Creature.h | 3 | ||||
-rw-r--r-- | src/server/game/Entities/Creature/CreatureData.h | 2 | ||||
-rw-r--r-- | src/server/game/Entities/Pet/Pet.cpp | 35 | ||||
-rw-r--r-- | src/server/game/Entities/Pet/Pet.h | 1 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.cpp | 216 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.h | 11 | ||||
-rw-r--r-- | src/server/game/Entities/Unit/StatSystem.cpp | 211 | ||||
-rw-r--r-- | src/server/game/Entities/Unit/Unit.cpp | 263 | ||||
-rw-r--r-- | src/server/game/Entities/Unit/Unit.h | 22 | ||||
-rw-r--r-- | src/server/game/Spells/Auras/SpellAuraEffects.cpp | 5 |
11 files changed, 420 insertions, 460 deletions
diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 59a899715c3..da267c3bb5a 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -319,8 +319,6 @@ Creature::Creature(bool isWorldObject) : Unit(isWorldObject), MapObject(), m_Pla m_formation(nullptr), m_triggerJustAppeared(true), m_respawnCompatibilityMode(false), _lastDamagedTime(0), _regenerateHealth(true), _creatureImmunitiesId(0), _gossipMenuId(0), _sparringHealthPct(0) { - m_regenTimer = CREATURE_REGEN_INTERVAL; - for (uint8 i = 0; i < MAX_CREATURE_SPELLS; ++i) m_spells[i] = 0; @@ -854,43 +852,6 @@ void Creature::Update(uint32 diff) if (!IsAlive()) break; - if (m_regenTimer > 0) - { - if (diff >= m_regenTimer) - m_regenTimer = 0; - else - m_regenTimer -= diff; - } - - if (m_regenTimer == 0) - { - if (!IsInEvadeMode()) - { - // regenerate health if not in combat or if polymorphed) - if (!IsEngaged() || IsPolymorphed()) - RegenerateHealth(); - else if (CanNotReachTarget()) - { - // regenerate health if cannot reach the target and the setting is set to do so. - // this allows to disable the health regen of raid bosses if pathfinding has issues for whatever reason - if (sWorld->getBoolConfig(CONFIG_REGEN_HP_CANNOT_REACH_TARGET_IN_RAID) || !GetMap()->IsRaid()) - { - RegenerateHealth(); - TC_LOG_DEBUG("entities.unit.chase", "RegenerateHealth() enabled because Creature cannot reach the target. Detail: {}", GetDebugInfo()); - } - else - TC_LOG_DEBUG("entities.unit.chase", "RegenerateHealth() disabled even if the Creature cannot reach the target. Detail: {}", GetDebugInfo()); - } - } - - if (GetPowerType() == POWER_ENERGY) - Regenerate(POWER_ENERGY); - else - Regenerate(POWER_MANA); - - m_regenTimer = CREATURE_REGEN_INTERVAL; - } - if (CanNotReachTarget() && !IsInEvadeMode() && !GetMap()->IsRaid()) { m_cannotReachTimer += diff; @@ -913,63 +874,32 @@ void Creature::Heartbeat() ForcePartyMembersIntoCombat(); } -void Creature::Regenerate(Powers power) +void Creature::RegenerateHealth() { - uint32 curValue = GetPower(power); - uint32 maxValue = GetMaxPower(power); - - if (!HasUnitFlag2(UNIT_FLAG2_REGENERATE_POWER)) - return; - - if (curValue >= maxValue) + if (!CanRegenerateHealth()) return; - float addvalue = 0.0f; - - switch (power) - { - case POWER_FOCUS: + if (!IsInEvadeMode()) { - // For hunter pets. - addvalue = 24 * sWorld->getRate(RATE_POWER_FOCUS); - break; - } - case POWER_ENERGY: - { - // For deathknight's ghoul. - addvalue = 20; - break; - } - case POWER_MANA: - { - // Combat and any controlled creature - if (IsInCombat() || GetCharmerOrOwnerGUID().IsEmpty()) + // regenerate health if not in combat or if polymorphed) OR + // if cannot reach the target and the setting is set to do so. + // this allows to disable the health regen of raid bosses if pathfinding has issues for whatever reason + if ((IsEngaged() && !IsPolymorphed())) { - float ManaIncreaseRate = sWorld->getRate(RATE_POWER_MANA); - - addvalue = uint32((27.0f / 5.0f + 17.0f) * ManaIncreaseRate); + if (CanNotReachTarget()) + { + if (sWorld->getBoolConfig(CONFIG_REGEN_HP_CANNOT_REACH_TARGET_IN_RAID) && GetMap()->IsRaid()) + TC_LOG_DEBUG("entities.unit.chase", "Creature::RegenerateHealth() enabled because Creature cannot reach the target. Detail: {}", GetDebugInfo()); + else + { + TC_LOG_DEBUG("entities.unit.chase", "Creature::RegenerateHealth() disabled even if the Creature cannot reach the target. Detail: {}", GetDebugInfo()); + return; + } + } + else + return; } - else - addvalue = maxValue / 3; - - break; } - default: - return; - } - - // Apply modifiers (if any). - addvalue *= GetTotalAuraMultiplierByMiscValue(SPELL_AURA_MOD_POWER_REGEN_PERCENT, power); - - addvalue += GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_POWER_REGEN, power) * (IsHunterPet() ? PET_FOCUS_REGEN_INTERVAL : CREATURE_REGEN_INTERVAL) / (5 * IN_MILLISECONDS); - - ModifyPower(power, int32(addvalue)); -} - -void Creature::RegenerateHealth() -{ - if (!CanRegenerateHealth()) - return; uint32 curValue = GetHealth(); uint32 maxValue = GetMaxHealth(); @@ -992,7 +922,7 @@ void Creature::RegenerateHealth() // Apply modifiers (if any). addvalue *= GetTotalAuraMultiplier(SPELL_AURA_MOD_HEALTH_REGEN_PERCENT); - addvalue += GetTotalAuraModifier(SPELL_AURA_MOD_REGEN) * CREATURE_REGEN_INTERVAL / (5 * IN_MILLISECONDS); + addvalue += GetTotalAuraModifier(SPELL_AURA_MOD_REGEN) * HEALTH_REGENERATION_INTERVAL / (5 * IN_MILLISECONDS); ModifyHealth(addvalue); } @@ -1602,6 +1532,7 @@ void Creature::UpdateLevelDependantStats() Powers powerType = CalculateDisplayPowerType(); SetCreateMana(stats->BaseMana); SetStatPctModifier(UnitMods(UNIT_MOD_POWER_START + AsUnderlyingType(powerType)), BASE_PCT, GetCreatureDifficulty()->ManaModifier); + RegisterPowerTypes(); SetPowerType(powerType, true, true); // damage diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index de10174db70..72ba0baa50f 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -480,8 +480,7 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma uint32 m_boundaryCheckTime; // (msecs) remaining time for next evade boundary check ReactStates m_reactState; // for AI, not charmInfo - void RegenerateHealth(); - void Regenerate(Powers power); + void RegenerateHealth() override; MovementGeneratorType m_defaultMovementType; ObjectGuid::LowType m_spawnId; ///< For new or temporary creatures is 0 for saved it is lowguid uint8 m_equipmentId; diff --git a/src/server/game/Entities/Creature/CreatureData.h b/src/server/game/Entities/Creature/CreatureData.h index c39241c7331..97cde1515c2 100644 --- a/src/server/game/Entities/Creature/CreatureData.h +++ b/src/server/game/Entities/Creature/CreatureData.h @@ -418,8 +418,6 @@ struct TC_GAME_API CreatureMovementData std::string ToString() const; }; -const uint32 CREATURE_REGEN_INTERVAL = 2 * IN_MILLISECONDS; -const uint32 PET_FOCUS_REGEN_INTERVAL = 4 * IN_MILLISECONDS; const uint32 CREATURE_NOPATH_EVADE_TIME = 5 * IN_MILLISECONDS; const uint8 MAX_KILL_CREDIT = 2; diff --git a/src/server/game/Entities/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp index fddb6bcfe75..84bc7d2a160 100644 --- a/src/server/game/Entities/Pet/Pet.cpp +++ b/src/server/game/Entities/Pet/Pet.cpp @@ -61,7 +61,6 @@ Pet::Pet(Player* owner, PetType type) : } m_name = "Pet"; - m_focusRegenTimer = PET_FOCUS_REGEN_INTERVAL; } Pet::~Pet() = default; @@ -667,39 +666,6 @@ void Pet::Update(uint32 diff) return; } } - - //regenerate focus for hunter pets or energy for deathknight's ghoul - if (m_focusRegenTimer) - { - if (m_focusRegenTimer > diff) - m_focusRegenTimer -= diff; - else - { - switch (GetPowerType()) - { - case POWER_FOCUS: - Regenerate(POWER_FOCUS); - m_focusRegenTimer += PET_FOCUS_REGEN_INTERVAL - diff; - if (!m_focusRegenTimer) ++m_focusRegenTimer; - - // Reset if large diff (lag) causes focus to get 'stuck' - if (m_focusRegenTimer > PET_FOCUS_REGEN_INTERVAL) - m_focusRegenTimer = PET_FOCUS_REGEN_INTERVAL; - - break; - - // in creature::update - //case POWER_ENERGY: - // Regenerate(POWER_ENERGY); - // m_regenTimer += CREATURE_REGEN_INTERVAL - diff; - // if (!m_regenTimer) ++m_regenTimer; - // break; - default: - m_focusRegenTimer = 0; - break; - } - } - } break; } default: @@ -920,6 +886,7 @@ bool Guardian::InitStatsForLevel(uint8 petlevel) } // Power + RegisterPowerTypes(); SetPowerType(powerType, true, true); // Damage diff --git a/src/server/game/Entities/Pet/Pet.h b/src/server/game/Entities/Pet/Pet.h index 28098098ff9..5e663fa0e3c 100644 --- a/src/server/game/Entities/Pet/Pet.h +++ b/src/server/game/Entities/Pet/Pet.h @@ -150,7 +150,6 @@ class TC_GAME_API Pet final : public Guardian PetType m_petType; int32 m_duration; // time until unsummon (used mostly for summoned guardians and not used for controlled pets) bool m_loading; - uint32 m_focusRegenTimer; uint32 m_groupUpdateMask; std::unique_ptr<DeclinedName> m_declinedname; diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 4704ba291db..09d8d6739f9 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -177,9 +177,6 @@ Player::Player(WorldSession* session) : Unit(true), m_sceneMgr(this) if (!GetSession()->HasPermission(rbac::RBAC_PERM_CAN_FILTER_WHISPERS)) SetAcceptWhispers(true); - m_regenInterruptTimestamp = GameTime::Now(); - m_regenTimer = 0; - m_regenTimerCount = 0; m_weaponChangeTimer = 0; m_zoneUpdateId = uint32(-1); @@ -318,8 +315,6 @@ Player::Player(WorldSession* session) : Unit(true), m_sceneMgr(this) m_ChampioningFaction = 0; - m_powerFraction.fill(0.0f); - isDebugAreaTriggers = false; m_WeeklyQuestChanged = false; @@ -460,6 +455,7 @@ bool Player::Create(ObjectGuid::LowType guidlow, WorldPackets::Character::Charac SetClass(createInfo->Class); SetGender(Gender(createInfo->Sex)); SetPowerType(Powers(powertype), false); + RegisterPowerTypes(); InitDisplayIds(); if (sWorld->getIntConfig(CONFIG_GAME_TYPE) == REALM_TYPE_PVP || sWorld->getIntConfig(CONFIG_GAME_TYPE) == REALM_TYPE_RPPVP) { @@ -1045,12 +1041,6 @@ void Player::Update(uint32 p_time) m_zoneUpdateTimer -= p_time; } - if (IsAlive()) - { - m_regenTimer += p_time; - RegenerateAll(); - } - if (m_deathState == JUST_DIED) KillPlayer(); @@ -1617,205 +1607,35 @@ bool Player::IsImmunedToSpellEffect(SpellInfo const* spellInfo, SpellEffectInfo return Unit::IsImmunedToSpellEffect(spellInfo, spellEffectInfo, caster, requireImmunityPurgesEffectAttribute); } -void Player::RegenerateAll() -{ - m_regenTimerCount += m_regenTimer; - - for (Powers power = POWER_MANA; power < MAX_POWERS; power = Powers(power + 1)) - 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) - { - for (uint8 i = 0; i < MAX_RUNES; i += 2) - { - uint8 runeToRegen = i; - 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 (G3D::fuzzyNe(secondRuneCd, 0.0f) && (runeCooldown > secondRuneCd || G3D::fuzzyEq(runeCooldown, 0.0f))) - { - runeToRegen = i + 1; - runeCooldown = secondRuneCd; - } - - PowerTypeEntry const* powerTypeEntry = sDB2Manager.GetPowerTypeEntry(POWER_RUNES); - - float cdDiff = (powerTypeEntry->RegenPeace + m_unitData->PowerRegenFlatModifier[GetPowerIndex(GetPowerTypeForBaseRune(runeToRegen))]) * 0.001f * m_regenTimer; - if (runeCooldown > cdDiff) - SetRuneCooldown(runeToRegen, runeCooldown - cdDiff); - else - SetRuneCooldown(runeToRegen, 0.0f); - } - } - - if (m_regenTimerCount >= 2000) - { - // Not in combat or they have regeneration - if (!IsInCombat() || IsPolymorphed() || m_baseHealthRegen || HasAuraType(SPELL_AURA_MOD_REGEN_DURING_COMBAT) || HasAuraType(SPELL_AURA_MOD_HEALTH_REGEN_IN_COMBAT)) - RegenerateHealth(); - - m_regenTimerCount -= 2000; - } - - m_regenTimer = 0; -} - -void Player::Regenerate(Powers power) +void Player::RegenerateRunes(uint32 diff) { - // Skip regeneration for power type we cannot have - uint32 powerIndex = GetPowerIndex(power); - if (powerIndex == MAX_POWERS || powerIndex >= MAX_POWERS_PER_CLASS) - return; - - /// @todo possible use of miscvalueb instead of amount - if (HasAuraTypeWithValue(SPELL_AURA_PREVENT_REGENERATE_POWER, power) || HasAuraType(SPELL_AURA_INTERRUPT_REGEN)) - return; - - int32 curValue = GetPower(power); - - PowerTypeEntry const* powerType = sDB2Manager.GetPowerTypeEntry(power); - if (!powerType) + if (GetClass() != CLASS_DEATH_KNIGHT) return; - float addvalue = 0.0f; - if (!IsInCombat()) + // Runes act as cooldowns, and they don't need to send any data + for (uint8 i = 0; i < MAX_RUNES; i += 2) { - if (powerType->GetFlags().HasFlag(PowerTypeFlags::UseRegenInterrupt) && m_regenInterruptTimestamp + Milliseconds(powerType->RegenInterruptTimeMS) >= GameTime::Now()) - return; - - addvalue = (powerType->RegenPeace + m_unitData->PowerRegenFlatModifier[powerIndex]) * 0.001f * m_regenTimer; - } - else - addvalue = (powerType->RegenCombat + m_unitData->PowerRegenInterruptedFlatModifier[powerIndex]) * 0.001f * m_regenTimer; - - static Rates const RatesForPower[MAX_POWERS] = - { - RATE_POWER_MANA, - RATE_POWER_RAGE_LOSS, - RATE_POWER_FOCUS, - RATE_POWER_ENERGY, - RATE_POWER_COMBO_POINTS_LOSS, - MAX_RATES, // runes - RATE_POWER_RUNIC_POWER_LOSS, - RATE_POWER_SOUL_SHARDS, - RATE_POWER_LUNAR_POWER, - RATE_POWER_HOLY_POWER, - MAX_RATES, // alternate - RATE_POWER_MAELSTROM, - RATE_POWER_CHI, - RATE_POWER_INSANITY, - MAX_RATES, // burning embers, unused - MAX_RATES, // demonic fury, unused - RATE_POWER_ARCANE_CHARGES, - RATE_POWER_FURY, - RATE_POWER_PAIN, - RATE_POWER_ESSENCE, - MAX_RATES, // runes - MAX_RATES, // runes - MAX_RATES, // runes - MAX_RATES, // alternate - MAX_RATES, // alternate - MAX_RATES, // alternate - }; - - if (RatesForPower[power] != MAX_RATES) - addvalue *= sWorld->getRate(RatesForPower[power]); - - int32 minPower = powerType->MinPower; - int32 maxPower = GetMaxPower(power); + uint8 runeToRegen = i; + float runeCooldown = GetRuneCooldown(i); + float secondRuneCd = GetRuneCooldown(i + 1); - if (powerType->CenterPower) - { - if (curValue > powerType->CenterPower) - { - addvalue = -std::abs(addvalue); - minPower = powerType->CenterPower; - } - else if (curValue < powerType->CenterPower) + // Regenerate second rune of the same type only after first rune is off the cooldown + if (G3D::fuzzyNe(secondRuneCd, 0.0f) && (runeCooldown > secondRuneCd || G3D::fuzzyEq(runeCooldown, 0.0f))) { - addvalue = std::abs(addvalue); - maxPower = powerType->CenterPower; + runeToRegen = i + 1; + runeCooldown = secondRuneCd; } - else - return; - } - - addvalue += m_powerFraction[powerIndex]; - int32 integerValue = int32(std::fabs(addvalue)); - if (addvalue < 0.0f) - { - if (curValue <= minPower) - return; - } - else if (addvalue > 0.0f) - { - if (curValue >= maxPower) - return; - } - else - return; + PowerTypeEntry const* powerTypeEntry = sDB2Manager.GetPowerTypeEntry(POWER_RUNES); - bool forcesSetPower = false; - if (addvalue < 0.0f) - { - if (curValue > minPower + integerValue) - { - curValue -= integerValue; - m_powerFraction[powerIndex] = addvalue + integerValue; - } - else - { - curValue = minPower; - m_powerFraction[powerIndex] = 0; - forcesSetPower = true; - } - } - else - { - if (curValue + integerValue <= maxPower) - { - curValue += integerValue; - m_powerFraction[powerIndex] = addvalue - integerValue; - } + float cdDiff = (powerTypeEntry->RegenPeace + m_unitData->PowerRegenFlatModifier[GetPowerIndex(GetPowerTypeForBaseRune(runeToRegen))]) * 0.001f * diff; + if (runeCooldown > cdDiff) + SetRuneCooldown(runeToRegen, runeCooldown - cdDiff); else - { - curValue = maxPower; - m_powerFraction[powerIndex] = 0; - forcesSetPower = true; - } - } - - if (GetCommandStatus(CHEAT_POWER)) - curValue = maxPower; - - if (m_regenTimerCount >= 2000 || forcesSetPower) - SetPower(power, curValue); - else - { - // throttle packet sending - DoWithSuppressingObjectUpdates([&]() - { - SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::Power, powerIndex), curValue); - const_cast<UF::UnitData&>(*m_unitData).ClearChanged(&UF::UnitData::Power, powerIndex); - }); + SetRuneCooldown(runeToRegen, 0.0f); } } -void Player::InterruptPowerRegen(Powers power) -{ - uint32 powerIndex = GetPowerIndex(power); - if (powerIndex == MAX_POWERS || powerIndex >= MAX_POWERS_PER_CLASS) - return; - - m_regenInterruptTimestamp = GameTime::Now(); - m_powerFraction[powerIndex] = 0.0f; - SendDirectMessage(WorldPackets::Combat::InterruptPowerRegen(power).Write()); -} - void Player::RegenerateHealth() { uint32 curValue = GetHealth(); @@ -17635,6 +17455,7 @@ bool Player::LoadFromDB(ObjectGuid guid, CharacterDatabaseQueryHolder const& hol if (chrSpec->ClassID == GetClass()) SetLootSpecId(lootSpecId); + RegisterPowerTypes(); UpdateDisplayPower(); _LoadTalents(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_TALENTS)); _LoadSpells(holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_SPELLS), holder.GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_SPELL_FAVORITES)); @@ -25422,7 +25243,6 @@ void Player::AtExitCombat() { Unit::AtExitCombat(); UpdatePotionCooldown(); - m_regenInterruptTimestamp = GameTime::Now(); } float Player::GetBlockPercent(uint8 attackerLevel) const diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index e3a388fa47c..4b659afd9ae 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1755,11 +1755,8 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player> void SetBindPoint(ObjectGuid guid) const; void SendRespecWipeConfirm(ObjectGuid const& guid, uint32 cost, SpecResetType respecType) const; - void RegenerateAll(); - void Regenerate(Powers power); - void InterruptPowerRegen(Powers power); - void RegenerateHealth(); - void setRegenTimerCount(uint32 time) {m_regenTimerCount = time;} + void RegenerateHealth() override; + void RegenerateRunes(uint32 diff) override; void setWeaponChangeTimer(uint32 time) {m_weaponChangeTimer = time;} uint64 GetMoney() const { return m_activePlayerData->Coinage; } @@ -2101,7 +2098,6 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player> void UpdateExpertise(WeaponAttackType attType); void ApplyManaRegenBonus(int32 amount, bool apply); void ApplyHealthRegenBonus(int32 amount, bool apply); - void UpdatePowerRegen(Powers powerType); void SetPetSpellPower(uint32 spellPower) { SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::PetSpellPower), spellPower); } @@ -2880,9 +2876,6 @@ class TC_GAME_API Player final : public Unit, public GridObject<Player> protected: // Gamemaster whisper whitelist GuidList WhisperList; - TimePoint m_regenInterruptTimestamp; - uint32 m_regenTimerCount; - std::array<float, MAX_POWERS_PER_CLASS> m_powerFraction; uint32 m_contestedPvPTimer; /*********************************************************/ diff --git a/src/server/game/Entities/Unit/StatSystem.cpp b/src/server/game/Entities/Unit/StatSystem.cpp index ed331937999..c2b6063d7b0 100644 --- a/src/server/game/Entities/Unit/StatSystem.cpp +++ b/src/server/game/Entities/Unit/StatSystem.cpp @@ -97,6 +97,92 @@ int32 Unit::GetCreatePowerValue(Powers power) const return 0; } +void Unit::UpdatePowerRegen(Powers powerType) +{ + uint32 powerIndex = GetPowerIndex(powerType); + if (powerIndex == MAX_POWERS || powerIndex >= MAX_POWERS_PER_CLASS) + return; + + float powerRegenMod = GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_POWER_REGEN, powerType) / 5.f; + float powerRegenModPct = GetTotalAuraMultiplierByMiscValue(SPELL_AURA_MOD_POWER_REGEN_PERCENT, powerType); + + switch (powerType) + { + case POWER_MANA: + { + // Get base of Mana Pool in sBaseMPGameTable + uint32 basemana = 0; + + if (IsPlayer()) + sObjectMgr->GetPlayerClassLevelInfo(GetClass(), GetLevel(), basemana); + else + basemana = GetCreateMana(); // this should also get replaced by the base mana game table in the future. + + float manaRegenModPct = GetTotalAuraMultiplierByMiscValue(SPELL_AURA_MOD_MANA_REGEN_PCT, POWER_MANA); + + // BaseRegen = 5% of Base Mana per five seconds + float baseRegen = basemana / 100.f; + // SPELL_AURA_MOD_POWER_REGEN flat bonus + baseRegen += powerRegenMod; + + // SpiritRegen = Spirit * GTRegenMpPerSpt * Sqrt(INT) * 5 + float spiritRegen = GetStat(STAT_SPIRIT) * GetGameTableColumnForClass(sRegenMpPerSptTable.GetRow(GetLevel()), GetClass()) * 5.0f; + if (GetStat(STAT_INTELLECT) > 0.0f) + spiritRegen *= std::sqrt(GetStat(STAT_INTELLECT)); + + // SPELL_AURA_MOD_POWER_REGEN_PERCENT pct bonus + baseRegen *= powerRegenModPct; + spiritRegen *= powerRegenModPct; + + // SPELL_AURA_MOD_MANA_REGEN_PCT pct bonus + baseRegen *= manaRegenModPct; + spiritRegen *= manaRegenModPct; + + // SPELL_AURA_MOD_MANA_REGEN_INTERRUPT allow some of the spirit regeneration to bypass the combat restriction + int32 modManaRegenInterrupt = GetTotalAuraModifier(SPELL_AURA_MOD_MANA_REGEN_INTERRUPT); + + SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::PowerRegenFlatModifier, powerIndex), baseRegen + spiritRegen); + SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::PowerRegenInterruptedFlatModifier, powerIndex), baseRegen + CalculatePct(spiritRegen, modManaRegenInterrupt)); + break; + } + default: + { + // Classic Only - Death Knight Runes use the flags of the POWER_RUNES + if (powerType == POWER_RUNE_BLOOD || powerType == POWER_RUNE_FROST || powerType == POWER_RUNE_UNHOLY) + powerType = POWER_RUNES; + + PowerTypeEntry const* powerTypeEntry = sDB2Manager.GetPowerTypeEntry(powerType); + if (!powerTypeEntry) + break; + + // Base Regen + float peaceRegen = powerTypeEntry->RegenPeace; + float combatRegen = powerTypeEntry->RegenCombat; + + // Haste Regen + if (powerTypeEntry->GetFlags().HasFlag(PowerTypeFlags::RegenAffectedByHaste) && G3D::fuzzyNe(m_unitData->ModHaste, 0.0f)) + { + peaceRegen /= m_unitData->ModHaste; + combatRegen /= m_unitData->ModHaste; + } + + peaceRegen *= powerRegenModPct; + combatRegen *= powerRegenModPct; + + // Subtract the base value to get the proper offset + peaceRegen -= powerTypeEntry->RegenPeace; + combatRegen -= powerTypeEntry->RegenCombat; + + peaceRegen += powerRegenMod; + combatRegen += powerRegenMod; + + SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::PowerRegenFlatModifier, powerIndex), peaceRegen); + SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::PowerRegenInterruptedFlatModifier, powerIndex), combatRegen); + break; + } + } +} + /*####################################### ######## ######## ######## PLAYERS STAT SYSTEM ######## @@ -209,9 +295,6 @@ bool Player::UpdateAllStats() UpdateAttackPowerAndDamage(true); UpdateMaxHealth(); - for (uint8 i = POWER_MANA; i < MAX_POWERS; ++i) - UpdateMaxPower(Powers(i)); - UpdateAllRatings(); UpdateAllCritPercentages(); UpdateSpellCritChance(); @@ -224,8 +307,14 @@ bool Player::UpdateAllStats() RecalculateRating(CR_ARMOR_PENETRATION); UpdateAllResistances(); - for (uint8 i = POWER_MANA; i < MAX_POWERS; ++i) - UpdatePowerRegen(Powers(i)); + for (Powers powerType : GetUsedPowerTypes()) + { + if (powerType == MAX_POWERS) + continue; + + UpdateMaxPower(powerType); + UpdatePowerRegen(powerType); + } return true; } @@ -795,85 +884,6 @@ void Player::ApplyHealthRegenBonus(int32 amount, bool apply) _ModifyUInt32(apply, m_baseHealthRegen, amount); } -void Player::UpdatePowerRegen(Powers powerType) -{ - uint32 powerIndex = GetPowerIndex(powerType); - if (powerIndex == MAX_POWERS) - return; - - float powerRegenMod = GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_POWER_REGEN, powerType) / 5.f; - float powerRegenModPct = GetTotalAuraMultiplierByMiscValue(SPELL_AURA_MOD_POWER_REGEN_PERCENT, powerType); - - switch (powerType) - { - case POWER_MANA: - { - // Get base of Mana Pool in sBaseMPGameTable - uint32 basemana = 0; - sObjectMgr->GetPlayerClassLevelInfo(GetClass(), GetLevel(), basemana); - - float manaRegenModPct = GetTotalAuraMultiplierByMiscValue(SPELL_AURA_MOD_MANA_REGEN_PCT, POWER_MANA); - - // BaseRegen = 5% of Base Mana per five seconds - float baseRegen = basemana / 100.f; - // SPELL_AURA_MOD_POWER_REGEN flat bonus - baseRegen += powerRegenMod; - - // SpiritRegen = Spirit * GTRegenMpPerSpt * Sqrt(INT) * 5 - float spiritRegen = GetStat(STAT_SPIRIT) * GetGameTableColumnForClass(sRegenMpPerSptTable.GetRow(GetLevel()), GetClass()) * 5.0f; - if (GetStat(STAT_INTELLECT) > 0.0f) - spiritRegen *= std::sqrt(GetStat(STAT_INTELLECT)); - - // SPELL_AURA_MOD_POWER_REGEN_PERCENT pct bonus - baseRegen *= powerRegenModPct; - spiritRegen *= powerRegenModPct; - - // SPELL_AURA_MOD_MANA_REGEN_PCT pct bonus - baseRegen *= manaRegenModPct; - spiritRegen *= manaRegenModPct; - - // SPELL_AURA_MOD_MANA_REGEN_INTERRUPT allow some of the spirit regeneration to bypass the combat restriction - int32 modManaRegenInterrupt = GetTotalAuraModifier(SPELL_AURA_MOD_MANA_REGEN_INTERRUPT); - - SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::PowerRegenFlatModifier, powerIndex), baseRegen + spiritRegen); - SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::PowerRegenInterruptedFlatModifier, powerIndex), baseRegen + CalculatePct(spiritRegen, modManaRegenInterrupt)); - break; - } - default: - { - // Classic Only - Death Knight Runes use the flags of the POWER_RUNES - if (powerType == POWER_RUNE_BLOOD || powerType == POWER_RUNE_FROST || powerType == POWER_RUNE_UNHOLY) - powerType = POWER_RUNES; - - PowerTypeEntry const* powerTypeEntry = sDB2Manager.GetPowerTypeEntry(powerType); - // Base Regen - float peaceRegen = powerTypeEntry->RegenPeace; - float combatRegen = powerTypeEntry->RegenCombat; - - // Haste Regen - if (powerTypeEntry->GetFlags().HasFlag(PowerTypeFlags::RegenAffectedByHaste) && G3D::fuzzyNe(m_unitData->ModHaste, 0.0f)) - { - peaceRegen /= m_unitData->ModHaste; - combatRegen /= m_unitData->ModHaste; - } - - peaceRegen *= powerRegenModPct; - combatRegen *= powerRegenModPct; - - // Subtract the base value to get the proper offset - peaceRegen -= powerTypeEntry->RegenPeace; - combatRegen -= powerTypeEntry->RegenCombat; - - peaceRegen += powerRegenMod; - combatRegen += powerRegenMod; - - SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::PowerRegenFlatModifier, powerIndex), peaceRegen); - SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::PowerRegenInterruptedFlatModifier, powerIndex), combatRegen); - break; - } - } -} - void Player::_ApplyAllStatBonuses() { SetCanModifyStats(false); @@ -924,8 +934,14 @@ bool Creature::UpdateAllStats() UpdateAttackPowerAndDamage(); UpdateAttackPowerAndDamage(true); - for (uint8 i = POWER_MANA; i < MAX_POWERS; ++i) - UpdateMaxPower(Powers(i)); + for (Powers powerType : GetUsedPowerTypes()) + { + if (powerType == MAX_POWERS) + continue; + + UpdateMaxPower(powerType); + UpdatePowerRegen(powerType); + } UpdateAllResistances(); @@ -955,12 +971,13 @@ uint32 Creature::GetPowerIndex(Powers power) const return 2; case POWER_ALTERNATE_POWER: return 1; - case POWER_ALTERNATE_QUEST: - return 3; - case POWER_ALTERNATE_ENCOUNTER: - return 4; - case POWER_ALTERNATE_MOUNT: - return 5; + // Classic Only - These Power Types don't exist + //case POWER_ALTERNATE_QUEST: + // return 3; + //case POWER_ALTERNATE_ENCOUNTER: + // return 4; + //case POWER_ALTERNATE_MOUNT: + // return 5; default: break; } @@ -1143,8 +1160,14 @@ bool Guardian::UpdateAllStats() for (uint8 i = STAT_STRENGTH; i < MAX_STATS; ++i) UpdateStats(Stats(i)); - for (uint8 i = POWER_MANA; i < MAX_POWERS; ++i) - UpdateMaxPower(Powers(i)); + for (Powers powerType : GetUsedPowerTypes()) + { + if (powerType == MAX_POWERS) + continue; + + UpdateMaxPower(powerType); + UpdatePowerRegen(powerType); + } UpdateAllResistances(); diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index f051842bc79..d5cdc00b814 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -310,7 +310,7 @@ Unit::Unit(bool isWorldObject) : m_ControlledByPlayer(false), m_procDeep(0), m_procChainLength(0), m_transformSpell(0), m_removedAurasCount(0), m_interruptMask(SpellAuraInterruptFlags::None), m_interruptMask2(SpellAuraInterruptFlags2::None), m_unitMovedByMe(nullptr), m_playerMovingMe(nullptr), m_charmer(nullptr), m_charmed(nullptr), - i_motionMaster(std::make_unique<MotionMaster>(this)), m_regenTimer(0), m_vehicle(nullptr), + i_motionMaster(std::make_unique<MotionMaster>(this)), m_vehicle(nullptr), m_unitTypeMask(UNIT_MASK_NONE), m_Diminishing(), m_combatManager(this), m_threatManager(this), m_aiLocked(false), _playHoverAnim(false), _aiAnimKitId(0), _movementAnimKitId(0), _meleeAnimKitId(0), _spellHistory(std::make_unique<SpellHistory>(this)) @@ -382,6 +382,12 @@ Unit::Unit(bool isWorldObject) : _isCombatDisallowed = false; _lastExtraAttackSpell = 0; + + _regenInterruptTimestamp = GameTime::Now(); + _powerRegenUpdateTimer = 0; + _healthRegenerationTimer = 0; + _powerFraction.fill(0.0f); + _usedPowerTypes.fill(MAX_POWERS); } //////////////////////////////////////////////////////////// @@ -470,6 +476,8 @@ void Unit::Update(uint32 p_time) if (IsAlive()) { + RegenerateAll(p_time); + ModifyAuraState(AURA_STATE_WOUNDED_20_PERCENT, HealthBelowPct(20)); ModifyAuraState(AURA_STATE_WOUNDED_25_PERCENT, HealthBelowPct(25)); ModifyAuraState(AURA_STATE_WOUNDED_35_PERCENT, HealthBelowPct(35)); @@ -5558,6 +5566,13 @@ void Unit::SetPowerType(Powers power, bool sendUpdate/* = true*/, bool onInit /* SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::DisplayPower), power); + // Creatures can swap out their power type at index 0 so we do keep track of the new power type here + if (IsCreature()) + { + _usedPowerTypes[GetPowerIndex(power)] = power; + UpdatePowerRegen(power); + } + // Update max power UpdateMaxPower(power); @@ -6644,10 +6659,9 @@ void Unit::SendEnergizeSpellLog(Unit* victim, uint32 spellID, int32 damage, int3 void Unit::EnergizeBySpell(Unit* victim, SpellInfo const* spellInfo, int32 damage, Powers powerType) { - if (Player* player = victim->ToPlayer()) - if (PowerTypeEntry const* powerTypeEntry = sDB2Manager.GetPowerTypeEntry(powerType)) - if (powerTypeEntry->GetFlags().HasFlag(PowerTypeFlags::UseRegenInterrupt)) - player->InterruptPowerRegen(powerType); + if (PowerTypeEntry const* powerTypeEntry = sDB2Manager.GetPowerTypeEntry(powerType)) + if (powerTypeEntry->GetFlags().HasFlag(PowerTypeFlags::UseRegenInterrupt)) + victim->InterruptPowerRegen(powerType); int32 gain = 0; int32 overEnergize = 0; @@ -8822,6 +8836,8 @@ void Unit::AtExitCombat() if (!IsInteractionAllowedInCombat()) UpdateNearbyPlayersInteractions(); + + _regenInterruptTimestamp = GameTime::Now(); } void Unit::AtTargetAttacked(Unit* target, bool canInitialAggro) @@ -10492,13 +10508,11 @@ void Unit::ApplyCastTimePercentMod(float val, bool apply) { ApplyPercentModUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::ModCastingSpeed), val, !apply); ApplyPercentModUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::ModSpellHaste), val, !apply); - ApplyPercentModUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::ModHasteRegen), val, !apply); } else { ApplyPercentModUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::ModCastingSpeed), -val, apply); ApplyPercentModUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::ModSpellHaste), -val, apply); - ApplyPercentModUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::ModHasteRegen), -val, apply); } } @@ -10509,28 +10523,24 @@ void Unit::ApplyHasteRegenMod(float val, bool apply) else ApplyPercentModUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::ModHasteRegen), -val, apply); - if (IsPlayer()) + for (Powers powerType : GetUsedPowerTypes()) { - for (uint8 powerType = POWER_MANA; powerType != MAX_POWERS; ++powerType) - { - uint32 powerIndex = GetPowerIndex(static_cast<Powers>(powerType)); - if (powerIndex == MAX_POWERS) - continue; + if (powerType == MAX_POWERS || GetPowerIndex(powerType) == MAX_POWERS) + continue; - PowerTypeEntry const* powerTypeEntry = sDB2Manager.GetPowerTypeEntry(static_cast<Powers>(powerType)); - if (!powerTypeEntry) - continue; + PowerTypeEntry const* powerTypeEntry = sDB2Manager.GetPowerTypeEntry(powerType); + if (!powerTypeEntry) + continue; - bool regenAffectedByHaste = powerTypeEntry->GetFlags().HasFlag(PowerTypeFlags::RegenAffectedByHaste); + bool regenAffectedByHaste = powerTypeEntry->GetFlags().HasFlag(PowerTypeFlags::RegenAffectedByHaste); - // Classic Only - Death Knight Runes use the flags of the POWER_RUNES - if (powerType == POWER_RUNE_BLOOD || powerType == POWER_RUNE_FROST || powerType == POWER_RUNE_UNHOLY) - if (PowerTypeEntry const* powerTypeEntry = sDB2Manager.GetPowerTypeEntry(POWER_RUNES)) - regenAffectedByHaste = powerTypeEntry->GetFlags().HasFlag(PowerTypeFlags::RegenAffectedByHaste); + // Classic Only - Death Knight Runes use the flags of the POWER_RUNES + if (powerType == POWER_RUNE_BLOOD || powerType == POWER_RUNE_FROST || powerType == POWER_RUNE_UNHOLY) + if (PowerTypeEntry const* powerTypeEntry = sDB2Manager.GetPowerTypeEntry(POWER_RUNES)) + regenAffectedByHaste = powerTypeEntry->GetFlags().HasFlag(PowerTypeFlags::RegenAffectedByHaste); - if (regenAffectedByHaste) - ToPlayer()->UpdatePowerRegen(static_cast<Powers>(powerType)); - } + if (regenAffectedByHaste) + UpdatePowerRegen(powerType); } } @@ -11943,6 +11953,211 @@ bool Unit::CanApplyResilience() const *damage -= target->GetDamageReduction(*damage); } +// Players update their powers in 2 seconds intervals, creatures in 1 second ones +constexpr uint32 CREATURE_POWER_REGEN_UPDATE_INTERVAL = 1 * IN_MILLISECONDS; +constexpr uint32 PLAYER_POWER_REGEN_UPDATE_INTERVAL = 2 * IN_MILLISECONDS; + +void Unit::RegenerateAll(uint32 diff) +{ + _powerRegenUpdateTimer += diff; + _healthRegenerationTimer += diff; + + for (Powers powerType : GetUsedPowerTypes()) + { + if (powerType == MAX_POWERS) + continue; + + // Classic only - Runes are regenerated separately + if (powerType == POWER_RUNE_BLOOD || powerType == POWER_RUNE_FROST || powerType == POWER_RUNE_UNHOLY || powerType == POWER_RUNES) + continue; + + Regenerate(powerType, diff); + } + + if (GetClass() == CLASS_DEATH_KNIGHT) + RegenerateRunes(diff); + + uint32 powerRegenUpdateInterval = IsPlayer() ? PLAYER_POWER_REGEN_UPDATE_INTERVAL : CREATURE_POWER_REGEN_UPDATE_INTERVAL; + if (_powerRegenUpdateTimer >= powerRegenUpdateInterval) + _powerRegenUpdateTimer -= powerRegenUpdateInterval; + + if (_healthRegenerationTimer >= HEALTH_REGENERATION_INTERVAL) + { + RegenerateHealth(); + _healthRegenerationTimer -= HEALTH_REGENERATION_INTERVAL; + } +} + +void Unit::Regenerate(Powers power, uint32 diff) +{ + if (!HasUnitFlag2(UNIT_FLAG2_REGENERATE_POWER)) + return; + + // Skip regeneration for power type we cannot have + uint32 powerIndex = GetPowerIndex(power); + if (powerIndex == MAX_POWERS || powerIndex >= MAX_POWERS_PER_CLASS) + return; + + /// @todo possible use of miscvalueb instead of amount + if (HasAuraTypeWithValue(SPELL_AURA_PREVENT_REGENERATE_POWER, power) || HasAuraType(SPELL_AURA_INTERRUPT_REGEN)) + return; + + int32 curValue = GetPower(power); + + PowerTypeEntry const* powerType = sDB2Manager.GetPowerTypeEntry(power); + if (!powerType) + return; + + float addvalue = 0.0f; + if (!IsInCombat()) + { + if (powerType->GetFlags().HasFlag(PowerTypeFlags::UseRegenInterrupt) && _regenInterruptTimestamp + Milliseconds(powerType->RegenInterruptTimeMS) >= GameTime::Now()) + return; + + addvalue = (powerType->RegenPeace + m_unitData->PowerRegenFlatModifier[powerIndex]) * 0.001f * diff; + } + else + addvalue = (powerType->RegenCombat + m_unitData->PowerRegenInterruptedFlatModifier[powerIndex]) * 0.001f * diff; + + static Rates const RatesForPower[MAX_POWERS] = + { + RATE_POWER_MANA, + RATE_POWER_RAGE_LOSS, + RATE_POWER_FOCUS, + RATE_POWER_ENERGY, + RATE_POWER_COMBO_POINTS_LOSS, + MAX_RATES, // runes + RATE_POWER_RUNIC_POWER_LOSS, + RATE_POWER_SOUL_SHARDS, + RATE_POWER_LUNAR_POWER, + RATE_POWER_HOLY_POWER, + MAX_RATES, // alternate + RATE_POWER_MAELSTROM, + RATE_POWER_CHI, + RATE_POWER_INSANITY, + MAX_RATES, // burning embers, unused + MAX_RATES, // demonic fury, unused + RATE_POWER_ARCANE_CHARGES, + RATE_POWER_FURY, + RATE_POWER_PAIN, + RATE_POWER_ESSENCE, + MAX_RATES, // runes + MAX_RATES, // runes + MAX_RATES, // runes + MAX_RATES, // alternate + MAX_RATES, // alternate + MAX_RATES, // alternate + }; + + if (RatesForPower[power] != MAX_RATES) + addvalue *= sWorld->getRate(RatesForPower[power]); + + int32 minPower = powerType->MinPower; + int32 maxPower = GetMaxPower(power); + + if (powerType->CenterPower) + { + if (curValue > powerType->CenterPower) + { + addvalue = -std::abs(addvalue); + minPower = powerType->CenterPower; + } + else if (curValue < powerType->CenterPower) + { + addvalue = std::abs(addvalue); + maxPower = powerType->CenterPower; + } + else + return; + } + + addvalue += _powerFraction[powerIndex]; + int32 integerValue = int32(std::fabs(addvalue)); + + if (addvalue < 0.0f) + { + if (curValue <= minPower) + return; + } + else if (addvalue > 0.0f) + { + if (curValue >= maxPower) + return; + } + else + return; + + bool forcesSetPower = false; + if (addvalue < 0.0f) + { + if (curValue > minPower + integerValue) + { + curValue -= integerValue; + _powerFraction[powerIndex] = addvalue + integerValue; + } + else + { + curValue = minPower; + _powerFraction[powerIndex] = 0; + forcesSetPower = true; + } + } + else + { + if (curValue + integerValue <= maxPower) + { + curValue += integerValue; + _powerFraction[powerIndex] = addvalue - integerValue; + } + else + { + curValue = maxPower; + _powerFraction[powerIndex] = 0; + forcesSetPower = true; + } + } + + if (IsPlayer() && ToPlayer()->GetCommandStatus(CHEAT_POWER)) + curValue = maxPower; + + if (_powerRegenUpdateTimer >= (IsPlayer() ? PLAYER_POWER_REGEN_UPDATE_INTERVAL : CREATURE_POWER_REGEN_UPDATE_INTERVAL) || forcesSetPower) + SetPower(power, curValue); + else + { + // throttle packet sending + DoWithSuppressingObjectUpdates([&]() + { + SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::Power, powerIndex), curValue); + const_cast<UF::UnitData&>(*m_unitData).ClearChanged(&UF::UnitData::Power, powerIndex); + }); + } +} + +void Unit::InterruptPowerRegen(Powers power) +{ + uint32 powerIndex = GetPowerIndex(power); + if (powerIndex == MAX_POWERS || powerIndex >= MAX_POWERS_PER_CLASS) + return; + + _regenInterruptTimestamp = GameTime::Now(); + _powerFraction[powerIndex] = 0.0f; + + if (IsPlayer()) + ToPlayer()->SendDirectMessage(WorldPackets::Combat::InterruptPowerRegen(power).Write()); +} + +void Unit::RegisterPowerTypes() +{ + for (uint8 i = POWER_MANA; i < MAX_POWERS; ++i) + { + uint32 powerIndex = GetPowerIndex(Powers(i)); + if (powerIndex == MAX_POWERS || powerIndex == MAX_POWERS_PER_CLASS) + continue; + + _usedPowerTypes[powerIndex] = static_cast<Powers>(i); + } +} + int32 Unit::CalculateAOEAvoidance(int32 damage, uint32 schoolMask, bool npcCaster) const { damage = int32(float(damage) * GetTotalAuraMultiplierByMiscMask(SPELL_AURA_MOD_AOE_DAMAGE_AVOIDANCE, schoolMask)); diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 5bdc465b23d..0f270c821f5 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -625,6 +625,8 @@ struct PositionUpdateInfo #define ATTACK_DISPLAY_DELAY 200 #define MAX_PLAYER_STEALTH_DETECT_RANGE 30.0f // max distance for detection targets by player +constexpr uint32 HEALTH_REGENERATION_INTERVAL = 2 * IN_MILLISECONDS; + class TC_GAME_API Unit : public WorldObject { public: @@ -950,6 +952,15 @@ class TC_GAME_API Unit : public WorldObject virtual bool CanApplyResilience() const; static void ApplyResilience(Unit const* victim, int32* damage); + // Regeneration handling + void RegenerateAll(uint32 diff); + void Regenerate(Powers powerType, uint32 diff); + void InterruptPowerRegen(Powers power); + void UpdatePowerRegen(Powers powerType); + void RegisterPowerTypes(); + virtual void RegenerateHealth() = 0; + virtual void RegenerateRunes(uint32 /*diff*/) { } + int32 CalculateAOEAvoidance(int32 damage, uint32 schoolMask, bool npcCaster) const; float MeleeSpellMissChance(Unit const* victim, WeaponAttackType attType, SpellInfo const* spellInfo) const override; @@ -1893,7 +1904,6 @@ class TC_GAME_API Unit : public WorldObject std::unique_ptr<MotionMaster> i_motionMaster; std::array<uint32, MAX_REACTIVE> m_reactiveTimer; - uint32 m_regenTimer; Vehicle* m_vehicle; Trinity::unique_trackable_ptr<Vehicle> m_vehicleKit; @@ -1987,6 +1997,16 @@ class TC_GAME_API Unit : public WorldObject PositionUpdateInfo _positionUpdateInfo; bool _isCombatDisallowed; + + // Power Regeneration + TimePoint _regenInterruptTimestamp; + uint32 _powerRegenUpdateTimer; + uint32 _healthRegenerationTimer; + std::array<float, MAX_POWERS_PER_CLASS> _powerFraction; + std::array<Powers, MAX_POWERS_PER_CLASS> _usedPowerTypes; + public: + // Returns an array that contains information about which power type is used at which power index. MAX_POWERS implies that a power at given index is not used. + std::array<Powers, MAX_POWERS_PER_CLASS> const& GetUsedPowerTypes() const { return _usedPowerTypes; } }; #endif diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp index ae121c44628..5be5832e743 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp +++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp @@ -2238,11 +2238,6 @@ void AuraEffect::HandleAuraTransform(AuraApplication const* aurApp, uint8 mode, // polymorph case if ((mode & AURA_EFFECT_HANDLE_REAL) && target->GetTypeId() == TYPEID_PLAYER && target->IsPolymorphed()) { - // for players, start regeneration after 1s (in polymorph fast regeneration case) - // only if caster is Player (after patch 2.4.2) - if (GetCasterGUID().IsPlayer()) - target->ToPlayer()->setRegenTimerCount(1*IN_MILLISECONDS); - //dismount polymorphed target (after patch 2.4.2) if (target->IsMounted()) target->RemoveAurasByType(SPELL_AURA_MOUNTED); |