From f6bfad2d4b9c888b59ded1fd8f1722f2d4a79514 Mon Sep 17 00:00:00 2001 From: Andreas Date: Mon, 23 Oct 2023 16:19:49 +0200 Subject: [PATCH] Switch from affixes.json to stats.json --- d2warehouse/data/affixes.json | 215 ---------------------------------- d2warehouse/item.py | 57 +++++---- d2warehouse/parser.py | 92 +++++++-------- 3 files changed, 73 insertions(+), 291 deletions(-) delete mode 100644 d2warehouse/data/affixes.json diff --git a/d2warehouse/data/affixes.json b/d2warehouse/data/affixes.json deleted file mode 100644 index 5cc3513..0000000 --- a/d2warehouse/data/affixes.json +++ /dev/null @@ -1,215 +0,0 @@ -{ - "0": {"bits": [8], "bias": 32, "text": "+{0} to Strength"}, - "1": {"bits": [7], "bias": 32, "text": "+{0} to Energy"}, - "2": {"bits": [7], "bias": 32, "text": "+{0} to Dexterity"}, - "3": {"bits": [7], "bias": 32, "text": "+{0} to Vitality"}, - "7": {"bits": [9], "bias": 32, "text": "+{0} to Life"}, - "9": {"bits": [8], "bias": 32, "text": "+{0} to Mana"}, - "11": {"bits": [8], "bias": 32, "text": "+{0} to Maximum Stamina"}, - "16": {"bits": [9], "bias": 0, "text": "+{0}% Enhanced Defense"}, - "17": {"bits": [9, 9], "bias": 0, "text": "+{0}% Enhanced Damage"}, - "19": {"bits": [10], "bias": 0, "text": "+{0} to Attack rating"}, - "20": {"bits": [6], "bias": 0, "text": "+{0}% Increased chance of blocking"}, - "21": {"bits": [6], "bias": 0, "text": "+{0} to Minimum 1-handed damage"}, - "22": {"bits": [7], "bias": 0, "text": "+{0} to Maximum 1-handed damage"}, - "23": {"bits": [6], "bias": 0, "text": "+{0} to Minimum 2-handed damage"}, - "24": {"bits": [7], "bias": 0, "text": "+{0} to Maximum 2-handed damage"}, - "25": {"bits": [8], "bias": 0, "text": "Unknown (Invisible)"}, - "26": {"bits": [8], "bias": 0, "text": "Unknown (Invisible)"}, - "27": {"bits": [8], "bias": 0, "text": "Regenerate Mana {0}%"}, - "28": {"bits": [8], "bias": 0, "text": "Heal Stamina {0}%"}, - "31": {"bits": [11], "bias": 10, "text": "+{0} Defense"}, - "32": {"bits": [9], "bias": 0, "text": "+{0} vs. Missile"}, - "33": {"bits": [8], "bias": 10, "text": "+{0} vs. Melee"}, - "34": {"bits": [6], "bias": 0, "text": "Damage Reduced by {0}"}, - "35": {"bits": [6], "bias": 0, "text": "Magic Damage Reduced by {0}"}, - "36": {"bits": [8], "bias": 0, "text": "Damage Reduced by {0}%"}, - "37": {"bits": [8], "bias": 0, "text": "Magic Resist +{0}%"}, - "38": {"bits": [8], "bias": 0, "text": "+{0}% to Maximum Magic Resist"}, - "39": {"bits": [8], "bias": 50, "text": "Fire Resist +{0}%"}, - "40": {"bits": [5], "bias": 0, "text": "+{0}% to Maximum Fire Resist"}, - "41": {"bits": [8], "bias": 50, "text": "Lightning Resist +{0}%"}, - "42": {"bits": [5], "bias": 0, "text": "+{0}% to Maximum Lightning Resist"}, - "43": {"bits": [9], "bias": 200, "text": "Cold Resist +{0}%"}, - "44": {"bits": [5], "bias": 0, "text": "+{0}% to Maximum Cold Resist"}, - "45": {"bits": [8], "bias": 50, "text": "Poison Resist +{0}%"}, - "46": {"bits": [5], "bias": 0, "text": "+{0}% to Maximum Poison Resist"}, - "48": {"bits": [8, 9], "bias": 0, "text": "Adds {0}-{1} Fire Damage"}, - "49": {"bits": [9], "bias": 0, "text": "+{0} to Maximum Fire Damage"}, - "50": {"bits": [6, 10], "bias": 0, "text": "Adds {0}-{1} Lightning Damage"}, - "52": {"bits": [8, 9], "bias": 0, "text": "Adds {0}-{1} Magic Damage"}, - "54": {"bits": [8, 9, 8], "bias": 0, "text": "Adds {0}-{1} Cold Damage"}, - "57": {"bits": [10, 10, 9], "bias": 0, "text": "Adds {0}-{1} Poison Damage over {2} Seconds"}, - "60": {"bits": [7], "bias": 0, "text": "{0}% Life Stolen Per Hit"}, - "62": {"bits": [7], "bias": 0, "text": "{0}% Mana Stolen Per Hit"}, - "67": {"bits": [7], "bias": 30, "text": "Unknown (Invisible)"}, - "68": {"bits": [7], "bias": 30, "text": "Unknown (Invisible)"}, - "71": {"bits": [8], "bias": 100, "text": "Unknown (Invisible)"}, - "72": {"bits": [9], "bias": 0, "text": "Unknown (Invisible)"}, - "73": {"bits": [8], "bias": 0, "text": "+{0} Maximum Durability"}, - "74": {"bits": [6], "bias": 30, "text": "Replenish Life +{0}"}, - "75": {"bits": [7], "bias": 20, "text": "Increase Maximum Durability {0}%"}, - "76": {"bits": [6], "bias": 10, "text": "Increase Maximum Life {0}%"}, - "77": {"bits": [6], "bias": 10, "text": "Increase Maximum Mana {0}%"}, - "78": {"bits": [7], "bias": 0, "text": "Attacker Takes Damage of {0}"}, - "79": {"bits": [9], "bias": 100, "text": "{0}% Extra Gold from Monsters"}, - "80": {"bits": [8], "bias": 100, "text": "{0}% Better Chance of Getting Magic Items"}, - "81": {"bits": [7], "bias": 0, "text": "Knockback"}, - "82": {"bits": [9], "bias": 20, "text": "Unknown (Invisible)"}, - "83": {"bits": [3, 3], "bias": 0, "text": "+{1} to {0} Skill Levels"}, - "84": {"bits": [3, 3], "bias": 0, "text": "+{1} to {0} Skill Levels"}, - "85": {"bits": [9], "bias": 50, "text": "{0}% To Experience Gained"}, - "86": {"bits": [7], "bias": 0, "text": "+{0} Life After Each Kill"}, - "87": {"bits": [7], "bias": 0, "text": "Reduces Prices {0}%"}, - "88": {"bits": [1], "bias": 0, "text": "Unknown (Invisible)"}, - "89": {"bits": [4], "bias": 4, "text": "+{0} to Light Radius"}, - "90": {"bits": [5], "bias": 0, "text": "Ambient light"}, - "91": {"bits": [8], "bias": 100, "text": "Requirements {0}%"}, - "92": {"bits": [7], "bias": 0, "text": "Level requirements +{0} (Invisible)"}, - "93": {"bits": [7], "bias": 20, "text": "{0}% Increased Attack Speed"}, - "94": {"bits": [7], "bias": 64, "text": "Unknown (Invisible)"}, - "96": {"bits": [7], "bias": 20, "text": "{0}% Faster Run/Walk"}, - "97": {"bits": [9, 6], "bias": 0, "text": "+{1} To {0}"}, - "98": {"bits": [8, 1], "bias": 0, "text": "{1}+ to {0} (Visual effect only)"}, - "99": {"bits": [7], "bias": 20, "text": "{0}% Faster Hit Recovery"}, - "102": {"bits": [7], "bias": 20, "text": "{0}% Faster Block Rate"}, - "105": {"bits": [7], "bias": 20, "text": "{0}% Faster Cast Rate"}, - "107": {"bits": [9, 3], "bias": 0, "text": "+{1} To {0}"}, - "108": {"bits": [1], "bias": 0, "text": "Rest In Peace"}, - "109": {"bits": [9], "bias": 0, "text": "Unknown (Invisible)"}, - "181": {"bits": [9, 5], "bias": 0, "text": "+{1} to spell {0} (char_class Only)"}, - "182": {"bits": [9, 5], "bias": 0, "text": "+{1} to spell {0} (char_class Only)"}, - "183": {"bits": [9, 5], "bias": 0, "text": "+{1} to spell {0} (char_class Only)"}, - "184": {"bits": [9, 5], "bias": 0, "text": "+{1} to spell {0} (char_class Only)"}, - "185": {"bits": [9, 5], "bias": 0, "text": "+{1} to spell {0} (char_class Only)"}, - "186": {"bits": [9, 5], "bias": 0, "text": "+{1} to spell {0} (char_class Only)"}, - "187": {"bits": [9, 5], "bias": 0, "text": "+{1} to spell {0} (char_class Only)"}, - "110": {"bits": [8], "bias": 20, "text": "Poison Length Reduced by {0}%"}, - "111": {"bits": [9], "bias": 20, "text": "Damage +{0}"}, - "112": {"bits": [7], "bias": 0, "text": "Hit Causes Monsters to Flee {0}%"}, - "113": {"bits": [7], "bias": 0, "text": "Hit Blinds Target +{0}"}, - "114": {"bits": [6], "bias": 0, "text": "{0}% Damage Taken Goes to Mana"}, - "115": {"bits": [1], "bias": 0, "text": "Ignore Target Defense"}, - "116": {"bits": [7], "bias": 0, "text": "{0}% Target Defense"}, - "117": {"bits": [7], "bias": 0, "text": "Prevent Monster Heal"}, - "118": {"bits": [1], "bias": 0, "text": "Half Freeze Duration"}, - "119": {"bits": [9], "bias": 20, "text": "{0}% Bonus to Attack Rating"}, - "120": {"bits": [7], "bias": 128, "text": "{0} to Monster Defense Per Hit"}, - "121": {"bits": [9], "bias": 20, "text": "+{0}% Damage to Demons"}, - "122": {"bits": [9], "bias": 20, "text": "+{0}% Damage to Undead"}, - "123": {"bits": [10], "bias": 128, "text": "+{0} to Attack Rating against Demons"}, - "124": {"bits": [10], "bias": 128, "text": "+{0} to Attack Rating against Undead"}, - "125": {"bits": [1], "bias": 0, "text": "Throwable"}, - "126": {"bits": [3, 3], "bias": 0, "text": "+{0} to Fire Skills"}, - "127": {"bits": [3], "bias": 0, "text": "+{0} to All Skill Levels"}, - "128": {"bits": [5], "bias": 0, "text": "Attacker Takes Lightning Damage of {0}"}, - "134": {"bits": [5], "bias": 0, "text": "Freezes Target +{0}"}, - "135": {"bits": [7], "bias": 0, "text": "{0}% Chance of Open Wounds"}, - "136": {"bits": [7], "bias": 0, "text": "{0}% Chance of Crushing Blow"}, - "137": {"bits": [7], "bias": 0, "text": "+{0} Kick Damage"}, - "138": {"bits": [7], "bias": 0, "text": "+{0} to Mana After Each Kill"}, - "139": {"bits": [7], "bias": 0, "text": "+{0} Life after each Demon Kill"}, - "140": {"bits": [7], "bias": 0, "text": "Extra Blood (Invisible)"}, - "141": {"bits": [7], "bias": 0, "text": "{0}% Deadly Strike"}, - "142": {"bits": [7], "bias": 0, "text": "Fire Absorb {0}%"}, - "143": {"bits": [7], "bias": 0, "text": "+{0} Fire Absorb"}, - "144": {"bits": [7], "bias": 0, "text": "Lightning Absorb {0}%"}, - "145": {"bits": [7], "bias": 0, "text": "+{0} Lightning Absorb"}, - "146": {"bits": [7], "bias": 0, "text": "Magic Absorb {0}%"}, - "147": {"bits": [7], "bias": 0, "text": "+{0} Magic Absorb"}, - "148": {"bits": [7], "bias": 0, "text": "Cold Absorb {0}%"}, - "149": {"bits": [7], "bias": 0, "text": "+{0} Cold Absorb"}, - "150": {"bits": [7], "bias": 0, "text": "Slows Target by {0}%"}, - "151": {"bits": [9, 5], "bias": 0, "text": "Level +{1} {0} When Equipped"}, - "152": {"bits": [1], "bias": 0, "text": "Indestructible"}, - "153": {"bits": [1], "bias": 0, "text": "Cannot Be Frozen"}, - "154": {"bits": [7], "bias": 20, "text": "{0}% Slower Stamina Drain"}, - "155": {"bits": [10, 7], "bias": 0, "text": "{0}% Chance to Reanimate Target"}, - "156": {"bits": [7], "bias": 0, "text": "Piercing Attack"}, - "157": {"bits": [7], "bias": 0, "text": "Fires Magic Arrows"}, - "158": {"bits": [7], "bias": 0, "text": "Fires Explosive Arrows or Bolts"}, - "159": {"bits": [6], "bias": 0, "text": "+{0} to Minimum Throw Damage"}, - "160": {"bits": [7], "bias": 0, "text": "+{0} to Maximum Throw Damage"}, - "179": {"bits": [3], "bias": 0, "text": "+{0} to Druid Skill Levels"}, - "180": {"bits": [3], "bias": 0, "text": "+{0} to Assassin Skill Levels"}, - "188": {"bits": [3, 13, 3], "bias": 0, "text": "+{2} to {0} Skills ({1} only)"}, - "189": {"bits": [10, 9], "bias": 0, "text": "+{0} to {1} Skills (char_class Only)"}, - "190": {"bits": [10, 9], "bias": 0, "text": "+{0} to {1} Skills (char_class Only)"}, - "191": {"bits": [10, 9], "bias": 0, "text": "+{0} to {1} Skills (char_class Only)"}, - "192": {"bits": [10, 9], "bias": 0, "text": "+{0} to {1} Skills (char_class Only)"}, - "193": {"bits": [10, 9], "bias": 0, "text": "+{0} to {1} Skills (char_class Only)"}, - "194": {"bits": [4], "bias": 0, "text": "Adds {0} extra sockets to the item"}, - "195": {"bits": [6, 10, 7], "bias": 0, "text": "{2}% Chance to Cast Level {0} {1} When you die"}, - "196": {"bits": [6, 10, 7], "bias": 0, "text": "{2}% Chance to Cast Level {0} {1} When you die"}, - "197": {"bits": [6, 10, 7], "bias": 0, "text": "{2}% Chance to Cast Level {0} {1} When you die"}, - "198": {"bits": [6, 10, 7], "bias": 0, "text": "{2}% Chance to Cast Level {0} {1} On Striking"}, - "199": {"bits": [6, 10, 7], "bias": 0, "text": "{2}% Chance to Cast Level {0} {1} On Striking"}, - "200": {"bits": [6, 10, 7], "bias": 0, "text": "{2}% Chance to Cast Level {0} {1} On Striking"}, - "201": {"bits": [6, 10, 7], "bias": 0, "text": "{2}% Chance to Cast Level {0} {1} When Struck"}, - "202": {"bits": [6, 10, 7], "bias": 0, "text": "{2}% Chance to Cast Level {0} {1} When Struck"}, - "203": {"bits": [6, 10, 7], "bias": 0, "text": "{2}% Chance to Cast Level {0} {1} When Struck"}, - "204": {"bits": [6, 10, 8, 8], "bias": 0, "text": "Level {0} {1} ({2}/{3} Charges)"}, - "205": {"bits": [6, 10, 8, 8], "bias": 0, "text": "Level {0} {1} ({2}/{3} Charges)"}, - "206": {"bits": [6, 10, 8, 8], "bias": 0, "text": "Level {0} {1} ({2}/{3} Charges)"}, - "207": {"bits": [6, 10, 8, 8], "bias": 0, "text": "Level {0} {1} ({2}/{3} Charges)"}, - "208": {"bits": [6, 10, 8, 8], "bias": 0, "text": "Level {0} {1} ({2}/{3} Charges)"}, - "209": {"bits": [6, 10, 8, 8], "bias": 0, "text": "Level {0} {1} ({2}/{3} Charges)"}, - "210": {"bits": [6, 10, 8, 8], "bias": 0, "text": "Level {0} {1} ({2}/{3} Charges)"}, - "211": {"bits": [6, 10, 8, 8], "bias": 0, "text": "Level {0} {1} ({2}/{3} Charges)"}, - "212": {"bits": [6, 10, 8, 8], "bias": 0, "text": "Level {0} {1} ({2}/{3} Charges)"}, - "213": {"bits": [6, 10, 8, 8], "bias": 0, "text": "Level {0} {1} ({2}/{3} Charges)"}, - "214": {"bits": [6], "bias": 0, "text": "+{0} to Defense (Based on Character Level)"}, - "215": {"bits": [6], "bias": 0, "text": "{0}% Enhanced Defense (Based on Character Level)"}, - "216": {"bits": [6], "bias": 0, "text": "+{0} to Life (Based on Character Level)"}, - "217": {"bits": [6], "bias": 0, "text": "+{0} to Mana (Based on Character Level)"}, - "218": {"bits": [6], "bias": 0, "text": "+{0} to Maximum Damage (Based on Character Level)"}, - "219": {"bits": [6], "bias": 0, "text": "{0}% Enhanced Maximum Damage (Based on Character Level)"}, - "220": {"bits": [6], "bias": 0, "text": "+{0} to Strength (Based on Character Level)"}, - "221": {"bits": [6], "bias": 0, "text": "+{0} to Dexterity (Based on Character Level)"}, - "222": {"bits": [6], "bias": 0, "text": "+{0} to Energy (Based on Character Level)"}, - "223": {"bits": [6], "bias": 0, "text": "+{0} to Vitality (Based on Character Level)"}, - "224": {"bits": [6], "bias": 0, "text": "+{0} to Attack Rating (Based on Character Level)"}, - "225": {"bits": [6], "bias": 0, "text": "{0}% Bonus to Attack Rating (Based on Character Level)"}, - "226": {"bits": [6], "bias": 0, "text": "+{0} Cold Damage (Based on Character Level)"}, - "227": {"bits": [6], "bias": 0, "text": "+{0} Fire Damage (Based on Character Level)"}, - "228": {"bits": [6], "bias": 0, "text": "+{0} Lightning Damage (Based on Character Level)"}, - "229": {"bits": [6], "bias": 0, "text": "+{0} Poison Damage (Based on Character Level)"}, - "230": {"bits": [6], "bias": 0, "text": "Cold Resist +{0}% (Based on Character Level)"}, - "231": {"bits": [6], "bias": 0, "text": "Fire Resist +{0}% (Based on Character Level)"}, - "232": {"bits": [6], "bias": 0, "text": "Lightning Resist +{0}% (Based on Character Level)"}, - "233": {"bits": [6], "bias": 0, "text": "Poison Resist +{0}% (Based on Character Level)"}, - "234": {"bits": [6], "bias": 0, "text": "+{0} Cold Absorb (Based on Character Level)"}, - "235": {"bits": [6], "bias": 0, "text": "+{0} Fire Absorb (Based on Character Level)"}, - "236": {"bits": [6], "bias": 0, "text": "+{0} Lightning Absorb (Based on Character Level)"}, - "237": {"bits": [6], "bias": 0, "text": "{0} Poison Absorb (Based on Character Level)"}, - "238": {"bits": [5], "bias": 0, "text": "Attacker Takes Damage of {0} (Based on Character Level)"}, - "239": {"bits": [6], "bias": 0, "text": "{0}% Extra Gold from Monsters (Based on Character Level)"}, - "240": {"bits": [6], "bias": 0, "text": "{0}% Better Chance of Getting Magic Items (Based on Character Level)"}, - "241": {"bits": [6], "bias": 0, "text": "Heal Stamina Plus {0}% (Based on Character Level)"}, - "242": {"bits": [6], "bias": 0, "text": "+{0} Maxmium Stamina (Based on Character Level)"}, - "243": {"bits": [6], "bias": 0, "text": "{0}% Damage to Demons (Based on Character Level)"}, - "244": {"bits": [6], "bias": 0, "text": "{0}% Damage to Undead (Based on Character Level)"}, - "245": {"bits": [6], "bias": 0, "text": "+{0} to Attack Rating against Demons (Based on Character Level)"}, - "246": {"bits": [6], "bias": 0, "text": "+{0} to Attack Rating against Undead (Based on Character Level)"}, - "247": {"bits": [6], "bias": 0, "text": "{0}% Chance of Crushing Blow (Based on Character Level)"}, - "248": {"bits": [6], "bias": 0, "text": "{0}% Chance of Open Wounds (Based on Character Level)"}, - "249": {"bits": [6], "bias": 0, "text": "+{0} Kick Damage (Based on Character Level)"}, - "250": {"bits": [6], "bias": 0, "text": "{0}% to Deadly Strike (Based on Character Level)"}, - "252": {"bits": [6], "bias": 0, "text": "Repairs 1 Durability in {0} Seconds"}, - "253": {"bits": [6], "bias": 0, "text": "Replenishes Quantity"}, - "254": {"bits": [8], "bias": 0, "text": "Increased Stack Size"}, - "305": {"bits": [8], "bias": 50, "text": "{0} Pierce Cold"}, - "306": {"bits": [8], "bias": 50, "text": "{0} Pierce Fire"}, - "307": {"bits": [8], "bias": 50, "text": "{0} Pierce Lightning"}, - "308": {"bits": [8], "bias": 50, "text": "{0} Pierce Poision"}, - "324": {"bits": [6], "bias": 0, "text": "Unknown (Invisible)"}, - "329": {"bits": [9], "bias": 50, "text": "{0}% To Fire Skill Damage"}, - "330": {"bits": [9], "bias": 50, "text": "{0}% To Lightning Skill Damage"}, - "331": {"bits": [9], "bias": 50, "text": "{0}% To Cold Skill Damage"}, - "332": {"bits": [9], "bias": 50, "text": "{0}% To Poison Skill Damage"}, - "333": {"bits": [8], "bias": 0, "text": "-{0}% To Enemy Fire Resistance"}, - "334": {"bits": [8], "bias": 0, "text": "-{0}% To Enemy Lightning Resistance"}, - "335": {"bits": [8], "bias": 0, "text": "-{0}% To Enemy Cold Resistance"}, - "336": {"bits": [8], "bias": 0, "text": "-{0}% To Enemy Poison Resistance"}, - "356": {"bits": [2], "bias": 0, "text": "Quest Item Difficulty +{0} (Invisible)"} -} diff --git a/d2warehouse/item.py b/d2warehouse/item.py index bb9b096..73ffdcd 100644 --- a/d2warehouse/item.py +++ b/d2warehouse/item.py @@ -16,13 +16,14 @@ # Mercator. If not, see . import json import os +import re from bitarray import bitarray from dataclasses import dataclass from enum import Enum _data_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data") _basetype_map = None -_affix_map = None +_stats_map = None class Quality(Enum): @@ -50,22 +51,19 @@ class LowQualityType(Enum): @dataclass -class Affix: - name_id: int - stat_id: int | None = None # TODO: These 3 should probably not be optional - stat_values: list[int] | None = None - stat_text: str | None = None +class Stat: + id: int | None = None # TODO: These 3 should probably not be optional + values: list[int] | None = None + parameter: int | None = None + text: str | None = None def print(self, indent=5): - # TODO: name lookup - if self.stat_text: - subst_text = self.stat_text - for i, val in enumerate(self.stat_values): - replace = "{" + str(i) + "}" - subst_text = subst_text.replace(replace, str(val)) - else: - subst_text = "" - print(" " * indent, f"{hex(self.name_id)}: {subst_text}") + subst_text = self.text + for val in self.values: + subst_text = subst_text.replace("#", str(val), 1) + if self.parameter: + subst_text = re.sub("\[[^\]]*\]", str(self.parameter), subst_text, 1) + print(" " * indent, subst_text) def txtbits(bits: bitarray) -> str: @@ -93,8 +91,8 @@ class Item: graphic: int | None = None inherent: int | None = None low_quality: LowQualityType | None = None - prefixes: list[Affix] | None = None - suffixes: list[Affix] | None = None + prefixes: list[int] | None = None + suffixes: list[int] | None = None set_id: int | None = None unique_id: int | None = None nameword1: int | None = None @@ -106,6 +104,7 @@ class Item: max_durability: int | None = None sockets: int | None = None quantity: int | None = None + stats: list[Stat] | None = None def print(self, indent=5, with_raw=False): properties = [] @@ -129,13 +128,9 @@ class Item: if self.quality: print(" " * indent, self.quality) if self.prefixes: - print(" " * indent, "Prefixes:") - for prefix in self.prefixes: - prefix.print(indent + 4) + print(" " * indent, "Prefixes:", self.prefixes) if self.suffixes: - print(" " * indent, "Suffixes:") - for suffix in self.suffixes: - suffix.print(indent + 4) + print(" " * indent, "Suffixes:", self.suffixes) if self.set_id: print(" " * indent, f"Set Id: {self.set_id}") # TODO: name lookup if self.unique_id: @@ -155,6 +150,10 @@ class Item: print(" " * indent, f"Num Sockets: {self.sockets}") if self.quantity: print(" " * indent, f"Quantity: {self.quantity}") + if self.stats: + print(" " * indent, "Stats:") + for stat in self.stats: + stat.print(indent + 4) if with_raw: print(" " * indent, "Raw Item Data:") bits = bitarray(endian="little") @@ -172,9 +171,9 @@ def lookup_basetype(code: str) -> dict: return _basetype_map[code] -def lookup_affix(code: int) -> dict: - global _affix_map - if _affix_map is None: - with open(os.path.join(_data_path, "affixes.json")) as f: - _affix_map = json.load(f) - return _affix_map[str(code)] +def lookup_stat(id: int) -> dict: + global _stats_map + if _stats_map is None: + with open(os.path.join(_data_path, "stats.json")) as f: + _stats_map = json.load(f) + return _stats_map[str(id)] diff --git a/d2warehouse/parser.py b/d2warehouse/parser.py index a25eef6..8298ed6 100644 --- a/d2warehouse/parser.py +++ b/d2warehouse/parser.py @@ -19,12 +19,12 @@ from bitarray import bitarray from bitarray.util import ba2int from d2warehouse.stash import Stash, StashTab from d2warehouse.item import ( - Affix, Item, LowQualityType, Quality, - lookup_affix, + Stat, lookup_basetype, + lookup_stat, ) import d2warehouse.huffman as huffman @@ -134,7 +134,6 @@ def parse_item(data: bytes) -> tuple[bytes, Item]: raise UnsupportedItemError("Ear items are not supported") simple_byte_sz = int((sockets_end + 7) / 8) - print("simple size", simple_byte_sz) item = Item( data[:simple_byte_sz], is_identified, @@ -185,10 +184,9 @@ def parse_item(data: bytes) -> tuple[bytes, Item]: enchantments_end += itembase_end extended_byte_size = int((enchantments_end + 7) / 8) - print("extended size", extended_byte_size) - item.raw_data = data[:] - return b"", item # TODO: properly return remaining data + item.raw_data = data[:extended_byte_size] + return data[extended_byte_size:], item def parse_item_graphic(bits: bitarray) -> tuple[int | None, int]: @@ -242,8 +240,8 @@ def parse_high_quality_data(bits: bitarray, item: Item) -> tuple[Item, int]: def parse_magic_data(bits: bitarray, item: Item) -> tuple[Item, int]: - item.prefixes = [Affix(name_id=ba2int(bits[0:11]))] - item.suffixes = [Affix(name_id=ba2int(bits[11:22]))] + item.prefixes = [ba2int(bits[0:11])] + item.suffixes = [ba2int(bits[11:22])] return item, 22 @@ -272,11 +270,11 @@ def parse_unique_data(bits: bitarray, item: Item) -> tuple[Item, int]: return item, 12 -def parse_affix(bits: bitarray) -> tuple[Affix | None, int]: +def parse_affix(bits: bitarray) -> tuple[int | None, int]: if not bits[0]: return None, 1 else: - return Affix(name_id=ba2int(bits[1:12])), 12 + return ba2int(bits[1:12]), 12 def parse_runeword(bits: bitarray) -> tuple[int, int]: @@ -302,10 +300,7 @@ def parse_basetype_data(bits: bitarray, item: Item) -> tuple[Item, int]: if cls == "tome": ptr += 5 # unknown field - # Unknown bit here, supposedly 96 bits of realm data?? follow if set - if bits[ptr : ptr + 1]: - print("Unknown bit set, realm data follows?") - # ptr += 96 -- doesnt seem right + # Unknown bit here ptr += 1 if cls == "armor": @@ -354,58 +349,61 @@ def parse_enchantments(bits: bitarray, item: Item) -> tuple[Item, int]: else: num_lists = 1 + # TODO: One extra list for runewords? + + if num_lists > 0: + item.stats = [] + while num_lists > 0: - affixes, ptr = parse_enchantment_list(bits[ptr:]) + stats, sz = parse_enchantment_list(bits[ptr:]) + ptr += sz num_lists -= 1 - - for i, affix in enumerate(affixes): - # TODO: this works for magic, rare & crafted. But set & unique items do not have - # names specified in the save data, but do instead need to be read from data files - # to know if the mod is prefix or suffix. For now we add them all to prefixes. - # TODO: Also note, that sets have lists of mods they get from the set. Which should go - # seperately (assuming they are in the data files when not equipped...?) - if item.quality in [Quality.UNIQUE, Quality.SET]: - item.prefixes.append(Affix(name_id=0)) - mod = item.prefixes[-1] - else: - # TODO: Here we assume that they are the same ordering as previously - mod = ( - item.prefixes[i] - if i < len(item.prefixes) - else item.suffixes[i - len(item.prefixes)] - ) - - mod.stat_id = affix.stat_id - mod.stat_values = affix.stat_values - mod.stat_text = affix.stat_text + item.stats.extend(stats) return item, ptr -def parse_enchantment_list(bits: bitarray) -> tuple[list[Affix], int]: - ptr = 0 # what is all this data? - affixes = [] +def parse_enchantment_list(bits: bitarray) -> tuple[list[Stat], int]: + ptr = 0 + stats = [] while True: id = ba2int(bits[ptr : ptr + 9]) ptr += 9 if id == 0x1FF: break - print("id", id) + print("stat id", id) - affix = lookup_affix(id) + if id == 107: + print(id, 107, "remainind data:") + print(bits) + + stat = lookup_stat(id) values = [] - for b in affix["bits"]: - print("bits", b) + param = None + if stat["save_param_bits"]: + param = ba2int(bits[ptr : ptr + stat["save_param_bits"]]) + ptr += stat["save_param_bits"] + for b in stat["save_bits"]: values.append(ba2int(bits[ptr : ptr + b])) ptr += b - values = [v - affix["bias"] for v in values] - text = affix["text"] + values = [v - stat["save_add"] for v in values] + text = stat["text"] - affixes.append(Affix(name_id=0, stat_id=id, stat_values=values, stat_text=text)) + stat = Stat( + id=id, + values=values, + parameter=param, + text=text, + ) - return affixes, ptr + print("Stat:") + stat.print() + + stats.append(stat) + + return stats, ptr def parse_items(data: bytes) -> list[Item]: