feat(corruption): the three 8.3 corruption drawback tiers - #75
Draft
svenbledt wants to merge 10 commits into
Draft
feat(corruption): the three 8.3 corruption drawback tiers#75svenbledt wants to merge 10 commits into
svenbledt wants to merge 10 commits into
Conversation
Collaborator
Author
|
@Hextv its a draft PR so i could need asisst on the last gaps i clearly documented but those are the last ones then were done here! |
Owner
|
I found that some items that should have the corruption spellid don't. For example 172227 [Shard of the Black Empire]. |
Collaborator
Author
|
I also found a problem: When unequipped items with corruption the corruption effects still trigger until u remove them from your bags or relog. |
ITEM_BONUS_ITEM_EFFECT_ID = 23 is declared in DBCEnums.h but had no case in BonusData::AddBonus, BonusData had no storage for effects, and every consumer read proto->Effects - the static ItemTemplate list. An item whose effect arrives through a bonus list therefore contributed its stats but never its effect spell. This is the whole reason corrupted gear does nothing on this core, but it is not corruption-specific: any bonus-granted effect was silently dropped. BonusData gains an effect set, seeded from the template in Initialize and appended to by AddBonus. Item::GetEffects() returns it and Item::GetEffectCount() its size. The migration rule is that a call site uses GetEffects() if and only if it holds an Item*; sites that hold only an ItemTemplate const* keep proto->Effects and carry a comment saying why - a loot filter runs before any Item exists, an auction listing is an entry plus bonus ids, a toy is used from the collection, and UpdatePotionCooldown is keyed off an item id kept after the Item is gone. Charge handling follows the effect set rather than the template. Charge slots are keyed by effect index into a 5-wide update field, so every walk is clamped to MAX_ITEM_SPELLS and indexed with uint8. Item::Create seeds from the template only and every caller applies bonus lists afterwards, so SeedSpellCharges fills the slots a newly applied bonus list just added without touching charges already spent. SaveToDB and LoadFromDB agree on the clamped width, and LoadFromDB now runs after SetBonuses and reads however many tokens are present instead of discarding every charge on a count mismatch. Four defects surfaced while migrating the call sites and are fixed here: - Spell::TakeCastItem judged "no charges left" from whichever effect it looked at last, and read a charge array that a stacked item never writes to. It now folds the verdict across every charged effect and only trusts a persisted count. - Spell::TakeReagents dereferenced m_CastItem in a block that had already cleared it, if a second reagent slot named the same item. - LootItem::AllowedForPlayer indexed Effects[1] unconditionally. - WorldSession::HandleUseItemOpcode rejected an in-combat use because a passive effect on the item was flagged unusable in combat, rather than judging the on-use effect being cast. MAX_BONUS_ITEM_EFFECTS caps the set at 16 so malformed hotfix data cannot grow it without limit; unknown effect ids are skipped silently, since logging would fire once per item instance. Verified by building worldserver on Windows / VS 2022 x64 RelWithDebInfo and in-game: a corrupted item's effect spell now applies on equip, and charged on-use items are consumed at the right time.
Aura 285 was named SPELL_AURA_MOD_ATTACK_POWER_OF_ARMOR and handled by HandleAuraModAttackPowerOfArmor, which added the target's armor to its attack power. The 8.3.7 client data does not describe that aura. It carries 359 rows of aura 285 across 336 spells, every one of them with a non-zero trigger spell, which is the shape of SPELL_AURA_LINKED (284) - not of a stat conversion. The old handler was inert in practice: nothing in this build's data reaches it. Aura 285 is therefore renamed SPELL_AURA_LINKED_2 and pointed at HandleAuraLinked, matching what the rows actually contain. The 284 + 285 link graph in this build was checked for safety before repointing: it has zero cycles and a maximum depth of 2, so no chain of linked auras can recurse. HandleAuraModAttackPowerOfArmor and its UpdateAttackPowerAndDamage counterpart are removed rather than left unreachable, and the two comments in StatSystem.cpp that cited the aura by name are corrected - UpdateArmor still refreshes melee attack power, just not for that reason. This is a generic change and deserves regression testing beyond the work that motivated it: any spell using aura 284 or 285 is in scope. Built clean on Windows / VS 2022 x64 RelWithDebInfo.
Player::UpdateCorruption had three problems, all of which left the CorruptionEffects.db2 threshold penalties out of step with the player. It re-applied live auras. The loop ended in an unconditional CastSpell(this, aura, true), so every corruption rating change re-cast penalties that were already present, restarting their durations and resetting their proc state. It now casts only what is missing and lets an aura that is already applied recalculate its own amounts in place. It was never re-evaluated on an area change. Each CorruptionEffects row carries a PlayerConditionID, and the sync ran only from UpdateRating(CR_CORRUPTION | CR_CORRUPTION_RESISTANCE) - a gear or rating-aura change. A player who walked into an area where the condition stopped matching kept the penalty indefinitely, and one who walked out never regained it. UpdateArea now schedules a sync. It was never re-evaluated on resurrect. RemoveAllAurasOnDeath strips the penalties and neither a rating change nor an area transition follows, so reviving where you fell left the player uncorrupted until they happened to cross an area border. That is why a corpse run appeared to fix it and .revive did not. Syncing at each mutation site is wrong in the other direction: a bulk item rebuild strips every item's corruption before re-applying it, so a per-mutation sync walks the total down to zero and tears down each tier's aura on the way. The sync is now coalesced behind a dirty flag and runs once per Player::Update tick. The two item-level rebuilds that did not bracket themselves with SetCanModifyStats now do, and UpdateItemLevelAreaBasedScaling settles its derived stats afterwards - without that the max health it scales the player to was one rebuild stale. GetEffectiveCorruption is split out of UpdateCorruption so callers that need the value do not repeat the rating subtraction. Verified in-game: penalties apply and remove as corruption crosses each threshold, survive a gear swap, and return on resurrect.
Corruption penalties are driven entirely by CorruptionEffects.db2, and when one fails to apply there is no way to see which of the three gates rejected it - the threshold, the PlayerConditionID, or the row being flagged disabled. The only alternative is equipping corrupted gear and inferring the answer from what happens. .debug corruption prints the selected player's effective corruption alongside the two ratings it derives from, then every CorruptionEffects row with its threshold, aura, condition, the verdict on that player and whether the aura is actually applied. A server whose client data has no CorruptionEffects rows at all reports that explicitly, since it is otherwise indistinguishable from a core fault. RBAC-gated behind RBAC_PERM_COMMAND_DEBUG like the rest of the table.
Nothing in this core ever wrote OverrideScaleCurve. AreaTrigger::Create fills in ExtraScaleCurve from the misc template and leaves this one default-constructed, so every areatrigger is drawn at exactly the size its template describes. That is fine while a spell keeps a fixed shape, but a script whose trigger has to change size has no way to tell the client, and the drawn ring then disagrees with whatever the server tests against. SetOverrideScaleCurve writes a flat two-point curve at the requested multiple of the template size; ClearOverrideScaleCurve returns the trigger to the template's own size. Rendering only - the server-side shape is unchanged, so a caller that scales the visual must scale its own range test to match.
The five corruption stat payload spells carry DB2 base points of 0 - the whole magnitude is supplied at runtime by their aura-285 container, through HandleAuraLinked into CastCustomSpell. Without SPELL_ATTR8_AURA_SEND_AMOUNT the amount never reaches the client (see AuraApplication::BuildUpdatePacket), so the server holds the real value while the tooltip renders 0.
The first corruption threshold penalty did nothing. Its snare 315176
carries BasePoints 0 in SpellEffect.db2 and no world DB override, so the
aura applied at "movement speed reduced by 0%" - retail computed the
magnitude server-side and shipped the client a placeholder, which is why
Blizzard's own 8.3 PTR tooltip read "slow your movement speed for 0
sec".
The curve is confirmed at its primary source rather than guessed:
Wowhead's 8.3 PTR article "Corrupted Items - Corruption Debuff
Breakpoints and Scaling of Debuffs" (news 295810) states outright that
"the magnitude of the slow is equal to {Corruption + 10}" and tabulates
it from 10 corruption (20%) to 90 (100%), which is what the constants
here produce.
The container also fired on heals. 315175 ships every TAKEN proc flag,
including PROC_FLAG_TAKEN_PERIODIC, which SpellMgr.h documents as
"damage / healing"; with no spell_proc row the auto-generated entry
takes PROC_SPELL_TYPE_MASK_ALL. Standing in a druid's Efflorescence
rooted the player once per healing tick. The new row restricts it to
PROC_SPELL_TYPE_DAMAGE and restores PROC_ATTR_TRIGGERED_CAN_PROC, which
a hand-written row would otherwise drop.
Verified in-game: the snare applies on damage taken, not on heals, and
its magnitude tracks effective corruption.
The Eye spawned and did nothing. Container 315169 procs on damage dealt and triggers 315154, which creates areatrigger 22815, but 22815 carried no ScriptName and no template actions and nothing cast the damage spell 315161 - so the Eye appeared, sat for its eight seconds and left. A template action could not fix it either: DoActions fires once on enter, while the Eye has to tick for as long as the player stays in range. What the client supplies is read rather than hardcoded. The 8 second duration is SpellDuration index 31, the 2 second tick is 315154 effect 1, the school is SpellMisc SchoolMask 32, and the per-stack amplification is 315161 effect 1 - the same row the client renders "damage taken increased by $s2%" from. What it does not supply is the damage and the radius, the two things the tooltip promises scale with corruption. Every scaling field on 315161 effect 0 is zero - EffectBasePoints, EffectBonusCoefficient, BonusCoefficientFromAP, Coefficient, Variance, EffectRealPointsPerLevel, ResourceCoefficient and EffectPointsPerResource - ContentTuningID is 0 and there is no hotfix row. Retail computed both server-side. Both come instead from Wowhead's 8.3 PTR sampling (news 295810): 875 * Corruption - 1000 per tick, and a radius of Corruption / 5 yards. The stated formulas are used rather than a fit to the article's own table, because the table's drift past 50 corruption is an artefact of Inevitable Doom amplifying all damage taken. The ring the player sees and the range the damage covers are now the same edge by construction, both taken from one division against the template radius. An earlier attempt scaled the radius but drove the graphic with SetObjectScale, and the damage cut off outside the visible ring. The range test is deliberately 2d and centre-to-centre: the zone is a cylinder, and both IsWithinDist2d and GetInsideUnits() route through IsInDist, which pads the radius by the player's CombatReach and so damages a player standing outside the edge they can see. Verified in-game: the Eye spawns, draws at a corruption-scaled radius and ticks while the player stands in it.
Container 315184 triggers 315186, which summons creature 161895 - the Thing From Beyond - and nothing happened after that. The creature was marked SmartAI with no smart_scripts rows of its own, so it spawned, stood still and despawned. Its appearance comes from a clone aura rather than the template. 161895's only creature_template_model row is display 11686, the invisible stalker shared by 8467 templates for bunnies and kill credit, which reads like an oversight and is not one: retail's Thing From Beyond is a copy of the player it chases, so the model was never meant to come from the template. The clone has to be a real aura, because WorldSession::HandleMirrorImageDataRequest answers the client's request for the copy's gear only if the unit carries SPELL_AURA_CLONE_CASTER, and reads the appearance off that aura's caster. It is applied rather than cast: Clone Caster is a positive spell and this summon is hostile (SummonProperties 4793 sets faction 14 over the template's friendly 35), so CheckCast answers SPELL_FAILED_BAD_TARGETS, and forgiving that requires TRIGGERED_IGNORE_TARGET_CHECK - which sits outside TRIGGERED_FULL_MASK and is therefore unreachable from CastSpell(..., true). The Thing is levelled to its summoner. GetEffectiveResistChance adds (victim level - attacker level) * 5 resistance, so a Thing below its target's level had roughly half its damage resisted away before it landed. The strike is measured the way the chase generator measures. IsWithinMeleeRange squares the height difference into the distance while ChaseMovementGenerator stops on a 2d test, so on uneven ground the pursuit arrived and then failed its own strike check for the whole eight seconds - one observed pursuit ended at 2d 5.72 with dz -1.34, which is 5.88 in three dimensions against a melee range of 5.08. Damage goes through SpellNonMeleeDamage rather than DealDamage, so it appears in the combat log and passes through absorbs, resistances and damage-taken modifiers instead of executing unconditionally. The tooltip figure - "reaching you deals about your health in damage" - is the cost of a whole pursuit, so it is divided across the strikes the Thing has time to land, and being caught late costs less than being caught at once. Connecting does not spend the Thing: Cascading Disaster's tooltip says being struck applies Grasping Tendrils and Eye of Corruption, which a pursuer that vanished on contact could not do. Pursuit speed is an approximation and is isolated in the GrandDelusions namespace for retuning. The tooltip states the speed rises with corruption but gives no numbers, creature_template.speed_run is TrinityCore's stock default on all three pursuer creatures, and there is no movement-speed aura anywhere in the corruption block. Verified in-game: the Thing spawns as a copy of its summoner, pursues, and its strike is reported as spell damage in the combat log.
svenbledt
force-pushed
the
corruption-system
branch
from
August 2, 2026 00:31
2a0e9d5 to
25fc4b5
Compare
Owner
|
[FOUND BUG] Corruption proc triggering globally on passive/utility actions (e.g., learning mounts) |
svenbledt
added a commit
to svenbledt/BFA-HavenCore
that referenced
this pull request
Aug 5, 2026
315169 had no `spell_proc` row, so LoadSpellProcs generated one with SpellTypeMask = PROC_SPELL_TYPE_MASK_ALL, and CanSpellTriggerProcOnEvent skips the type filter when the mask admits everything. Its DONE flags include PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS, so any positive damage-class-none cast summoned an Eye - reported on PR Hextv#75 as learning a mount spawning one. SpellTypeMask 3 keeps damage and heals and drops PROC_SPELL_TYPE_NO_DMG_HEAL, the bucket Spell.cpp assigns to a cast that neither heals nor damages. SpellPhaseMask has to be set because these flags are DONE and the load path does not default it, and has to stay HIT-only: Spell::finish raises a second proc at PROC_SPELL_PHASE_CAST passing MASK_ALL as the event's own type mask, which no SpellTypeMask can reject. Also corrects 2026_08_01_01's claim that 315169 kept PROC_ATTR_TRIGGERED_CAN_PROC. addTriggerFlag is gated on TAKEN_HIT_PROC_FLAG_MASK and 315169's flags are all DONE, so it never had the attribute. Not verified in game. Data-only change; confirming it needs the migration applied, a worldserver restart and a 20+ corruption character.
315169 had no `spell_proc` row, so LoadSpellProcs generated one with SpellTypeMask = PROC_SPELL_TYPE_MASK_ALL, and CanSpellTriggerProcOnEvent skips the type filter when the mask admits everything. Its DONE flags include PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS, so any positive damage-class-none cast summoned an Eye - reported on PR Hextv#75 as learning a mount spawning one. SpellTypeMask 3 keeps damage and heals and drops PROC_SPELL_TYPE_NO_DMG_HEAL, the bucket Spell.cpp assigns to a cast that neither heals nor damages. SpellPhaseMask has to be set because these flags are DONE and the load path does not default it, and has to stay HIT-only: Spell::finish raises a second proc at PROC_SPELL_PHASE_CAST passing MASK_ALL as the event's own type mask, which no SpellTypeMask can reject. No AttributesMask, unlike the rows in 2026_08_02_00 and 2026_08_02_02: addTriggerFlag is gated on TAKEN_HIT_PROC_FLAG_MASK, so 315169 never carried PROC_ATTR_TRIGGERED_CAN_PROC. Not verified in game. Data-only change; confirming it needs the migration applied, a worldserver restart and a 20+ corruption character.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changes Proposed:
This PR proposes changes to:
The three 8.3 corruption drawback tiers, plus the two core capabilities they need.
core/areatrigger: let a script resize the trigger the client drawsNothing in this core ever wrote
OverrideScaleCurve.AreaTrigger::Createfills inExtraScaleCurvefrom the misc template and leaves this one default-constructed, so every areatrigger is drawn at
exactly the size its template describes. A script whose trigger has to change size had no way to tell
the client, and the drawn ring then disagreed with whatever the server tested against.
SetOverrideScaleCurvewrites a flat two-point curve at the requested multiple of the template size;ClearOverrideScaleCurvereturns the trigger to the template's own size. Rendering only — theserver-side shape is unchanged, so a caller that scales the visual must scale its own range test to
match.
core/spells: send runtime aura amounts for the corruption stat payloadsThe five corruption stat payload spells carry DB2 base points of 0. The whole magnitude is supplied
at runtime by their aura-285 container, through
HandleAuraLinkedintoCastCustomSpell. WithoutSPELL_ATTR8_AURA_SEND_AMOUNTthe amount never reaches the client (seeAuraApplication::BuildUpdatePacket), so the server holds the real value while the tooltip renders 0.Grasping Tendrils — the 1+ corruption drawback
The first threshold penalty did nothing. Its snare 315176 carries
BasePoints 0inSpellEffect.db2and has no world DB override, so the aura applied at "movement speed reduced by0%". Retail computed the magnitude server-side and shipped the client a placeholder — which is why
Blizzard's own 8.3 PTR tooltip read "slow your movement speed for 0 sec".
The curve is taken from its primary source rather than guessed. Wowhead's 8.3 PTR article
Corrupted Items — Corruption Debuff Breakpoints and Scaling of Debuffs
states outright that "the magnitude of the slow is equal to {Corruption + 10}", and tabulates it
from 10 corruption (20%) through 90 (100%). That is what the constants here produce.
The container also fired on heals. 315175 ships every
TAKENproc flag, includingPROC_FLAG_TAKEN_PERIODIC, whichSpellMgr.hdocuments as "damage / healing"; with nospell_procrow, the auto-generated entry takesPROC_SPELL_TYPE_MASK_ALL. Standing in a druid'sEfflorescence rooted the player once per healing tick. The new row restricts it to
PROC_SPELL_TYPE_DAMAGEand restoresPROC_ATTR_TRIGGERED_CAN_PROC, which a hand-written row wouldotherwise drop.
Eye of Corruption — the 20+ corruption drawback
The Eye spawned and did nothing. Container 315169 procs on damage dealt and triggers 315154,
which creates areatrigger 22815 — but 22815 carried no
ScriptNameand no template actions, andnothing cast the damage spell 315161. The Eye appeared, sat for its eight seconds and left. A
template action could not fix it either:
DoActionsfires once on enter, while the Eye has to tickfor as long as the player stays in range.
What the client supplies is read, not hardcoded. The 8 second duration is
SpellDurationindex31, the 2 second tick is 315154 effect 1, the school is
SpellMiscSchoolMask32, and theper-stack amplification is 315161 effect 1 — the same row the client renders "damage taken increased
by $s2%" from.
What it does not supply is the damage and the radius — the two things the tooltip promises scale
with corruption. Every scaling field on 315161 effect 0 is zero:
EffectBasePoints,EffectBonusCoefficient,BonusCoefficientFromAP,Coefficient,Variance,EffectRealPointsPerLevel,ResourceCoefficientandEffectPointsPerResource.ContentTuningIDis0 and there is no hotfix row. Retail computed both server-side. Both come instead from the same
Wowhead PTR sampling (news 295810): 875 × Corruption − 1000
per tick, and a radius of Corruption / 5 yards. The stated formulas are used rather than a fit
to the article's own table, because the table's drift past 50 corruption is an artefact of Inevitable
Doom amplifying all damage taken.
The ring the player sees and the range the damage covers are the same edge by construction — both
come from one division against the template radius. An earlier attempt scaled the radius but drove
the graphic with
SetObjectScale, and the damage then cut off outside the visible ring. The rangetest is deliberately 2d and centre-to-centre: the zone is a cylinder, and both
IsWithinDist2dandGetInsideUnits()route throughIsInDist, which pads the radius by the player'sCombatReachandso damages a player standing outside the edge they can see.
Grand Delusions — the 40+ corruption drawback
Container 315184 triggers 315186, which summons creature 161895 — the Thing From Beyond —
and nothing happened after that. The creature was marked SmartAI with no
smart_scriptsrows of itsown, so it spawned, stood still and despawned.
Its appearance comes from a clone aura, not the template. 161895's only
creature_template_modelrow is display 11686 — the invisible stalker shared by 8467 templates for bunnies and kill credit.
That reads like an oversight and is not one: retail's Thing From Beyond is a copy of the player it
chases, so the model was never meant to come from the template. The clone has to be a real aura,
because
WorldSession::HandleMirrorImageDataRequestanswers the client's request for the copy's gearonly if the unit carries
SPELL_AURA_CLONE_CASTER, and reads the appearance off that aura's caster.It is applied rather than cast: Clone Caster is a positive spell and this summon is hostile
(
SummonProperties4793 sets faction 14 over the template's friendly 35), soCheckCastanswersSPELL_FAILED_BAD_TARGETS— and forgiving that requiresTRIGGERED_IGNORE_TARGET_CHECK, which sitsoutside
TRIGGERED_FULL_MASKand is therefore unreachable fromCastSpell(..., true).The Thing is levelled to its summoner.
GetEffectiveResistChanceadds(victim level − attacker level) × 5resistance, so a Thing below its target's level had roughly halfits damage resisted away before it landed.
The strike is measured the way the chase generator measures.
IsWithinMeleeRangesquares theheight difference into the distance while
ChaseMovementGeneratorstops on a 2d test, so on unevenground the pursuit arrived and then failed its own strike check for the whole eight seconds. One
observed pursuit ended at 2d 5.72 with
dz−1.34 — 5.88 in three dimensions, against a melee rangeof 5.08.
Damage goes through
SpellNonMeleeDamage, notDealDamage, so it appears in the combat log andpasses through absorbs, resistances and damage-taken modifiers instead of executing unconditionally.
The tooltip figure — "reaching you deals about your health in damage" — is the cost of a whole
pursuit, so it is divided across the strikes the Thing has time to land: being caught late costs less
than being caught at once. Connecting does not spend the Thing, because Cascading Disaster's tooltip
says being struck applies Grasping Tendrils and Eye of Corruption, which a pursuer that vanished on
contact could not do.
Pursuit speed is an approximation and is isolated in the
GrandDelusionsnamespace for retuning.The tooltip states the speed rises with corruption but gives no numbers,
creature_template.speed_runis TrinityCore's stock default on all three pursuer creatures, andthere is no movement-speed aura anywhere in the corruption block.
Removed from the previous revision
An earlier revision played the Thing From Beyond's own spell visual kits on the summon and added
.debug visualkit/.debug spellvisualto iterate on them. The visual kits did not produce theretail appearance — the clone aura does — so that code and its two debug commands are gone rather
than left in place. The
TC_LOG_DEBUGinstrumentation used during that investigation is gone too.AI-assisted Pull Requests
call-site tracing, drafting, and the comment cleanup / history rewrite described at the top.
Every line has been reviewed and is defensible by the author.
Issues Addressed:
SOURCE:
The changes have been validated through:
Two distinct sources, kept separate on purpose:
masks, amplification percentages, summon properties, faction. Cited inline above by DB2 table and
field so a reviewer can check each one.
things retail computed server-side and never shipped: the Grasping Tendrils slow curve and the Eye
of Corruption damage and radius. These are the numbers most worth a second opinion, because no
client-side ground truth exists for them.
Where neither source has an answer — pursuit speed — that is stated as an approximation rather than
presented as retail behaviour.
Tests Performed:
Builds clean on Windows / VS 2022 x64 RelWithDebInfo. Verified in-game, per tier:
effective corruption.
player stands in it.
reported as spell damage in the combat log.
Not tested: the numbers themselves against retail. Nobody has sniffed 8.3 corruption damage; the
formulas are as good as the Wowhead sampling they come from.
How to Test the Changes:
.additem <itemId> 1 <bonusListId>with a corruption bonus list — e.g.6552(Infinite Stars,20 corruption). The third argument is a bonus list id, not a spell id.
.debug corruption(from core/player: re-evaluate corruption penalties without re-applying them #81) to confirm the effective total and which threshold rows qualify.Then stand in a HoT or an Efflorescence and confirm it does not fire on heals.
damages you, and that it ticks every 2 seconds.
in the combat log rather than executing silently. Test on sloped ground specifically — that is the
case the 2d/3d measurement mismatch broke.
Known Issues and TODO List:
item's affix keeps firing after the item is unequipped, until it leaves the bags or the player
relogs. Root cause is not established: the effects are confirmed
TriggerType = ON_EQUIP, andApplyItemEquipSpell(item, false)is reached on unequip, but the removal is not matching.This is why the PR is still a draft.
172227[Shard of the Black Empire]. Triaged as bonus-list data rather than code, but unconfirmed.
Devastation, Infinite Stars and the rest — are not.
sniff, it should replace them.
files are replaced by three final-state ones. The three reach the same end state on their own,
with one exception: the old
2026_08_01_02briefly moved the AI onto creature160966and2026_08_01_03moved it back. If you applied_02but not_03, clear that row by hand —UPDATE creature_template SET AIName = 'SmartAI', ScriptName = '' WHERE entry = 160966;