diff options
-rw-r--r-- | src/server/game/Entities/Creature/Creature.cpp | 94 | ||||
-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/Player/Player.cpp | 164 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.h | 9 | ||||
-rw-r--r-- | src/server/game/Entities/Unit/StatSystem.cpp | 162 | ||||
-rw-r--r-- | src/server/game/Entities/Unit/Unit.cpp | 224 | ||||
-rw-r--r-- | src/server/game/Entities/Unit/Unit.h | 15 |
8 files changed, 302 insertions, 371 deletions
diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index f57af6cf1c6..ac1df5d005f 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,59 +874,6 @@ void Creature::Heartbeat() ForcePartyMembersIntoCombat(); } -void Creature::Regenerate(Powers power) -{ - uint32 curValue = GetPower(power); - uint32 maxValue = GetMaxPower(power); - - if (!HasUnitFlag2(UNIT_FLAG2_REGENERATE_POWER)) - return; - - if (curValue >= maxValue) - return; - - float addvalue = 0.0f; - - switch (power) - { - case POWER_FOCUS: - { - // 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()) - { - float ManaIncreaseRate = sWorld->getRate(RATE_POWER_MANA); - - addvalue = uint32((27.0f / 5.0f + 17.0f) * ManaIncreaseRate); - } - 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()) @@ -992,7 +900,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); } 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/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 4704ba291db..50cd509faff 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; @@ -1045,12 +1040,6 @@ void Player::Update(uint32 p_time) m_zoneUpdateTimer -= p_time; } - if (IsAlive()) - { - m_regenTimer += p_time; - RegenerateAll(); - } - if (m_deathState == JUST_DIED) KillPlayer(); @@ -1663,159 +1652,6 @@ void Player::RegenerateAll() m_regenTimer = 0; } -void Player::Regenerate(Powers power) -{ - // 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) && 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); - - 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 += 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; - - 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; - } - 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); - }); - } -} - -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(); diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index e3a388fa47c..f22baa02f5a 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1755,10 +1755,7 @@ 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 RegenerateHealth() override; void setRegenTimerCount(uint32 time) {m_regenTimerCount = time;} void setWeaponChangeTimer(uint32 time) {m_weaponChangeTimer = time;} @@ -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..d847acb4d7a 100644 --- a/src/server/game/Entities/Unit/StatSystem.cpp +++ b/src/server/game/Entities/Unit/StatSystem.cpp @@ -97,6 +97,89 @@ int32 Unit::GetCreatePowerValue(Powers power) const return 0; } +void Unit::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; + + if (IsPlayer()) + sObjectMgr->GetPlayerClassLevelInfo(GetClass(), GetLevel(), basemana); + else + basemana = GetCreateMana(); + + 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; + } + } +} + /*####################################### ######## ######## ######## PLAYERS STAT SYSTEM ######## @@ -795,85 +878,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); diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index f051842bc79..aff90d1fbcc 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)) @@ -470,6 +470,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)); @@ -6644,10 +6646,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)) + InterruptPowerRegen(powerType); int32 gain = 0; int32 overEnergize = 0; @@ -10509,28 +10510,25 @@ void Unit::ApplyHasteRegenMod(float val, bool apply) else ApplyPercentModUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::ModHasteRegen), -val, apply); - if (IsPlayer()) + for (uint8 powerType = POWER_MANA; powerType != MAX_POWERS; ++powerType) { - for (uint8 powerType = POWER_MANA; powerType != MAX_POWERS; ++powerType) - { - uint32 powerIndex = GetPowerIndex(static_cast<Powers>(powerType)); - if (powerIndex == MAX_POWERS) - continue; + uint32 powerIndex = GetPowerIndex(static_cast<Powers>(powerType)); + if (powerIndex == MAX_POWERS) + continue; - PowerTypeEntry const* powerTypeEntry = sDB2Manager.GetPowerTypeEntry(static_cast<Powers>(powerType)); - if (!powerTypeEntry) - continue; + PowerTypeEntry const* powerTypeEntry = sDB2Manager.GetPowerTypeEntry(static_cast<Powers>(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(static_cast<Powers>(powerType)); } } @@ -11943,6 +11941,188 @@ 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 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, diff); + + uint32 powerRegenUpdateInterval = IsPlayer() ? PLAYER_POWER_REGEN_UPDATE_INTERVAL : CREATURE_POWER_REGEN_UPDATE_INTERVAL; + if (_powerRegenUpdateTimer >= powerRegenUpdateInterval) + _powerRegenUpdateTimer = 0; + + if (_healthRegenerationTimer >= HEALTH_REGENERATION_INTERVAL) + { + RegenerateHealth(); + _healthRegenerationTimer = 0; + } +} + +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()); +} + 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..5779f74a2d7 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,13 @@ 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); + virtual void RegenerateHealth() = 0; + int32 CalculateAOEAvoidance(int32 damage, uint32 schoolMask, bool npcCaster) const; float MeleeSpellMissChance(Unit const* victim, WeaponAttackType attType, SpellInfo const* spellInfo) const override; @@ -1893,7 +1902,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 +1995,11 @@ class TC_GAME_API Unit : public WorldObject PositionUpdateInfo _positionUpdateInfo; bool _isCombatDisallowed; + + TimePoint _regenInterruptTimestamp; + uint32 _powerRegenUpdateTimer; // Controls the interval in which the regenerated power is being sent in update object packets + uint32 _healthRegenerationTimer; + std::array<float, MAX_POWERS_PER_CLASS> _powerFraction; }; #endif |