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 <noreply@anthropic.com>
This commit is contained in:
MagicBotandClaude Opus 5.5 committed 2026-10-21 08:25:20 -04:00
1 parent 98f09d0713
commit 83f57b4def
3 files changed
+112

No files matched your search

+22
View File
@@ -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)
+31
View File
@@ -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<RuneBaseAttribute> getAttributesForRune(RuneTemplate rune) {
ArrayList<RuneBaseAttribute> 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<RuneBaseAttribute> attributes, EnumMap<mbEnums.AttributeType, Integer> 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();
+59
View File
@@ -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<Integer, AbstractGameObject> 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<String> getAttributeSet(ArrayList<RuneBaseAttribute> attributes) {
HashSet<String> attributeSet = new HashSet<>();
if (attributes == null)
return attributeSet;
for (RuneBaseAttribute attribute : attributes)
attributeSet.add(attribute.getAttributeID() + "=" + attribute.getModValue());
return attributeSet;
}
}