Skip to content

Fix creature level reverting, invisible mobs, unscaled adds, evade healing, and gender-byte writes - #20

Open
talia-danielsdottir wants to merge 1 commit into
InstanceForge:mainfrom
talia-danielsdottir:fix/creature-level-ownership-and-visuals
Open

Fix creature level reverting, invisible mobs, unscaled adds, evade healing, and gender-byte writes#20
talia-danielsdottir wants to merge 1 commit into
InstanceForge:mainfrom
talia-danielsdottir:fix/creature-level-ownership-and-visuals

Conversation

@talia-danielsdottir

Copy link
Copy Markdown

Five defects, all observed on a live realm running this module. Happy to split
this into separate PRs if you'd rather review them independently — they're
described separately below and touch mostly distinct code.

1. Spawned creature levels revert (#13)

Creature::SelectLevel() re-rolls urand(minlevel, maxlevel) straight from
creature_template on the respawn path and from UpdateEntry(updateAI = true),
and other modules call SetLevel() from their own update hooks. mod-autobalance
is the common case: it hooks OnPlayerLevelChanged, marks every creature on the
map stale, and ResetCreatureIfNeeded() then calls SetLevel(UnmodifiedLevel)
the template value. Either way the level band chosen for the run is lost, which
is why mobs sprout a skull the moment a party member levels up.

The manager now records the level and max health it assigns and re-asserts them:

  • case 1 is intercepted via OnBeforeCreatureSelectLevel;
  • case 2 is a plain SetLevel() from outside this module and cannot be hooked,
    so it is corrected on this module's own update tick.

Doing it on our own tick keeps the fix self-healing and independent of script
registration order, so it behaves identically with or without AutoBalance
present. An atomic counter gates the hooks so they cost nothing when no session
is running, and ownership is released on creature removal and on session cleanup.

2. Invisible mobs

The trash pool filtered suspicious names but not degenerate models. A
creature_model_info row with BoundingRadius or CombatReach of 0 renders as
nothing client-side: the mob is targetable and fights back, but cannot be seen.

Around 2,100 otherwise-eligible entries in a stock 3.3.5a world DB are affected,
so a name filter or an entry blacklist doesn't scale. The pool query now drops any
entry with a degenerate model, checking every model an entry has, since the client
picks among them at spawn time. On my world DB the trash pool goes from 13,600 to
11,480 entries with all nine creature types still well populated.

3. Summoned adds keep their own level

SelectCreatureForTheme() matches on creature type only and there was no summon
hook, so an add summoned by a dungeon creature entered at its own template level —
e.g. a level-69 Fel Imp inside a level-25 run. Adds are now scaled to the session
level via OnCreatureAddWorld. Player pets, totems and guardians are explicitly
excluded.

4. Creatures heal to full mid-fight

Creatures are spawned at generated points, frequently off the navmesh or far from
a home position the core considers reachable, so the core fires
EVADE_REASON_BOUNDARY / EVADE_REASON_NO_PATH during combat. _EnterEvadeMode()
drops combat and LoadCreaturesAddon(true) restores the creature, which then walks
home at full health — in play, a mob that heals faster than the group can damage it.

Genuine resets (empty threat list, sequence break) are honoured; the positional
ones are refused while the creature is alive and still engaged. The override also
forwards why, which the previous one dropped.

5. Bogus UNIT_FIELD_BYTES_0 writes

Two sites wrote byte 2 of UNIT_FIELD_BYTES_0 intending to set elite/rare rank:

c->SetByteValue(UNIT_FIELD_BYTES_0, 2, 1);  // Elite rank -> gold dragon frame
r->SetByteValue(UNIT_FIELD_BYTES_0, 2, 4);  // Silver dragon portrait

That byte is gender (byte 0 = race, 1 = class, 2 = gender, 3 = power type — the
field has no classification byte). Rank lives in creature_template and is cached
by the client per entry, so it can't be set at runtime at all. The writes only
changed gender, and the "rare" site wrote 4, which isn't a valid gender value.

Both are removed, and nothing is lost: the boss and rare pools are already filtered
to rank IN (1,2) (305 rank-1 and 2 rank-2 entries on my world DB, i.e. all of
them), so those creatures carry an elite frame from their template and the client
draws the dragon portrait on its own. The SetObjectScale() calls are deliberate
design and are kept.

Testing

Compiled with -DMODULES=static against AzerothCore rev 190184a04539 (Playerbot
branch) and deployed to a live realm. Clean boot, no errors, restarts=0, and the
pool loads 8,585 trash / 2,895 boss entries across all nine creature types.

One note on scope: I left the gold-reward calculation alone. It uses the session
level rather than the killed creature's level, which interacts oddly with fix 3,
but that's a design call rather than a defect so it seemed better left to you.

…aling, and gender-byte writes

Five defects, all observed on a live realm.

1. Spawned creature levels revert (issue InstanceForge#13)

   Creature::SelectLevel() re-rolls urand(minlevel, maxlevel) straight from
   creature_template on the respawn path and from UpdateEntry(updateAI = true),
   and other modules call SetLevel() from their own update hooks. mod-autobalance
   is the common case: it hooks OnPlayerLevelChanged, marks every creature on the
   map stale, and ResetCreatureIfNeeded() then calls SetLevel(UnmodifiedLevel) —
   the template value. Either way the level band chosen for the run is lost and
   the party sees skulls the moment someone dings.

   The manager now records the level and max health it assigns, and re-asserts
   them. Case 1 is intercepted via OnBeforeCreatureSelectLevel. Case 2 is a plain
   SetLevel() from outside this module and cannot be hooked, so it is corrected on
   this module's own update tick — self-healing and independent of script
   registration order, so it behaves identically with or without AutoBalance
   present. An atomic counter gates the hooks so they cost nothing when no session
   is running, and ownership is released on creature removal and session cleanup.

2. Invisible mobs

   The trash pool excluded suspicious names but not degenerate models. A
   creature_model_info row with BoundingRadius or CombatReach of 0 renders as
   nothing client-side: the mob is targetable and fights back, but cannot be seen.
   Roughly 2,100 otherwise-eligible entries in a stock 3.3.5a world DB are
   affected, so a name filter or entry blacklist does not scale. The pool query
   now drops any entry with a degenerate model, checking every model an entry has
   since the client picks among them at spawn time.

3. Summoned adds keep their own level

   SelectCreatureForTheme() matches on creature type only and no summon hook was
   installed, so an add summoned by a dungeon creature entered at its template
   level — e.g. a level-69 Fel Imp inside a level-25 run. Adds are now scaled to
   the session level via OnCreatureAddWorld. Player pets, totems and guardians are
   explicitly excluded.

4. Creatures heal to full mid-fight

   Creatures are spawned at generated points, frequently off the navmesh or far
   from a home position the core considers reachable, so the core fires
   EVADE_REASON_BOUNDARY / EVADE_REASON_NO_PATH during combat. _EnterEvadeMode()
   drops combat and LoadCreaturesAddon(true) restores the creature, which then
   walks home at full health — in play, a mob that heals faster than the group can
   damage it. Genuine resets are honoured; the positional ones are refused while
   the creature is alive and still engaged. The override also forwards `why`,
   which the previous one dropped.

5. Bogus UNIT_FIELD_BYTES_0 writes

   Two sites wrote byte 2 of UNIT_FIELD_BYTES_0 intending to set elite/rare rank.
   That byte is gender (byte 0=race, 1=class, 2=gender, 3=power type — the field
   has no classification byte). Rank lives in creature_template and is cached by
   the client per entry, so it cannot be set at runtime at all. The writes only
   changed gender; the "rare" site wrote 4, which is not a valid gender value.

   Both are removed. Nothing is lost: the boss and rare pools are already filtered
   to `rank IN (1,2)`, so those creatures carry an elite frame from their template
   and the client draws the dragon portrait on its own. The scale changes are
   deliberate and kept.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant