Build PromotionClass from its class rune and compare every field

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>
This commit is contained in:
MagicBotandClaude Opus 5.5 committed 2026-10-20 10:33:08 -04:00
1 parent 992177eb0c
commit e91c803a32
2 files changed
+108

No files matched your search

+49
View File
@@ -9,10 +9,17 @@
package engine.parsers.cache;
import engine.gameManager.ConfigManager;
import engine.gameManager.DbManager;
import engine.mbEnums;
import engine.objects.AbstractGameObject;
import engine.objects.PromotionClass;
import engine.parsers.objects.RuneTemplate;
import org.pmw.tinylog.Logger;
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 +35,10 @@ public class ParityCheck {
private int differenceCount = 0;
private int missingCount = 0;
static {
register("static_rune_promotion", ParityCheck::comparePromotionClasses);
}
private ParityCheck(String table) {
this.table = table;
}
@@ -81,4 +92,42 @@ public class ParityCheck {
differenceCount++;
Logger.warn("Parity: " + table + "/" + id + "/" + field + ": db=" + dbValue + " json=" + jsonValue);
}
private static void comparePromotionClasses(ParityCheck check) {
ConcurrentHashMap<Integer, AbstractGameObject> cache;
PromotionClass promotionClass;
PromotionClass jsonClass;
RuneTemplate rune;
int id;
cache = DbManager.getMap(mbEnums.GameObjectType.PromotionClass);
if (cache == null) {
Logger.warn("Parity: static_rune_promotion: no PromotionClass objects loaded");
return;
}
for (AbstractGameObject cached : cache.values()) {
promotionClass = (PromotionClass) cached;
id = promotionClass.getObjectUUID();
rune = RuneTemplate._runes.get(id);
// The CSR/admin rows 2901+ are TALENT runes in the JSON, not promotions
if (rune != null && !PromotionClass.isPromotionRune(rune))
rune = null;
if (!check.compareRow(id, rune))
continue;
jsonClass = new PromotionClass(rune);
check.compareField(id, "name", promotionClass.getName(), jsonClass.getName());
check.compareField(id, "token", promotionClass.getToken(), jsonClass.getToken());
check.compareField(id, "healthMod", promotionClass.getHealthMod(), jsonClass.getHealthMod());
check.compareField(id, "manaMod", promotionClass.getManaMod(), jsonClass.getManaMod());
check.compareField(id, "staminaMod", promotionClass.getStaminaMod(), jsonClass.getStaminaMod());
check.compareField(id, "allowedRunes", new HashSet<>(promotionClass.getRuneList()), new HashSet<>(jsonClass.getRuneList()));
}
}
}