PowersManager.loadPowerActions builds every AbstractPowerAction from
PowerActionTemplate (template ctor on all subclasses), in id order with IDs
1..3046; OPENGATE keeps 5000. The ResultSet loader stays until removal.
The parser now reads the SimpleDamage (min, max), Teleport (bool), Track
(two bools) and TransferStat (bool at token 8) header fields.
Parity (JSON-Refactor-Plan/scripts/p47.py, every column): ids 3046/3046 plus
the DB-only AELFBORN row, which nothing references (the cfg has
BOON-AELFBORN). Effect ids, deferred powers, RemoveEffect source/REMOVEALL,
track filter/max, transfer types/ramps/ramp-add, mob level identical. The old
flags match the new header fields: Track 1024/2048 = tokens 3/4,
TransferStat 16384 = token 8, Teleport 32768 = SAB-001A's TRUE.
Value changes (PowerActions.cfg wins):
- DamageOverTime/TransferStatOT: the cfg value is the tick count; tick length
= action duration / ticks. The DB stored cfg+1 as tick seconds, which only
worked for 30s DoTs (5 x 6s, unchanged). HOLY-DOT-17 (60s) goes 10 -> 5
ticks, BLEED-DOT (4s) 0 -> 4, TransferStatOT 30s 6 -> 5 ticks.
- TransferStat amounts are read with ToInt as the client does: 16 actions
drop by 0.5 (BEL-001A 31.5 -> 31, CON-001A 13.5 -> 13, ...).
- PRE-334..337 become ApplyEffects with one effect; ApplyEffectsPowerAction
now allows a missing second effect on baked-in items.
- Charm level cap ramp from the LEVELCAP= curve (same values as the DB).
- Charm actions with effect FEAR/CONFUSION (12, DB type Confusion) stay
ConfusionPowerAction no-ops; CHARM ones use CharmPowerAction (decided).
- CreateMob: the cfg client mob id (49..74, 3500, 3501) maps to the mobbase
through a map in CreateMobPowerAction, from the old DB values.
- The 50 Charm/Block/SpireDisable rows with no DB effect id get the cfg id
(unread by those classes); isAggressive differs on 330 rows (unread).
- SimpleDamage keeps min/max instead of the always-0 simpleDamage (unread).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
levelCapRamp is a float column (e.g. 0.65) but was read with getInt, so the
Charm level cap never grew with trains. Charm caps now add ramp * trains.
FearPowerAction gets the same fix; nothing builds it today.
TrackPowerAction tested (flags & 1024) == 1, which is always false. No
caller reads trackPlayer/trackCorpse, so that part changes nothing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Effects, modifiers, source types, effect fail conditions and item
enchant values all come from Effects.cfg (4.6b). Removes
dbEffectsBaseHandler, dbPowerHandler, the EffectsBase and effect
modifier ResultSet constructors, EffectsBase.getFailConditions and
LOAD_ENCHANT_VALUES.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each EffectsBase builds its modifiers from its EffectTemplate MODS
lines, with ramps from the CompoundCurve helper. The Value mods now
fill Item.enchantValues, replacing LOAD_ENCHANT_VALUES. The ItemName
skip list of DB row IDs is gone; it only hid Centurion's and
Imperturbable from ./makeitem.
Value changes (cfg wins): weapon power X effects lose the DB-only OCV
mod; PRE-210A..237A resists and SUF-153A..171A resists (doubled);
Speed on SPRINT, SNT-010A, BHN-001A, HNT/RNG-037A, BRD-016A, PRS-031A,
and RNG-049A loses Speed; ARM-058A Necromancy +1; TMP-040A, WLR-018A;
PARRY/CRU-032A gain their Parry line. RESTORE's damage type is NONE
(cfg typo, as the client reads it).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Effects, their fail conditions and source types come from
EffectTemplate. IDs are assigned at boot in file order. The power
actions that the server also keys as effects (item enchantment parents
and the action tokens sent in effect messages) are built from the
ApplyEffect/ApplyEffects/Invis actions with no Effects.cfg block: 776
keys, the 712 old registry rows plus 64 such as BRD-014A, which live
also sent.
Value changes (cfg wins): BRD-016A/PRS-031A lose the Chant source,
RUC-002A..004A lose Buff/Effect, and PRS-031A/RNG-037A no longer break
on Attack. Effect modifiers still load from the DB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ActionsBase IDs (IgnoreStack effect keys) and EffectsBase IDs are both
written as Integer.toString keys into the same effects map, and their
ranges overlapped. Action lines now start at 10001. Also drops the
unused EffectsBase copy constructor and its NewID counter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both branches of the getUUID() != 334 checks were identical, and they
tied the code to a DB row ID.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client hash (0x5547E0) has no overrides. Hashing the power id
SafeMode must give the real power token for 4.8. Source types are now
keyed by the effect's own token, which keeps the SafeModeA/B override
in EffectsBase.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deletes dbEffectsResourceCostHandler, EffectsResourceCosts and
DbManager.EffectsResourceCostsQueries. GameObjectType.EffectsResourceCosts
stays because the enum ordinals are sent to the client and stored in the DB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PowersManager._effect_costMaps now comes from PowActionCostTemplate
instead of static_effect_costmaps. Parity (scripts/p45.py, dev DB):
524/524 ids, 522 identical. Value changes, cfg wins (D2, user
decision 2026-10-26):
- PRE-092 Yglandor: Sulfur 1 -> 6
- PRE-097 Jagged: adds Sulfur 1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PowersManager.AnimationOverrides was loaded from
static_power_animation_override and never read. validItemFlags on
AbstractPowerAction was always 0 and its getter had no callers; its
loader (static_power_effect_allowed_item) was already commented out.
No value change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SkillsBase comes from Skills.cfg since ff52d1d7 (tested). Drop the
ResultSet ctor; static_skill_skillsbase is shrunk to ID, name
(sql/4.3-skillsbase-shrink.sql, run on mbdb_108 and patched into
magicbane.sql).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SkillsBase name, description, token (SBStringHash of the name) and
attribute weights now come from SkillTemplate._skills. The skill ids
stay in static_skill_skillsbase (ID, name), which dyn_character_skill
and SkillReq use; the loader joins each row's name to the cfg skill and
logs an error for a name with no cfg entry. Both servers load all 63 at
boot after Skills.cfg; GET_BASE/GET_BASE_BY_NAME/GET_BASE_BY_TOKEN read
the boot caches only. The other 31 cfg skills stay in SkillTemplate.
Parity (scripts/p43.py, dev DB): 63/63 names, 63/63 tokens, 63/63
weights. Only value change: description was empty for all 63 in the DB
and is now the cfg text (no server reader).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kits come from StartingKitTable.cfg since c1e1ed91 (tested); drop
dbKitHandler, DbManager.KitQueries, the Kit ResultSet ctor and
RaceClassIDMap.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kit is built from StartingKitTemplate._kits and looked up by race name,
sex and class name like the client's kit map (0x620250), replacing the
hard-coded racebaseclass IDs in GetKitIDByRaceClass. Equip slots come
from each item's item_eq_slots_or, filled from the last cfg item so the
weapon keeps RHELD. dbKitHandler and the ResultSet ctor stay until the
removal commit.
Parity (scripts/p42.py, dev static_rune_validkit vs cfg): 68/68 headers,
451/451 item sets, 449/451 kits with identical slots. Value changes:
- Irekei female Mage kit 3 (182460/185090/29970) was kitNumber 4 in the
DB, so the client's kit 3 was refused at creation; now selectable.
- Dwarf male Fighter kits 4 and 5 list two boots; the DB put Black Cuffed
Boots (185120) in CHEST. They are a FEET item, so they are now skipped
with a warning and Black Riding Boots (185090) stay in FEET.
- Old Half-Giant male (2019) now finds the Half-Giant male kits by name
instead of a missing map entry.
Noob gear item set unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replaces the hard-coded NON_CREATION_TALENTS exclusions. ATTRRUNES (84)
equals the 92 flagged creation talents minus exactly those 8 runes, so
the talent lists are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FlatLine -> (0, add), SlopedLine p -> (p, add), SlopedInitValLine p ->
(p, no add), checked against every static_power_effectmod and
static_power_action row (6228 rows; the 11 ramp value diffs are the
known 4.0 effectmod diffs). DefaultCurveUp/Down have no ramp form and
no mod or action uses them. The world boot checks five known DB rows
and logs curves without a ramp form.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CompoundCurves (needed by the Powers.cfg parser), Powers, Skills,
StartingKitTable and CharCreateRuneList now load at login boot from the
shared mb.data/wpak. No consumer reads them yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The wpak parse now runs ahead of initPowersManager, so the Powers.cfg
target type step runs at the end of InitializePowers instead of as a
separate call after the wpak load. No value change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 ranks a power by adding every POWERSADJ (item_power_action)
step at or below the character's level (0x4B34B0). The Standing Guard
rune's Wizard steps (10@10, 10@20, 10@30, 5@40..70) now set the rank of
every captain spell, capped at the power's MaxLevel as player casts are.
Value change by guard rank 1-7:
before 10/15/20/25/30/35/40
after 10/20/30/35/40/40/40
All 28 captain spells have MaxLevel 40; the uncapped sum would be 50.
Using one curve for the non-Wizard faction spells and the cap are user
decisions (Task 3.9 follow-up 3).
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>
Nothing reads the table since the captain powers moved to MinionType.
Drops LOAD_RUNE_POWERS and the ResultSet RunePowerEntry constructor.
The table drop is MBDATA sql/3.9-rune-powers-drop.sql, run after deploy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 14 Magister Captain contracts' powers have no client rune or deed
(Task 3.9), so they live on the MinionType mage entries instead of the
DB fallback in loadAllRunePowers. canCast, mobCast and aiInfoCmd read
them through MinionType.getCaptainPowers.
No value change: same 56 power ids, rank 40, and all 56 DB tokens equal
the powerbase token of their power id (checked on mbdb_108).
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>
LOAD_ALL_MOBBASE_SPEEDS and MobBase.updateSpeeds have had no caller since
3.3 moved mob speeds to rune_speed. No value change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AbstractCharacter.getResists fell back to Resists.getResists(0), but the
table has no ID 0, so the fallback always returned null. It now returns
the field directly. Removes dbResistHandler, the cache, the ResultSet
constructor and the empty setMobResists stub. No value change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MobBase builds its stats from the race rune's rune_attr_adj since 3.2, so
LOAD_STATS and the MobBaseStats ResultSet constructor have no callers.
No value change. The table is dropped by sql/3.6-mob-stats-resists-drop.sql.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each line now has the effects map key (effect UUID or stack type such as
PetBuff), the effect id and name, rank, time left or static, and the
power that applied it. Help and usage strings no longer describe
/printstats.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PowersBase.monsterTypeRestrictions is now an EnumSet<MonsterType>
(DB values Siege/Centaur/Dwarf/Vampire read with CacheParserUtil.readEnum).
MobBase gets monsterType from its race rune's rune_sub_type (all 2380
RACE runes convert); players use RaceType.getMonsterType(). No string
compares and no rune lookup per cast. Same results as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Players use their race rune's rune_sub_type, like mobs. Only GOR-003
Crimson Pact (single-target ghoul transformation) restricts player
types: it no longer lands on Centaur 2004/2005, Dwarf 2006 or Vampire
2028/2029, matching its description ("Cannot be cast on a vampire,
centaur, or dwarf"). No player race rune has sub-type Siege, so the
Siege-restricted PC/GROUP powers are unaffected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
static_power_powerbase monsterTypeRestrict1-3 were loaded into
PowersBase.monsterTypeRestrictions but never read. getAllTargets now
drops a Mob whose race rune rune_sub_type is restricted (the rune
sub-type is the client's monster type, g_MonsterTypeEnum at rune +0xC).
86 powers restrict Siege (DB and Powers.cfg agree). Value changes:
- Imbue Golem, Dark Lord's Boon, Strengthen Beast no longer land on the
Siege-type summons Siege Golem 11999 (ANM-004) and Ice Fiend 11998
(SUM-005); they still buff the other pets.
- The 83 Siege-restricted harmful powers (bleeds, DOTs, procs, debuffs,
Challenge, The Lesser Kiss, ...) no longer affect Trebuchet 13171,
Mangonel 13757, Ballista 13758 or the two Siege summons.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
validateTarget tested MASK_MOB before MASK_PET, and summoned pets carry
both, so every single-target PET power (pet heals and buffs such as
Repair Golem, Dark Lord's Favor, Strengthen Beast, Vashteera's Favor)
was rejected on any pet. Pets are now tested first and accept PET or
MOBILE powers, matching the client, where Mobile covers every ArcMobile
and Pet is left to the server (0x49E440). Non-pet mobs are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No power uses it after DRU-066 moved to the Powers.cfg Pet type, and its
area query (WorldGrid.getObjectsInRangePartialNecroPets) was an empty
stub. The DB rows are aligned with sql/necro-pet-targeting.sql.
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>
PowersBase gets setTargetType (the old inline parse, now resetting the
flags first) and the world server re-applies every power's target type
from PowerTemplate after the wpak load. The login server only uses power
tokens and keeps the DB value.
Parity vs static_power_powerbase (1465 powers): 1460 identical, area,
radius type, exclude and target effect prereqs identical for all.
Value changes:
- NEC-018 Sepulchre's Bounty: PET -> MOBILE. Its AoE now collects mobs
and pets; the NECROPETSTATS target prereq keeps only necro pets.
- DRU-066 Braialla's Judgement: NECROPET -> PET.
Kept on the DB (user, behaviour change, for the Phase 4 power task):
HLR-025 Succor PC (cfg CORPSE), HNT-023 Phaedra's Fury PC (cfg SELF),
RNG-033 Fleet of Foot GROUP (cfg SELF).
Client: ArcPower_IsValidTargetForType 0x49E440 accepts any live
ArcMobile for Mobile and has no case for Pet (server decides).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mob effects come from the race rune (2.10b builder) and necro pets get
NECROPETSTATS in code, so GET_RUNEBASE_EFFECTS, the MobBaseEffects
ResultSet constructor, its unused mobBaseID and the equals/hashCode used
for the merge are gone. The table is now unread; drop it with
sql/3.4-mobbase-effects-drop.sql.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No client data grants NECROPETSTATS (no rune, summon power or item; no
reference in sb.exe), but NEC-018 Sepulchre's Bounty and DRU-066
Braialla's Judgement require it on the target, so the server adds it to
Grobones 12021 and Wight 12022 in applyMobbaseEffects.
Rank is the highest rank of the pet's own ladder (PET_NECRO /
PET_NECRO_HARDHIT) at or below its level, which is exactly what the 100
server-made static_npc_mobbase_effects rows held (same 50 rank/reqLvl
pairs). Tying it to that ladder is a guess at the official behaviour.
No value changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
static_npc_mobbase_effects is shrunk to the 100 NECROPETSTATS rows
(sql/3.4-mobbase-effects-shrink.sql); the Wild Boar 13236 PEACE row is
dropped as unused (no spawns, contracts or summon power).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MobBase now takes its effects from MobBaseEffects.getEffectsForRune (the
2.10b builder) and adds the DB rows the rune doesn't have on top.
Parity (scripts/p34.py, 2375 mobbases): 1028 identical, 1344 empty on
both sides, 3 with server-only DB rows that stay in the DB:
- 12021 Grobones, 12022 Wight: 50 NECROPETSTATS rows each
- 13236 Wild Boar: PEACE rank 40, reqLvl 1
No value changes. Effect order does not matter: applyMobbaseEffects keeps
the highest eligible rank per effect.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MobBase walk/walkCombat/run/runCombat now come from rune_speed
WALK/COMBATWALK/RUN/COMBATRUN instead of static_npc_mobbase_race.
Parity (JSON-Refactor-Plan/scripts/p33.py, compared as Java floats):
2375/2375 loaded mobbases identical, every one has a race row on dev.
No value changes. The 253 unused race rows (no mobbase) and the
fly/swim columns had no reader.
LOAD_ALL_MOBBASE_SPEEDS and static_npc_mobbase_race are now unread;
they go in Task 3.6.
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>
Parity (scripts/p32.py, static_npc_mobbase_stats by loadID vs rune_attr_adj):
2301 of 2375 mobbases identical. 74 have no stats row and went from all-zero
stats to the rune values: CSR 2020/2021, summoner pets 12024-12035 and
13266-13301, clay constructs 12200-12202/12375/12382, Guard 13170 (not spawned
or contracted), necro undead 13339-13348, and 13961, 14167, 14168, 14260-14262,
14285, 14292. On dev 47 obj_mob spawns use these (13343, 14167, 14168, 14285,
14292). Effect: higher rune skill values for these mobs via CharacterSkill.
LOAD_STATS stays until the table is dropped.
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>
Mobbases whose ID is their own race rune (2375 of 2781) now take name,
level, health, atr, defense, min/max damage, fsm and enemy/notEnemy from
the rune; the 406 server-made mobbases keep their DB row. Server-only
columns (bootySet, flags, seeInvis, spawnTime, goldMod, scale,
attackRange) stay DB.
Parity (MBDATA scripts/p31.py vs magicbane.sql), JSON for all (D2,
user decision 2026-10-23):
- fsm 84: DB None where the rune has no ROOTFSMID; no change
- enemy/notEnemy: no change
- level 95: rune 0 (player races, guards, pets, golems, necro pets)
- level 8: 13314 53->42, 13581 53->40, 13758 78->40, 14034 54->40,
14172 54->40, 14174 55->40, 14177 54->40, 13171 10->1
- health/atr/def/dmg 61: DB defaults 100/10/20 -> 0 (tombstones
2030-2040, pets 12024-12035 and 13266-13301, CSR 2020/2021 atr/def/dmg)
- defense 27: DB 100 -> rune 140-780 (clay, risen/doom, skeletal lines,
12011, 13961, 14167, 14168, 14285, 14292); damage/atr there and on
14037/14087/14296/14303 already came from the rune at runtime
- name 33: DB names -> obj_name (e.g. Sharduk -> Hell Hound)
No ParityCheck comparator: p31.py covers it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Client ArcCharacter_ExpForLevelUp (0x4715D0): XP to reach level L is
(L-1)^3 * max(1, 1 + (L-40) * 0.0512f) * class * race rune_exp_req_to_level,
rounded half up. getLevel steps levels with it up to LEVELCAP. Both methods
now take the PlayerCharacter so the factors come from its runes; the CSR
promotions 2901+ have no rune and use the base class factor.
Same values: every player race is 10 and every class rune 15, and the formula
equals the removed table for all 76 levels. Levels above the table no longer
use the old approximate fallback.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The DB views char_perAccount and dyn_trash_detail lost their race and class
names when 2.11 dropped the Phase 2 tables. The world server now rewrites the
generated static_rune_lookup table (ID, type, name) after the second
Race.loadAllRaces(): 26 player races, 4 base classes, the promotion runes and
the CSR promotion rows 2901-2911. One transaction (delete, batch insert,
commit); on any failure it rolls back and logs a warning, the boot continues.
Names come from the runes: races use obj_name (as in 2.3), classes the rune
names, so DB typos such as "Theif" become "Thief" in the views. No gameplay
change. Needs JSON-Refactor-Plan/sql/2.12-rune-lookup.sql (table, level
functions, views) run before boot.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deletes the uncalled static_rune_skill_adjusts, static_rune_maxskills,
static_skill_skillsgranted, static_skill_skillreq and static_power_powergrant
loaders and their ResultSet constructors. LOAD_RUNE_POWERS stays for the 14
Magister Captain guard contract ids. No value changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
static_rune_race is read only for the race ids and the server-only heights;
the hair/beard style, racebaseclass, baseclass and promotionrunereq queries
are gone. GET_BASE_CLASS is cache-only, which also drops the obj_account
cache-miss query. CSR/admin promotions 2901+ (no promotionrunereq rows) get an
empty allowed list. No value changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RuneBaseEffect, its handler, every effectsGranted field and getter (including
the PromotionClass promoID*10+2500..2503 lookups) and the empty
PlayerBonuses.grantEffect had no reader (2.10a). The GameObjectType constant
stays so later ordinals are unchanged. No value changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 315 static_rune_runebase rows are exactly the non-RACE runes, so the loader
selects by rune_type. Removes the server type/subtype columns (no live reader),
the runereq/overwrite path, the GET_RUNEBASE cache-miss query, the runebase
attribute handler, the racerune/baseclassrune loaders and the 111111 checks.
No value changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MobBaseEffects.getEffectsForRune builds each rune's list from the RUNE JSON
on first use and caches it: exact repeats are dropped (54 rune files list
their effects twice), the action id resolves to its effect through
PowerActions.cfg, token = SBStringHash(effect id), rank = arg 0,
reqLvl = arg 1. Used by Race, BaseClass, PromotionClass and RuneBase;
MobBase stays on GET_RUNEBASE_EFFECTS until Task 3.4.
Parity (scripts/p210b.py, token level): 1074 runes match. Differences:
- Tireless, Tough as Nails, Taint of Madness: JSON action TRT-* resolves
to the same effect the DB stores; no change
- CSR 2901-2904, 2906, 2908, 2910, 2911, Non-Aggro Rune 252624: DB token
was the NoNPCAggro action (no effect found, skipped); now NONPCAGGRO,
which has no mods; no gameplay change
- Mob-only 12021/12022 NECROPETSTATS and 13236 PEACE are server rows on
the MobBase path, untouched
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Files now load from mb.data/wpak/<archive>/<file>, matching MBDATA's
data/wpak layout and the client's Config/Config.wpak naming, so more
archives can sit beside Config/ later. All nine current files are in
wpak/Config/.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replaced by engine.parsers.wpak. Nothing referenced it except the
commented-out calls and the "Loading WPAK data" log line in
ConfigManager, which are removed too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SkillParser: uid, name, icon, attribute weights and description per
Skills.cfg line (94 skills; the server's ids 1-63 stay in the DB).
StartingKitParser: each kit line is a list of up to 10 item ids under
its RACE= line, as the client reads it; the file's tab columns are not
used (451 kits, same count as static_rune_validkit).
CharCreateRuneParser: race (with sex), class, promotion and attribute
creation rune lists. PowActionCostParser: resource costs per id, the
source of static_effect_costmaps (524 entries). No consumers change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PowerParser loads Powers.cfg into PowerTemplate._powers. The 22 header
fields are named after the client loader's error messages (0x56E410);
ACTION= lines keep trains, duration, curve and stack fields; prereqs,
curves, flags, messages and CONDITION blocks are kept. Keys compare
case-insensitively, as in the client. Four keys the client ignores
(APPLYDAMAGESELF=, APPLYEFFECTCASTER=, APPLYEFFECTTARGET= and a
POWEREND= typo) are skipped; any other unknown key fails the load.
Numbers are read like the client's atoi/atof with a warning when a
token isn't clean: PRL-033 init time "2.9.0" -> 2.9 and SGE-001
precision "10.0" -> 10, which matches the DB. Ten EQPREREQ lines end
without a required flag, which the client reads as 0.
Needs the fixed Powers.cfg (SRDX-DB header had "SPELL 15 Unknown"
pasted twice; fixed in MBDATA, findings section 6).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CompoundCurveParser keeps every curve segment (the CompoundCurveType enum
holds one value per curve). ModTypeParser keeps each mod type's Behavior,
which decides the argument layout of a MODS line. EffectParser loads
Effects.cfg into EffectTemplate._effects: header, the four flag keys
(case-insensitive, as in the client), SOURCE lines, every MODS line in
order, and CONDITION lines. Mod layouts follow the client's ArcMod*_Parse
functions; curve names in effects and power actions must exist in
CompoundCurves.cfg. Boot order: curves, mod types, power actions, effects.
No consumers change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New engine.parsers.wpak importer (replaces engine.wpak, which stays unused).
WpakFileReader tokenizes cfg lines (quotes, '#'/'*' comments, trailing '#')
and builds XxxBEGIN/XxxEND blocks; every error names file:line.
PowerActionParser loads PowerActions.cfg into PowerActionTemplate._powerActions,
keyed by action id, at world boot. Header layouts per action type come from
the client's ArcPowerAction*_ParseHeader functions: the trailing 0/1 on
ApplyEffect and friends is the aggressive flag, not a level. effect_ids holds
the action -> effect map needed by Task 2.10b. Three ids (PRE-235..237)
repeat identically; the later entry wins, as in the client. No consumers change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SkillReq.getSkillReqsForRune builds each rune's list from the RUNE JSON
on first use and caches it: skill_value is the grant level and
skill_granted_skills are the prereqs. The prereq amounts (80, Warrior 50,
Sun Dancer 60) are loaded but not yet read; CharacterSkill keeps its
hard-coded 80 and validForWarrior.
Parity (scripts/p29.py): 47 of 79 DB runes match. Value changes (JSON
wins, Task 2.9 decisions):
- Thief Dagger Mastery, Throwing, Unarmed Combat Mastery: level 15 -> 20
- Sentinel Axe/Great Axe/Hammer/Great Hammer/Sword Mastery: 20 -> 15;
gains Great Sword Mastery (15, Sword); loses Wear Armor, Medium
- Channeler loses Sword; Storm Lord loses Stormcalling
- Blood Horn gains Hammer Mastery (20, Hammer)
- No weapon prereq any more on Archer Archery, Blade Weaver Blade Weaving,
Sun Dancer Sun Dancing and the 11 Master of X talents
- No effect: Druid/Prelate Sword and Fury Spear 10 -> 1 (promotion is at
10), discipline/talent level changes (they need level 20), Throwing
prereqs (always granted), Blade Master Sword prereq (granted at 1)
- JSON-only runes (Archon, Duelist, War Correspondent, Pet, mob/guard
races) gain lists; Archon resistances and Duelist Dueling are not
skills and are skipped with a warning
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Their item_power_action entries are mob power ranks by level, not trains,
and getGrantedTrains only looks up a player's race, class, promotion and
runes. RACE runes load only when they are a player Race (static_rune_race).
Cuts the table from 379384 entries / 969 powers to 2596 / 925; no value a
player can read changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Amount = power_arguments[0] of each power type's first action, keyed by
SBStringHash(power_type). Parity (scripts/p28.py): 69 of the 70 DB runes
match; Wizard 2521 WIZ-064 Conjure Familiar goes from 1 to 20 (decided JSON).
The JSON adds grants on 2189 runes: mob and guard races (2100+), Archon 2018
(ARCHON-001..005 40, CSR-UNINVIS 30), and NPC trainer/guard talents
(252569-252655, 253002). Only Archon is a player race; the talents are not
player runes. The DB loader stays until 2.11.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ends the name clash with the parser's engine.parsers.objects.PowerGrant.
It no longer extends AbstractGameObject: it was never cached or sent as a
game object, and the renamed class has no GameObjectType entry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Parity (scripts/p28.py): all 1066 DB runes with a JSON file match in value
and order; token = SBStringHash(power_type), rank = power_value, name = obj_name.
The 14 Magister Captain guard contract ids (848, 952, 1052, 1182, 1252, 1352,
1452, 1502, 1527, 1552, 1557, 1602, 1652, 980102) have no rune file; guard
spells are server-made, so they keep their DB rows.
The JSON adds 170 class/discipline/talent runes; none is a mobbase, and the
trainer contracts among them never reach the guard contract path in MobAI,
so no reachable value change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
applyRuneSkills checked mob.skills for the rune name rather than the skill
type, so every qualifying [rank, level] pair replaced the skill and only
the last one counted. Pairs at or below the mob's level now add up, the
same way trainer caps sum their pairs.
Value changes: only mobbases with several pairs for one skill, Guard Dog
12017 and Guard 13170. Only Guard Dog is in use (contract 910 Personal
Guard Dog); Guard 13170 has no spawns or contracts on dev. Example at
level 50: Dodge and Unarmed Combat 30 -> 110, Unarmed Combat Mastery
20 -> 80. Guard ATR/defense/damage formulas don't read these skills.
No mobbase has a base skill matching a rune skill, so applyMobbaseSkill
values are unaffected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Load moved after the rune templates are parsed.
Parity (scripts/p27.py): 76 of 107 DB runes match. Value changes (JSON
wins, Task 2.7 decisions):
- Blade Master 3005: Sword +10 removed (no rune_skill_adj in the JSON;
Blade Weaver 3006 keeps Sword +10). Reaches players via PlayerBonuses.
- Pet 2522: Dodge and Unarmed Combat 1 -> 22. Not reachable; players
never hold the Pet rune.
- 29 Master X / trainer runes lose their rows (no level-0 pairs in the
JSON). Not reachable; players never hold trainer runes. Their caps
still load into MaxSkills.
- 2243 mob/guard race runes gain rows. Not reachable; PlayerBonuses
only reads player race/class/promotion/held runes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Parity (scripts/p27.py): all 78 DB runes match (token, skillLevel = pair
level, maxSkillPercent = pair rank). Talents only, as decided in Task 2.7:
class/discipline/race adjusts are skill grants, and contracts whose class
ID is such a rune (Thief, Huntress, Warlock, Fighter, 8 disciplines,
Vampire) would otherwise gain trainer caps.
The selector adds Brawler 250054 and guard talents 252621-252623; no
contract keys on them, so no reachable value change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Parity (scripts/p27.py): all 2259 DB runes and 69290 rows match in value
and order; token = SBStringHash(skill_type), name = obj_name.
The JSON adds 95 class/discipline/talent runes; applyRuneSkills reads by
mobbase ID and none of them is a mobbase, so no reachable value change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
All validation refusals in generatePCFromCommitNewCharacterMsg now go
through refuseCreation, which warns with the reason, account and IP. The
negative stat line used to be the only one that named the account.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follows CharCreate_AdjustAttribute (0xBAC140): a stat must stay at 1 or
more and can't be raised past the racial maximum. Checked after the rune
bonuses and max adjustments but before the rune penalties, since Giant's
Blood lowers the INT/DEX max by 10 but the stat by only 5.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rune count limit drops from 16 to the client's 10 slots, race and class
included (CharCreate_ApplyRune 0xBA8C80), and more than 8 talents is
refused (ArcCharacter_AddRune 0x4695D0).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Character creation now checks duplicates by rune id instead of relying on
RuneBase object identity, and groups talents by rune_sub_type
(RuneBase.getSubTypeToken) instead of the DB SubType byte. Creation talents
are rank 0, so two in the same group block each other, as in
ArcCharacter_AddRune (0x4695D0).
New groups over the DB (scripts/p26b.py):
- weapon proficiencies 250080-250087: only one at creation
- Blood Gift: Blood of the Dragon, Giant's Blood, Taint of Madness, Taint of Chaos
- Juggernaut + Tough Hide; Tireless + Tough as Nails
- Childhood gains Witch Sight 250112; Dark Whispers and Divine Touch join
the Background group
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AllowedRaceRunesMap and AllowedBaseClassRunesMap now come from TALENT runes
with rune_is_standard_character_creation, using item_race_req and
item_class_req (restrict true = blacklist, false = whitelist) over the
creation race runes (2000-2029) and class runes (2500-2503). Every creation
race and class gets an explicit list, so the 111111 sentinel is no longer
used. static_rune_racerune / baseclassrune loaders stay until 2.11.
Eight flagged runes that are not in CharCreateRuneList.cfg ATTRRUNES are left
out: 2910 Wolfpack Developer, 2911 QA Test Rune, the unused duplicates
250116/250117/250123/250124/250125, and 250081 Proficient with Bows
(commented out in the cfg).
Parity (offline, scripts/p26.py): 84 talents, the same as the DB apart from
these value changes, all decided JSON (2026-10-22):
- 250081 Proficient with Bows: no longer accepted at creation (DB: any race/class)
- 250088 Raised by Barbarians: Elf, Irekei lose it
- 250089 Raised by Centaurs: Elf loses it
- 250092 Raised by Thieves' Guild: Dwarf loses it
- 250093 Raised in the Woods: female Irekei loses it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Test on dev (character Moo): with Enhanced Strength held, the client shaded
an Increased Strength runestone red and showed its refusal popup, but the
server applied it as a downgrade. The client's 0x4768D0 (ArcRuneTemplate
vtable slot 0x1E8) blocks a talent when a held talent with the same
rune_sub_type has an equal or higher rank, rank 0 included. AddRune's
rank-0 replace branch is never reached past that check.
Value changes vs the previous commit:
- Rank-0 stat runes are refused while any rank of their chain is held
(same as the old DB type 4 block)
- Rank-0 talents in the same group (e.g. Bloodline sub-race runestones)
are refused instead of replacing each other
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RuneBase(RuneTemplate) fills the race, class and discipline maps from
item_race_req / item_class_req / item_disc_req (names to rune ids through
CObjectIndex; class runes with rune_is_standard_character_creation go to
baseClass, the rest to promotionClass) with a restrict flag for each, and
the level from item_level_req. static_rune_runereq is no longer used for
cached rune bases; GET_RUNE_REQS stays until 2.11.
ApplyRuneMsg follows ArcCharacter_MeetsItemReqs (0x475F40) and
ArcItemRestrict_Check (0x4BAC60): restrict true = blacklist, false =
whitelist. The class check passes when the base class or the promotion is
listed. The discipline check is against the discs already held, using the
new rune's list only.
Parity (offline, scripts/p25a.py with feasible race/promotion pairs under
the client rule; scripts/p25b.py over all 315 rune bases). Value changes,
all decided JSON (D6, 2026-10-21):
- 3001 Animator: + Irekei (4 pairs); Summoner blocks it
- 3008 Commander: Saboteur blocks it
- 3010 Duelist: level 20, 5 races, 7 promotions (had no DB rows; -111 pairs)
- 3018 Enchanter, 3030 Conjurer: + Vampire (4 pairs each)
- 3020 Summoner: - Thief/Warrior/Barbarian (28 pairs), + Centaur Druid/Priest
(4); Animator blocks it
- 3031 Darksworn: + Irekei Priest (2); 3032 Valkyr: + Human/Aelfborn Warrior (2)
- 3037 Artillerist: + Sentinel (11); 3044 Strigoi: Belgosch blocks it
- 252127 Master of Throwing: - Scout (16 pairs)
- 70 more runes with no DB rows gain their JSON lists (class runes 2500-2526
except the Pet rune, creation talents and Bloodline talents); only runestones are checked
- Stat chains: the DB "higher rank held" blocks leave the discipline map.
Ranks 1-8 are still refused by the sub type rule; a rank-0 rune now
replaces a higher rank as the client does (decided: follow the client)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follows the client's ArcCharacter_AddRune (0x4695D0, specs/RuneRequirements.md):
a talent replaces a held talent with the same non-zero rune_sub_type when its
rank is 0 or higher than the held rank; otherwise the apply fails. With no
talent to replace, a new talent needs fewer than 8 held talents.
RuneBase gets runeType, a sub type token (SBStringHash(rune_sub_type), or the
stored hash) and rank from the rune. It no longer copies the static_rune_runereq
type 5 overwrite list, and ApplyRuneMsg no longer reads it.
Value changes:
- Stat chains (*MaxIncrease) replace the same ranks as before; the DB typos go
away (250026 listed 250019 twice and missed 250018)
- Talents in the same group now replace each other when applied from a
runestone (e.g. Bloodline sub-race talents); before, both were added
- Limit: 8 talents instead of more than 12 runes of any kind
- Rank-0 downgrade (decided: follow the client) takes effect in the next
commit, when the DB stat-chain blocks leave the discipline map
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
applyRune returns false on every failed requirement without telling the
player, so using a rune the character doesn't qualify for did nothing. The
caller now sends popup 161 on refusal, matching popup 160 on success. The id
comes from the server's ErrorPopupMsg list; the popup strings aren't in the
client exe, so it isn't confirmed in IDA.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LoadAllRuneBases builds one RuneBase per static_rune_runebase row from the rune
with the same id (all 315 have one) and fills runeBaseAttributeMap from the
rune; the row now only supplies type, subtype, the runereq maps and effects. A
row without a rune would stay the DB object, without attributes, with a
warning. static_rune_runebaseattribute is no longer read at boot (the
LoadAllAttributes calls are gone from both servers). The SQL loaders stay until
task 2.11.
Value changes (decisions: JSON, D2):
- name: Confesser -> Confessor, Theif -> Thief, LagMaster -> Lagmaster,
WP Dev -> Wolfpack Developer, Exception X -> Exceptional X (Strength,
Dexterity, Constitution, Intelligence, Spirit). No code matches these names.
- description: '' / CSR text -> rune_dsc (unread)
- attributes added where the DB had none:
- admin 2905 Lagmaster spi +200, spi max +200; 2907 Watcher con +200;
2909 Feature Character every max +200; 2911 QA Test Rune con +200
(applies to characters granted them; none hold them on dev)
- creation cost 5/12/10/10/10 on 250116, 250117, 250123-250125 (duplicate
talents not offered at creation)
- base classes 2500-2503, Pet 2522, guards 252621-252623: start/max
attributes (never player CharacterRunes; NPC rune attributes are unread)
All 365 DB attribute rows match the JSON; final parity before the switch
(scripts/p24.py): 0 other differences. The static_rune_runebase comparator is
removed; it would only compare the JSON with itself now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds RuneBase(RuneTemplate, RuneBase): name from item_base_name, description
from rune_dsc. type and subtype stay the static_rune_runebase values: they are
server enums that don't map cleanly from rune_type/rune_sub_type (CLASS runes
carry 0/2/3/5/256/289; the creation exclusive-group subtypes 1-10 leave out
Blood Gift, the MaxIncrease chains and the hashed sub types). The requirement
maps and effects are copied from the DB row until 2.5b/2.10.
RuneBaseAttribute.getAttributesForRune builds attribute 0 from
rune_creation_cost, 1-5 from rune_attr_adj, 6-10 from rune_max_attr_adj and
11-15 from item_attr_req. A static_rune_runebase comparator checks name and
attributes against the JSON.
Offline parity (scripts/p24.py, 315 runebase rows + 365 attribute rows vs the
RUNE JSON): every row has a rune; all 365 DB attribute rows match. Differences:
- name: 9 DB typos (Confesser, Theif, LagMaster, WP Dev, Exception Strength/
Dexterity/Constitution/Intelligence/Spirit)
- attributes: 17 runes with no DB rows gain JSON values: base classes
2500-2503, Pet 2522, admin 2905/2907/2909/2911, guards 252621-252623, and
creation cost on 250116, 250117, 250123-250125
- description: '' (or CSR text on 2901-2911) -> rune_dsc; nothing reads it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
loadAllRaces builds one Race per static_rune_race row from the RACE rune with
the same id (all 26, including 2018-2021, decision: JSON); the row now only
supplies minHeight and strHeightMod, and a row without a race rune would load
from the DB with a warning. Colors stay in the DB. racebaseclass,
racehairstyle and racebeardstyle are no longer read at boot. The SQL loader
stays until task 2.11.
Value changes (decisions: JSON, D2):
- name drops the ", Male"/", Female" suffix ("Aelfborn, Male" -> "Aelfborn").
WhoResponseMsg's split(",") and runAfterLoad's suffix strip still work;
runAfterLoad now resolves 2019 to HalfGiant (it threw on the DB name).
- description '' -> rune_dsc blurb (unread)
- Minotaur hair 6958 -> 6598; Vampire M beard 5020170 -> 5020300
- beard style 0 dropped on Shade M/F, Minotaur, Vampire F (beard styles are
never validated); hair 0 kept on Aracoix M/F and Shade M/F by the
empty-hair rule
- 2018 Archon: startingPoints 0 -> 30, token 0 -> -317458791, health 0 -> 20,
stamina 0 -> 90
- 2019 Half-Giant-Old Man: strMax 150 -> 140, dex 40/65 -> 30/75, con 60/140
-> 50/130, spi 30/60 -> 35/80, startingPoints 20 -> 15, health/mana/stamina
0 -> 10/5/70, baseClasses {} -> {Fighter 2500}
- 2020/2021 CSR: all stats 60/120 -> 40/100, startingPoints 0 -> 30, token
0 -> 52803, health/mana/stamina 0 -> 40/40/10000
All other fields unchanged; final parity before the switch (scripts/p23.py):
22 creation races, 0 differences in attributes, bonuses, startingPoints,
token and base classes. The static_rune_race comparator is removed; it would
only compare the JSON with itself now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds Race(RuneTemplate, Race): start/max attributes from rune_attr_adj/
rune_max_attr_adj (min is always 0), health/mana/stamina bonus from
rune_health/mana/stamina, startingPoints = 30 - rune_creation_cost, token =
SBStringHash(rune_sub_type), name from obj_name, description from rune_dsc,
hair/beard styles from rune_hair/rune_beard (style 0 when rune_hair is empty),
and baseClasses = the creation class runes whose item_race_req names the race
sub type. minHeight/strHeightMod come from the static_rune_race row; colors,
skills, powers and effects still load from the DB by rune id. A
static_rune_race comparator checks every field against a JSON-built Race.
Offline parity (scripts/p23.py, 26 rows + racebaseclass/hairstyle/beardstyle
vs the RACE runes): the 22 creation races match in every attribute, bonus,
startingPoints, token and base class. Differences:
- name: "Aelfborn, Male" -> "Aelfborn" on every row
- hair 0 dropped by the JSON on Aracoix M/F and Shade M/F (kept by the
empty-hair rule); beard 0 dropped on Shade M/F, Minotaur, Vampire F
- Minotaur hair 6958 -> 6598, Vampire M beard 5020170 -> 5020300
- 2018 Archon: startingPoints 0 -> 30, token 0 -> SBStringHash("Celestial"),
health 0 -> 20, stamina 0 -> 90
- 2019 Half-Giant-Old Man: str/dex/con/spi start and max, startingPoints
20 -> 15, health/mana/stamina 0 -> 10/5/70, baseClasses {} -> {Fighter}
- 2020/2021 CSR: stats 60/120 -> 40/100, startingPoints 0 -> 30, token 0 ->
SBStringHash("CSR"), health/mana/stamina 0 -> 40/40/10000
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LoadAllPromotions builds one PromotionClass per promotion CLASS rune (22,
2504-2526 without 2522) and caches it, then runs the DB loader, which skips
cached ids, so only the CSR/admin rows 2901-2911 still come from
static_rune_promotion (decision: DB fallback). The world server calls it in
place of GET_ALL_PROMOTIONS; the login server now preloads promotions too, so
character load there gets the same cached objects. The SQL loader stays until
task 2.11.
Value changes: Templar 2516 manaMod 14 -> 15 (decision: JSON, D2).
description NULL -> rune_dsc blurb (unread). All other fields unchanged;
final parity before the switch (scripts/p22.py): 22 promotion rows, 0
differences in name, token, health, stamina and the 163 allowed rune tokens.
The static_rune_promotion comparator is removed; it would only compare the
JSON with itself now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds PromotionClass(RuneTemplate): health/mana/stamina from rune_health/mana/
stamina, token = SBStringHash(rune_sub_type), description from rune_dsc, and
allowedRunes = the sub type tokens of the race and base class runes named in
item_race_req/item_class_req (all whitelists). Skills, powers and effects still
load from the DB by rune id (tasks 2.8, 2.9, 2.10). isPromotionRune selects the
non-creation CLASS runes that require a base class (skips 2500-2503 and Pet).
A static_rune_promotion comparator checks every field against a JSON-built
PromotionClass; rows 2901+ (TALENT runes in the JSON) report as missing.
Offline parity (scripts/p22.py, 33 rows + 163 promotionrunereq rows vs 22
promotion runes): 0 differences in name, token, health, stamina and allowed
rune tokens. Templar 2516 manaMod is 14 in the DB and 15 in the JSON.
2901-2911 have no promotion rune. description is NULL in the DB and the
rune_dsc blurb in the JSON; nothing reads PromotionClass.getDescription().
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Character load and the promotion trainer re-read static_rune_promotion on every
call and replaced the cached object. They now get the object loaded at boot,
as GET_BASE_CLASS already does; the DB query is only a miss fallback.
No value changes: the cached and re-read objects hold the same row.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LoadAllBaseClasses builds one BaseClass per CLASS rune with
rune_is_standard_character_creation (2500-2503; promotions and Pet are
skipped) and caches it. Race.baseClasses picks up the cached objects through
GET_BASECLASS_FOR_RACE. The SQL loader stays until task 2.11.
Value changes: description goes from '' to the rune_dsc class blurb (unread).
All other fields unchanged; final parity before the switch (scripts/p21.py):
4 rows, 0 differences in name, attribute mods, token, health/mana/stamina mods.
The static_rune_baseclass comparator is removed; it would only compare the
JSON with itself now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds BaseClass(RuneTemplate): attributes from rune_attr_adj, health/mana/stamina
from rune_health/mana/stamina, token = SBStringHash(rune_sub_type), description
from rune_dsc. Skills, powers and effects still load from the DB by rune id
(tasks 2.8, 2.9, 2.10). The static_rune_baseclass comparator now checks every
field against a JSON-built BaseClass.
Offline parity (scripts/p21.py, 4 rows vs the CLASS runes with
rune_is_standard_character_creation = 2500-2503): 0 differences except
description, which is '' in the DB and the class blurb in rune_dsc. Nothing
reads BaseClass.getDescription().
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every reader moved to CObjectTemplate in 1.7, so the DB-loaded
engine.objects.ItemTemplate, LOAD_ITEM_TEMPLATES and linkCacheTemplates
are dead. No value changes: nothing read the legacy maps any more.
The boot log loses the legacy per-type count and the link warnings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The command now acts on the selected building and refuses anything
whose blueprint is not a warehouse, or a warehouse that is not its
city's active one, instead of using the city the player stands in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sets every resource in the warehouse of the city the player stands in,
either to an optional amount or, by default, to each resource's
deposit limit. Makes forge and work order testing practical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MakeItemCmd left the container at NONE, which item_CREATE's cont
parameter rejects, so PERSIST returned null and addItemToInventory
threw a NullPointerException. Set the container to INVENTORY as
CreateItemCmd does, and report an error if PERSIST still fails.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dev commands, BuildingManager, ChatManager, ForgeManager, NPCManager,
NPC, Warehouse, WorkOrder and ManageNPCMsg now call
CObjectTemplate.lookup instead of ItemTemplate.templates.get. Forge
production cost reads getItemResourceCosts(). Only the DB loader and
linkCacheTemplates still use the legacy class (removed in Task 1.8).
Removes the static_item_templates ParityCheck comparator. Final parity
(offline p17.py, 7586 rows): every id has a cache template, 0 item_type
differences, 0 item_resource_costs differences (84 ITEMs have costs).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Item.template is now the cache template from CObjectTemplate.lookup
instead of the legacy DB ItemTemplate, so item.template.cacheTemplate.X
becomes item.template.X. MobLoot, LootManager, ItemManager.isTwoHanded/
isShield and CombatManager.getSwingAnimation take a CObjectTemplate.
No value changes: every getter already read the cache template through
the bridge, and the lookup resolves the same 7586 ids as
static_item_templates with the same item_type (offline parity p17.py,
0 differences).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>