ItemTemplate follows ArcItemTemplate_Load (sb.exe 0x4AABD0): weapon and armor blocks keyed by item type, sheath and stub blocks, and empty equip slot masks read as empty sets. Adds SheathSlot and readHashLabel, which keeps unresolved hashes such as item_skill_used in 195340. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
249 lines
13 KiB
Java
249 lines
13 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.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;
|
|
}
|
|
}
|