Add CObjectIndex lookups over the cache templates
Built at login and world bootstrap after the three parsers load (Task 0.1): - lookup(id) across ITEM, then DEED, then RUNE - race (MonsterType) -> creation race rune ids, male and female - ClassType -> class rune id, DisciplineType -> discipline rune id - SBStringHash(rune_sub_type) token -> runes - skill and power name <-> SBStringHash token, from every template field that names one No consumers yet; no values change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cb427de440
commit
d9d1c41d9e
3 files changed
+243
No files matched your search
+235
@@ -0,0 +1,235 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.cache;
|
||||
|
||||
import engine.mbEnums;
|
||||
import engine.parsers.objects.CObjectTemplate;
|
||||
import engine.parsers.objects.DeedTemplate;
|
||||
import engine.parsers.objects.ItemTemplate;
|
||||
import engine.parsers.objects.PowerGrant;
|
||||
import engine.parsers.objects.RuneTemplate;
|
||||
import engine.parsers.objects.UserPowerAction;
|
||||
import engine.util.Hasher;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
// Lookups across the ITEM, DEED and RUNE cache templates.
|
||||
// The JSON references runes, skills and powers by name; the server and DB use ids and tokens.
|
||||
// Build once after all three parsers have loaded. Read only afterwards.
|
||||
|
||||
public class CObjectIndex {
|
||||
|
||||
private static final EnumMap<mbEnums.MonsterType, ArrayList<Integer>> raceRuneIds = new EnumMap<>(mbEnums.MonsterType.class);
|
||||
private static final EnumMap<mbEnums.ClassType, Integer> classRuneIds = new EnumMap<>(mbEnums.ClassType.class);
|
||||
private static final EnumMap<mbEnums.DisciplineType, Integer> disciplineRuneIds = new EnumMap<>(mbEnums.DisciplineType.class);
|
||||
private static final HashMap<Integer, ArrayList<RuneTemplate>> runesBySubTypeToken = new HashMap<>();
|
||||
private static final HashMap<String, Integer> skillTokens = new HashMap<>();
|
||||
private static final HashMap<Integer, String> skillNames = new HashMap<>();
|
||||
private static final HashMap<String, Integer> powerTokens = new HashMap<>();
|
||||
private static final HashMap<Integer, String> powerNames = new HashMap<>();
|
||||
|
||||
// Same order as the client's id space checks (JSON-Refactor-Plan D1).
|
||||
|
||||
public static CObjectTemplate lookup(int templateId) {
|
||||
|
||||
CObjectTemplate template;
|
||||
|
||||
template = ItemTemplate._items.get(templateId);
|
||||
|
||||
if (template == null)
|
||||
template = DeedTemplate._deeds.get(templateId);
|
||||
|
||||
if (template == null)
|
||||
template = RuneTemplate._runes.get(templateId);
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
// Male and female runes share a race, so most races return two ids.
|
||||
|
||||
public static ArrayList<Integer> getRaceRuneIds(mbEnums.MonsterType race) {
|
||||
return raceRuneIds.getOrDefault(race, new ArrayList<>());
|
||||
}
|
||||
|
||||
public static Integer getClassRuneId(mbEnums.ClassType classType) {
|
||||
return classRuneIds.get(classType);
|
||||
}
|
||||
|
||||
public static Integer getDisciplineRuneId(mbEnums.DisciplineType discipline) {
|
||||
return disciplineRuneIds.get(discipline);
|
||||
}
|
||||
|
||||
// DB race and class tokens are SBStringHash(rune_sub_type). Mob and talent runes share sub types.
|
||||
|
||||
public static ArrayList<RuneTemplate> getRunesBySubTypeToken(int token) {
|
||||
return runesBySubTypeToken.getOrDefault(token, new ArrayList<>());
|
||||
}
|
||||
|
||||
public static Integer getSkillToken(String skillName) {
|
||||
return skillTokens.get(skillName);
|
||||
}
|
||||
|
||||
public static String getSkillName(int token) {
|
||||
return skillNames.get(token);
|
||||
}
|
||||
|
||||
public static Integer getPowerToken(String powerName) {
|
||||
return powerTokens.get(powerName);
|
||||
}
|
||||
|
||||
public static String getPowerName(int token) {
|
||||
return powerNames.get(token);
|
||||
}
|
||||
|
||||
public static void build() {
|
||||
|
||||
int overlapCount = 0;
|
||||
|
||||
for (Integer templateId : RuneTemplate._runes.keySet())
|
||||
if (ItemTemplate._items.containsKey(templateId) || DeedTemplate._deeds.containsKey(templateId))
|
||||
overlapCount++;
|
||||
|
||||
for (Integer templateId : DeedTemplate._deeds.keySet())
|
||||
if (ItemTemplate._items.containsKey(templateId))
|
||||
overlapCount++;
|
||||
|
||||
if (overlapCount > 0)
|
||||
Logger.warn(overlapCount + " template ids exist in more than one of ITEM/DEED/RUNE");
|
||||
|
||||
for (RuneTemplate rune : RuneTemplate._runes.values()) {
|
||||
indexRuneName(rune);
|
||||
indexSubTypeToken(rune);
|
||||
indexRuneNames(rune);
|
||||
}
|
||||
|
||||
for (ItemTemplate item : ItemTemplate._items.values()) {
|
||||
addSkill(item.item_skill_used);
|
||||
addSkill(item.item_skill_mastery_used);
|
||||
indexItemFields(item.item_skill_req, item.item_user_power_action, item.item_power_grant, item.item_power_action);
|
||||
}
|
||||
|
||||
for (DeedTemplate deed : DeedTemplate._deeds.values()) {
|
||||
addSkill(deed.item_skill_used);
|
||||
addSkill(deed.item_skill_mastery_used);
|
||||
indexItemFields(deed.item_skill_req, deed.item_user_power_action, deed.item_power_grant, deed.item_power_action);
|
||||
}
|
||||
|
||||
Logger.info("Indexed " + raceRuneIds.size() + " races, " + classRuneIds.size() + " classes, "
|
||||
+ disciplineRuneIds.size() + " disciplines, " + runesBySubTypeToken.size() + " rune sub type tokens, "
|
||||
+ skillTokens.size() + " skills, " + powerTokens.size() + " powers");
|
||||
}
|
||||
|
||||
private static void indexRuneName(RuneTemplate rune) {
|
||||
|
||||
Integer previousId;
|
||||
|
||||
if (rune.rune_type == null || rune.rune_sub_type.isEmpty())
|
||||
return;
|
||||
|
||||
try {
|
||||
switch (rune.rune_type) {
|
||||
case RACE:
|
||||
// Mob runes reuse race sub types; only creation races map to player race ids
|
||||
if (rune.rune_is_standard_character_creation)
|
||||
raceRuneIds.computeIfAbsent(CacheParserUtil.readEnum(mbEnums.MonsterType.class, rune.rune_sub_type, rune.template_id, "rune_sub_type"),
|
||||
k -> new ArrayList<>()).add(rune.template_id);
|
||||
break;
|
||||
case CLASS:
|
||||
previousId = classRuneIds.put(CacheParserUtil.readEnum(mbEnums.ClassType.class, rune.rune_sub_type, rune.template_id, "rune_sub_type"), rune.template_id);
|
||||
if (previousId != null)
|
||||
Logger.warn(rune.template_id + ": class " + rune.rune_sub_type + " already indexed by " + previousId);
|
||||
break;
|
||||
case DISCIPLINE:
|
||||
previousId = disciplineRuneIds.put(CacheParserUtil.readEnum(mbEnums.DisciplineType.class, rune.rune_sub_type, rune.template_id, "rune_sub_type"), rune.template_id);
|
||||
if (previousId != null)
|
||||
Logger.warn(rune.template_id + ": discipline " + rune.rune_sub_type + " already indexed by " + previousId);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// The Pet class rune has no ClassType
|
||||
Logger.warn(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void indexSubTypeToken(RuneTemplate rune) {
|
||||
|
||||
int token;
|
||||
|
||||
if (!rune.rune_sub_type.isEmpty())
|
||||
token = Hasher.SBStringHash(rune.rune_sub_type);
|
||||
else if (rune.rune_sub_type_hash != 0)
|
||||
token = rune.rune_sub_type_hash;
|
||||
else
|
||||
return;
|
||||
|
||||
runesBySubTypeToken.computeIfAbsent(token, k -> new ArrayList<>()).add(rune);
|
||||
}
|
||||
|
||||
private static void indexRuneNames(RuneTemplate rune) {
|
||||
|
||||
for (RuneTemplate.SkillGrant grant : rune.rune_skill_grant) {
|
||||
addSkill(grant.skill_type);
|
||||
grant.skill_granted_skills.keySet().forEach(CObjectIndex::addSkill);
|
||||
grant.skill_granted_powers.keySet().forEach(CObjectIndex::addPower);
|
||||
}
|
||||
|
||||
rune.rune_skill_adj.keySet().forEach(CObjectIndex::addSkill);
|
||||
addSkill(rune.item_skill_used);
|
||||
addSkill(rune.item_skill_mastery_used);
|
||||
indexItemFields(rune.item_skill_req, rune.item_user_power_action, rune.item_power_grant, rune.item_power_action);
|
||||
}
|
||||
|
||||
private static void indexItemFields(Map<String, Integer> skillReq, ArrayList<UserPowerAction> userPowerActions,
|
||||
ArrayList<PowerGrant> powerGrants, Map<String, ArrayList<int[]>> powerActions) {
|
||||
|
||||
skillReq.keySet().forEach(CObjectIndex::addSkill);
|
||||
|
||||
for (UserPowerAction action : userPowerActions)
|
||||
addPower(action.power);
|
||||
|
||||
for (PowerGrant grant : powerGrants) {
|
||||
addPower(grant.power_type);
|
||||
grant.power_granted_skills.keySet().forEach(CObjectIndex::addSkill);
|
||||
grant.power_granted_powers.keySet().forEach(CObjectIndex::addPower);
|
||||
}
|
||||
|
||||
powerActions.keySet().forEach(CObjectIndex::addPower);
|
||||
}
|
||||
|
||||
private static void addSkill(String skillName) {
|
||||
addName(skillName, skillTokens, skillNames, "skill");
|
||||
}
|
||||
|
||||
private static void addPower(String powerName) {
|
||||
addName(powerName, powerTokens, powerNames, "power");
|
||||
}
|
||||
|
||||
private static void addName(String name, HashMap<String, Integer> tokens, HashMap<Integer, String> names, String kind) {
|
||||
|
||||
int token;
|
||||
String previousName;
|
||||
|
||||
// readHashLabel keeps unresolved hashes as decimal strings; those are not names
|
||||
if (name == null || name.isEmpty() || name.matches("-?\\d+") || tokens.containsKey(name))
|
||||
return;
|
||||
|
||||
token = Hasher.SBStringHash(name);
|
||||
tokens.put(name, token);
|
||||
previousName = names.putIfAbsent(token, name);
|
||||
|
||||
if (previousName != null)
|
||||
Logger.warn(kind + " token " + token + " shared by '" + previousName + "' and '" + name + "'");
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import engine.net.client.ClientConnection;
|
||||
import engine.net.client.ClientConnectionManager;
|
||||
import engine.net.client.msg.login.*;
|
||||
import engine.objects.*;
|
||||
import engine.parsers.cache.CObjectIndex;
|
||||
import engine.parsers.cache.DeedParser;
|
||||
import engine.parsers.cache.ItemParser;
|
||||
import engine.parsers.cache.RuneParser;
|
||||
@@ -314,6 +315,9 @@ public class LoginServer {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
Logger.info("Indexing Cache Templates");
|
||||
CObjectIndex.build();
|
||||
|
||||
Logger.info("Initializing Network");
|
||||
Network.init();
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ import engine.net.client.ClientConnectionManager;
|
||||
import engine.net.client.msg.UpdateStateMsg;
|
||||
import engine.net.client.msg.chat.ChatSystemMsg;
|
||||
import engine.objects.*;
|
||||
import engine.parsers.cache.CObjectIndex;
|
||||
import engine.parsers.cache.DeedParser;
|
||||
import engine.parsers.cache.ItemParser;
|
||||
import engine.parsers.cache.RuneParser;
|
||||
@@ -333,6 +334,9 @@ public class WorldServer {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
Logger.info("Indexing Cache Templates");
|
||||
CObjectIndex.build();
|
||||
|
||||
Logger.info("Loading PromotionClasses");
|
||||
DbManager.PromotionQueries.GET_ALL_PROMOTIONS();
|
||||
|
||||
|
||||
Reference in new issue
Block a user