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:
1 parent
992177eb0c
commit
e91c803a32
2 files changed
+108
No files matched your search
@@ -10,8 +10,12 @@
|
||||
package engine.objects;
|
||||
|
||||
import engine.gameManager.DbManager;
|
||||
import engine.mbEnums;
|
||||
import engine.mbEnums.GameObjectType;
|
||||
import engine.net.ByteBufferWriter;
|
||||
import engine.parsers.cache.CObjectIndex;
|
||||
import engine.parsers.objects.RuneTemplate;
|
||||
import engine.util.Hasher;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
@@ -103,6 +107,61 @@ public class PromotionClass extends AbstractGameObject {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Rune Template Constructor
|
||||
*/
|
||||
public PromotionClass(RuneTemplate rune) {
|
||||
super(rune.template_id);
|
||||
|
||||
this.name = rune.obj_name;
|
||||
this.description = rune.rune_dsc;
|
||||
this.token = Hasher.SBStringHash(rune.rune_sub_type);
|
||||
this.healthMod = rune.rune_health;
|
||||
this.manaMod = rune.rune_mana;
|
||||
this.staminaMod = rune.rune_stamina;
|
||||
this.allowedRunes = getAllowedRuneTokens(rune);
|
||||
this.skillsGranted = DbManager.SkillReqQueries.GET_REQS_FOR_RUNE(this.getObjectUUID());
|
||||
this.powersGranted = PowerReq.getPowerReqsForRune(this.getObjectUUID());
|
||||
this.effectsGranted = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE(this.getObjectUUID());
|
||||
this.effectsGrantedFighter = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE((this.getObjectUUID() * 10) + 2500);
|
||||
this.effectsGrantedHealer = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE((this.getObjectUUID() * 10) + 2501);
|
||||
this.effectsGrantedRogue = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE((this.getObjectUUID() * 10) + 2502);
|
||||
this.effectsGrantedMage = DbManager.RuneBaseEffectQueries.GET_EFFECTS_FOR_RUNEBASE((this.getObjectUUID() * 10) + 2503);
|
||||
this.effectsList = DbManager.MobBaseQueries.GET_RUNEBASE_EFFECTS(this.getObjectUUID());
|
||||
}
|
||||
|
||||
// Promotions are the CLASS runes that require a base class; this skips the creation classes and Pet 2522.
|
||||
|
||||
public static boolean isPromotionRune(RuneTemplate rune) {
|
||||
return rune.rune_type == mbEnums.RuneType.CLASS && !rune.rune_is_standard_character_creation
|
||||
&& !rune.item_class_req.isEmpty();
|
||||
}
|
||||
|
||||
// Race and base class tokens are SBStringHash(rune_sub_type) of the runes named in the req lists.
|
||||
// Every promotion rune's lists are whitelists (restrict false).
|
||||
|
||||
private static ArrayList<Integer> getAllowedRuneTokens(RuneTemplate rune) {
|
||||
|
||||
ArrayList<Integer> tokens = new ArrayList<>();
|
||||
Integer classRuneId;
|
||||
int token;
|
||||
|
||||
for (mbEnums.MonsterType race : rune.item_race_req)
|
||||
for (int raceRuneId : CObjectIndex.getRaceRuneIds(race)) {
|
||||
token = Hasher.SBStringHash(RuneTemplate._runes.get(raceRuneId).rune_sub_type);
|
||||
if (!tokens.contains(token))
|
||||
tokens.add(token);
|
||||
}
|
||||
|
||||
for (mbEnums.ClassType classType : rune.item_class_req) {
|
||||
classRuneId = CObjectIndex.getClassRuneId(classType);
|
||||
if (classRuneId != null)
|
||||
tokens.add(Hasher.SBStringHash(RuneTemplate._runes.get(classRuneId).rune_sub_type));
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
public static void serializeForClientMsg(PromotionClass promotionClass, ByteBufferWriter writer) {
|
||||
writer.putInt(3); // For BaseClass
|
||||
writer.putInt(0); // Pad
|
||||
|
||||
+49
@@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user