2026-07-26 19:08:44 -04:00
|
|
|
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
|
|
|
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
|
|
|
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
|
|
|
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
|
|
|
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
|
|
|
|
// Magicbane Emulator Project © 2013 - 2026
|
|
|
|
|
// https://www.magicbane.com
|
2022-04-30 09:41:17 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
package engine.objects;
|
|
|
|
|
|
|
|
|
|
import engine.gameManager.DbManager;
|
2024-05-12 13:36:47 -04:00
|
|
|
import engine.mbEnums;
|
2026-10-21 10:01:52 -04:00
|
|
|
import engine.parsers.cache.CObjectIndex;
|
2026-10-21 08:25:20 -04:00
|
|
|
import engine.parsers.objects.RuneTemplate;
|
2022-04-30 09:41:17 -04:00
|
|
|
import engine.server.MBServerStatics;
|
2026-10-21 10:00:46 -04:00
|
|
|
import engine.util.Hasher;
|
2026-10-21 08:30:43 -04:00
|
|
|
import org.pmw.tinylog.Logger;
|
2022-04-30 09:41:17 -04:00
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
2026-10-22 06:51:39 -04:00
|
|
|
import java.util.Arrays;
|
2022-04-30 09:41:17 -04:00
|
|
|
import java.util.HashMap;
|
2026-10-22 06:51:39 -04:00
|
|
|
import java.util.HashSet;
|
2026-10-22 06:49:55 -04:00
|
|
|
import java.util.Set;
|
2022-04-30 09:41:17 -04:00
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public class RuneBase extends AbstractGameObject {
|
|
|
|
|
|
2023-07-15 09:23:48 -04:00
|
|
|
public static HashMap<Integer, ArrayList<Integer>> AllowedBaseClassRunesMap = new HashMap<>();
|
|
|
|
|
public static HashMap<Integer, ArrayList<Integer>> AllowedRaceRunesMap = new HashMap<>();
|
2026-10-22 06:49:55 -04:00
|
|
|
// Flagged rune_is_standard_character_creation but missing from CharCreateRuneList.cfg ATTRRUNES:
|
|
|
|
|
// admin runes 2910/2911, unused duplicates, and Proficient with Bows (commented out in the cfg).
|
2026-10-22 06:51:39 -04:00
|
|
|
private static final Set<Integer> NON_CREATION_TALENTS = new HashSet<>(Arrays.asList(2910, 2911, 250081, 250116, 250117, 250123, 250124, 250125));
|
2023-07-15 09:23:48 -04:00
|
|
|
private final String name;
|
|
|
|
|
private final String description;
|
|
|
|
|
private final ConcurrentHashMap<Integer, Boolean> race = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
|
|
|
|
|
private final ConcurrentHashMap<Integer, Boolean> baseClass = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
|
|
|
|
|
private final ConcurrentHashMap<Integer, Boolean> promotionClass = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
|
|
|
|
|
private final ConcurrentHashMap<Integer, Boolean> discipline = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW);
|
|
|
|
|
private int levelRequired = 1;
|
|
|
|
|
private ArrayList<MobBaseEffects> effectsList = new ArrayList<>();
|
2026-10-21 10:00:46 -04:00
|
|
|
private mbEnums.RuneType runeType;
|
|
|
|
|
private int subTypeToken;
|
|
|
|
|
private int rank;
|
2026-10-21 10:01:52 -04:00
|
|
|
private boolean raceRestrict;
|
|
|
|
|
private boolean classRestrict;
|
|
|
|
|
private boolean discRestrict;
|
2022-04-30 09:41:17 -04:00
|
|
|
|
2026-10-21 08:25:20 -04:00
|
|
|
/**
|
|
|
|
|
* Rune Template Constructor
|
2026-10-21 10:00:46 -04:00
|
|
|
* Talents with the same sub type token replace each other by rank (client 0x4695D0), so no overwrite list.
|
2026-10-21 08:25:20 -04:00
|
|
|
*/
|
2026-10-22 12:36:34 -04:00
|
|
|
public RuneBase(RuneTemplate rune) {
|
2026-10-21 08:25:20 -04:00
|
|
|
super(rune.template_id);
|
|
|
|
|
|
2026-10-21 10:01:52 -04:00
|
|
|
Integer classRuneID;
|
|
|
|
|
Integer discRuneID;
|
|
|
|
|
ArrayList<Integer> raceRuneIDs;
|
|
|
|
|
RuneTemplate classRune;
|
|
|
|
|
|
2026-10-21 08:25:20 -04:00
|
|
|
this.name = rune.item_base_name;
|
|
|
|
|
this.description = rune.rune_dsc;
|
2026-10-22 12:10:29 -04:00
|
|
|
this.effectsList = MobBaseEffects.getEffectsForRune(rune.template_id);
|
2026-10-21 10:00:46 -04:00
|
|
|
this.runeType = rune.rune_type;
|
|
|
|
|
this.rank = rune.rune_rank;
|
2026-10-21 10:01:52 -04:00
|
|
|
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);
|
|
|
|
|
}
|
2026-10-21 10:00:46 -04:00
|
|
|
|
|
|
|
|
// 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;
|
2026-10-21 08:25:20 -04:00
|
|
|
}
|
|
|
|
|
|
2023-07-15 09:23:48 -04:00
|
|
|
public static RuneBase getRuneBase(int tableId) {
|
2022-04-30 09:41:17 -04:00
|
|
|
|
2023-07-15 09:23:48 -04:00
|
|
|
if (tableId == 0)
|
|
|
|
|
return null;
|
2022-04-30 09:41:17 -04:00
|
|
|
|
2026-10-22 12:36:34 -04:00
|
|
|
return (RuneBase) DbManager.getFromCache(mbEnums.GameObjectType.RuneBase, tableId);
|
2023-07-15 09:23:48 -04:00
|
|
|
}
|
2022-04-30 09:41:17 -04:00
|
|
|
|
2026-10-22 12:36:34 -04:00
|
|
|
// Rune bases are every non-RACE rune: the classes, disciplines and talents (the 315 former static_rune_runebase rows).
|
2026-10-21 08:30:43 -04:00
|
|
|
|
2023-07-15 09:23:48 -04:00
|
|
|
public static void LoadAllRuneBases() {
|
|
|
|
|
|
2026-10-21 08:30:43 -04:00
|
|
|
ArrayList<RuneBaseAttribute> attributes;
|
|
|
|
|
int loadedCount = 0;
|
|
|
|
|
|
|
|
|
|
RuneBaseAttribute.runeBaseAttributeMap.clear();
|
|
|
|
|
|
2026-10-22 12:36:34 -04:00
|
|
|
for (RuneTemplate rune : RuneTemplate._runes.values()) {
|
2026-10-21 08:30:43 -04:00
|
|
|
|
2026-10-22 12:36:34 -04:00
|
|
|
if (rune.rune_type == mbEnums.RuneType.RACE)
|
2026-10-21 08:30:43 -04:00
|
|
|
continue;
|
|
|
|
|
|
2026-10-22 12:36:34 -04:00
|
|
|
DbManager.addToCache(new RuneBase(rune));
|
2026-10-21 08:30:43 -04:00
|
|
|
attributes = RuneBaseAttribute.getAttributesForRune(rune);
|
|
|
|
|
|
2026-10-22 12:36:34 -04:00
|
|
|
// getAttrs() returns null for a rune without attributes
|
2026-10-21 08:30:43 -04:00
|
|
|
if (!attributes.isEmpty())
|
|
|
|
|
RuneBaseAttribute.runeBaseAttributeMap.put(rune.template_id, attributes);
|
|
|
|
|
|
|
|
|
|
loadedCount++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Logger.info("Loaded " + loadedCount + " rune bases from runes");
|
|
|
|
|
|
2026-10-22 06:49:55 -04:00
|
|
|
loadCreationTalents();
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-22 12:36:34 -04:00
|
|
|
// Every creation race and class gets an explicit list; there is no "any" entry.
|
2026-10-22 06:49:55 -04:00
|
|
|
|
|
|
|
|
private static void loadCreationTalents() {
|
|
|
|
|
|
|
|
|
|
ArrayList<Integer> creationRaceIDs = new ArrayList<>();
|
|
|
|
|
ArrayList<Integer> creationClassIDs = new ArrayList<>();
|
|
|
|
|
ArrayList<Integer> listedRaceIDs;
|
|
|
|
|
ArrayList<Integer> listedClassIDs;
|
|
|
|
|
Integer classRuneID;
|
|
|
|
|
boolean listed;
|
|
|
|
|
boolean allowed;
|
|
|
|
|
int talentCount = 0;
|
|
|
|
|
|
|
|
|
|
AllowedRaceRunesMap = new HashMap<>();
|
|
|
|
|
AllowedBaseClassRunesMap = new HashMap<>();
|
|
|
|
|
|
|
|
|
|
for (RuneTemplate rune : RuneTemplate._runes.values()) {
|
|
|
|
|
|
|
|
|
|
if (!rune.rune_is_standard_character_creation)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (rune.rune_type == mbEnums.RuneType.RACE) {
|
|
|
|
|
creationRaceIDs.add(rune.template_id);
|
|
|
|
|
AllowedRaceRunesMap.put(rune.template_id, new ArrayList<>());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (rune.rune_type == mbEnums.RuneType.CLASS) {
|
|
|
|
|
creationClassIDs.add(rune.template_id);
|
|
|
|
|
AllowedBaseClassRunesMap.put(rune.template_id, new ArrayList<>());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (RuneTemplate rune : RuneTemplate._runes.values()) {
|
|
|
|
|
|
|
|
|
|
if (!rune.rune_is_standard_character_creation || rune.rune_type != mbEnums.RuneType.TALENT)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (NON_CREATION_TALENTS.contains(rune.template_id))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
listedRaceIDs = new ArrayList<>();
|
|
|
|
|
listedClassIDs = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
for (mbEnums.MonsterType raceType : rune.item_race_req)
|
|
|
|
|
listedRaceIDs.addAll(CObjectIndex.getRaceRuneIds(raceType));
|
|
|
|
|
|
|
|
|
|
for (mbEnums.ClassType classType : rune.item_class_req) {
|
|
|
|
|
classRuneID = CObjectIndex.getClassRuneId(classType);
|
|
|
|
|
|
|
|
|
|
if (classRuneID != null)
|
|
|
|
|
listedClassIDs.add(classRuneID);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// restrict true = blacklist, false = whitelist (ArcItemRestrict_Check 0x4BAC60)
|
|
|
|
|
for (int raceRuneID : creationRaceIDs) {
|
|
|
|
|
listed = listedRaceIDs.contains(raceRuneID);
|
|
|
|
|
allowed = rune.item_race_req_restrict ? !listed : listed;
|
|
|
|
|
|
|
|
|
|
if (allowed)
|
|
|
|
|
AllowedRaceRunesMap.get(raceRuneID).add(rune.template_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int baseClassID : creationClassIDs) {
|
|
|
|
|
listed = listedClassIDs.contains(baseClassID);
|
|
|
|
|
allowed = rune.item_class_req_restrict ? !listed : listed;
|
|
|
|
|
|
|
|
|
|
if (allowed)
|
|
|
|
|
AllowedBaseClassRunesMap.get(baseClassID).add(rune.template_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
talentCount++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Logger.info("Loaded " + talentCount + " creation talents for " + creationRaceIDs.size() + " races and "
|
|
|
|
|
+ creationClassIDs.size() + " classes from runes");
|
2023-07-15 09:23:48 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2022-04-30 09:41:17 -04:00
|
|
|
|
2023-07-15 09:23:48 -04:00
|
|
|
return false;
|
|
|
|
|
}
|
2022-04-30 09:41:17 -04:00
|
|
|
|
2023-07-15 09:23:48 -04:00
|
|
|
/*
|
|
|
|
|
* Getters
|
|
|
|
|
*/
|
|
|
|
|
public String getName() {
|
|
|
|
|
return name;
|
|
|
|
|
}
|
2022-04-30 09:41:17 -04:00
|
|
|
|
2023-07-15 09:23:48 -04:00
|
|
|
public String getDescription() {
|
|
|
|
|
return description;
|
|
|
|
|
}
|
2022-04-30 09:41:17 -04:00
|
|
|
|
2023-07-15 09:23:48 -04:00
|
|
|
/**
|
|
|
|
|
* @return the attrs
|
|
|
|
|
*/
|
|
|
|
|
public ArrayList<RuneBaseAttribute> getAttrs() {
|
|
|
|
|
return RuneBaseAttribute.runeBaseAttributeMap.get(this.getObjectUUID());
|
|
|
|
|
}
|
2022-04-30 09:41:17 -04:00
|
|
|
|
2023-07-15 09:23:48 -04:00
|
|
|
public ConcurrentHashMap<Integer, Boolean> getRace() {
|
|
|
|
|
return this.race;
|
|
|
|
|
}
|
2022-04-30 09:41:17 -04:00
|
|
|
|
2023-07-15 09:23:48 -04:00
|
|
|
public ConcurrentHashMap<Integer, Boolean> getBaseClass() {
|
|
|
|
|
return this.baseClass;
|
|
|
|
|
}
|
2022-04-30 09:41:17 -04:00
|
|
|
|
2023-07-15 09:23:48 -04:00
|
|
|
public ConcurrentHashMap<Integer, Boolean> getPromotionClass() {
|
|
|
|
|
return this.promotionClass;
|
|
|
|
|
}
|
2022-04-30 09:41:17 -04:00
|
|
|
|
2023-07-15 09:23:48 -04:00
|
|
|
public ConcurrentHashMap<Integer, Boolean> getDiscipline() {
|
|
|
|
|
return this.discipline;
|
|
|
|
|
}
|
2022-04-30 09:41:17 -04:00
|
|
|
|
2026-10-21 10:00:46 -04:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-21 10:01:52 -04:00
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-15 09:23:48 -04:00
|
|
|
/*
|
|
|
|
|
* Serializing
|
|
|
|
|
*/
|
2022-04-30 09:41:17 -04:00
|
|
|
|
2023-07-15 09:23:48 -04:00
|
|
|
public int getLevelRequired() {
|
|
|
|
|
return this.levelRequired;
|
|
|
|
|
}
|
2022-04-30 09:41:17 -04:00
|
|
|
|
2023-07-15 09:23:48 -04:00
|
|
|
public void setLevelRequired(int levelRequired) {
|
|
|
|
|
this.levelRequired = levelRequired;
|
|
|
|
|
}
|
2022-04-30 09:41:17 -04:00
|
|
|
|
2023-07-15 09:23:48 -04:00
|
|
|
@Override
|
|
|
|
|
public void updateDatabase() {
|
|
|
|
|
// TODO Auto-generated method stub
|
|
|
|
|
}
|
2022-04-30 09:41:17 -04:00
|
|
|
|
2023-07-15 09:23:48 -04:00
|
|
|
/**
|
|
|
|
|
* @return the effectsList
|
|
|
|
|
*/
|
|
|
|
|
public ArrayList<MobBaseEffects> getEffectsList() {
|
|
|
|
|
return effectsList;
|
|
|
|
|
}
|
2022-04-30 09:41:17 -04:00
|
|
|
|
|
|
|
|
}
|