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]: