aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorOvahlord <dreadkiller@gmx.de>2024-07-22 23:45:44 +0200
committerGitHub <noreply@github.com>2024-07-22 23:45:44 +0200
commit17c35e585736e13ac7a4a7758a50ccc54442a6af (patch)
treeabfb2042ff3407b8630df340bfdccfb493e9971b /src
parent0aa82cde51c08fff64ca904c07ee3a28414bdbee (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.cpp111
-rw-r--r--src/server/game/Entities/Creature/Creature.h3
-rw-r--r--src/server/game/Entities/Creature/CreatureData.h2
-rw-r--r--src/server/game/Entities/Pet/Pet.cpp35
-rw-r--r--src/server/game/Entities/Pet/Pet.h1
-rw-r--r--src/server/game/Entities/Player/Player.cpp216
-rw-r--r--src/server/game/Entities/Player/Player.h11
-rw-r--r--src/server/game/Entities/Unit/StatSystem.cpp211
-rw-r--r--src/server/game/Entities/Unit/Unit.cpp263
-rw-r--r--src/server/game/Entities/Unit/Unit.h22
-rw-r--r--src/server/game/Spells/Auras/SpellAuraEffects.cpp5
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);