From 83f57b4def59f36604b2081a525f89cabc7581ff Mon Sep 17 00:00:00 2001 From: MagicBot Date: Wed, 21 Oct 2026 08:25:20 -0400 Subject: [PATCH] Build RuneBase and its attributes from the rune and compare them 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 --- src/engine/objects/RuneBase.java | 22 +++++++++ src/engine/objects/RuneBaseAttribute.java | 31 ++++++++++++ src/engine/parsers/cache/ParityCheck.java | 59 +++++++++++++++++++++++ 3 files changed, 112 insertions(+) diff --git a/src/engine/objects/RuneBase.java b/src/engine/objects/RuneBase.java index 51e83afe..49d06241 100644 --- a/src/engine/objects/RuneBase.java +++ b/src/engine/objects/RuneBase.java @@ -12,6 +12,7 @@ package engine.objects; import engine.gameManager.DbManager; import engine.mbEnums; import engine.net.ByteBufferWriter; +import engine.parsers.objects.RuneTemplate; import engine.server.MBServerStatics; import java.sql.ResultSet; @@ -78,6 +79,27 @@ public class RuneBase extends AbstractGameObject { this.effectsList = DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.getObjectUUID()); } + /** + * Rune Template Constructor + * type and subtype are server enums that don't map cleanly from rune_type/rune_sub_type, so they + * come from the static_rune_runebase row, as do the requirement maps and effects until 2.5b/2.10. + */ + public RuneBase(RuneTemplate rune, RuneBase dbRuneBase) { + super(rune.template_id); + + this.name = rune.item_base_name; + this.description = rune.rune_dsc; + this.type = dbRuneBase.type; + this.subtype = dbRuneBase.subtype; + this.race.putAll(dbRuneBase.race); + this.baseClass.putAll(dbRuneBase.baseClass); + this.promotionClass.putAll(dbRuneBase.promotionClass); + this.discipline.putAll(dbRuneBase.discipline); + this.overwrite.addAll(dbRuneBase.overwrite); + this.levelRequired = dbRuneBase.levelRequired; + this.effectsList = dbRuneBase.effectsList; + } + public static RuneBase getRuneBase(int tableId) { if (tableId == 0) diff --git a/src/engine/objects/RuneBaseAttribute.java b/src/engine/objects/RuneBaseAttribute.java index 1fdf6719..6da30197 100644 --- a/src/engine/objects/RuneBaseAttribute.java +++ b/src/engine/objects/RuneBaseAttribute.java @@ -10,11 +10,15 @@ package engine.objects; import engine.gameManager.DbManager; +import engine.mbEnums; import engine.mbEnums.GameObjectType; +import engine.parsers.objects.RuneTemplate; +import engine.server.MBServerStatics; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; +import java.util.EnumMap; import java.util.HashMap; @@ -57,6 +61,33 @@ public class RuneBaseAttribute extends AbstractGameObject { this.runeBaseID = rs.getInt("RuneBaseID"); } + // Attribute ids: 0 = rune_creation_cost, 1-5 = rune_attr_adj, 6-10 = rune_max_attr_adj, + // 11-15 = item_attr_req; the AttributeType ordinals Strength..Spirit are 1-5. + + public static ArrayList getAttributesForRune(RuneTemplate rune) { + + ArrayList attributes = new ArrayList<>(); + + if (rune.rune_creation_cost != 0) + attributes.add(new RuneBaseAttribute((short) MBServerStatics.RUNE_COST_ATTRIBUTE_ID, (short) rune.rune_creation_cost)); + + addAttributes(attributes, rune.rune_attr_adj, 0); + addAttributes(attributes, rune.rune_max_attr_adj, 5); + addAttributes(attributes, rune.item_attr_req, 10); + + return attributes; + } + + private static void addAttributes(ArrayList attributes, EnumMap values, int idOffset) { + + for (mbEnums.AttributeType attributeType : values.keySet()) { + if (values.get(attributeType) == 0) + continue; + + attributes.add(new RuneBaseAttribute((short) (attributeType.ordinal() + idOffset), values.get(attributeType).shortValue())); + } + } + public static void LoadAllAttributes() { DbManager.RuneBaseAttributeQueries.GET_ATTRIBUTES_FOR_RUNEBASE(); diff --git a/src/engine/parsers/cache/ParityCheck.java b/src/engine/parsers/cache/ParityCheck.java index 4d861267..75c965bf 100644 --- a/src/engine/parsers/cache/ParityCheck.java +++ b/src/engine/parsers/cache/ParityCheck.java @@ -9,10 +9,19 @@ package engine.parsers.cache; import engine.gameManager.ConfigManager; +import engine.gameManager.DbManager; +import engine.mbEnums; +import engine.objects.AbstractGameObject; +import engine.objects.RuneBase; +import engine.objects.RuneBaseAttribute; +import engine.parsers.objects.RuneTemplate; import org.pmw.tinylog.Logger; +import java.util.ArrayList; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; // Dev-only check that compares DB-built objects with the cache JSON before a loader is switched. @@ -28,6 +37,10 @@ public class ParityCheck { private int differenceCount = 0; private int missingCount = 0; + static { + register("static_rune_runebase", ParityCheck::compareRuneBases); + } + private ParityCheck(String table) { this.table = table; } @@ -81,4 +94,50 @@ public class ParityCheck { differenceCount++; Logger.warn("Parity: " + table + "/" + id + "/" + field + ": db=" + dbValue + " json=" + jsonValue); } + + // type, subtype and the requirement maps are copied from the DB row, so only name and attributes can differ. + // description is skipped: '' in the DB, the rune_dsc blurb in the JSON, and nothing reads it. + + private static void compareRuneBases(ParityCheck check) { + + ConcurrentHashMap cache; + RuneBase runeBase; + RuneBase jsonRuneBase; + RuneTemplate rune; + int id; + + cache = DbManager.getMap(mbEnums.GameObjectType.RuneBase); + + if (cache == null) { + Logger.warn("Parity: static_rune_runebase: no RuneBase objects loaded"); + return; + } + + for (AbstractGameObject cached : cache.values()) { + runeBase = (RuneBase) cached; + id = runeBase.getObjectUUID(); + rune = RuneTemplate._runes.get(id); + + if (!check.compareRow(id, rune)) + continue; + + jsonRuneBase = new RuneBase(rune, runeBase); + + check.compareField(id, "name", runeBase.getName(), jsonRuneBase.getName()); + check.compareField(id, "attributes", getAttributeSet(runeBase.getAttrs()), getAttributeSet(RuneBaseAttribute.getAttributesForRune(rune))); + } + } + + private static HashSet getAttributeSet(ArrayList attributes) { + + HashSet attributeSet = new HashSet<>(); + + if (attributes == null) + return attributeSet; + + for (RuneBaseAttribute attribute : attributes) + attributeSet.add(attribute.getAttributeID() + "=" + attribute.getModValue()); + + return attributeSet; + } }