Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c78bc7fa5 | ||
|
|
4e30a04619 | ||
|
|
42a1c8a532 | ||
|
|
2a2c528981 | ||
|
|
58e0fa32fb | ||
|
|
62b4a2a897 | ||
|
|
d9d1c41d9e | ||
|
|
cb427de440 | ||
|
|
74828c620d | ||
|
|
fbd38732dc |
No files matched your search
@@ -57,7 +57,7 @@ public class dbNPCHandler extends dbHandlerBase {
|
||||
runeSets.put(runeSetID, runeList);
|
||||
} else {
|
||||
ArrayList<Integer> runeList = runeSets.get(runeSetID);
|
||||
runeList.add(runeSetID);
|
||||
runeList.add(runeBaseID);
|
||||
runeSets.put(runeSetID, runeList);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,11 +62,10 @@ public class MakeItemCmd extends AbstractDevCmd {
|
||||
ItemTemplate template;
|
||||
try {
|
||||
templateID = Integer.parseInt(words[0]);
|
||||
template = ItemTemplate.templates.get(words[0].toLowerCase());
|
||||
template = ItemTemplate.templates.get(templateID);
|
||||
|
||||
if (template == null) {
|
||||
throwbackError(pc, "Supplied type " + words[0]
|
||||
+ " failed to parse to an Integer");
|
||||
throwbackError(pc, "No item template with id " + words[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -480,6 +480,11 @@ public enum NPCManager {
|
||||
npc.runes.add(shopkeeper);
|
||||
if (NPCManager._runeSetMap.containsKey(npc.runeSetID)) {
|
||||
for (int runeID : _runeSetMap.get(npc.runeSetID)) {
|
||||
|
||||
// Shopkeeper is already applied above and most rune sets repeat it
|
||||
if (runeID == 252620)
|
||||
continue;
|
||||
|
||||
RuneBase rb = RuneBase.getRuneBase(runeID);
|
||||
if (rb != null) {
|
||||
CharacterRune toApply = new CharacterRune(rb, npc.getObjectUUID());
|
||||
|
||||
@@ -2910,6 +2910,14 @@ public class mbEnums {
|
||||
EMPTY_NEWITEM;
|
||||
}
|
||||
|
||||
// Cache JSON names. UNKNOWN is raw 0, which the client treats as no slot (sb.exe 0x4AC810).
|
||||
public enum SheathSlot {
|
||||
UNKNOWN,
|
||||
BACK,
|
||||
WAIST,
|
||||
NONE
|
||||
}
|
||||
|
||||
public enum AttributeType {
|
||||
None,
|
||||
Strength,
|
||||
|
||||
@@ -498,7 +498,7 @@ public class Warehouse {
|
||||
if (playerCharacter == null)
|
||||
return false;
|
||||
|
||||
ItemTemplate template = ItemTemplate.templates.get(resourceType);
|
||||
ItemTemplate template = ItemTemplate.templates.get(resourceType.templateID);
|
||||
|
||||
if (template == null)
|
||||
return false;
|
||||
|
||||
+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 + "'");
|
||||
}
|
||||
}
|
||||
+38
-1
@@ -35,7 +35,7 @@ public class CacheParserUtil {
|
||||
public static File[] listJsonFiles(String folderPath) {
|
||||
|
||||
File folder = new File(folderPath);
|
||||
File[] jsonFiles = folder.listFiles((dir, name) -> name.endsWith(".json"));
|
||||
File[] jsonFiles = folder.listFiles((dir, name) -> name.endsWith(".json") && hasTemplateId(name));
|
||||
|
||||
if (jsonFiles == null) {
|
||||
Logger.error("Cache folder not found: " + folder.getAbsolutePath());
|
||||
@@ -45,6 +45,22 @@ public class CacheParserUtil {
|
||||
return jsonFiles;
|
||||
}
|
||||
|
||||
// Template ids are 32-bit. The client cache has RUNE/10788957847552, which is
|
||||
// 2512 << 32 (the Huntress id in the high word); files like it are skipped.
|
||||
|
||||
private static boolean hasTemplateId(String fileName) {
|
||||
|
||||
String idText = fileName.substring(0, fileName.length() - ".json".length());
|
||||
|
||||
try {
|
||||
Integer.parseInt(idText);
|
||||
return true;
|
||||
} catch (NumberFormatException e) {
|
||||
Logger.warn("Skipping " + fileName + ": name is not a 32-bit template id");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static int templateIdFromFile(File jsonFile) {
|
||||
|
||||
String fileName = jsonFile.getName();
|
||||
@@ -227,6 +243,27 @@ public class CacheParserUtil {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Like readLabel, but a non-zero number is a hash the exporter could not
|
||||
// name; it is kept as its decimal string rather than dropped.
|
||||
|
||||
public static String readHashLabel(JSONObject json, String key, int templateId) {
|
||||
|
||||
Object value = json.get(key);
|
||||
long hash;
|
||||
|
||||
if (value instanceof String)
|
||||
return (String) value;
|
||||
|
||||
hash = json.getLong(key);
|
||||
|
||||
if (hash == 0)
|
||||
return "";
|
||||
|
||||
Logger.warn(templateId + ": " + key + " has unresolved hash " + hash);
|
||||
|
||||
return Long.toString(hash);
|
||||
}
|
||||
|
||||
public static ArrayList<UserPowerAction> readUserPowerActions(JSONArray actionsJson) {
|
||||
|
||||
ArrayList<UserPowerAction> userPowerActions = new ArrayList<>();
|
||||
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.cache;
|
||||
|
||||
import engine.gameManager.ConfigManager;
|
||||
import engine.mbEnums;
|
||||
import engine.parsers.objects.ItemTemplate;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
|
||||
// Loads ITEM templates from the client cache JSON (COBJECTS/ITEM/<id>.json).
|
||||
|
||||
public class ItemParser {
|
||||
|
||||
private static final String itemPath = ConfigManager.DEFAULT_DATA_DIR + "COBJECTS/ITEM/";
|
||||
|
||||
public static HashMap<Integer, ItemTemplate> parseItemFolder() {
|
||||
|
||||
HashMap<Integer, ItemTemplate> itemTemplates = new HashMap<>();
|
||||
File[] itemFiles = CacheParserUtil.listJsonFiles(itemPath);
|
||||
int templateId;
|
||||
JSONObject itemJson;
|
||||
int failedCount = 0;
|
||||
|
||||
for (File itemFile : itemFiles) {
|
||||
try {
|
||||
templateId = CacheParserUtil.templateIdFromFile(itemFile);
|
||||
itemJson = CacheParserUtil.readJsonFile(itemFile);
|
||||
itemTemplates.put(templateId, parseItemEntry(templateId, itemJson));
|
||||
} catch (Exception e) {
|
||||
Logger.error(itemFile.getName() + ": " + e);
|
||||
failedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.info("Loaded " + itemTemplates.size() + " of " + itemFiles.length + " item templates");
|
||||
|
||||
// Partial item data is never usable; null tells the caller to abort
|
||||
if (failedCount > 0)
|
||||
return null;
|
||||
|
||||
return itemTemplates;
|
||||
}
|
||||
|
||||
private static ItemTemplate parseItemEntry(int templateId, JSONObject itemJson) {
|
||||
|
||||
ItemTemplate item = new ItemTemplate();
|
||||
JSONObject raceReq;
|
||||
JSONObject classReq;
|
||||
JSONObject discReq;
|
||||
JSONArray itemFlags;
|
||||
JSONArray useFlags;
|
||||
String flag;
|
||||
Object stubJson;
|
||||
JSONArray stubHolder;
|
||||
|
||||
item.template_id = templateId;
|
||||
|
||||
// obj_
|
||||
|
||||
item.obj_name = itemJson.getString("obj_name");
|
||||
item.obj_pickable = itemJson.getBoolean("obj_pickable");
|
||||
item.obj_gravity = itemJson.getFloat("obj_gravity");
|
||||
item.obj_cull_distance = itemJson.getFloat("obj_cull_distance");
|
||||
item.obj_scale = CacheParserUtil.readVector3(itemJson.getJSONArray("obj_scale"));
|
||||
item.obj_render_object = itemJson.getInt("obj_render_object");
|
||||
item.obj_double_fusion = itemJson.getBoolean("obj_double_fusion");
|
||||
item.obj_forward_vector = CacheParserUtil.readVector3(itemJson.getJSONArray("obj_forward_vector"));
|
||||
item.obj_tracking_name = itemJson.getString("obj_tracking_name");
|
||||
item.obj_max_tracking_distance = itemJson.getFloat("obj_max_tracking_distance");
|
||||
item.obj_icon = itemJson.getInt("obj_icon");
|
||||
item.obj_gravity_f = itemJson.getFloat("obj_gravity_f");
|
||||
item.obj_sound_events.putAll(CacheParserUtil.readStringValueMap(itemJson.getJSONArray("obj_sound_events"), "sound_type", "sound_event"));
|
||||
item.obj_arc_hardpoint_list = itemJson.getJSONArray("obj_arc_hardpoint_list");
|
||||
item.obj_sparse_data.putAll(CacheParserUtil.readSparseData(itemJson.getJSONObject("obj_sparse_data")));
|
||||
item.obj_render_object_low_detail = itemJson.getInt("obj_render_object_low_detail");
|
||||
item.obj_default_alignment = CacheParserUtil.readVector3(itemJson.getJSONArray("obj_default_alignment"));
|
||||
item.obj_sound_table = CacheParserUtil.readHashLabel(itemJson, "obj_sound_table", templateId);
|
||||
|
||||
// combat_
|
||||
|
||||
item.combat_health_current = itemJson.getFloat("combat_health_current");
|
||||
item.combat_health_full = itemJson.getFloat("combat_health_full");
|
||||
item.combat_attack_resist.putAll(CacheParserUtil.readEnumFloatMap(mbEnums.DamageType.class, itemJson.getJSONObject("combat_attack_resist"),
|
||||
templateId, "combat_attack_resist"));
|
||||
item.combat_powers = itemJson.getJSONArray("combat_powers");
|
||||
|
||||
// item_
|
||||
|
||||
item.item_type = CacheParserUtil.readEnum(mbEnums.ItemType.class, itemJson.getString("item_type"), templateId, "item_type");
|
||||
item.item_eq_slots_value = itemJson.getInt("item_eq_slots_value");
|
||||
item.item_eq_slots_type = itemJson.getBoolean("item_eq_slots_type");
|
||||
|
||||
// The exporter writes ["NONE"] for an empty slot mask
|
||||
|
||||
item.item_eq_slots_or.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, itemJson.getJSONArray("item_eq_slots_or"), templateId, "item_eq_slots_or"));
|
||||
item.item_eq_slots_or.remove(mbEnums.EquipSlotType.NONE);
|
||||
item.item_eq_slots_and.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, itemJson.getJSONArray("item_eq_slots_and"), templateId, "item_eq_slots_and"));
|
||||
item.item_eq_slots_and.remove(mbEnums.EquipSlotType.NONE);
|
||||
|
||||
item.item_takeable = itemJson.getBoolean("item_takeable");
|
||||
item.item_value = itemJson.getInt("item_value");
|
||||
item.item_wt = itemJson.getInt("item_wt");
|
||||
item.item_passive_defense_mod = itemJson.getFloat("item_passive_defense_mod");
|
||||
item.item_base_name = itemJson.getString("item_base_name");
|
||||
item.item_dsc = itemJson.getString("item_dsc");
|
||||
item.item_render_object_female = itemJson.getInt("item_render_object_female");
|
||||
item.item_health_full = itemJson.getFloat("item_health_full");
|
||||
item.item_skill_used = CacheParserUtil.readHashLabel(itemJson, "item_skill_used", templateId);
|
||||
item.item_skill_mastery_used = CacheParserUtil.readHashLabel(itemJson, "item_skill_mastery_used", templateId);
|
||||
item.item_parry_anim_id = itemJson.getInt("item_parry_anim_id");
|
||||
item.item_offering_info.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, itemJson.getJSONArray("item_offering_info"),
|
||||
"offering_type", "offering_value", templateId, "item_offering_info"));
|
||||
|
||||
// The client reads these blocks by item type, not by key presence
|
||||
|
||||
if (item.item_type == mbEnums.ItemType.WEAPON) {
|
||||
item.item_weapon = parseWeapon(templateId, itemJson.getJSONObject("item_weapon"));
|
||||
item.item_primary_attr = CacheParserUtil.readEnum(mbEnums.AttributeType.class, itemJson.getString("item_primary_attr"), templateId, "item_primary_attr");
|
||||
item.item_secondary_attr = CacheParserUtil.readEnum(mbEnums.AttributeType.class, itemJson.getString("item_secondary_attr"), templateId, "item_secondary_attr");
|
||||
}
|
||||
|
||||
if (item.item_type == mbEnums.ItemType.ARMOR) {
|
||||
item.item_bulk_factor = itemJson.getFloat("item_bulk_factor");
|
||||
item.item_defense_rating = itemJson.getInt("item_defense_rating");
|
||||
}
|
||||
|
||||
// "None" is bit 0 in the client's flag tables, not a real flag
|
||||
|
||||
itemFlags = itemJson.getJSONArray("item_flags");
|
||||
|
||||
for (int i = 0; i < itemFlags.length(); i++) {
|
||||
flag = itemFlags.getString(i);
|
||||
|
||||
if (flag.equals("None"))
|
||||
continue;
|
||||
|
||||
item.item_flags.add(CacheParserUtil.readEnum(mbEnums.ItemFlags.class, flag, templateId, "item_flags"));
|
||||
}
|
||||
|
||||
useFlags = itemJson.getJSONArray("item_use_flags");
|
||||
|
||||
for (int i = 0; i < useFlags.length(); i++) {
|
||||
flag = useFlags.getString(i);
|
||||
|
||||
if (flag.equals("NONE"))
|
||||
continue;
|
||||
|
||||
item.item_use_flags.add(CacheParserUtil.readEnum(mbEnums.ItemUseFlags.class, flag, templateId, "item_use_flags"));
|
||||
}
|
||||
|
||||
item.item_post_item_id = itemJson.getInt("item_post_item_id");
|
||||
item.item_initial_charges = itemJson.getInt("item_initial_charges");
|
||||
item.item_book_arcana = itemJson.getInt("item_book_arcana");
|
||||
item.item_skill_req.putAll(CacheParserUtil.readStringValueMap(itemJson.getJSONArray("item_skill_req"), "skill_type", "skill_level"));
|
||||
|
||||
raceReq = itemJson.getJSONObject("item_race_req");
|
||||
item.item_race_req_restrict = raceReq.getBoolean("restrict");
|
||||
item.item_race_req.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class, raceReq.getJSONArray("races"), templateId, "item_race_req"));
|
||||
|
||||
classReq = itemJson.getJSONObject("item_class_req");
|
||||
item.item_class_req_restrict = classReq.getBoolean("restrict");
|
||||
item.item_class_req.addAll(CacheParserUtil.readEnumSet(mbEnums.ClassType.class, classReq.getJSONArray("classes"), templateId, "item_class_req"));
|
||||
|
||||
discReq = itemJson.getJSONObject("item_disc_req");
|
||||
item.item_disc_req_restrict = discReq.getBoolean("restrict");
|
||||
item.item_disc_req.addAll(CacheParserUtil.readEnumSet(mbEnums.DisciplineType.class, discReq.getJSONArray("discs"), templateId, "item_disc_req"));
|
||||
|
||||
item.item_attr_req.putAll(CacheParserUtil.readEnumValueMap(mbEnums.AttributeType.class, itemJson.getJSONArray("item_attr_req"),
|
||||
"attr_type", "attr_value", templateId, "item_attr_req"));
|
||||
item.item_level_req = itemJson.getInt("item_level_req");
|
||||
item.item_rank_req = itemJson.getInt("item_rank_req");
|
||||
item.item_sex_req = CacheParserUtil.readEnum(mbEnums.SexType.class, itemJson.getString("item_sex_req"), templateId, "item_sex_req");
|
||||
item.item_user_power_action.addAll(CacheParserUtil.readUserPowerActions(itemJson.getJSONArray("item_user_power_action")));
|
||||
item.item_power_grant.addAll(CacheParserUtil.readPowerGrants(itemJson.getJSONArray("item_power_grant"), templateId));
|
||||
item.item_power_action.putAll(CacheParserUtil.readPowerActions(itemJson.getJSONArray("item_power_action")));
|
||||
|
||||
item.item_sheathable = itemJson.getBoolean("item_sheathable");
|
||||
|
||||
if (item.item_sheathable) {
|
||||
item.item_sheath_slot = CacheParserUtil.readEnum(mbEnums.SheathSlot.class, itemJson.getString("item_sheath_slot"), templateId, "item_sheath_slot");
|
||||
item.item_sheath_offset = CacheParserUtil.readVector3(itemJson.getJSONArray("item_sheath_offset"));
|
||||
item.item_sheath_rotation = CacheParserUtil.readVector3(itemJson.getJSONArray("item_sheath_rotation"));
|
||||
}
|
||||
|
||||
stubJson = itemJson.get("item_has_stub");
|
||||
|
||||
if (stubJson instanceof JSONArray) {
|
||||
stubHolder = (JSONArray) stubJson;
|
||||
item.item_has_stub = true;
|
||||
item.item_stub_flag = stubHolder.getBoolean(0);
|
||||
item.item_stub_id = stubHolder.getInt(1);
|
||||
} else {
|
||||
item.item_has_stub = itemJson.getBoolean("item_has_stub");
|
||||
}
|
||||
|
||||
item.item_offering_adjustments.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, itemJson.getJSONArray("item_offering_adjustments"),
|
||||
"offering_type", "offering_value", templateId, "item_offering_adjustments"));
|
||||
item.item_resource_costs.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ResourceType.class, itemJson.getJSONArray("item_resource_costs"),
|
||||
"resource_type", "resource_value", templateId, "item_resource_costs"));
|
||||
item.item_bane_rank = itemJson.getInt("item_bane_rank");
|
||||
item.item_ignore_saved_actions = itemJson.getBoolean("item_ignore_saved_actions");
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private static ItemTemplate.Weapon parseWeapon(int templateId, JSONObject weaponJson) {
|
||||
|
||||
ItemTemplate.Weapon weapon = new ItemTemplate.Weapon();
|
||||
JSONArray damageJson;
|
||||
JSONObject damageEntry;
|
||||
ItemTemplate.Damage damage;
|
||||
|
||||
weapon.weapon_wepspeed = weaponJson.getFloat("weapon_wepspeed");
|
||||
weapon.weapon_max_range = weaponJson.getFloat("weapon_max_range");
|
||||
weapon.weapon_projectile_id = weaponJson.getInt("weapon_projectile_id");
|
||||
weapon.weapon_projectile_speed = weaponJson.getFloat("weapon_projectile_speed");
|
||||
weapon.weapon_combat_idle_anim = weaponJson.getInt("weapon_combat_idle_anim");
|
||||
|
||||
damageJson = weaponJson.getJSONArray("weapon_damage");
|
||||
|
||||
for (int i = 0; i < damageJson.length(); i++) {
|
||||
damageEntry = damageJson.getJSONObject(i);
|
||||
damage = new ItemTemplate.Damage();
|
||||
|
||||
damage.damage_type = CacheParserUtil.readEnum(mbEnums.DamageType.class, damageEntry.getString("damage_type"), templateId, "weapon_damage");
|
||||
damage.damage_min = damageEntry.getInt("damage_min");
|
||||
damage.damage_max = damageEntry.getInt("damage_max");
|
||||
|
||||
weapon.weapon_damage.add(damage);
|
||||
}
|
||||
|
||||
weapon.weapon_attack_anim_right.addAll(CacheParserUtil.readIntArrays(weaponJson.getJSONArray("weapon_attack_anim_right")));
|
||||
weapon.weapon_attack_anim_left.addAll(CacheParserUtil.readIntArrays(weaponJson.getJSONArray("weapon_attack_anim_left")));
|
||||
|
||||
return weapon;
|
||||
}
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.cache;
|
||||
|
||||
import engine.gameManager.ConfigManager;
|
||||
import engine.gameManager.DbManager;
|
||||
import engine.mbEnums;
|
||||
import engine.objects.AbstractGameObject;
|
||||
import engine.objects.BaseClass;
|
||||
import engine.parsers.objects.RuneTemplate;
|
||||
import org.pmw.tinylog.Logger;
|
||||
|
||||
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.
|
||||
// Enabled by the optional env var MB_PARITY_CHECK; not in the ConfigManager enum, which requires every entry.
|
||||
|
||||
public class ParityCheck {
|
||||
|
||||
private static final String FLAG_NAME = "MB_PARITY_CHECK";
|
||||
private static final LinkedHashMap<String, Consumer<ParityCheck>> comparators = new LinkedHashMap<>();
|
||||
|
||||
private final String table;
|
||||
private int comparedCount = 0;
|
||||
private int differenceCount = 0;
|
||||
private int missingCount = 0;
|
||||
|
||||
static {
|
||||
register("static_rune_baseclass", ParityCheck::compareBaseClasses);
|
||||
}
|
||||
|
||||
private ParityCheck(String table) {
|
||||
this.table = table;
|
||||
}
|
||||
|
||||
public static boolean isEnabled() {
|
||||
return ConfigManager.configMap.getOrDefault(FLAG_NAME, "false").equalsIgnoreCase("true");
|
||||
}
|
||||
|
||||
public static void register(String table, Consumer<ParityCheck> comparator) {
|
||||
comparators.put(table, comparator);
|
||||
}
|
||||
|
||||
public static void run() {
|
||||
|
||||
ParityCheck check;
|
||||
|
||||
for (String table : comparators.keySet()) {
|
||||
check = new ParityCheck(table);
|
||||
|
||||
try {
|
||||
comparators.get(table).accept(check);
|
||||
} catch (Exception e) {
|
||||
Logger.error("Parity: " + table + ": comparator failed: " + e);
|
||||
}
|
||||
|
||||
Logger.info("Parity: " + table + ": " + check.comparedCount + " compared, "
|
||||
+ check.differenceCount + " differences, " + check.missingCount + " missing in json");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns false when the row has no JSON counterpart, so the comparator can skip its fields.
|
||||
|
||||
public boolean compareRow(int id, Object jsonObject) {
|
||||
|
||||
comparedCount++;
|
||||
|
||||
if (jsonObject == null) {
|
||||
missingCount++;
|
||||
Logger.warn("Parity: " + table + "/" + id + ": missing in json");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void compareField(int id, String field, Object dbValue, Object jsonValue) {
|
||||
|
||||
if (Objects.equals(dbValue, jsonValue))
|
||||
return;
|
||||
|
||||
differenceCount++;
|
||||
Logger.warn("Parity: " + table + "/" + id + "/" + field + ": db=" + dbValue + " json=" + jsonValue);
|
||||
}
|
||||
|
||||
private static void compareBaseClasses(ParityCheck check) {
|
||||
|
||||
ConcurrentHashMap<Integer, AbstractGameObject> cache;
|
||||
BaseClass baseClass;
|
||||
RuneTemplate rune;
|
||||
|
||||
cache = DbManager.getMap(mbEnums.GameObjectType.BaseClass);
|
||||
|
||||
if (cache == null) {
|
||||
Logger.warn("Parity: static_rune_baseclass: no BaseClass objects loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
for (AbstractGameObject cached : cache.values()) {
|
||||
baseClass = (BaseClass) cached;
|
||||
rune = RuneTemplate._runes.get(baseClass.getObjectUUID());
|
||||
|
||||
if (!check.compareRow(baseClass.getObjectUUID(), rune))
|
||||
continue;
|
||||
|
||||
check.compareField(baseClass.getObjectUUID(), "name", baseClass.getName(), rune.obj_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.objects;
|
||||
|
||||
// Common type for ITEM, DEED and RUNE cache templates, which share one id space.
|
||||
// Shared obj_/combat_/item_ getters arrive with JSON-Refactor-Plan Task 1.1.
|
||||
|
||||
public interface CObjectTemplate {
|
||||
}
|
||||
@@ -27,7 +27,7 @@ import java.util.LinkedHashMap;
|
||||
//
|
||||
// Read only once loaded.
|
||||
|
||||
public class DeedTemplate {
|
||||
public class DeedTemplate implements CObjectTemplate {
|
||||
|
||||
public static HashMap<Integer, DeedTemplate> _deeds = new HashMap<>();
|
||||
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||
// Magicbane Emulator Project © 2013 - 2026
|
||||
// https://www.magicbane.com
|
||||
|
||||
package engine.parsers.objects;
|
||||
|
||||
import engine.math.Vector3fImmutable;
|
||||
import engine.mbEnums;
|
||||
import org.json.JSONArray;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
// Item template as defined by the client's cache data
|
||||
// (MBDATA/data/COBJECTS/ITEM). Field names match the JSON keys.
|
||||
// Field layout follows ArcItemTemplate_Load (sb.exe 0x4AABD0).
|
||||
//
|
||||
// Values are kept as the client defines them: no req/res splitting,
|
||||
// no defaults substituted. Strings with a matching enum in mbEnums
|
||||
// are stored as that enum; everything else stays a String.
|
||||
//
|
||||
// Not to be confused with engine.objects.ItemTemplate, which is loaded
|
||||
// from the database. Read only once loaded.
|
||||
|
||||
public class ItemTemplate implements CObjectTemplate {
|
||||
|
||||
public static HashMap<Integer, ItemTemplate> _items = new HashMap<>();
|
||||
|
||||
public int template_id;
|
||||
|
||||
// obj_
|
||||
|
||||
public String obj_name;
|
||||
public boolean obj_pickable;
|
||||
public float obj_gravity;
|
||||
public float obj_cull_distance;
|
||||
public Vector3fImmutable obj_scale;
|
||||
public int obj_render_object;
|
||||
public boolean obj_double_fusion;
|
||||
public Vector3fImmutable obj_forward_vector;
|
||||
public String obj_tracking_name;
|
||||
public float obj_max_tracking_distance;
|
||||
public int obj_icon;
|
||||
public float obj_gravity_f;
|
||||
public final LinkedHashMap<String, Integer> obj_sound_events = new LinkedHashMap<>();
|
||||
public JSONArray obj_arc_hardpoint_list;
|
||||
public final HashMap<String, String> obj_sparse_data = new HashMap<>();
|
||||
public int obj_render_object_low_detail;
|
||||
public Vector3fImmutable obj_default_alignment;
|
||||
public String obj_sound_table;
|
||||
|
||||
// combat_
|
||||
|
||||
public float combat_health_current;
|
||||
public float combat_health_full;
|
||||
public final EnumMap<mbEnums.DamageType, Float> combat_attack_resist = new EnumMap<>(mbEnums.DamageType.class);
|
||||
public JSONArray combat_powers;
|
||||
|
||||
// item_
|
||||
|
||||
public mbEnums.ItemType item_type;
|
||||
|
||||
// Index into the client's equip slot names; the leading slot of EQSLOTAND.
|
||||
public int item_eq_slots_value;
|
||||
|
||||
// true: any one of item_eq_slots_or; false: all of item_eq_slots_and (sb.exe 0x4AC430).
|
||||
public boolean item_eq_slots_type;
|
||||
public final EnumSet<mbEnums.EquipSlotType> item_eq_slots_or = EnumSet.noneOf(mbEnums.EquipSlotType.class);
|
||||
public final EnumSet<mbEnums.EquipSlotType> item_eq_slots_and = EnumSet.noneOf(mbEnums.EquipSlotType.class);
|
||||
public boolean item_takeable;
|
||||
public int item_value;
|
||||
public int item_wt;
|
||||
public float item_passive_defense_mod;
|
||||
public String item_base_name;
|
||||
public String item_dsc;
|
||||
public int item_render_object_female;
|
||||
public float item_health_full;
|
||||
public String item_skill_used;
|
||||
public String item_skill_mastery_used;
|
||||
public int item_parry_anim_id;
|
||||
public final EnumMap<mbEnums.ShrineType, Integer> item_offering_info = new EnumMap<>(mbEnums.ShrineType.class);
|
||||
|
||||
// WEAPON only; null otherwise.
|
||||
public Weapon item_weapon;
|
||||
public mbEnums.AttributeType item_primary_attr;
|
||||
public mbEnums.AttributeType item_secondary_attr;
|
||||
|
||||
// ARMOR only.
|
||||
public float item_bulk_factor;
|
||||
public int item_defense_rating;
|
||||
|
||||
public final EnumSet<mbEnums.ItemFlags> item_flags = EnumSet.noneOf(mbEnums.ItemFlags.class);
|
||||
public final EnumSet<mbEnums.ItemUseFlags> item_use_flags = EnumSet.noneOf(mbEnums.ItemUseFlags.class);
|
||||
public int item_post_item_id;
|
||||
public int item_initial_charges;
|
||||
|
||||
// Read as an unsigned value, possibly a hash; 0 in every ITEM file.
|
||||
public int item_book_arcana;
|
||||
public final LinkedHashMap<String, Integer> item_skill_req = new LinkedHashMap<>();
|
||||
|
||||
// For race, class and disc: restrict true means the list is excluded
|
||||
// (RACERESTRICT), false means it is required (RACEREQ).
|
||||
public boolean item_race_req_restrict;
|
||||
public final EnumSet<mbEnums.MonsterType> item_race_req = EnumSet.noneOf(mbEnums.MonsterType.class);
|
||||
public boolean item_class_req_restrict;
|
||||
public final EnumSet<mbEnums.ClassType> item_class_req = EnumSet.noneOf(mbEnums.ClassType.class);
|
||||
public boolean item_disc_req_restrict;
|
||||
public final EnumSet<mbEnums.DisciplineType> item_disc_req = EnumSet.noneOf(mbEnums.DisciplineType.class);
|
||||
public final EnumMap<mbEnums.AttributeType, Integer> item_attr_req = new EnumMap<>(mbEnums.AttributeType.class);
|
||||
public int item_level_req;
|
||||
public int item_rank_req;
|
||||
public mbEnums.SexType item_sex_req;
|
||||
public final ArrayList<UserPowerAction> item_user_power_action = new ArrayList<>();
|
||||
public final ArrayList<PowerGrant> item_power_grant = new ArrayList<>();
|
||||
public final LinkedHashMap<String, ArrayList<int[]>> item_power_action = new LinkedHashMap<>();
|
||||
|
||||
// Sheath fields are only present when item_sheathable is true.
|
||||
public boolean item_sheathable;
|
||||
public mbEnums.SheathSlot item_sheath_slot;
|
||||
public Vector3fImmutable item_sheath_offset;
|
||||
public Vector3fImmutable item_sheath_rotation;
|
||||
|
||||
// The JSON holds false, or [flag, id] when the stub is present (ArcItemStubHolder2, sb.exe 0x4A8D40).
|
||||
// Meaning of the flag is not known.
|
||||
public boolean item_has_stub;
|
||||
public boolean item_stub_flag;
|
||||
public int item_stub_id;
|
||||
|
||||
public final EnumMap<mbEnums.ShrineType, Integer> item_offering_adjustments = new EnumMap<>(mbEnums.ShrineType.class);
|
||||
public final EnumMap<mbEnums.ResourceType, Integer> item_resource_costs = new EnumMap<>(mbEnums.ResourceType.class);
|
||||
public int item_bane_rank;
|
||||
public boolean item_ignore_saved_actions;
|
||||
|
||||
// item_weapon (ArcAttackInfo_LoadWeapon, sb.exe 0x61FA60).
|
||||
|
||||
public static class Weapon {
|
||||
|
||||
public float weapon_wepspeed;
|
||||
public float weapon_max_range;
|
||||
public int weapon_projectile_id;
|
||||
public float weapon_projectile_speed;
|
||||
public int weapon_combat_idle_anim;
|
||||
public final ArrayList<Damage> weapon_damage = new ArrayList<>();
|
||||
|
||||
// [animation, duration] pairs in file order; names from engine.objects.ItemTemplate, not the client.
|
||||
public final ArrayList<int[]> weapon_attack_anim_right = new ArrayList<>();
|
||||
public final ArrayList<int[]> weapon_attack_anim_left = new ArrayList<>();
|
||||
}
|
||||
|
||||
// One entry of weapon_damage.
|
||||
|
||||
public static class Damage {
|
||||
|
||||
public mbEnums.DamageType damage_type;
|
||||
public int damage_min;
|
||||
public int damage_max;
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ import java.util.LinkedHashMap;
|
||||
//
|
||||
// Read only once loaded.
|
||||
|
||||
public class RuneTemplate {
|
||||
public class RuneTemplate implements CObjectTemplate {
|
||||
|
||||
public static HashMap<Integer, RuneTemplate> _runes = new HashMap<>();
|
||||
|
||||
|
||||
@@ -21,7 +21,9 @@ 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;
|
||||
import engine.parsers.objects.DeedTemplate;
|
||||
import engine.parsers.objects.RuneTemplate;
|
||||
@@ -304,6 +306,18 @@ public class LoginServer {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Qualified: engine.objects.ItemTemplate is the database-loaded class
|
||||
Logger.info("Loading Cache Item Templates");
|
||||
engine.parsers.objects.ItemTemplate._items = ItemParser.parseItemFolder();
|
||||
|
||||
if (engine.parsers.objects.ItemTemplate._items == null || engine.parsers.objects.ItemTemplate._items.isEmpty()) {
|
||||
Logger.error("ABORT! Cache item templates failed to load!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
Logger.info("Indexing Cache Templates");
|
||||
CObjectIndex.build();
|
||||
|
||||
Logger.info("Initializing Network");
|
||||
Network.init();
|
||||
|
||||
|
||||
@@ -31,7 +31,10 @@ 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.ParityCheck;
|
||||
import engine.parsers.cache.RuneParser;
|
||||
import engine.parsers.objects.DeedTemplate;
|
||||
import engine.parsers.objects.RuneTemplate;
|
||||
@@ -323,6 +326,18 @@ public class WorldServer {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Qualified: engine.objects.ItemTemplate is the database-loaded class
|
||||
Logger.info("Loading Cache Item Templates");
|
||||
engine.parsers.objects.ItemTemplate._items = ItemParser.parseItemFolder();
|
||||
|
||||
if (engine.parsers.objects.ItemTemplate._items == null || engine.parsers.objects.ItemTemplate._items.isEmpty()) {
|
||||
Logger.error("ABORT! Cache item templates failed to load!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
Logger.info("Indexing Cache Templates");
|
||||
CObjectIndex.build();
|
||||
|
||||
Logger.info("Loading PromotionClasses");
|
||||
DbManager.PromotionQueries.GET_ALL_PROMOTIONS();
|
||||
|
||||
@@ -381,6 +396,11 @@ public class WorldServer {
|
||||
mbEnums.RaceType.initRaceTypeTables();
|
||||
Race.loadAllRaces();
|
||||
|
||||
if (ParityCheck.isEnabled()) {
|
||||
Logger.info("Running DB/JSON parity check");
|
||||
ParityCheck.run();
|
||||
}
|
||||
|
||||
Logger.info("Loading building slot/stuck location data.");
|
||||
BuildingLocation.loadBuildingLocations();
|
||||
|
||||
|
||||
Reference in new issue
Block a user