RuneType covers the rune_type values in the client cache data (CLASS, DISCIPLINE, RACE, TALENT). ANTISIEGE matches the combat_attack_resist key in the cache data. RuneTemplate.rune_type and combat_attack_resist on both cache templates now use these enums. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
334 lines
18 KiB
Java
334 lines
18 KiB
Java
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
|
// Magicbane Emulator Project © 2013 - 2026
|
|
// https://www.magicbane.com
|
|
|
|
package engine.parsers.cache;
|
|
|
|
import engine.gameManager.ConfigManager;
|
|
import engine.mbEnums;
|
|
import engine.parsers.objects.RuneTemplate;
|
|
import org.json.JSONArray;
|
|
import org.json.JSONObject;
|
|
import org.pmw.tinylog.Logger;
|
|
|
|
import java.io.File;
|
|
import java.util.HashMap;
|
|
|
|
// Loads RUNE templates from the client cache JSON (COBJECTS/RUNE/<id>.json).
|
|
// Not yet wired into server startup.
|
|
|
|
public class RuneParser {
|
|
|
|
private static final String runePath = ConfigManager.DEFAULT_DATA_DIR + "COBJECTS/RUNE/";
|
|
|
|
public static HashMap<Integer, RuneTemplate> parseRuneFolder() {
|
|
|
|
HashMap<Integer, RuneTemplate> runeTemplates = new HashMap<>();
|
|
File[] runeFiles = CacheParserUtil.listJsonFiles(runePath);
|
|
int templateId;
|
|
JSONObject runeJson;
|
|
|
|
for (File runeFile : runeFiles) {
|
|
try {
|
|
templateId = CacheParserUtil.templateIdFromFile(runeFile);
|
|
runeJson = CacheParserUtil.readJsonFile(runeFile);
|
|
runeTemplates.put(templateId, parseRuneEntry(templateId, runeJson));
|
|
} catch (Exception e) {
|
|
Logger.error(runeFile.getName() + ": " + e);
|
|
}
|
|
}
|
|
|
|
Logger.info("Loaded " + runeTemplates.size() + " of " + runeFiles.length + " rune templates");
|
|
|
|
return runeTemplates;
|
|
}
|
|
|
|
private static RuneTemplate parseRuneEntry(int templateId, JSONObject runeJson) {
|
|
|
|
RuneTemplate rune = new RuneTemplate();
|
|
JSONObject raceReq;
|
|
JSONObject classReq;
|
|
JSONObject discReq;
|
|
JSONArray itemFlags;
|
|
String flag;
|
|
mbEnums.ItemFlags itemFlag;
|
|
Object subType;
|
|
JSONObject speedJson;
|
|
mbEnums.MovementType movementType;
|
|
JSONObject groupJson;
|
|
JSONArray skillGrantsJson;
|
|
JSONObject skillGrantJson;
|
|
RuneTemplate.SkillGrant skillGrant;
|
|
JSONArray skillAdjJson;
|
|
JSONObject skillAdjEntry;
|
|
JSONArray naturalAttacksJson;
|
|
JSONArray inventoryJson;
|
|
JSONObject inventoryEntryJson;
|
|
RuneTemplate.InventoryEntry inventoryEntry;
|
|
JSONArray bodyPartsJson;
|
|
JSONObject bodyPartJson;
|
|
RuneTemplate.BodyPart bodyPart;
|
|
|
|
rune.template_id = templateId;
|
|
|
|
// obj_
|
|
|
|
rune.obj_name = runeJson.getString("obj_name");
|
|
rune.obj_pickable = runeJson.getBoolean("obj_pickable");
|
|
rune.obj_gravity = runeJson.getFloat("obj_gravity");
|
|
rune.obj_cull_distance = runeJson.getFloat("obj_cull_distance");
|
|
rune.obj_scale = CacheParserUtil.readVector3(runeJson.getJSONArray("obj_scale"));
|
|
rune.obj_render_object = runeJson.getInt("obj_render_object");
|
|
rune.obj_double_fusion = runeJson.getBoolean("obj_double_fusion");
|
|
rune.obj_forward_vector = CacheParserUtil.readVector3(runeJson.getJSONArray("obj_forward_vector"));
|
|
rune.obj_tracking_name = runeJson.getString("obj_tracking_name");
|
|
rune.obj_max_tracking_distance = runeJson.getFloat("obj_max_tracking_distance");
|
|
rune.obj_icon = runeJson.getInt("obj_icon");
|
|
rune.obj_gravity_f = runeJson.getFloat("obj_gravity_f");
|
|
rune.obj_sound_events.putAll(CacheParserUtil.readStringValueMap(runeJson.getJSONArray("obj_sound_events"), "sound_type", "sound_event"));
|
|
rune.obj_arc_hardpoint_list = runeJson.getJSONArray("obj_arc_hardpoint_list");
|
|
rune.obj_sparse_data.putAll(CacheParserUtil.readSparseData(runeJson.getJSONObject("obj_sparse_data")));
|
|
rune.obj_render_object_low_detail = runeJson.getInt("obj_render_object_low_detail");
|
|
rune.obj_default_alignment = CacheParserUtil.readVector3(runeJson.getJSONArray("obj_default_alignment"));
|
|
rune.obj_sound_table = CacheParserUtil.readLabel(runeJson, "obj_sound_table");
|
|
|
|
// combat_
|
|
|
|
rune.combat_health_current = runeJson.getFloat("combat_health_current");
|
|
rune.combat_health_full = runeJson.getFloat("combat_health_full");
|
|
rune.combat_attack_resist.putAll(CacheParserUtil.readEnumFloatMap(mbEnums.DamageType.class, runeJson.getJSONObject("combat_attack_resist"),
|
|
templateId, "combat_attack_resist"));
|
|
rune.combat_powers = runeJson.getJSONArray("combat_powers");
|
|
|
|
// item_
|
|
|
|
rune.item_type = CacheParserUtil.readEnum(mbEnums.ItemType.class, runeJson.getString("item_type"), templateId, "item_type");
|
|
rune.item_eq_slots_value = runeJson.getInt("item_eq_slots_value");
|
|
rune.item_eq_slots_type = runeJson.getBoolean("item_eq_slots_type");
|
|
rune.item_eq_slots_or.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, runeJson.getJSONArray("item_eq_slots_or"), templateId, "item_eq_slots_or"));
|
|
rune.item_eq_slots_and.addAll(CacheParserUtil.readEnumSet(mbEnums.EquipSlotType.class, runeJson.getJSONArray("item_eq_slots_and"), templateId, "item_eq_slots_and"));
|
|
rune.item_takeable = runeJson.getBoolean("item_takeable");
|
|
rune.item_value = runeJson.getInt("item_value");
|
|
rune.item_wt = runeJson.getInt("item_wt");
|
|
rune.item_passive_defense_mod = runeJson.getFloat("item_passive_defense_mod");
|
|
rune.item_base_name = runeJson.getString("item_base_name");
|
|
rune.item_dsc = runeJson.getString("item_dsc");
|
|
rune.item_render_object_female = runeJson.getInt("item_render_object_female");
|
|
rune.item_health_full = runeJson.getFloat("item_health_full");
|
|
rune.item_skill_used = CacheParserUtil.readLabel(runeJson, "item_skill_used");
|
|
rune.item_skill_mastery_used = CacheParserUtil.readLabel(runeJson, "item_skill_mastery_used");
|
|
rune.item_parry_anim_id = runeJson.getInt("item_parry_anim_id");
|
|
rune.item_offering_info.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, runeJson.getJSONArray("item_offering_info"),
|
|
"offering_type", "offering_value", templateId, "item_offering_info"));
|
|
|
|
// "None" is the client's empty marker, not a flag
|
|
|
|
itemFlags = runeJson.getJSONArray("item_flags");
|
|
|
|
for (int i = 0; i < itemFlags.length(); i++) {
|
|
flag = itemFlags.getString(i);
|
|
|
|
if (flag.equals("None"))
|
|
continue;
|
|
|
|
itemFlag = CacheParserUtil.readEnum(mbEnums.ItemFlags.class, flag, templateId, "item_flags");
|
|
|
|
if (itemFlag != null)
|
|
rune.item_flags.add(itemFlag);
|
|
}
|
|
|
|
rune.item_use_flags.addAll(CacheParserUtil.readEnumSet(mbEnums.ItemUseFlags.class, runeJson.getJSONArray("item_use_flags"), templateId, "item_use_flags"));
|
|
rune.item_post_item_id = runeJson.getInt("item_post_item_id");
|
|
rune.item_initial_charges = runeJson.getInt("item_initial_charges");
|
|
rune.item_book_arcana = runeJson.getInt("item_book_arcana");
|
|
rune.item_skill_req.putAll(CacheParserUtil.readStringValueMap(runeJson.getJSONArray("item_skill_req"), "skill_type", "skill_level"));
|
|
|
|
raceReq = runeJson.getJSONObject("item_race_req");
|
|
rune.item_race_req_restrict = raceReq.getBoolean("restrict");
|
|
rune.item_race_req.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class, raceReq.getJSONArray("races"), templateId, "item_race_req"));
|
|
|
|
classReq = runeJson.getJSONObject("item_class_req");
|
|
rune.item_class_req_restrict = classReq.getBoolean("restrict");
|
|
rune.item_class_req.addAll(CacheParserUtil.readEnumSet(mbEnums.ClassType.class, classReq.getJSONArray("classes"), templateId, "item_class_req"));
|
|
|
|
discReq = runeJson.getJSONObject("item_disc_req");
|
|
rune.item_disc_req_restrict = discReq.getBoolean("restrict");
|
|
rune.item_disc_req.addAll(CacheParserUtil.readEnumSet(mbEnums.DisciplineType.class, discReq.getJSONArray("discs"), templateId, "item_disc_req"));
|
|
|
|
rune.item_attr_req.putAll(CacheParserUtil.readEnumValueMap(mbEnums.AttributeType.class, runeJson.getJSONArray("item_attr_req"),
|
|
"attr_type", "attr_value", templateId, "item_attr_req"));
|
|
rune.item_level_req = runeJson.getInt("item_level_req");
|
|
rune.item_rank_req = runeJson.getInt("item_rank_req");
|
|
rune.item_sex_req = CacheParserUtil.readEnum(mbEnums.SexType.class, runeJson.getString("item_sex_req"), templateId, "item_sex_req");
|
|
rune.item_user_power_action.addAll(CacheParserUtil.readUserPowerActions(runeJson.getJSONArray("item_user_power_action")));
|
|
rune.item_power_grant.addAll(CacheParserUtil.readPowerGrants(runeJson.getJSONArray("item_power_grant")));
|
|
rune.item_power_action.putAll(CacheParserUtil.readPowerActions(runeJson.getJSONArray("item_power_action")));
|
|
rune.item_sheathable = runeJson.getBoolean("item_sheathable");
|
|
rune.item_has_stub = runeJson.getBoolean("item_has_stub");
|
|
rune.item_offering_adjustments.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ShrineType.class, runeJson.getJSONArray("item_offering_adjustments"),
|
|
"offering_type", "offering_value", templateId, "item_offering_adjustments"));
|
|
rune.item_resource_costs.putAll(CacheParserUtil.readEnumValueMap(mbEnums.ResourceType.class, runeJson.getJSONArray("item_resource_costs"),
|
|
"resource_type", "resource_value", templateId, "item_resource_costs"));
|
|
rune.item_bane_rank = runeJson.getInt("item_bane_rank");
|
|
rune.item_ignore_saved_actions = runeJson.getBoolean("item_ignore_saved_actions");
|
|
|
|
// rune_
|
|
|
|
rune.rune_type = CacheParserUtil.readEnum(mbEnums.RuneType.class, runeJson.getString("rune_type"), templateId, "rune_type");
|
|
|
|
subType = runeJson.get("rune_sub_type");
|
|
|
|
if (subType instanceof String) {
|
|
rune.rune_sub_type = (String) subType;
|
|
} else {
|
|
rune.rune_sub_type = "";
|
|
rune.rune_sub_type_hash = (int) runeJson.getLong("rune_sub_type");
|
|
}
|
|
|
|
rune.rune_is_standard_character_creation = runeJson.getBoolean("rune_is_standard_character_creation");
|
|
rune.rune_creation_cost = runeJson.getInt("rune_creation_cost");
|
|
rune.rune_rank = runeJson.getInt("rune_rank");
|
|
rune.rune_pracs_per_level = runeJson.getInt("rune_pracs_per_level");
|
|
rune.rune_exp_req_to_level = runeJson.getFloat("rune_exp_req_to_level");
|
|
rune.rune_sex = CacheParserUtil.readEnum(mbEnums.SexType.class, runeJson.getString("rune_sex"), templateId, "rune_sex");
|
|
rune.rune_class_icon = runeJson.getInt("rune_class_icon");
|
|
rune.rune_health = runeJson.getInt("rune_health");
|
|
rune.rune_mana = runeJson.getInt("rune_mana");
|
|
rune.rune_stamina = runeJson.getInt("rune_stamina");
|
|
rune.rune_min_damage = runeJson.getFloat("rune_min_damage");
|
|
rune.rune_max_damage = runeJson.getFloat("rune_max_damage");
|
|
rune.rune_attack = runeJson.getInt("rune_attack");
|
|
rune.rune_defense = runeJson.getInt("rune_defense");
|
|
rune.rune_level = runeJson.getInt("rune_level");
|
|
|
|
speedJson = runeJson.getJSONObject("rune_speed");
|
|
|
|
for (String speedKey : speedJson.keySet()) {
|
|
movementType = CacheParserUtil.readEnum(mbEnums.MovementType.class, speedKey, templateId, "rune_speed");
|
|
|
|
if (movementType != null)
|
|
rune.rune_speed.put(movementType, speedJson.getFloat(speedKey));
|
|
}
|
|
|
|
groupJson = runeJson.getJSONObject("rune_group");
|
|
rune.rune_group_type = groupJson.getInt("group_type");
|
|
rune.rune_group_is_faction = groupJson.getBoolean("group_is_faction");
|
|
rune.rune_group_is_guild = groupJson.getBoolean("group_is_guild");
|
|
|
|
rune.rune_dsc = runeJson.getString("rune_dsc");
|
|
rune.rune_fx_txt = runeJson.getString("rune_fx_txt");
|
|
rune.rune_group_tactics = runeJson.getLong("rune_group_tactics");
|
|
rune.rune_group_role_set = runeJson.getLong("rune_group_role_set");
|
|
rune.rune_enemy_monster_types.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class, runeJson.getJSONArray("rune_enemy_monster_types"),
|
|
templateId, "rune_enemy_monster_types"));
|
|
rune.rune_groupee_monster_types.addAll(CacheParserUtil.readIntList(runeJson.getJSONArray("rune_groupee_monster_types")));
|
|
rune.rune_helper_monster_types.addAll(CacheParserUtil.readIntList(runeJson.getJSONArray("rune_helper_monster_types")));
|
|
rune.rune_not_enemy_monster_types.addAll(CacheParserUtil.readEnumSet(mbEnums.MonsterType.class, runeJson.getJSONArray("rune_not_enemy_monster_types"),
|
|
templateId, "rune_not_enemy_monster_types"));
|
|
rune.rune_enemy_gender = runeJson.getJSONArray("rune_enemy_gender");
|
|
|
|
skillGrantsJson = runeJson.getJSONArray("rune_skill_grant");
|
|
|
|
for (int i = 0; i < skillGrantsJson.length(); i++) {
|
|
skillGrantJson = skillGrantsJson.getJSONObject(i);
|
|
skillGrant = new RuneTemplate.SkillGrant();
|
|
|
|
skillGrant.skill_type = skillGrantJson.getString("skill_type");
|
|
skillGrant.skill_value = skillGrantJson.getInt("skill_value");
|
|
skillGrant.skill_granted_attrs = skillGrantJson.getJSONArray("skill_granted_attrs");
|
|
skillGrant.skill_granted_skills.addAll(CacheParserUtil.readIntArrays(skillGrantJson.getJSONArray("skill_granted_skills")));
|
|
skillGrant.skill_granted_powers = skillGrantJson.getJSONArray("skill_granted_powers");
|
|
skillGrant.skill_monster_types = skillGrantJson.getJSONArray("skill_monster_types");
|
|
|
|
rune.rune_skill_grant.add(skillGrant);
|
|
}
|
|
|
|
skillAdjJson = runeJson.getJSONArray("rune_skill_adj");
|
|
|
|
for (int i = 0; i < skillAdjJson.length(); i++) {
|
|
skillAdjEntry = skillAdjJson.getJSONObject(i);
|
|
rune.rune_skill_adj.put(skillAdjEntry.getString("skill_type"),
|
|
CacheParserUtil.readIntArrays(skillAdjEntry.getJSONArray("skill_adjusts")));
|
|
}
|
|
|
|
rune.rune_attr_adj.putAll(CacheParserUtil.readEnumValueMap(mbEnums.AttributeType.class, runeJson.getJSONArray("rune_attr_adj"),
|
|
"attr_type", "attr_value", templateId, "rune_attr_adj"));
|
|
rune.rune_max_attr_adj.putAll(CacheParserUtil.readEnumValueMap(mbEnums.AttributeType.class, runeJson.getJSONArray("rune_max_attr_adj"),
|
|
"attr_type", "attr_value", templateId, "rune_max_attr_adj"));
|
|
|
|
naturalAttacksJson = runeJson.getJSONArray("rune_naturalattacks");
|
|
|
|
for (int i = 0; i < naturalAttacksJson.length(); i++)
|
|
rune.rune_naturalattacks.add(CacheParserUtil.readFloatArray(naturalAttacksJson.getJSONArray(i)));
|
|
|
|
rune.rune_enchantment_type = runeJson.getJSONArray("rune_enchantment_type");
|
|
|
|
inventoryJson = runeJson.getJSONArray("rune_inventory_contents");
|
|
|
|
for (int i = 0; i < inventoryJson.length(); i++) {
|
|
inventoryEntryJson = inventoryJson.getJSONObject(i);
|
|
inventoryEntry = new RuneTemplate.InventoryEntry();
|
|
inventoryEntry.type = inventoryEntryJson.getString("type");
|
|
|
|
if (inventoryEntryJson.has("gold"))
|
|
inventoryEntry.values = CacheParserUtil.readFloatArray(inventoryEntryJson.getJSONArray("gold"));
|
|
else if (inventoryEntryJson.has("table"))
|
|
inventoryEntry.values = CacheParserUtil.readFloatArray(inventoryEntryJson.getJSONArray("table"));
|
|
else
|
|
Logger.error(templateId + ": rune_inventory_contents entry of type " + inventoryEntry.type + " has no values");
|
|
|
|
rune.rune_inventory_contents.add(inventoryEntry);
|
|
}
|
|
|
|
rune.rune_renderable = runeJson.getBoolean("rune_renderable");
|
|
|
|
// Render fields are absent on some runes
|
|
|
|
if (runeJson.has("rune_scale_factor"))
|
|
rune.rune_scale_factor = CacheParserUtil.readVector3(runeJson.getJSONArray("rune_scale_factor"));
|
|
|
|
if (runeJson.has("rune_skeleton"))
|
|
rune.rune_skeleton = runeJson.getInt("rune_skeleton");
|
|
|
|
if (runeJson.has("rune_slope_hugger"))
|
|
rune.rune_slope_hugger = runeJson.getBoolean("rune_slope_hugger");
|
|
|
|
if (runeJson.has("rune_can_fly"))
|
|
rune.rune_can_fly = runeJson.getBoolean("rune_can_fly");
|
|
|
|
if (runeJson.has("rune_death_effect"))
|
|
rune.rune_death_effect = runeJson.getInt("rune_death_effect");
|
|
|
|
if (runeJson.has("rune_tombstone_id"))
|
|
rune.rune_tombstone_id = runeJson.getInt("rune_tombstone_id");
|
|
|
|
if (runeJson.has("rune_body_parts")) {
|
|
|
|
bodyPartsJson = runeJson.getJSONArray("rune_body_parts");
|
|
|
|
for (int i = 0; i < bodyPartsJson.length(); i++) {
|
|
bodyPartJson = bodyPartsJson.getJSONObject(i);
|
|
bodyPart = new RuneTemplate.BodyPart();
|
|
bodyPart.body_part_render = bodyPartJson.getInt("body_part_render");
|
|
bodyPart.body_part_position = CacheParserUtil.readVector3(bodyPartJson.getJSONArray("body_part_position"));
|
|
rune.rune_body_parts.add(bodyPart);
|
|
}
|
|
}
|
|
|
|
if (runeJson.has("rune_hair"))
|
|
rune.rune_hair.addAll(CacheParserUtil.readIntList(runeJson.getJSONArray("rune_hair")));
|
|
|
|
if (runeJson.has("rune_beard"))
|
|
rune.rune_beard.addAll(CacheParserUtil.readIntList(runeJson.getJSONArray("rune_beard")));
|
|
|
|
rune.rune_natural_power_attack = runeJson.getInt("rune_natural_power_attack");
|
|
rune.rune_sparse_data.putAll(CacheParserUtil.readSparseData(runeJson.getJSONObject("rune_sparse_data")));
|
|
|
|
return rune;
|
|
}
|
|
}
|