// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ // ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ // Magicbane Emulator Project © 2013 - 2026 // https://www.magicbane.com package engine.objects; import engine.gameManager.DbManager; import engine.mbEnums; import engine.net.ByteBufferWriter; import engine.parsers.cache.CObjectIndex; import engine.parsers.objects.RuneTemplate; import engine.server.MBServerStatics; import engine.util.Hasher; import org.pmw.tinylog.Logger; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.concurrent.ConcurrentHashMap; public class RuneBase extends AbstractGameObject { public static HashMap> AllowedBaseClassRunesMap = new HashMap<>(); public static HashMap> AllowedRaceRunesMap = new HashMap<>(); private final String name; private final String description; private final int type; private final byte subtype; private final ConcurrentHashMap race = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); private final ConcurrentHashMap baseClass = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); private final ConcurrentHashMap promotionClass = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); private final ConcurrentHashMap discipline = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); private final ArrayList overwrite = new ArrayList<>(); private int levelRequired = 1; private ArrayList effectsList = new ArrayList<>(); private mbEnums.RuneType runeType; private int subTypeToken; private int rank; private boolean raceRestrict; private boolean classRestrict; private boolean discRestrict; /** * No Table ID Constructor */ public RuneBase(String name, String description, int type, byte subtype, ArrayList attrs) { super(); this.name = name; this.description = description; this.type = type; this.subtype = subtype; } /** * Normal Constructor */ public RuneBase(String name, String description, int type, byte subtype, ArrayList attrs, int newUUID) { super(newUUID); this.name = name; this.description = description; this.type = type; this.subtype = subtype; } /** * ResultSet Constructor */ public RuneBase(ResultSet rs) throws SQLException { super(rs); this.name = rs.getString("name"); this.description = rs.getString("description"); this.type = rs.getInt("type"); this.subtype = rs.getByte("subtype"); // static_rune_runereq race/class rows are whitelists, its discipline rows prohibitions this.discRestrict = true; DbManager.RuneBaseQueries.GET_RUNE_REQS(this); 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 effects until 2.10. * Talents with the same sub type token replace each other by rank (client 0x4695D0), so no overwrite list. */ public RuneBase(RuneTemplate rune, RuneBase dbRuneBase) { super(rune.template_id); Integer classRuneID; Integer discRuneID; ArrayList raceRuneIDs; RuneTemplate classRune; this.name = rune.item_base_name; this.description = rune.rune_dsc; this.type = dbRuneBase.type; this.subtype = dbRuneBase.subtype; this.effectsList = dbRuneBase.effectsList; this.runeType = rune.rune_type; this.rank = rune.rune_rank; this.levelRequired = rune.item_level_req; this.raceRestrict = rune.item_race_req_restrict; this.classRestrict = rune.item_class_req_restrict; this.discRestrict = rune.item_disc_req_restrict; for (mbEnums.MonsterType raceType : rune.item_race_req) { raceRuneIDs = CObjectIndex.getRaceRuneIds(raceType); if (raceRuneIDs.isEmpty()) Logger.warn("RuneBase " + rune.template_id + ": race " + raceType + " has no race rune"); for (int raceRuneID : raceRuneIDs) this.race.put(raceRuneID, true); } for (mbEnums.ClassType classType : rune.item_class_req) { classRuneID = CObjectIndex.getClassRuneId(classType); if (classRuneID == null) { Logger.warn("RuneBase " + rune.template_id + ": class " + classType + " has no class rune"); continue; } classRune = RuneTemplate._runes.get(classRuneID); if (classRune.rune_is_standard_character_creation) this.baseClass.put(classRuneID, true); else this.promotionClass.put(classRuneID, true); } for (mbEnums.DisciplineType discType : rune.item_disc_req) { discRuneID = CObjectIndex.getDisciplineRuneId(discType); if (discRuneID == null) { Logger.warn("RuneBase " + rune.template_id + ": discipline " + discType + " has no discipline rune"); continue; } this.discipline.put(discRuneID, true); } // Same token as CObjectIndex: unresolved sub types are stored as their hash if (!rune.rune_sub_type.isEmpty()) this.subTypeToken = Hasher.SBStringHash(rune.rune_sub_type); else this.subTypeToken = rune.rune_sub_type_hash; } public static RuneBase getRuneBase(int tableId) { if (tableId == 0) return null; RuneBase rb = (RuneBase) DbManager.getFromCache(mbEnums.GameObjectType.RuneBase, tableId); if (rb != null) return rb; return DbManager.RuneBaseQueries.GET_RUNEBASE(tableId); } public static void serializeForClientMsg(RuneBase runeBase, ByteBufferWriter writer) { writer.putInt(runeBase.type); writer.putInt(0); // Pad writer.putInt(runeBase.getObjectUUID()); writer.putInt(runeBase.getObjectType().ordinal()); writer.putInt(runeBase.getObjectUUID()); } // Iterates the static_rune_runebase rows: no rune field separates the player runes from the mob // talents, and each row still holds the server type/subtype. public static void LoadAllRuneBases() { ArrayList dbRuneBases; ArrayList attributes; RuneTemplate rune; int loadedCount = 0; dbRuneBases = DbManager.RuneBaseQueries.LOAD_ALL_RUNEBASES(); RuneBaseAttribute.runeBaseAttributeMap.clear(); for (RuneBase dbRuneBase : dbRuneBases) { rune = RuneTemplate._runes.get(dbRuneBase.getObjectUUID()); if (rune == null) { Logger.warn("RuneBase " + dbRuneBase.getObjectUUID() + " has no rune; loaded from static_rune_runebase without attributes"); continue; } DbManager.addToCache(new RuneBase(rune, dbRuneBase)); attributes = RuneBaseAttribute.getAttributesForRune(rune); // getAttrs() returns null for a rune without attributes, as the DB map did if (!attributes.isEmpty()) RuneBaseAttribute.runeBaseAttributeMap.put(rune.template_id, attributes); loadedCount++; } Logger.info("Loaded " + loadedCount + " rune bases from runes"); RuneBase.AllowedBaseClassRunesMap = DbManager.RuneBaseQueries.LOAD_ALLOWED_STARTING_RUNES_FOR_BASECLASS(); RuneBase.AllowedRaceRunesMap = DbManager.RuneBaseQueries.LOAD_ALLOWED_STARTING_RUNES_FOR_RACE(); } @Override public boolean equals(Object obj) { if (!super.equals(obj)) { return false; } if (obj instanceof RuneBase) { RuneBase rbObj = (RuneBase) obj; if (!this.name.equals(rbObj.name)) { return false; } if (!this.description.equals(rbObj.description)) { return false; } if (this.type != rbObj.type) { return false; } if (this.subtype != rbObj.subtype) { return false; } return true; } return false; } /* * Getters */ public String getName() { return name; } public String getDescription() { return description; } public int getType() { return type; } /** * @return the subtype */ public byte getSubtype() { return subtype; } /** * @return the attrs */ public ArrayList getAttrs() { return RuneBaseAttribute.runeBaseAttributeMap.get(this.getObjectUUID()); } public ConcurrentHashMap getRace() { return this.race; } public ConcurrentHashMap getBaseClass() { return this.baseClass; } public ConcurrentHashMap getPromotionClass() { return this.promotionClass; } public ConcurrentHashMap getDiscipline() { return this.discipline; } public ArrayList getOverwrite() { return this.overwrite; } public mbEnums.RuneType getRuneType() { return this.runeType; } // 0 when the rune has no sub type; talents sharing a non-zero token replace each other public int getSubTypeToken() { return this.subTypeToken; } public int getRank() { return this.rank; } // restrict true: the list is a blacklist; false: a whitelist public boolean isRaceRestrict() { return this.raceRestrict; } public boolean isClassRestrict() { return this.classRestrict; } public boolean isDiscRestrict() { return this.discRestrict; } /* * Serializing */ public int getLevelRequired() { return this.levelRequired; } public void setLevelRequired(int levelRequired) { this.levelRequired = levelRequired; } @Override public void updateDatabase() { // TODO Auto-generated method stub } /** * @return the effectsList */ public ArrayList getEffectsList() { return effectsList; } }