Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion src/server/game/Entities/Player/Player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,12 @@ void Player::Update(uint32 p_time)
if (!IsInWorld())
return;

if (m_corruptionNeedsUpdate)
{
m_corruptionNeedsUpdate = false;
UpdateCorruption();
}

// undelivered mail
if (m_nextMailDelivereTime && m_nextMailDelivereTime <= time(nullptr))
{
Expand Down Expand Up @@ -4635,6 +4641,11 @@ void Player::ResurrectPlayer(float restore_percent, bool applySickness)

setDeathState(ALIVE);

// RemoveAllAurasOnDeath stripped the corruption penalties and nothing puts them back:
// the sync is driven by a rating change or an area transition, and resurrecting is
// neither. Reviving where you fell otherwise leaves the player uncorrupted.
ScheduleCorruptionUpdate();

// add the flag to make sure opcode is always sent
AddUnitMovementFlag(MOVEMENTFLAG_WATERWALKING);
SetWaterWalking(false);
Expand Down Expand Up @@ -5526,7 +5537,11 @@ void Player::UpdateRating(CombatRating cr)
break;
case CR_CORRUPTION:
case CR_CORRUPTION_RESISTANCE:
UpdateCorruption();
// Bulk rebuilds strip every item's corruption before re-applying it, so syncing per
// mutation would walk the total down to zero and tear down each tier's aura on the
// way. Callers that clear this flag are responsible for one sync when they finish.
if (affectStats)
ScheduleCorruptionUpdate();
break;
case CR_SPEED:
case CR_RESILIENCE_PLAYER_DAMAGE:
Expand Down Expand Up @@ -7689,6 +7704,11 @@ void Player::UpdateArea(uint32 newArea)
if (garrison.second->IsAllowedArea(newArea))
garrison.second->Enter();
}

// A corruption penalty can be gated on a PlayerConditionID that reads the player's
// location, and nothing else re-evaluates those conditions when only the area
// changes. Safe on every transition: the sync casts only what is missing.
ScheduleCorruptionUpdate();
}
}

Expand Down Expand Up @@ -30832,12 +30852,15 @@ void Player::CreateChallengeKey(Item* item)
void Player::SetEffectiveLevelAndMaxItemLevel(uint32 effectiveLevel, uint32 maxItemLevel)
{
float healthPct = GetHealthPct();
SetCanModifyStats(false);
_RemoveAllItemMods();

SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::EffectiveLevel), effectiveLevel);
SetUpdateFieldValue(m_values.ModifyValue(&Unit::m_unitData).ModifyValue(&UF::UnitData::MaxItemLevel), maxItemLevel);

_ApplyAllItemMods();
SetCanModifyStats(true);

UpdateAverageItemLevel();

uint32 basemana = 0;
Expand Down Expand Up @@ -30865,9 +30888,15 @@ void Player::UpdateItemLevelAreaBasedScaling()
if (_usePvpItemLevels != pvpActivity)
{
float healthPct = GetHealthPct();
SetCanModifyStats(false);
_RemoveAllItemMods();
ActivatePvpItemLevels(pvpActivity);
_ApplyAllItemMods();
SetCanModifyStats(true);
// The bracket defers every derived stat, not only corruption, and neither item-mod
// pass recomputes on its own - so settle them all once here, as _ApplyAllStatBonuses
// does, leaving the max health scaled below freshly computed rather than stale.
UpdateAllStats();
SetHealth(CalculatePct(GetMaxHealth(), healthPct));
}
// @todo other types of power scaling
Expand Down
6 changes: 6 additions & 0 deletions src/server/game/Entities/Player/Player.h
Original file line number Diff line number Diff line change
Expand Up @@ -1993,7 +1993,12 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
void UpdateLeechPercentage();

void UpdateSpellCritChance();
float GetEffectiveCorruption() const;
void UpdateCorruption();
// Corruption penalties are synced once per tick rather than at each mutation site.
// A single item swap can move several corrupted pieces through several intermediate
// states; syncing each one would apply and remove the same penalty auras repeatedly.
void ScheduleCorruptionUpdate() { m_corruptionNeedsUpdate = true; }
void UpdateArmorPenetration(int32 amount);
void UpdateExpertise(WeaponAttackType attType);
void ApplyManaRegenBonus(int32 amount, bool apply);
Expand Down Expand Up @@ -3016,6 +3021,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
bool m_canParry;
bool m_canBlock;
bool m_canTitanGrip;
bool m_corruptionNeedsUpdate = false;
uint32 m_titanGripPenaltySpellId;
uint8 m_swingErrorMsg;

Expand Down
15 changes: 13 additions & 2 deletions src/server/game/Entities/Unit/StatSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -732,9 +732,14 @@ void Player::UpdateSpellCritChance()
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::SpellCritPercentage), crit);
}

float Player::GetEffectiveCorruption() const
{
return GetRatingBonusValue(CR_CORRUPTION) - GetRatingBonusValue(CR_CORRUPTION_RESISTANCE);
}

void Player::UpdateCorruption()
{
float effectiveCorruption = GetRatingBonusValue(CR_CORRUPTION) - GetRatingBonusValue(CR_CORRUPTION_RESISTANCE);
float const effectiveCorruption = GetEffectiveCorruption();
for (CorruptionEffectsEntry const* corruptionEffect : sCorruptionEffectsStore)
{
if ((CorruptionEffectsFlag(corruptionEffect->Flags) & CorruptionEffectsFlag::Disabled) != CorruptionEffectsFlag::None)
Expand All @@ -755,7 +760,13 @@ void Player::UpdateCorruption()
}
}

CastSpell(this, corruptionEffect->Aura, true);
// Re-casting a penalty that is already applied restarts its duration and resets its
// proc state, and this runs on every rating change and area transition. Cast only
// what is missing; what is already there recomputes its magnitudes in place.
if (Aura* corruptionAura = GetAura(corruptionEffect->Aura))
corruptionAura->RecalculateAmountOfEffects();
else
CastSpell(this, corruptionEffect->Aura, true);
}
}

Expand Down
43 changes: 43 additions & 0 deletions src/server/scripts/Commands/cs_debug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ class debug_commandscript : public CommandScript
{ "transportState", rbac::RBAC_PERM_COMMAND_DEBUG, false, &HandleDebugTransportStateCommand, "" },
{ "worldstate" , rbac::RBAC_PERM_COMMAND_DEBUG, false, &HandleDebugWorldStateCommand, "" },
{ "wsexpression" , rbac::RBAC_PERM_COMMAND_DEBUG, false, &HandleDebugWSExpressionCommand, "" },
{ "corruption", rbac::RBAC_PERM_COMMAND_DEBUG, false, &HandleDebugCorruptionCommand, "" },
};
static std::vector<ChatCommand> commandTable =
{
Expand Down Expand Up @@ -1429,6 +1430,48 @@ class debug_commandscript : public CommandScript
return true;
}

static bool HandleDebugCorruptionCommand(ChatHandler* handler, char const* /*args*/)
{
Player* target = handler->getSelectedPlayerOrSelf();
if (!target)
{
handler->SendSysMessage(LANG_NO_CHAR_SELECTED);
handler->SetSentErrorMessage(true);
return false;
}

float const effectiveCorruption = target->GetEffectiveCorruption();
handler->PSendSysMessage("Corruption for %s: effective %.2f (corruption %.2f - resistance %.2f)",
target->GetName().c_str(), effectiveCorruption,
target->GetRatingBonusValue(CR_CORRUPTION), target->GetRatingBonusValue(CR_CORRUPTION_RESISTANCE));

uint32 rows = 0;
for (CorruptionEffectsEntry const* corruptionEffect : sCorruptionEffectsStore)
{
++rows;

char const* state;
if ((CorruptionEffectsFlag(corruptionEffect->Flags) & CorruptionEffectsFlag::Disabled) != CorruptionEffectsFlag::None)
state = "disabled";
else if (effectiveCorruption < corruptionEffect->MinCorruption)
state = "below threshold";
else if (PlayerConditionEntry const* playerCondition = sPlayerConditionStore.LookupEntry(corruptionEffect->PlayerConditionID))
state = ConditionMgr::IsPlayerMeetingCondition(target, playerCondition) ? "qualifies" : "condition failed";
else
state = "qualifies";

handler->PSendSysMessage(" row %u: min %.2f aura %d cond %d - %s, aura %s",
corruptionEffect->ID, corruptionEffect->MinCorruption, corruptionEffect->Aura,
corruptionEffect->PlayerConditionID, state,
target->HasAura(corruptionEffect->Aura) ? "APPLIED" : "absent");
}

if (!rows)
handler->SendSysMessage("CorruptionEffects.db2 holds no rows - no penalty can apply on this server. That is missing client data, not a core fault.");

return true;
}

static bool HandleDebugMaxItemLevelCommand(ChatHandler* handler, char const* args)
{
CommandArgs commandArgs = CommandArgs(handler, args, { CommandArgs::ARG_UINT, CommandArgs::ARG_UINT });
Expand Down