Wall archers are now sent 252622 Wall Guard in place of 252623 Standing
Guard (serialize and getClientRuneIDs). The rune roots its holder (ROOT2,
ROOTFSMID=Power), which matches wall archers never moving (canRoam false).
No server value change: 252622 and 252623 add the same +100 to all five
attributes with the same max, the only fields the server reads from the
sent runes. Wall archers don't cast, so the 252623 power rank steps don't
apply to them. The rune's effects and its MOB-SNARE grant are not applied
by the server, as with the other guard runes.
Client side: 252623 is the only rune that sets the client's is-guard flag
(0x67AB1B), so the CloseToGuard hint no longer fires near wall archers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client sets its is-guard flag only for rune 252623 (0x67AB1B), and
that flag drives the CloseToGuard passive help hint. The server sent
252621 Guard, so the hint never fired. Contract mobs and player guards
now get 252623 in place of 252621.
No server value change: both runes add +100 to all five attributes
with the same max, the only fields the server reads from the sent runes.
The runes differ only in their effects (252621 MOVE-B-15%, OCV-B-20%,
RES-ELEMENTS-B-50; 252623 GUARD), which the server never applied.
The dead 100570/100962/100965 branches keep 252621.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Client ArcCharacter_RecalcMaxAttributes (0x470440) and RecalcAttributes
(0x470610) treat the stats in the mob message as spent points and add
rune_attr_adj from all of the character's runes, clamped to
[1, max(1, sum of rune_max_attr_adj)]. The server summed only the race
rune and sent the result, so the client counted it twice.
Mob.getClientRuneIDs lists the runes serializeForClientMsgOtherPlayer
sends (race, plus Pet 2522 for pets, Warrior 2518 + Guard 252621 for
contract and guard mobs); getRuneAttribute applies the client rule.
calculateModifiedStats and the mob branch of CharacterSkill use it,
and the message now sends 0 (no spent points), as NPCs already do.
MobBaseStats (race rune only) has no users left and is removed.
Value changes (server side):
- Contract mobs and player guards: +100 to all five attributes
(Guard rune 252621; its max adj is also +100, so no new clamp).
- Pets: + Pet rune 2522 (Str 76, Dex 76, Con 120, Int 40, Spi 40;
max Str 500, Dex 500, Con 2000, Int 40, Spi 40).
- The new max clamp changes no loaded mobbase on its own (checked all
2375 against their race runes).
These feed mob skill values (CharacterSkill) and statXxxCurrent.
Guard ATR/defense/damage/health still come from the NPCManager guard
formulas.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The mob message paired Dex with the Spirit hash (0xACB82E33), Int with
the Dexterity hash (0xE07B3336) and Spi with the Intelligence hash
(0xFF665EC3); the inline comments were wrong. Verified with
Hasher.SBStringHash. Mob and NPC now use MBServerStatics.STAT_*_ID.
Value change: the client now gets a mob's Dex, Int and Spi in the right
slots. NPCs send 0 for all five, so no change there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client builds a mob's scale from its race rune SCALEFACTOR
(ArcCharacter_BuildCharFromRune 0x458160) and overwrites the three floats
the server sends, so the DB column (always 1) never had a visible effect.
MobBase now holds rune_scale_factor (1,1,1 when absent) and the mob message
sends it. No client-visible change. The column drop is
sql/3.7-mobbase-scale-drop.sql, to run after this build is deployed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every static_npc_mobbase row has ID = loadID since 3.6 dropped the
server-made rows, so the race rune and rune effects are looked up by ID and
the loaders no longer filter on loadID. No value change (dev: 2375 of 2375
rows have ID = loadID). Mob.loadID and NPC.loadID are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DRU-066 Braialla's Judgement targets Pet and requires NECROPETSTATS, so
necro pets must be found by the Pet AoE query; the client leaves Pet
targeting to the server (0x49E440 has no Pet case). Reverts the necro
pet exclusion from 40799f96.
Other MASK_PET readers: MobAI pet aggro scan is still off
(PET_AGGRO_ENABLED = false); InterestManager and validateTarget test
MASK_MOB first, so they don't change. DRU-066 is the only AoE PET power.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
static_npc_mobbase_stats.baseSkills never matches a static_skill_skillsbase
token on dev (2007496699 on 2208 rows is no known skill hash and is absent
from the client), so applyMobbaseSkill never added a skill and only cost a
skillsbase query per mob spawn. No value changes. Nothing reads the
baseSkills/skillAmount columns now; the table goes in 3.6.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 406 ID <> loadID mobbases (15000-17266) were junk: no RUNE file, nothing
on dev references them. MobBase now always reads its core fields from the
race rune and logs an error if the rune is missing; the loader skips any
ID <> loadID rows still present. GetClassType only matched those IDs, so it
always returned 2518 and is replaced by the constant.
DB: MBDATA/JSON-Refactor-Plan/sql/3.6-mobbase-cleanup.sql deletes the rows,
the 10 orphan static_npc_mobbase_effects rows (17061/17122/17154) and drops
13 columns no longer read: lootTableID, name, level, health, atr, defense,
minDmg, maxDmg, noaggro, raceType, fsm, notEnemy, enemy.
Value changes: none for the 2375 loaded mobbases.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summoned pets (not necro pets) now carry MASK_PET, so pet-targeted
AOE powers can find them. The checkForAggro pet scan is held off with
PET_AGGRO_ENABLED until it checks rune enemy lists, filters targets
and keeps pet targets (plan-mask-pet.md steps 2-4).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A siege engine switches to whoever damages it, player or pet, even when
it already has a target. Pre-roll melee retaliation, harmful-power
direct aggro (which also fires on misses) and Taunt no longer change a
siege engine's target. The attack timer is untouched, so a switched
engine still waits for its next cycle before firing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>