Add runeword parsing

This commit is contained in:
2023-10-24 20:26:21 +02:00
parent 29af4ccaa7
commit c170241746
5 changed files with 1185 additions and 3 deletions

View File

@@ -0,0 +1,27 @@
import csv
import json
import os
import sys
path = sys.argv[1] if len(sys.argv) >= 2 else "."
runewords = {}
id = 0
with open(os.path.join(path, "runes.txt")) as f:
dr = csv.DictReader(f, delimiter="\t")
for row in dr:
id += 1
if row["complete"] != "1":
continue
runewords[id + 26] = {
"name": row["*Rune Name"],
"itembases": [
row[f"itype{i}"] for i in range(1, 7) if len(row[f"itype{i}"]) > 0
],
"runes": [row[f"Rune{i}"] for i in range(1, 7) if len(row[f"Rune{i}"]) > 0],
}
with open("runewords.json", "w", newline="\n") as f:
json.dump(runewords, f, indent=4)
f.write("\n")

File diff suppressed because it is too large Load Diff

View File

@@ -26,6 +26,7 @@ _basetype_map = None
_stats_map = None _stats_map = None
_unique_map = None _unique_map = None
_set_item_map = None _set_item_map = None
_runeword_map = None
class Quality(Enum): class Quality(Enum):
@@ -127,6 +128,8 @@ class Item:
properties.append("Unidentified") properties.append("Unidentified")
if self.is_socketed: if self.is_socketed:
properties.append("Socketed") properties.append("Socketed")
if self.is_runeword:
properties.append("Runeword")
if properties: if properties:
print(" " * indent, ", ".join(properties)) print(" " * indent, ", ".join(properties))
print(" " * indent, f"at {self.pos_x}, {self.pos_y}") print(" " * indent, f"at {self.pos_x}, {self.pos_y}")
@@ -143,7 +146,8 @@ class Item:
itm = lookup_unique(self.unique_id) itm = lookup_unique(self.unique_id)
print(" " * indent, f"{itm['name']} ({self.unique_id})") print(" " * indent, f"{itm['name']} ({self.unique_id})")
if self.runeword_id: if self.runeword_id:
print(" " * indent, f"Runeword Id: {self.runeword_id}") # TODO: name lookup rw = lookup_runeword(self.runeword_id)
print(" " * indent, f"{rw['name']} runeword")
if self.personal_name: if self.personal_name:
print(" " * indent, f"Personal name: {self.personal_name}") print(" " * indent, f"Personal name: {self.personal_name}")
if self.defense: if self.defense:
@@ -200,3 +204,11 @@ def lookup_set_item(id: int) -> dict:
with open(os.path.join(_data_path, "sets.json")) as f: with open(os.path.join(_data_path, "sets.json")) as f:
_set_item_map = json.load(f) _set_item_map = json.load(f)
return _set_item_map[str(id)] return _set_item_map[str(id)]
def lookup_runeword(id: int) -> dict:
global _runeword_map
if _runeword_map is None:
with open(os.path.join(_data_path, "runewords.json")) as f:
_runeword_map = json.load(f)
return _runeword_map[str(id)]

View File

@@ -164,7 +164,7 @@ def parse_item(data: bytes) -> tuple[bytes, Item]:
quality_end += inherent_end quality_end += inherent_end
if item.is_runeword: if item.is_runeword:
item.runeword_id, runeword_end = parse_runeword(bits[quality_end:], item) item.runeword_id, runeword_end = parse_runeword(bits[quality_end:])
runeword_end += quality_end runeword_end += quality_end
else: else:
runeword_end = quality_end runeword_end = quality_end
@@ -279,6 +279,7 @@ def parse_affix(bits: bitarray) -> tuple[int | None, int]:
def parse_runeword(bits: bitarray) -> tuple[int, int]: def parse_runeword(bits: bitarray) -> tuple[int, int]:
id = ba2int(bits[0:12]) id = ba2int(bits[0:12])
# + 4 unknown bits
return id, 16 return id, 16
@@ -346,6 +347,9 @@ def parse_enchantments(bits: bitarray, item: Item) -> tuple[Item, int]:
} }
num_lists = 1 + table[val] num_lists = 1 + table[val]
ptr += 5 ptr += 5
elif item.is_runeword:
# Runewords have one empty dummy list of only 0x1ff followed by the runeword stats
num_lists = 2
else: else:
num_lists = 1 num_lists = 1

View File

@@ -1,6 +1,6 @@
import unittest import unittest
from d2warehouse.parser import parse_item from d2warehouse.parser import parse_item
from d2warehouse.item import Quality from d2warehouse.item import Quality, lookup_runeword
class ParseItemTest(unittest.TestCase): class ParseItemTest(unittest.TestCase):
@@ -80,3 +80,22 @@ class ParseItemTest(unittest.TestCase):
self.assertEqual(item.code, "spr") self.assertEqual(item.code, "spr")
self.assertEqual(str(item.stats[0]), "+13% Enhanced Damage") self.assertEqual(str(item.stats[0]), "+13% Enhanced Damage")
self.assertEqual(str(item.stats[2]), "+2 to Maximum Damage") self.assertEqual(str(item.stats[2]), "+2 to Maximum Damage")
def test_runeworld_lore(self):
# Lore: Ort Sol
data = bytes.fromhex(
"1008800405c055c637f1073af4558697412981070881506049e87f005516fb134582ff1000a0003500e07cbb001000a0003504e07c9800"
)
data, item = parse_item(data)
# TODO: requires socket parsing
# self.assertEqual(data, b"")
self.assertTrue(item.is_runeword)
self.assertEqual(item.sockets, 2)
rw = lookup_runeword(item.runeword_id)
self.assertEqual(rw["name"], "Lore")
for stat in item.stats:
print(str(stat))
self.assertEqual(str(item.stats[4]), "+1 to All Skills") # runeword stat
self.assertEqual(str(item.stats[2]), "+10 to Energy") # runeword stat
# TODO: requires socket parsing
# self.assertEqual(str(item.stats[1]), "Lightning Resist 30%") # sol rune stat