From 91a62f03acfc2cc75b44a2035f571def5e2052dd Mon Sep 17 00:00:00 2001 From: Andreas Date: Wed, 25 Oct 2023 18:08:48 +0200 Subject: [PATCH] Fix extracting +X to Class Skills with param value --- contrib/stats.py | 96 ++++++++++++++++------------ d2warehouse/data/stats.json | 31 ++++++++- d2warehouse/item.py | 11 +++- d2warehouse/tests/test_parse_item.py | 15 +++++ 4 files changed, 109 insertions(+), 44 deletions(-) diff --git a/contrib/stats.py b/contrib/stats.py index 58793d5..22c4f4c 100644 --- a/contrib/stats.py +++ b/contrib/stats.py @@ -8,27 +8,30 @@ import sys path = sys.argv[1] if len(sys.argv) >= 2 else "." -# NOTE: These map "linked stats" to code in properties.txt. -# If None is given, the stat is excluded from the output. +# NOTE: Special handling of stats. None = Skip stat. # Note that All Resistance is actually 4 stats in the save files, that the game combines # into one. Same applies for all stats. special_stats = { - "firemindam": "dmg-fire", + "firemindam": {"template": "dmg-fire"}, "firemaxdam": None, - "lightmindam": "dmg-ltng", + "lightmindam": {"template": "dmg-ltng"}, "lightmaxdam": None, - "magicmindam": "dmg-mag", + "magicmindam": {"template": "dmg-mag"}, "magicmaxdam": None, - "coldmindam": "dmg-cold", + "coldmindam": {"template": "dmg-cold"}, "coldmaxdam": None, "coldlength": None, - "poisonmindam": "dmg-pois", + "poisonmindam": {"template": "dmg-pois"}, "poisonmaxdam": None, "poisonlength": None, - "mindamage": "dmg-norm", + "mindamage": {"template": "dmg-norm"}, "maxdamage": None, - "item_maxdamage_percent": "dmg%", # max before min for this stat + "item_maxdamage_percent": {"template": "dmg%"}, # max before min for this stat "item_mindamage_percent": None, + "item_addclassskills": { + "template": "ama", + "param_tooltips": ["ama", "pal", "nec", "sor", "bar", "dru", "ass"], + }, } # Patching of missing data in properties.txt @@ -53,6 +56,7 @@ with open(os.path.join(path, "properties.txt")) as f: prop = { "stats": [row[f"stat{i}"] for i in range(1, 8) if len(row[f"stat{i}"]) > 0], "tooltip": row["*Tooltip"], + "val1": None if len(row["val1"]) == 0 else int(row["val1"]), } properties[row["code"]] = prop if len(prop["stats"]) == 1 and prop["stats"][0] not in stat_to_prop: @@ -72,39 +76,51 @@ with open(os.path.join(path, "itemstatcost.txt")) as f: } stats = {} -for stat, statdat in itemstatcost.items(): - if stat in special_stats: - if special_stats[stat] is None: - continue - else: - prop = properties[special_stats[stat]] - obj = { - "text": prop["tooltip"], - "save_bits": [], - "save_add": itemstatcost[prop["stats"][0]]["save_add"], - "save_param_bits": itemstatcost[prop["stats"][0]]["save_param_bits"], - } - for prop_stat in prop["stats"]: - if itemstatcost[prop_stat]["save_add"] != obj["save_add"]: - print( - f"Unexpected divergence in save_add for stats in prop {special_stats[stat]}" - ) - obj["save_bits"].append(itemstatcost[prop_stat]["save_bits"]) - else: - try: - prop = properties[stat_to_prop[stat]] - except KeyError: +for stat in itemstatcost: + try: + special = ( + special_stats[stat] + if stat in special_stats + else {"template": stat_to_prop[stat]} + ) + except KeyError: + print( + f"Failed getting property for stat {stat}. Skipping! (See `special_stats` for fixing)" + ) + continue + # special = None -> Skip + if special is None: + continue + + # special.template: properties.txt row + prop = properties[special["template"]] + obj = { + "text": prop["tooltip"], + "save_bits": [], + "save_add": itemstatcost[prop["stats"][0]]["save_add"], + "save_param_bits": itemstatcost[prop["stats"][0]]["save_param_bits"], + } + for prop_stat in prop["stats"]: + if itemstatcost[prop_stat]["save_add"] != obj["save_add"]: print( - f"Failed getting property for stat {stat}. Skipping! (See `special_stats` for fixing)" + f"Unexpected divergence in save_add for stats in prop {special_stats[stat]}" ) - continue - obj = { - "text": prop["tooltip"], - "save_bits": [statdat["save_bits"]], - "save_add": statdat["save_add"], - "save_param_bits": statdat["save_param_bits"], - } - stats[statdat["id"]] = obj + obj["save_bits"].append(itemstatcost[prop_stat]["save_bits"]) + + # special.param_tooltips: extra tooltips depending on param value + if "param_tooltips" in special: + tooltips = [] + for id in special["param_tooltips"]: + prop = properties[id] + tooltips.append( + { + "param": prop["val1"], + "text": prop["tooltip"], + } + ) + obj["text"] = tooltips + + stats[itemstatcost[stat]["id"]] = obj with open("stats.json", "w", newline="\n") as f: json.dump(stats, f, indent=4) diff --git a/d2warehouse/data/stats.json b/d2warehouse/data/stats.json index e7aa2df..8a9d947 100644 --- a/d2warehouse/data/stats.json +++ b/d2warehouse/data/stats.json @@ -393,7 +393,36 @@ "save_param_bits": null }, "83": { - "text": "+# to Amazon Skill Levels", + "text": [ + { + "param": 0, + "text": "+# to Amazon Skill Levels" + }, + { + "param": 3, + "text": "+# to Paladin Skill Levels" + }, + { + "param": 2, + "text": "+# to Necromancer Skill Levels" + }, + { + "param": 1, + "text": "+# to Sorceress Skill Levels" + }, + { + "param": 4, + "text": "+# to Barbarian Skill Levels" + }, + { + "param": 5, + "text": "+# to Druid Skill Levels" + }, + { + "param": 6, + "text": "+# to Assassin Skill Levels" + } + ], "save_bits": [ 3 ], diff --git a/d2warehouse/item.py b/d2warehouse/item.py index dea2dc7..ffb241e 100644 --- a/d2warehouse/item.py +++ b/d2warehouse/item.py @@ -65,11 +65,16 @@ class Stat: print(" " * indent, str(self)) def __str__(self): - subst_text = self.text + param = self.parameter + if isinstance(self.text, list): + subst_text = next(filter(lambda v: v["param"] == param, self.text))["text"] + param = None + else: + subst_text = self.text for val in self.values: subst_text = subst_text.replace("#", str(val), 1) - if self.parameter: - subst_text = re.sub(r"\[[^\]]*\]", str(self.parameter), subst_text, 1) + if param: + subst_text = re.sub(r"\[[^\]]*\]", str(param), subst_text, 1) return subst_text diff --git a/d2warehouse/tests/test_parse_item.py b/d2warehouse/tests/test_parse_item.py index 3d62678..8ea5737 100644 --- a/d2warehouse/tests/test_parse_item.py +++ b/d2warehouse/tests/test_parse_item.py @@ -123,4 +123,19 @@ class ParseItemTest(unittest.TestCase): # this is one of the items that had stackable "0" instead of "" data = bytes.fromhex("100080000524d4fc5f308dc1e10908fe03") data, item = parse_item(data) + + def test_stat_to_class_skills(self): + # Redeemer with +2 to Paladin skill levels + data = bytes.fromhex( + "1000800005c4d593081e37d107dfa330c868881031e29429cb6d415953466bf8eccd1c3aa13ce9fe03" + ) + data, item = parse_item(data) self.assertEqual(data, b"") + self.assertEqual(str(item.stats[1]), "+2 to Paladin Skill Levels") + # Jalal with +2 to Druid skills levels + data = bytes.fromhex( + "1000800005c4753c2f5c4c4e3d638f8fd0068a08006802d020ac3a612e85b915e65a989b528d91dd41a1a8c02b0540ff01" + ) + data, item = parse_item(data) + self.assertEqual(data, b"") + self.assertEqual(str(item.stats[7]), "+2 to Druid Skill Levels")