diff --git a/src/engine/parsers/cache/CacheParserUtil.java b/src/engine/parsers/cache/CacheParserUtil.java index 8f968149..c4d0bfaa 100644 --- a/src/engine/parsers/cache/CacheParserUtil.java +++ b/src/engine/parsers/cache/CacheParserUtil.java @@ -121,8 +121,6 @@ public class CacheParserUtil { } // [{keyName: "Strength", valueName: 40}, ...] -> enum map - // Negative values are stored unsigned in the JSON (-5 is 4294967291), - // so values are read as long and wrapped to int. public static > EnumMap readEnumValueMap(Class enumClass, JSONArray entriesJson, String keyName, String valueName, @@ -137,7 +135,7 @@ public class CacheParserUtil { enumValue = readEnum(enumClass, entry.getString(keyName), templateId, fieldName); if (enumValue != null) - enumMap.put(enumValue, (int) entry.getLong(valueName)); + enumMap.put(enumValue, entry.getInt(valueName)); } return enumMap; @@ -176,7 +174,7 @@ public class CacheParserUtil { return enumMap; } - // Values are read as long and wrapped to int so unsigned 32-bit hashes survive. + // Hash and flag fields are still unsigned in the JSON; read as long and wrapped to int. public static int[] readIntArray(JSONArray valuesJson) { @@ -258,7 +256,34 @@ public class CacheParserUtil { return userPowerActions; } - public static ArrayList readPowerGrants(JSONArray grantsJson) { + // [[key, value], ...] -> map in file order. Keys are names when the exporter + // knows the hash, otherwise the raw hash, which is kept as its decimal string. + + public static LinkedHashMap readGrantPairs(JSONArray pairsJson, int templateId, String fieldName) { + + LinkedHashMap grantPairs = new LinkedHashMap<>(); + JSONArray pair; + Object key; + String keyName; + + for (int i = 0; i < pairsJson.length(); i++) { + pair = pairsJson.getJSONArray(i); + key = pair.get(0); + + if (key instanceof String) { + keyName = (String) key; + } else { + keyName = Long.toString(pair.getLong(0)); + Logger.warn(templateId + ": " + fieldName + " has unresolved hash " + keyName); + } + + grantPairs.put(keyName, pair.getInt(1)); + } + + return grantPairs; + } + + public static ArrayList readPowerGrants(JSONArray grantsJson, int templateId) { ArrayList powerGrants = new ArrayList<>(); JSONObject entry; @@ -270,9 +295,9 @@ public class CacheParserUtil { powerGrant.power_type = entry.getString("power_type"); powerGrant.power_value = entry.getInt("power_value"); - powerGrant.power_granted_attrs = entry.getJSONArray("power_granted_attrs"); - powerGrant.power_granted_skills.addAll(readIntArrays(entry.getJSONArray("power_granted_skills"))); - powerGrant.power_granted_powers.addAll(readIntArrays(entry.getJSONArray("power_granted_powers"))); + powerGrant.power_granted_attrs.putAll(readGrantPairs(entry.getJSONArray("power_granted_attrs"), templateId, "power_granted_attrs")); + powerGrant.power_granted_skills.putAll(readGrantPairs(entry.getJSONArray("power_granted_skills"), templateId, "power_granted_skills")); + powerGrant.power_granted_powers.putAll(readGrantPairs(entry.getJSONArray("power_granted_powers"), templateId, "power_granted_powers")); powerGrant.power_monster_types = entry.getJSONArray("power_monster_types"); powerGrants.add(powerGrant); diff --git a/src/engine/parsers/cache/DeedParser.java b/src/engine/parsers/cache/DeedParser.java index ceb0e21c..576ffae6 100644 --- a/src/engine/parsers/cache/DeedParser.java +++ b/src/engine/parsers/cache/DeedParser.java @@ -149,7 +149,7 @@ public class DeedParser { deed.item_rank_req = deedJson.getInt("item_rank_req"); deed.item_sex_req = CacheParserUtil.readEnum(mbEnums.SexType.class, deedJson.getString("item_sex_req"), templateId, "item_sex_req"); deed.item_user_power_action.addAll(CacheParserUtil.readUserPowerActions(deedJson.getJSONArray("item_user_power_action"))); - deed.item_power_grant.addAll(CacheParserUtil.readPowerGrants(deedJson.getJSONArray("item_power_grant"))); + deed.item_power_grant.addAll(CacheParserUtil.readPowerGrants(deedJson.getJSONArray("item_power_grant"), templateId)); deed.item_power_action.putAll(CacheParserUtil.readPowerActions(deedJson.getJSONArray("item_power_action"))); deed.item_sheathable = deedJson.getBoolean("item_sheathable"); deed.item_has_stub = deedJson.getBoolean("item_has_stub"); diff --git a/src/engine/parsers/cache/RuneParser.java b/src/engine/parsers/cache/RuneParser.java index 277cb68f..528c5661 100644 --- a/src/engine/parsers/cache/RuneParser.java +++ b/src/engine/parsers/cache/RuneParser.java @@ -165,7 +165,7 @@ public class RuneParser { 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_grant.addAll(CacheParserUtil.readPowerGrants(runeJson.getJSONArray("item_power_grant"), templateId)); 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"); @@ -239,9 +239,12 @@ public class RuneParser { 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_granted_attrs.putAll(CacheParserUtil.readGrantPairs(skillGrantJson.getJSONArray("skill_granted_attrs"), + templateId, "skill_granted_attrs")); + skillGrant.skill_granted_skills.putAll(CacheParserUtil.readGrantPairs(skillGrantJson.getJSONArray("skill_granted_skills"), + templateId, "skill_granted_skills")); + skillGrant.skill_granted_powers.putAll(CacheParserUtil.readGrantPairs(skillGrantJson.getJSONArray("skill_granted_powers"), + templateId, "skill_granted_powers")); skillGrant.skill_monster_types = skillGrantJson.getJSONArray("skill_monster_types"); rune.rune_skill_grant.add(skillGrant); diff --git a/src/engine/parsers/objects/PowerGrant.java b/src/engine/parsers/objects/PowerGrant.java index b719cb48..94558041 100644 --- a/src/engine/parsers/objects/PowerGrant.java +++ b/src/engine/parsers/objects/PowerGrant.java @@ -10,24 +10,18 @@ package engine.parsers.objects; import org.json.JSONArray; -import java.util.ArrayList; +import java.util.LinkedHashMap; // One entry of item_power_grant. -// -// power_granted_skills and power_granted_powers entries look like -// [hash, value] pairs (first element is a 32-bit string hash, some -// exceed Integer.MAX_VALUE in the JSON and are stored wrapped to int). -// This reading is from the data only and has not been confirmed in the client. -// -// power_granted_attrs and power_monster_types are empty in every DEED and -// RUNE file, so their shape is unknown and they are kept as raw JSON. +// The granted lists are [key, value] pairs, unsigned key and signed value (sb.exe 0x6D7640). +// power_monster_types is empty in every DEED and RUNE file, so it is kept as raw JSON. public class PowerGrant { public String power_type; public int power_value; - public JSONArray power_granted_attrs; - public final ArrayList power_granted_skills = new ArrayList<>(); - public final ArrayList power_granted_powers = new ArrayList<>(); + public final LinkedHashMap power_granted_attrs = new LinkedHashMap<>(); + public final LinkedHashMap power_granted_skills = new LinkedHashMap<>(); + public final LinkedHashMap power_granted_powers = new LinkedHashMap<>(); public JSONArray power_monster_types; } diff --git a/src/engine/parsers/objects/RuneTemplate.java b/src/engine/parsers/objects/RuneTemplate.java index 0de46045..f24b7d4e 100644 --- a/src/engine/parsers/objects/RuneTemplate.java +++ b/src/engine/parsers/objects/RuneTemplate.java @@ -189,18 +189,16 @@ public class RuneTemplate { public int rune_natural_power_attack; public final HashMap rune_sparse_data = new HashMap<>(); - // One entry of rune_skill_grant. - // skill_granted_skills entries look like [hash, value] pairs; not confirmed in the client. - // skill_granted_attrs, skill_granted_powers and skill_monster_types are empty - // in every RUNE file, so they are kept as raw JSON. + // One entry of rune_skill_grant; same layout as PowerGrant. + // skill_monster_types is empty in every RUNE file, so it is kept as raw JSON. public static class SkillGrant { public String skill_type; public int skill_value; - public JSONArray skill_granted_attrs; - public final ArrayList skill_granted_skills = new ArrayList<>(); - public JSONArray skill_granted_powers; + public final LinkedHashMap skill_granted_attrs = new LinkedHashMap<>(); + public final LinkedHashMap skill_granted_skills = new LinkedHashMap<>(); + public final LinkedHashMap skill_granted_powers = new LinkedHashMap<>(); public JSONArray skill_monster_types; }