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>
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>
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>
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>
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>
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>
LOAD_TEMPLATE_MODTABLES now returns a templateID -> modTable map that
ForgeManager owns (vendorModTables). ForgeManager.forgeItem, WorkOrder
validation and the ManageNPCMsg forge item list read it through
ForgeManager.getVendorModTable(), and ItemTemplate.modTable is removed.
Values are unchanged: static_vendor_items has 5089 rows, one per
templateID, all present in static_item_templates, modTable 0-30.
Templates not in the table still read 0. The old loader stopped at the
first templateID missing from ItemTemplate.templates; there are none.
No comparator added or removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each engine.objects.ItemTemplate gets a cacheTemplate field, filled by
linkCacheTemplates() right after CObjectIndex.build() on both servers,
so consumers can move to CObjectTemplate one field at a time.
ParityCheck gains a static_item_templates comparator: a null bridge is
reported as missing in json, and obj_name and item_type are compared
through the interface to catch a wrong folder pick.
Import convention until the legacy class is removed (Task 1.8): the
legacy engine.objects.ItemTemplate keeps the plain import; the cache
class is always written engine.parsers.objects.ItemTemplate. Migrated
consumers should depend on CObjectTemplate instead.
JSON-Refactor-Plan Task 1.1. No consumers change; no values change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ParityCheck compares DB-built objects with the cache JSON and logs
table/id/field differences plus a per-table summary. It runs only when
the optional env var MB_PARITY_CHECK=true is set; it is not added to the
ConfigManager enum because init() aborts on any missing enum entry.
First comparator: static_rune_baseclass name vs RUNE obj_name.
No gameplay values change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Built at login and world bootstrap after the three parsers load (Task 0.1):
- lookup(id) across ITEM, then DEED, then RUNE
- race (MonsterType) -> creation race rune ids, male and female
- ClassType -> class rune id, DisciplineType -> discipline rune id
- SBStringHash(rune_sub_type) token -> runes
- skill and power name <-> SBStringHash token, from every template field
that names one
No consumers yet; no values change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>