15 Commits

18 changed files with 152 additions and 1734 deletions

View File

@@ -1,23 +1,8 @@
# Installation Quick & dirty commit of current progress to share with others.
Don't.
# Development installation
Create a virtual environment and install the development set:
``` ```
python -m venv venv python -m venv venv
source venv/bin/activate source venv/bin/activate
pip install --editable .[dev] pip install --editable .[dev]
```
Set your save path and run the webserver to interact with the storage system:
```
export D2SAVE_PATH="/path/to/saves"
flask --app d2warehouse.app run
```
Some debug tooling:
```
d2dump /path/to/stash.d2i d2dump /path/to/stash.d2i
``` ```

View File

@@ -1,28 +0,0 @@
import json
import csv
import os
import sys
path = sys.argv[1] if len(sys.argv) >= 2 else "."
items = {}
item_patches = {
"tbk": {"class": "tome"},
"ibk": {"class": "tome"},
}
with open(os.path.join(path, "skills.json"), encoding="utf-8-sig") as f:
rows = json.load(f)
lookup_table = {}
for entry in rows:
key = entry["Key"]
text = entry["enUS"]
if len(text.strip()) == 0:
continue
lookup_table[key] = text
with open("skills.json", "w", newline="\n") as f:
json.dump(lookup_table, f, indent=4)
f.write("\n")

View File

@@ -13,9 +13,7 @@ path = sys.argv[1] if len(sys.argv) >= 2 else "."
# into one. Same applies for all stats. # into one. Same applies for all stats.
special_stats = { special_stats = {
"firemindam": {"template": "dmg-fire"}, "firemindam": {"template": "dmg-fire"},
"firemaxdam": { "firemaxdam": None,
"template": "fire-max"
}, # Cathan's rule, no other max ele dmg source exists
"lightmindam": {"template": "dmg-ltng"}, "lightmindam": {"template": "dmg-ltng"},
"lightmaxdam": None, "lightmaxdam": None,
"magicmindam": {"template": "dmg-mag"}, "magicmindam": {"template": "dmg-mag"},

View File

@@ -1,36 +1,21 @@
import csv import csv
import json import json
import argparse import os
from pathlib import Path import sys
parser = argparse.ArgumentParser( path = sys.argv[1] if len(sys.argv) >= 2 else "."
description="Process unique and set items from game data"
)
parser.add_argument(
"DATA_DIR", help="Path to d2 data dir containing local/ and global/"
)
parser.add_argument("OUTPUT_DIR", help="Path to destination directory")
args = parser.parse_args()
excelpath = Path(args.DATA_DIR) / "global/excel"
outputpath = Path(args.OUTPUT_DIR)
namespath = Path(args.DATA_DIR) / "local/lng/strings/item-names.json"
with namespath.open(encoding="utf-8-sig") as f:
names = json.load(f)
names = {name["Key"]: name["enUS"] for name in names}
category = "Base" category = "Base"
setitems = {} setitems = {}
with (excelpath / "setitems.txt").open() as f: with open(os.path.join(path, "setitems.txt")) as f:
dr = csv.DictReader(f, delimiter="\t") dr = csv.DictReader(f, delimiter="\t")
for row in dr: for row in dr:
if row["index"] == "Expansion": if row["index"] == "Expansion":
category = row["index"] category = row["index"]
continue continue
setitems[row["*ID"]] = { setitems[row["*ID"]] = {
"name": names[row["index"]], "name": row["index"],
"set": names[row["set"]], "set": row["set"],
"itembase": row["item"], "itembase": row["item"],
"req_lvl": int(row["lvl req"]), "req_lvl": int(row["lvl req"]),
"ilvl": int(row["lvl"]), "ilvl": int(row["lvl"]),
@@ -40,7 +25,7 @@ with (excelpath / "setitems.txt").open() as f:
category = "Base" category = "Base"
uniqueitems = {} uniqueitems = {}
with (excelpath / "uniqueitems.txt").open() as f: with open(os.path.join(path, "uniqueitems.txt")) as f:
dr = csv.DictReader(f, delimiter="\t") dr = csv.DictReader(f, delimiter="\t")
for row in dr: for row in dr:
if row["index"] in [ if row["index"] in [
@@ -57,7 +42,7 @@ with (excelpath / "uniqueitems.txt").open() as f:
if len(row["lvl req"]) == 0: if len(row["lvl req"]) == 0:
continue # deleted uniques continue # deleted uniques
uniqueitems[row["*ID"]] = { uniqueitems[row["*ID"]] = {
"name": names[row["index"]], "name": row["index"],
"itembase": row["code"], "itembase": row["code"],
"req_lvl": int(row["lvl req"]), "req_lvl": int(row["lvl req"]),
"ilvl": int(row["lvl"]), "ilvl": int(row["lvl"]),
@@ -65,10 +50,10 @@ with (excelpath / "uniqueitems.txt").open() as f:
"category": category, "category": category,
} }
with (outputpath / "uniques.json").open("w", newline="\n") as f: with open("uniques.json", "w", newline="\n") as f:
json.dump(uniqueitems, f, indent=4) json.dump(uniqueitems, f, indent=4)
f.write("\n") f.write("\n")
with (outputpath / "sets.json").open("w", newline="\n") as f: with open("sets.json", "w", newline="\n") as f:
json.dump(setitems, f, indent=4) json.dump(setitems, f, indent=4)
f.write("\n") f.write("\n")

View File

@@ -5,7 +5,7 @@ import shutil
from datetime import datetime from datetime import datetime
import psutil import psutil
from d2warehouse.item import Item, Quality, lookup_basetype from d2warehouse.item import Item, Quality
from d2warehouse.parser import parse_stash from d2warehouse.parser import parse_stash
import d2warehouse.db as base_db import d2warehouse.db as base_db
from d2warehouse.app.db import get_db, close_db from d2warehouse.app.db import get_db, close_db
@@ -22,59 +22,6 @@ DB_FILES = {
"softcore": "d2warehouse.softcore.sqlite3", "softcore": "d2warehouse.softcore.sqlite3",
"hardcore": "d2warehouse.hardcore.sqlite3", "hardcore": "d2warehouse.hardcore.sqlite3",
} }
CURRENCY_RUNES = [f"r{i + 1:02d}" for i in range(33)]
CURRENCY_GEMS = [
"gcv",
"gfv",
"gsv",
"gzv",
"gpv",
"gcb",
"gfb",
"gsb",
"glb",
"gpb",
"gcg",
"gfg",
"gsg",
"glg",
"gpg",
"gcr",
"gfr",
"gsr",
"glr",
"gpr",
"gcw",
"gfw",
"gsw",
"glw",
"gpw",
"gcy",
"gfy",
"gsy",
"gly",
"gpy",
"skc",
"skf",
"sku",
"skl",
"skz",
]
CURRENCY_KEYS = [
"pk1",
"pk2",
"pk3",
"bey",
"mbr",
"dhn",
]
CURRENCY_ESSENCES = [
"tes",
"ceh",
"bet",
"fed",
"toa",
]
def d2_running() -> bool: def d2_running() -> bool:
@@ -82,7 +29,7 @@ def d2_running() -> bool:
try: try:
if proc.cmdline()[0].endswith("D2R.exe"): if proc.cmdline()[0].endswith("D2R.exe"):
return True return True
except (IndexError, psutil.AccessDenied, psutil.ZombieProcess): except (IndexError, psutil.AccessDenied):
pass pass
return False return False
@@ -92,22 +39,21 @@ def storage_count(item: Item, stash: str) -> int | str:
db = get_stash_db(stash) db = get_stash_db(stash)
if item.is_simple: if item.is_simple:
return db.execute( return db.execute(
"SELECT COUNT(id) FROM item WHERE code = ? AND deleted IS NULL", "SELECT COUNT(id) FROM item WHERE code = ?", (item.code,)
(item.code,),
).fetchone()[0] ).fetchone()[0]
elif item.quality == Quality.UNIQUE: elif item.quality == Quality.UNIQUE:
return db.execute( return db.execute(
"SELECT COUNT(id) FROM item INNER JOIN item_extra ON id = item_id WHERE code = ? AND unique_id = ? AND deleted IS NULL", "SELECT COUNT(id) FROM item INNER JOIN item_extra ON id = item_id WHERE code = ? AND unique_id = ?",
(item.code, item.unique_id), (item.code, item.unique_id),
).fetchone()[0] ).fetchone()[0]
elif item.quality == Quality.SET: elif item.quality == Quality.SET:
return db.execute( return db.execute(
"SELECT COUNT(id) FROM item INNER JOIN item_extra ON id = item_id WHERE code = ? AND set_id = ? AND deleted IS NULL", "SELECT COUNT(id) FROM item INNER JOIN item_extra ON id = item_id WHERE code = ? AND set_id = ?",
(item.code, item.set_id), (item.code, item.set_id),
).fetchone()[0] ).fetchone()[0]
elif item.is_runeword: elif item.is_runeword:
return db.execute( return db.execute(
"SELECT COUNT(id) FROM item INNER JOIN item_extra ON id = item_id WHERE code = ? AND runeword_id = ? AND deleted IS NULL", "SELECT COUNT(id) FROM item INNER JOIN item_extra ON id = item_id WHERE code = ? AND runeword_id = ?",
(item.code, item.runeword_id), (item.code, item.runeword_id),
).fetchone()[0] ).fetchone()[0]
else: else:
@@ -222,10 +168,7 @@ def list_storage(stash_name: str):
items[row["id"]] = Item.load_from_db(row["id"], db=db) items[row["id"]] = Item.load_from_db(row["id"], db=db)
return render_template( return render_template(
"list_storage.html", "list_storage.html", stash_name=stash_name, storage_items=items
stash_name=stash_name,
storage_items=items,
storage_count=lambda x: storage_count(x, stash_name),
) )
@@ -257,10 +200,7 @@ def list_storage_category(stash_name: str, category: str):
items[row["id"]] = Item.load_from_db(row["id"], db=db) items[row["id"]] = Item.load_from_db(row["id"], db=db)
return render_template( return render_template(
"list_storage.html", "list_storage.html", stash_name=stash_name, storage_items=items
stash_name=stash_name,
storage_items=items,
storage_count=lambda x: storage_count(x, stash_name),
) )
@@ -317,29 +257,3 @@ def storage_take_items(stash_name: str):
tmp_path.replace(stash_path) tmp_path.replace(stash_path)
return redirect(f"/storage/{stash_name}", code=303) return redirect(f"/storage/{stash_name}", code=303)
def storage_currency_counts(item_codes: list[str], stash_name: str) -> dict:
db = get_stash_db(stash_name)
currencies = {}
for code in item_codes:
currencies[code] = {
"count": db.execute(
"SELECT COUNT(id) FROM item WHERE code = ? AND deleted IS NULL", (code,)
).fetchone()[0],
"name": lookup_basetype(code)["name"],
}
return currencies
@app.route("/storage/<stash_name>/currency")
def storage_currency(stash_name: str):
if stash_name not in DB_FILES:
abort(404)
runes = storage_currency_counts(CURRENCY_RUNES, stash_name)
gems = storage_currency_counts(CURRENCY_GEMS, stash_name)
keys = storage_currency_counts(CURRENCY_KEYS, stash_name)
essences = storage_currency_counts(CURRENCY_ESSENCES, stash_name)
return render_template(
"currency.html", runes=runes, gems=gems, keys=keys, essences=essences
)

View File

@@ -1,13 +0,0 @@
function toggleSelectAll(tabIndex) {
const tab = document.querySelector(`[data-tab="${tabIndex}"]`);
const checkboxes = tab.querySelectorAll('input[type="checkbox"]');
if (checkboxes.length === 0)
return;
const allSelected = Array.from(checkboxes).every(cb => cb.checked);
checkboxes.forEach(cb => {
cb.checked = !allSelected;
});
}

View File

@@ -12,19 +12,6 @@ body {
margin: 0 auto; margin: 0 auto;
} }
.currencies {
display: flex;
gap: 50px
}
.currencies th {
text-align: left;
}
.currencies td {
text-align: right;
}
@media (max-width: 1600px) { @media (max-width: 1600px) {
.stash-tab { .stash-tab {
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(4, 1fr);
@@ -47,27 +34,6 @@ body {
display: none; display: none;
} }
.storage-item-entry {
position: relative;
width: fit-content;
}
.item-hover {
display: none;
position: absolute;
left: 0;
top: 30px;
z-index: 1000;
background: #222;
border: 1px solid #555;
padding: 8px;
min-width: 300px;
}
.storage-item-entry:hover .item-hover {
display: block;
}
.item .name { .item .name {
font-weight: bold; font-weight: bold;
} }

View File

@@ -1,53 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Shared Stash</title>
<link rel="stylesheet" href="/static/style.css" />
<head>
<body>
<div class="currencies">
<div>
<table>
{% for code,currency in runes.items() %}
<tr>
<th>{{currency.name}}</th>
<td>{{currency.count}}</td>
</tr>
{% endfor %}
</table>
</div>
<div>
<table>
{% for code,currency in gems.items() %}
<tr>
<th>{{currency.name}}</th>
<td>{{currency.count}}</td>
</tr>
{% endfor %}
</table>
</div>
<div>
<table>
{% for code,currency in keys.items() %}
<tr>
<th>{{currency.name}}</th>
<td>{{currency.count}}</td>
</tr>
{% endfor %}
</table>
</div>
<div>
<table>
{% for code,currency in essences.items() %}
<tr>
<th>{{currency.name}}</th>
<td>{{currency.count}}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
</body>
</html>

View File

@@ -1,13 +1,16 @@
<ul> <input type="checkbox" id="item_{{tabloop.index0}}_{{itemloop.index0}}" name="item_{{tabloop.index0}}_{{itemloop.index0}}" value="remove" />
<li class="name color-{{item.color}}">{{item.name}}</li> <label class="item" for="item_{{tabloop.index0}}_{{itemloop.index0}}">
{% if item.quality and item.quality >= 5 %} <ul>
<li class="name color-{{item.color}}">{{item.basename}}</li> <li class="name color-{{item.color}}">{{item.name}}</li>
{% endif %} {% if item.quality and item.quality >= 5 %}
<li>(in storage: {{storage_count(item)}})</li> <li class="name color-{{item.color}}">{{item.basename}}</li>
{% if item.stats %} {% endif %}
{% for stat in item.stats %} <li>(in storage: {{storage_count(item)}})</li>
<li>{{stat}}</li> {% if item.stats %}
{% endfor %} {% for stat in item.stats %}
{% endif %} <li>{{stat}}</li>
<li><input class="raw-item" type="text" name="raw item" value="{{item.raw().hex()}}" onfocus="this.select()" readonly></li> {% endfor %}
</ul> {% endif %}
<li><input class="raw-item" type="text" name="raw item" value="{{item.raw().hex()}}" onfocus="this.select()" readonly></li>
</ul>
</label>

View File

@@ -4,20 +4,16 @@
<meta charset="utf-8"> <meta charset="utf-8">
<title>Shared Stash</title> <title>Shared Stash</title>
<link rel="stylesheet" href="/static/style.css" /> <link rel="stylesheet" href="/static/style.css" />
<script src="/static/helpers.js"></script>
<head> <head>
<body> <body>
<form action="/stash/{{stash_name}}/store" method="POST"> <form action="/stash/{{stash_name}}/store" method="POST">
{% for tab in stash.tabs %} {% for tab in stash.tabs %}
{% set tabloop = loop %} {% set tabloop = loop %}
<h2>Tab {{tabloop.index}} <button type="button" onclick="toggleSelectAll({{tabloop.index}})">Select All</button> </h2> <h2>Tab {{tabloop.index}}</h2>
<div class="stash-tab" data-tab="{{tabloop.index}}"> <div class="stash-tab">
{% for item in tab.items %} {% for item in tab.items %}
{% set itemloop = loop %} {% set itemloop = loop %}
<input type="checkbox" id="item_{{tabloop.index0}}_{{itemloop.index0}}" name="item_{{tabloop.index0}}_{{itemloop.index0}}" value="remove" />
<label class="item" for="item_{{tabloop.index0}}_{{itemloop.index0}}">
{% include "item.html" %} {% include "item.html" %}
</label>
{% endfor %} {% endfor %}
</div> </div>
{% endfor %} {% endfor %}

View File

@@ -10,12 +10,10 @@
<div> <div>
<!-- TODO: Include item.html --> <!-- TODO: Include item.html -->
{% for db_id, item in storage_items.items() %} {% for db_id, item in storage_items.items() %}
<div class="storage-item-entry"> <div>
<input type="checkbox" name="item_{{db_id}}" id="item_{{db_id}}" value="take" /> <input type="checkbox" name="item_{{db_id}}" value="take" />
<label for="item_{{db_id}}">{{item.name}} ({{db_id}})</label> {{item.name}}
<div class="item-hover"> ({{db_id}})
{% include "item.html" %}
</div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>

View File

@@ -397,7 +397,7 @@
}, },
"44": { "44": {
"name": "Berserker's Headgear", "name": "Berserker's Headgear",
"set": "Berserker's Arsenal", "set": "Berserker's Garb",
"itembase": "hlm", "itembase": "hlm",
"req_lvl": 3, "req_lvl": 3,
"ilvl": 5, "ilvl": 5,
@@ -406,7 +406,7 @@
}, },
"45": { "45": {
"name": "Berserker's Hauberk", "name": "Berserker's Hauberk",
"set": "Berserker's Arsenal", "set": "Berserker's Garb",
"itembase": "spl", "itembase": "spl",
"req_lvl": 3, "req_lvl": 3,
"ilvl": 5, "ilvl": 5,
@@ -415,7 +415,7 @@
}, },
"46": { "46": {
"name": "Berserker's Hatchet", "name": "Berserker's Hatchet",
"set": "Berserker's Arsenal", "set": "Berserker's Garb",
"itembase": "2ax", "itembase": "2ax",
"req_lvl": 3, "req_lvl": 3,
"ilvl": 5, "ilvl": 5,
@@ -451,7 +451,7 @@
}, },
"50": { "50": {
"name": "Angelic Sickle", "name": "Angelic Sickle",
"set": "Angelic Raiment", "set": "Angelical Raiment",
"itembase": "sbr", "itembase": "sbr",
"req_lvl": 12, "req_lvl": 12,
"ilvl": 17, "ilvl": 17,
@@ -460,7 +460,7 @@
}, },
"51": { "51": {
"name": "Angelic Mantle", "name": "Angelic Mantle",
"set": "Angelic Raiment", "set": "Angelical Raiment",
"itembase": "rng", "itembase": "rng",
"req_lvl": 12, "req_lvl": 12,
"ilvl": 17, "ilvl": 17,
@@ -469,7 +469,7 @@
}, },
"52": { "52": {
"name": "Angelic Halo", "name": "Angelic Halo",
"set": "Angelic Raiment", "set": "Angelical Raiment",
"itembase": "rin", "itembase": "rin",
"req_lvl": 12, "req_lvl": 12,
"ilvl": 17, "ilvl": 17,
@@ -478,7 +478,7 @@
}, },
"53": { "53": {
"name": "Angelic Wings", "name": "Angelic Wings",
"set": "Angelic Raiment", "set": "Angelical Raiment",
"itembase": "amu", "itembase": "amu",
"req_lvl": 12, "req_lvl": 12,
"ilvl": 17, "ilvl": 17,
@@ -612,7 +612,7 @@
"category": "Expansion" "category": "Expansion"
}, },
"68": { "68": {
"name": "Aldur's Rhythm", "name": "Aldur's Gauntlet",
"set": "Aldur's Watchtower", "set": "Aldur's Watchtower",
"itembase": "9mt", "itembase": "9mt",
"req_lvl": 42, "req_lvl": 42,
@@ -684,7 +684,7 @@
"category": "Expansion" "category": "Expansion"
}, },
"76": { "76": {
"name": "Tal Rasha's Fine-Spun Cloth", "name": "Tal Rasha's Fire-Spun Cloth",
"set": "Tal Rasha's Wrappings", "set": "Tal Rasha's Wrappings",
"itembase": "zmb", "itembase": "zmb",
"req_lvl": 53, "req_lvl": 53,
@@ -711,7 +711,7 @@
"category": "Expansion" "category": "Expansion"
}, },
"79": { "79": {
"name": "Tal Rasha's Guardianship", "name": "Tal Rasha's Howling Wind",
"set": "Tal Rasha's Wrappings", "set": "Tal Rasha's Wrappings",
"itembase": "uth", "itembase": "uth",
"req_lvl": 71, "req_lvl": 71,
@@ -747,7 +747,7 @@
"category": "Expansion" "category": "Expansion"
}, },
"83": { "83": {
"name": "Griswold's Redemption", "name": "Griswolds's Redemption",
"set": "Griswold's Legacy", "set": "Griswold's Legacy",
"itembase": "7ws", "itembase": "7ws",
"req_lvl": 53, "req_lvl": 53,
@@ -882,7 +882,7 @@
"category": "Expansion" "category": "Expansion"
}, },
"98": { "98": {
"name": "Dark Adherent", "name": "Spiritual Custodian",
"set": "The Disciple", "set": "The Disciple",
"itembase": "uui", "itembase": "uui",
"req_lvl": 43, "req_lvl": 43,
@@ -909,7 +909,7 @@
"category": "Expansion" "category": "Expansion"
}, },
"101": { "101": {
"name": "Taebaek's Glory", "name": "Heaven's Taebaek",
"set": "Heaven's Brethren", "set": "Heaven's Brethren",
"itembase": "uts", "itembase": "uts",
"req_lvl": 81, "req_lvl": 81,
@@ -918,7 +918,7 @@
"category": "Expansion" "category": "Expansion"
}, },
"102": { "102": {
"name": "Haemosu's Adamant", "name": "Haemosu's Adament",
"set": "Heaven's Brethren", "set": "Heaven's Brethren",
"itembase": "xrs", "itembase": "xrs",
"req_lvl": 44, "req_lvl": 44,
@@ -963,7 +963,7 @@
"category": "Expansion" "category": "Expansion"
}, },
"107": { "107": {
"name": "Whitstan's Guard", "name": "Wihtstan's Guard",
"set": "Orphan's Call", "set": "Orphan's Call",
"itembase": "xml", "itembase": "xml",
"req_lvl": 29, "req_lvl": 29,
@@ -990,7 +990,7 @@
"category": "Expansion" "category": "Expansion"
}, },
"110": { "110": {
"name": "Hwanin's Blessing", "name": "Hwanin's Seal",
"set": "Hwanin's Majesty", "set": "Hwanin's Majesty",
"itembase": "mbl", "itembase": "mbl",
"req_lvl": 35, "req_lvl": 35,
@@ -1071,7 +1071,7 @@
"category": "Expansion" "category": "Expansion"
}, },
"119": { "119": {
"name": "Cow King's Hooves", "name": "Cow King's Hoofs",
"set": "Cow King's Leathers", "set": "Cow King's Leathers",
"itembase": "vbt", "itembase": "vbt",
"req_lvl": 13, "req_lvl": 13,
@@ -1081,7 +1081,7 @@
}, },
"120": { "120": {
"name": "Naj's Puzzler", "name": "Naj's Puzzler",
"set": "Naj's Ancient Vestige", "set": "Naj's Ancient Set",
"itembase": "6cs", "itembase": "6cs",
"req_lvl": 78, "req_lvl": 78,
"ilvl": 43, "ilvl": 43,
@@ -1090,7 +1090,7 @@
}, },
"121": { "121": {
"name": "Naj's Light Plate", "name": "Naj's Light Plate",
"set": "Naj's Ancient Vestige", "set": "Naj's Ancient Set",
"itembase": "ult", "itembase": "ult",
"req_lvl": 71, "req_lvl": 71,
"ilvl": 43, "ilvl": 43,
@@ -1099,7 +1099,7 @@
}, },
"122": { "122": {
"name": "Naj's Circlet", "name": "Naj's Circlet",
"set": "Naj's Ancient Vestige", "set": "Naj's Ancient Set",
"itembase": "ci0", "itembase": "ci0",
"req_lvl": 28, "req_lvl": 28,
"ilvl": 43, "ilvl": 43,
@@ -1107,8 +1107,8 @@
"category": "Expansion" "category": "Expansion"
}, },
"123": { "123": {
"name": "Sander's Paragon", "name": "McAuley's Paragon",
"set": "Sander's Folly", "set": "McAuley's Folly",
"itembase": "cap", "itembase": "cap",
"req_lvl": 25, "req_lvl": 25,
"ilvl": 20, "ilvl": 20,
@@ -1116,8 +1116,8 @@
"category": "Expansion" "category": "Expansion"
}, },
"124": { "124": {
"name": "Sander's Riprap", "name": "McAuley's Riprap",
"set": "Sander's Folly", "set": "McAuley's Folly",
"itembase": "vbt", "itembase": "vbt",
"req_lvl": 20, "req_lvl": 20,
"ilvl": 20, "ilvl": 20,
@@ -1125,8 +1125,8 @@
"category": "Expansion" "category": "Expansion"
}, },
"125": { "125": {
"name": "Sander's Taboo", "name": "McAuley's Taboo",
"set": "Sander's Folly", "set": "McAuley's Folly",
"itembase": "vgl", "itembase": "vgl",
"req_lvl": 28, "req_lvl": 28,
"ilvl": 20, "ilvl": 20,
@@ -1134,8 +1134,8 @@
"category": "Expansion" "category": "Expansion"
}, },
"126": { "126": {
"name": "Sander's Superstition", "name": "McAuley's Superstition",
"set": "Sander's Folly", "set": "McAuley's Folly",
"itembase": "bwn", "itembase": "bwn",
"req_lvl": 25, "req_lvl": 25,
"ilvl": 20, "ilvl": 20,

File diff suppressed because it is too large Load Diff

View File

@@ -273,14 +273,6 @@
"save_add": 0, "save_add": 0,
"save_param_bits": null "save_param_bits": null
}, },
"49": {
"text": "+# to Maximum Fire Damage",
"save_bits": [
9
],
"save_add": 0,
"save_param_bits": null
},
"50": { "50": {
"text": "Adds #-# Lightning Damage", "text": "Adds #-# Lightning Damage",
"save_bits": [ "save_bits": [

View File

@@ -24,7 +24,7 @@
"category": "Base" "category": "Base"
}, },
"3": { "3": {
"name": "Skull Splitter", "name": "Mindrend",
"itembase": "mpi", "itembase": "mpi",
"req_lvl": 21, "req_lvl": 21,
"ilvl": 28, "ilvl": 28,
@@ -40,7 +40,7 @@
"category": "Base" "category": "Base"
}, },
"5": { "5": {
"name": "Axe of Fechmar", "name": "Fechmars Axe",
"itembase": "lax", "itembase": "lax",
"req_lvl": 8, "req_lvl": 8,
"ilvl": 11, "ilvl": 11,
@@ -56,7 +56,7 @@
"category": "Base" "category": "Base"
}, },
"7": { "7": {
"name": "The Chieftain", "name": "The Chieftan",
"itembase": "btx", "itembase": "btx",
"req_lvl": 19, "req_lvl": 19,
"ilvl": 26, "ilvl": 26,
@@ -72,7 +72,7 @@
"category": "Base" "category": "Base"
}, },
"9": { "9": {
"name": "Humongous", "name": "The Humongous",
"itembase": "gix", "itembase": "gix",
"req_lvl": 29, "req_lvl": 29,
"ilvl": 39, "ilvl": 39,
@@ -80,7 +80,7 @@
"category": "Base" "category": "Base"
}, },
"10": { "10": {
"name": "Torch of Iro", "name": "Iros Torch",
"itembase": "wnd", "itembase": "wnd",
"req_lvl": 5, "req_lvl": 5,
"ilvl": 7, "ilvl": 7,
@@ -88,7 +88,7 @@
"category": "Base" "category": "Base"
}, },
"11": { "11": {
"name": "Maelstrom", "name": "Maelstromwrath",
"itembase": "ywn", "itembase": "ywn",
"req_lvl": 14, "req_lvl": 14,
"ilvl": 19, "ilvl": 19,
@@ -104,7 +104,7 @@
"category": "Base" "category": "Base"
}, },
"13": { "13": {
"name": "Ume's Lament", "name": "Umes Lament",
"itembase": "gwn", "itembase": "gwn",
"req_lvl": 28, "req_lvl": 28,
"ilvl": 38, "ilvl": 38,
@@ -168,7 +168,7 @@
"category": "Base" "category": "Base"
}, },
"21": { "21": {
"name": "The General's Tan Do Li Ga", "name": "The Generals Tan Do Li Ga",
"itembase": "fla", "itembase": "fla",
"req_lvl": 21, "req_lvl": 21,
"ilvl": 28, "ilvl": 28,
@@ -184,7 +184,7 @@
"category": "Base" "category": "Base"
}, },
"23": { "23": {
"name": "Bonesnap", "name": "Bonesob",
"itembase": "mau", "itembase": "mau",
"req_lvl": 24, "req_lvl": 24,
"ilvl": 32, "ilvl": 32,
@@ -200,7 +200,7 @@
"category": "Base" "category": "Base"
}, },
"25": { "25": {
"name": "Rixot's Keen", "name": "Rixots Keen",
"itembase": "ssd", "itembase": "ssd",
"req_lvl": 2, "req_lvl": 2,
"ilvl": 3, "ilvl": 3,
@@ -216,7 +216,7 @@
"category": "Base" "category": "Base"
}, },
"27": { "27": {
"name": "Skewer of Krintiz", "name": "Krintizs Skewer",
"itembase": "sbr", "itembase": "sbr",
"req_lvl": 10, "req_lvl": 10,
"ilvl": 14, "ilvl": 14,
@@ -240,7 +240,7 @@
"category": "Base" "category": "Base"
}, },
"30": { "30": {
"name": "Griswold's Edge", "name": "Griswolds Edge",
"itembase": "bsd", "itembase": "bsd",
"req_lvl": 17, "req_lvl": 17,
"ilvl": 23, "ilvl": 23,
@@ -256,7 +256,7 @@
"category": "Base" "category": "Base"
}, },
"32": { "32": {
"name": "Culwen's Point", "name": "Culwens Point",
"itembase": "wsd", "itembase": "wsd",
"req_lvl": 29, "req_lvl": 29,
"ilvl": 39, "ilvl": 39,
@@ -280,7 +280,7 @@
"category": "Base" "category": "Base"
}, },
"35": { "35": {
"name": "Kinemil's Awl", "name": "Kinemils Awl",
"itembase": "gis", "itembase": "gis",
"req_lvl": 23, "req_lvl": 23,
"ilvl": 31, "ilvl": 31,
@@ -336,7 +336,7 @@
"category": "Base" "category": "Base"
}, },
"42": { "42": {
"name": "Spectral Shard", "name": "Irices Shard",
"itembase": "bld", "itembase": "bld",
"req_lvl": 25, "req_lvl": 25,
"ilvl": 34, "ilvl": 34,
@@ -384,7 +384,7 @@
"category": "Base" "category": "Base"
}, },
"48": { "48": {
"name": "Dimoak's Hew", "name": "Dimoaks Hew",
"itembase": "bar", "itembase": "bar",
"req_lvl": 8, "req_lvl": 8,
"ilvl": 11, "ilvl": 11,
@@ -448,7 +448,7 @@
"category": "Base" "category": "Base"
}, },
"56": { "56": {
"name": "Spire of Lazarus", "name": "Lazarus Spire",
"itembase": "cst", "itembase": "cst",
"req_lvl": 18, "req_lvl": 18,
"ilvl": 24, "ilvl": 24,
@@ -488,7 +488,7 @@
"category": "Base" "category": "Base"
}, },
"61": { "61": {
"name": "Raven Claw", "name": "Rimeraven",
"itembase": "lbw", "itembase": "lbw",
"req_lvl": 15, "req_lvl": 15,
"ilvl": 20, "ilvl": 20,
@@ -496,7 +496,7 @@
"category": "Base" "category": "Base"
}, },
"62": { "62": {
"name": "Rogue's Bow", "name": "Piercerib",
"itembase": "cbw", "itembase": "cbw",
"req_lvl": 20, "req_lvl": 20,
"ilvl": 27, "ilvl": 27,
@@ -504,7 +504,7 @@
"category": "Base" "category": "Base"
}, },
"63": { "63": {
"name": "Stormstrike", "name": "Pullspite",
"itembase": "sbb", "itembase": "sbb",
"req_lvl": 25, "req_lvl": 25,
"ilvl": 34, "ilvl": 34,
@@ -560,7 +560,7 @@
"category": "Base" "category": "Base"
}, },
"70": { "70": {
"name": "Doomslinger", "name": "Doomspittle",
"itembase": "rxb", "itembase": "rxb",
"req_lvl": 28, "req_lvl": 28,
"ilvl": 38, "ilvl": 38,
@@ -568,7 +568,7 @@
"category": "Base" "category": "Base"
}, },
"71": { "71": {
"name": "Biggin's Bonnet", "name": "War Bonnet",
"itembase": "cap", "itembase": "cap",
"req_lvl": 3, "req_lvl": 3,
"ilvl": 4, "ilvl": 4,
@@ -640,7 +640,7 @@
"category": "Base" "category": "Base"
}, },
"80": { "80": {
"name": "Blinkbat's Form", "name": "Blinkbats Form",
"itembase": "lea", "itembase": "lea",
"req_lvl": 12, "req_lvl": 12,
"ilvl": 16, "ilvl": 16,
@@ -688,7 +688,7 @@
"category": "Base" "category": "Base"
}, },
"86": { "86": {
"name": "Venom Ward", "name": "Venomsward",
"itembase": "brs", "itembase": "brs",
"req_lvl": 20, "req_lvl": 20,
"ilvl": 27, "ilvl": 27,
@@ -736,7 +736,7 @@
"category": "Base" "category": "Base"
}, },
"92": { "92": {
"name": "Silks of the Victor", "name": "Victors Silk",
"itembase": "aar", "itembase": "aar",
"req_lvl": 28, "req_lvl": 28,
"ilvl": 38, "ilvl": 38,
@@ -896,7 +896,7 @@
"category": "Base" "category": "Base"
}, },
"112": { "112": {
"name": "Lenymo", "name": "Lenyms Cord",
"itembase": "lbl", "itembase": "lbl",
"req_lvl": 7, "req_lvl": 7,
"ilvl": 10, "ilvl": 10,
@@ -1016,7 +1016,7 @@
"category": "Base" "category": "Base"
}, },
"127": { "127": {
"name": "Khalim's Flail", "name": "KhalimFlail",
"itembase": "qf1", "itembase": "qf1",
"req_lvl": 0, "req_lvl": 0,
"ilvl": 0, "ilvl": 0,
@@ -1024,7 +1024,7 @@
"category": "Base" "category": "Base"
}, },
"128": { "128": {
"name": "Khalim's Will", "name": "SuperKhalimFlail",
"itembase": "qf2", "itembase": "qf2",
"req_lvl": 0, "req_lvl": 0,
"ilvl": 0, "ilvl": 0,
@@ -1056,7 +1056,7 @@
"category": "Expansion" "category": "Expansion"
}, },
"132": { "132": {
"name": "Pompeii's Wrath", "name": "Pompe's Wrath",
"itembase": "9mp", "itembase": "9mp",
"req_lvl": 45, "req_lvl": 45,
"ilvl": 53, "ilvl": 53,
@@ -1104,7 +1104,7 @@
"category": "Expansion" "category": "Expansion"
}, },
"138": { "138": {
"name": "The Minotaur", "name": "The Minataur",
"itembase": "9gi", "itembase": "9gi",
"req_lvl": 45, "req_lvl": 45,
"ilvl": 53, "ilvl": 53,
@@ -1288,7 +1288,7 @@
"category": "Expansion" "category": "Expansion"
}, },
"161": { "161": {
"name": "The Atlantean", "name": "The Atlantian",
"itembase": "9wd", "itembase": "9wd",
"req_lvl": 42, "req_lvl": 42,
"ilvl": 50, "ilvl": 50,
@@ -1496,7 +1496,7 @@
"category": "Expansion" "category": "Expansion"
}, },
"187": { "187": {
"name": "Skull Collector", "name": "Skullcollector",
"itembase": "8ws", "itembase": "8ws",
"req_lvl": 41, "req_lvl": 41,
"ilvl": 49, "ilvl": 49,
@@ -1536,7 +1536,7 @@
"category": "Expansion" "category": "Expansion"
}, },
"192": { "192": {
"name": "Witchwild String", "name": "Whichwild String",
"itembase": "8s8", "itembase": "8s8",
"req_lvl": 39, "req_lvl": 39,
"ilvl": 47, "ilvl": 47,
@@ -1560,7 +1560,7 @@
"category": "Expansion" "category": "Expansion"
}, },
"195": { "195": {
"name": "Goldstrike Arch", "name": "Godstrike Arch",
"itembase": "8lw", "itembase": "8lw",
"req_lvl": 46, "req_lvl": 46,
"ilvl": 54, "ilvl": 54,
@@ -1576,7 +1576,7 @@
"category": "Expansion" "category": "Expansion"
}, },
"197": { "197": {
"name": "Pus Spitter", "name": "Pus Spiter",
"itembase": "8mx", "itembase": "8mx",
"req_lvl": 36, "req_lvl": 36,
"ilvl": 44, "ilvl": 44,
@@ -1600,7 +1600,7 @@
"category": "Expansion" "category": "Expansion"
}, },
"201": { "201": {
"name": "Peasant Crown", "name": "Peasent Crown",
"itembase": "xap", "itembase": "xap",
"req_lvl": 28, "req_lvl": 28,
"ilvl": 36, "ilvl": 36,
@@ -1632,7 +1632,7 @@
"category": "Armor" "category": "Armor"
}, },
"205": { "205": {
"name": "Valkyrie Wing", "name": "Valkiry Wing",
"itembase": "xhm", "itembase": "xhm",
"req_lvl": 44, "req_lvl": 44,
"ilvl": 52, "ilvl": 52,
@@ -1656,7 +1656,7 @@
"category": "Armor" "category": "Armor"
}, },
"208": { "208": {
"name": "Vampire Gaze", "name": "Vampiregaze",
"itembase": "xh9", "itembase": "xh9",
"req_lvl": 41, "req_lvl": 41,
"ilvl": 49, "ilvl": 49,
@@ -1680,7 +1680,7 @@
"category": "Armor" "category": "Armor"
}, },
"211": { "211": {
"name": "Skin of the Flayed One", "name": "Skin of the Flayerd One",
"itembase": "xla", "itembase": "xla",
"req_lvl": 31, "req_lvl": 31,
"ilvl": 39, "ilvl": 39,
@@ -1688,7 +1688,7 @@
"category": "Armor" "category": "Armor"
}, },
"212": { "212": {
"name": "Iron Pelt", "name": "Ironpelt",
"itembase": "xtu", "itembase": "xtu",
"req_lvl": 33, "req_lvl": 33,
"ilvl": 41, "ilvl": 41,
@@ -1696,7 +1696,7 @@
"category": "Armor" "category": "Armor"
}, },
"213": { "213": {
"name": "Spirit Forge", "name": "Spiritforge",
"itembase": "xng", "itembase": "xng",
"req_lvl": 35, "req_lvl": 35,
"ilvl": 43, "ilvl": 43,
@@ -1776,7 +1776,7 @@
"category": "Armor" "category": "Armor"
}, },
"223": { "223": {
"name": "Que-Hegan's Wisdom", "name": "Que-Hegan's Wisdon",
"itembase": "xtp", "itembase": "xtp",
"req_lvl": 51, "req_lvl": 51,
"ilvl": 59, "ilvl": 59,
@@ -1792,7 +1792,7 @@
"category": "Armor" "category": "Armor"
}, },
"225": { "225": {
"name": "Moser's Blessed Circle", "name": "Mosers Blessed Circle",
"itembase": "xml", "itembase": "xml",
"req_lvl": 31, "req_lvl": 31,
"ilvl": 39, "ilvl": 39,
@@ -1816,7 +1816,7 @@
"category": "Armor" "category": "Armor"
}, },
"228": { "228": {
"name": "Gerke's Sanctuary", "name": "Kerke's Sanctuary",
"itembase": "xow", "itembase": "xow",
"req_lvl": 44, "req_lvl": 44,
"ilvl": 52, "ilvl": 52,
@@ -1824,7 +1824,7 @@
"category": "Armor" "category": "Armor"
}, },
"229": { "229": {
"name": "Radament's Sphere", "name": "Radimant's Sphere",
"itembase": "xts", "itembase": "xts",
"req_lvl": 50, "req_lvl": 50,
"ilvl": 58, "ilvl": 58,
@@ -1872,7 +1872,7 @@
"category": "Armor" "category": "Armor"
}, },
"235": { "235": {
"name": "Lava Gout", "name": "Lavagout",
"itembase": "xtg", "itembase": "xtg",
"req_lvl": 42, "req_lvl": 42,
"ilvl": 50, "ilvl": 50,
@@ -1912,7 +1912,7 @@
"category": "Armor" "category": "Armor"
}, },
"240": { "240": {
"name": "War Traveler", "name": "Wartraveler",
"itembase": "xtb", "itembase": "xtb",
"req_lvl": 42, "req_lvl": 42,
"ilvl": 50, "ilvl": 50,
@@ -1920,7 +1920,7 @@
"category": "Armor" "category": "Armor"
}, },
"241": { "241": {
"name": "Gore Rider", "name": "Gorerider",
"itembase": "xhb", "itembase": "xhb",
"req_lvl": 47, "req_lvl": 47,
"ilvl": 55, "ilvl": 55,
@@ -1944,7 +1944,7 @@
"category": "Armor" "category": "Armor"
}, },
"244": { "244": {
"name": "Gloom's Trap", "name": "Gloomstrap",
"itembase": "zmb", "itembase": "zmb",
"req_lvl": 36, "req_lvl": 36,
"ilvl": 45, "ilvl": 45,
@@ -1960,7 +1960,7 @@
"category": "Armor" "category": "Armor"
}, },
"246": { "246": {
"name": "Thundergod's Vigor", "name": "Thudergod's Vigor",
"itembase": "zhb", "itembase": "zhb",
"req_lvl": 47, "req_lvl": 47,
"ilvl": 55, "ilvl": 55,
@@ -2120,7 +2120,7 @@
"category": "Elite Uniques" "category": "Elite Uniques"
}, },
"268": { "268": {
"name": "Bul-Kathos' Wedding Band", "name": "Bul Katho's Wedding Band",
"itembase": "rin", "itembase": "rin",
"req_lvl": 58, "req_lvl": 58,
"ilvl": 66, "ilvl": 66,
@@ -2256,7 +2256,7 @@
"category": "Class Specific" "category": "Class Specific"
}, },
"286": { "286": {
"name": "Bartuc's Cut-Throat", "name": "Cutthroat1",
"itembase": "9tw", "itembase": "9tw",
"req_lvl": 42, "req_lvl": 42,
"ilvl": 50, "ilvl": 50,
@@ -2288,7 +2288,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"290": { "290": {
"name": "Djinn Slayer", "name": "Djinnslayer",
"itembase": "7sm", "itembase": "7sm",
"req_lvl": 65, "req_lvl": 65,
"ilvl": 73, "ilvl": 73,
@@ -2312,7 +2312,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"293": { "293": {
"name": "Gut Siphon", "name": "Gutsiphon",
"itembase": "6rx", "itembase": "6rx",
"req_lvl": 71, "req_lvl": 71,
"ilvl": 79, "ilvl": 79,
@@ -2320,7 +2320,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"294": { "294": {
"name": "Razor's Edge", "name": "Razoredge",
"itembase": "7ha", "itembase": "7ha",
"req_lvl": 67, "req_lvl": 67,
"ilvl": 75, "ilvl": 75,
@@ -2328,7 +2328,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"296": { "296": {
"name": "Demon Limb", "name": "Demonlimb",
"itembase": "7sp", "itembase": "7sp",
"req_lvl": 63, "req_lvl": 63,
"ilvl": 71, "ilvl": 71,
@@ -2336,7 +2336,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"297": { "297": {
"name": "Steel Shade", "name": "Steelshade",
"itembase": "ulm", "itembase": "ulm",
"req_lvl": 62, "req_lvl": 62,
"ilvl": 70, "ilvl": 70,
@@ -2352,7 +2352,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"299": { "299": {
"name": "Death's Web", "name": "Deaths's Web",
"itembase": "7gw", "itembase": "7gw",
"req_lvl": 66, "req_lvl": 66,
"ilvl": 74, "ilvl": 74,
@@ -2408,7 +2408,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"308": { "308": {
"name": "Jade Talon", "name": "Jadetalon",
"itembase": "7wb", "itembase": "7wb",
"req_lvl": 66, "req_lvl": 66,
"ilvl": 74, "ilvl": 74,
@@ -2416,7 +2416,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"309": { "309": {
"name": "Shadow Dancer", "name": "Shadowdancer",
"itembase": "uhb", "itembase": "uhb",
"req_lvl": 71, "req_lvl": 71,
"ilvl": 79, "ilvl": 79,
@@ -2424,7 +2424,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"310": { "310": {
"name": "Cerebus' Bite", "name": "Cerebus",
"itembase": "drb", "itembase": "drb",
"req_lvl": 63, "req_lvl": 63,
"ilvl": 71, "ilvl": 71,
@@ -2440,7 +2440,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"312": { "312": {
"name": "Soul Drainer", "name": "Souldrain",
"itembase": "umg", "itembase": "umg",
"req_lvl": 74, "req_lvl": 74,
"ilvl": 82, "ilvl": 82,
@@ -2448,7 +2448,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"313": { "313": {
"name": "Rune Master", "name": "Runemaster",
"itembase": "72a", "itembase": "72a",
"req_lvl": 72, "req_lvl": 72,
"ilvl": 80, "ilvl": 80,
@@ -2456,7 +2456,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"314": { "314": {
"name": "Death Cleaver", "name": "Deathcleaver",
"itembase": "7wa", "itembase": "7wa",
"req_lvl": 70, "req_lvl": 70,
"ilvl": 78, "ilvl": 78,
@@ -2488,7 +2488,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"319": { "319": {
"name": "Wisp Projector", "name": "Wisp",
"itembase": "rin", "itembase": "rin",
"req_lvl": 76, "req_lvl": 76,
"ilvl": 84, "ilvl": 84,
@@ -2552,7 +2552,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"327": { "327": {
"name": "Spirit Keeper", "name": "Spiritkeeper",
"itembase": "drd", "itembase": "drd",
"req_lvl": 67, "req_lvl": 67,
"ilvl": 75, "ilvl": 75,
@@ -2576,7 +2576,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"330": { "330": {
"name": "Darkforce Spawn", "name": "Darkforge Spawn",
"itembase": "nef", "itembase": "nef",
"req_lvl": 64, "req_lvl": 64,
"ilvl": 72, "ilvl": 72,
@@ -2592,7 +2592,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"332": { "332": {
"name": "Blood Raven's Charge", "name": "Bloodraven's Charge",
"itembase": "amb", "itembase": "amb",
"req_lvl": 71, "req_lvl": 71,
"ilvl": 79, "ilvl": 79,
@@ -2608,7 +2608,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"334": { "334": {
"name": "Shadow Killer", "name": "Shadowkiller",
"itembase": "7cs", "itembase": "7cs",
"req_lvl": 78, "req_lvl": 78,
"ilvl": 85, "ilvl": 85,
@@ -2664,7 +2664,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"342": { "342": {
"name": "Steel Pillar", "name": "Steelpillar",
"itembase": "7p7", "itembase": "7p7",
"req_lvl": 69, "req_lvl": 69,
"ilvl": 77, "ilvl": 77,
@@ -2704,7 +2704,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"348": { "348": {
"name": "Steel Carapace", "name": "Steel Carapice",
"itembase": "uul", "itembase": "uul",
"req_lvl": 66, "req_lvl": 66,
"ilvl": 74, "ilvl": 74,
@@ -2744,7 +2744,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"354": { "354": {
"name": "Death's Fathom", "name": "Fathom",
"itembase": "obf", "itembase": "obf",
"req_lvl": 73, "req_lvl": 73,
"ilvl": 81, "ilvl": 81,
@@ -2840,7 +2840,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"367": { "367": {
"name": "Eschuta's Temper", "name": "Eschuta's temper",
"itembase": "obc", "itembase": "obc",
"req_lvl": 72, "req_lvl": 72,
"ilvl": 80, "ilvl": 80,
@@ -2904,7 +2904,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"376": { "376": {
"name": "Verdungo's Hearty Cord", "name": "Verdugo's Hearty Cord",
"itembase": "umc", "itembase": "umc",
"req_lvl": 63, "req_lvl": 63,
"ilvl": 71, "ilvl": 71,
@@ -2920,7 +2920,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"379": { "379": {
"name": "Giant Skull", "name": "Giantskull",
"itembase": "uh9", "itembase": "uh9",
"req_lvl": 65, "req_lvl": 65,
"ilvl": 73, "ilvl": 73,
@@ -2928,7 +2928,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"380": { "380": {
"name": "Astreon's Iron Ward", "name": "Ironward",
"itembase": "7ws", "itembase": "7ws",
"req_lvl": 60, "req_lvl": 60,
"ilvl": 68, "ilvl": 68,
@@ -2968,7 +2968,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"385": { "385": {
"name": "Earth Shifter", "name": "Earthshifter",
"itembase": "7gm", "itembase": "7gm",
"req_lvl": 69, "req_lvl": 69,
"ilvl": 77, "ilvl": 77,
@@ -2976,7 +2976,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"386": { "386": {
"name": "Wraith Flight", "name": "Wraithflight",
"itembase": "7gl", "itembase": "7gl",
"req_lvl": 76, "req_lvl": 76,
"ilvl": 84, "ilvl": 84,
@@ -3000,7 +3000,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"389": { "389": {
"name": "The Redeemer", "name": "The Reedeemer",
"itembase": "7sc", "itembase": "7sc",
"req_lvl": 72, "req_lvl": 72,
"ilvl": 80, "ilvl": 80,
@@ -3008,7 +3008,7 @@
"category": "Patch 1.10+" "category": "Patch 1.10+"
}, },
"390": { "390": {
"name": "Head Hunter's Glory", "name": "Headhunter's Glory",
"itembase": "ush", "itembase": "ush",
"req_lvl": 75, "req_lvl": 75,
"ilvl": 83, "ilvl": 83,

View File

@@ -62,7 +62,7 @@ decode_tree = decodetree(code)
def decode(bits: bitarray, n) -> tuple[str, int]: def decode(bits: bitarray, n) -> tuple[str, int]:
text = "".join(itertools.islice(bits.decode(decode_tree), n)) text = "".join(itertools.islice(bits.iterdecode(decode_tree), n))
length = len(encode(text)) length = len(encode(text))
return text, length return text, length

View File

@@ -31,7 +31,6 @@ _unique_map = None
_set_item_map = None _set_item_map = None
_runeword_map = None _runeword_map = None
_affix_map = None _affix_map = None
_skills_map = None
class Quality(IntEnum): class Quality(IntEnum):
@@ -78,18 +77,8 @@ class Stat:
for val in self.values: for val in self.values:
subst_text = subst_text.replace("#", str(val), 1) subst_text = subst_text.replace("#", str(val), 1)
if param: if param:
subst_text = self.try_add_skill_text(subst_text) subst_text = re.sub(r"\[[^\]]*\]", str(param), subst_text, 1)
return subst_text return subst_text
def try_add_skill_text(self, subst_text: str) -> str | None:
if self.id == 107: # +X to [Skill] ([Class] only)
return re.sub(r"\[[^\]]*\]", lookup_skill_name(self.parameter), subst_text, 1)
elif self.id == 188: # +X to [Skill]
if '[' in subst_text:
return subst_text[:subst_text.find('[')] + lookup_random_skill_tab(self.parameter)
return re.sub(r"\[[^\]]*\]", lookup_random_skill_tab(self.parameter), subst_text, 1)
else:
return re.sub(r"\[[^\]]*\]", str(self.parameter), subst_text, 1)
def txtbits(bits: bitarray) -> str: def txtbits(bits: bitarray) -> str:
@@ -557,57 +546,3 @@ def lookup_affix(id: int, prefix: bool) -> dict:
with open(os.path.join(_data_path, "affixes.json")) as f: with open(os.path.join(_data_path, "affixes.json")) as f:
_affix_map = json.load(f) _affix_map = json.load(f)
return _affix_map["prefixes" if prefix else "suffixes"][str(id)] return _affix_map["prefixes" if prefix else "suffixes"][str(id)]
def _get_skills_map():
global _skills_map
if _skills_map is None:
with open(os.path.join(_data_path, "skills.json")) as f:
_skills_map = json.load(f)
return _skills_map
def lookup_skill_name(id: int) -> str:
# FIXME: This hackish way of calculating the key is because we directly index into local/lng/strings/skills.json
# but the actual ID points to global/excel/skills.txt
skills_map = _get_skills_map()
try:
try:
return skills_map[f"Skillname{id + 1}"]
except KeyError:
return skills_map[f"skillname{id}"]
except KeyError:
return f"<Invalid key: skillname{id} or Skillname{id + 1}>"
def lookup_random_skill_tab(id: int) -> str:
# (ClassId * 8) is the id of the first tab for that class
skills_map = _get_skills_map()
class_name = lookup_class(int(id / 8))
two_letter_class_code = lookup_class(int(id / 8))[:2]
tab_index = 3 - id % 8 # for some reason local/lng/strings/skills.json index backwards (0 -> 3, 1 -> 2, ...)
try:
return skills_map[f"SkillCategory{two_letter_class_code}{tab_index}"] + f" ({class_name} only)"
except KeyError:
return f"<Invalid random skill tab: {id}>"
def lookup_class(id: int) -> str:
match id:
case 0:
return "Amazon"
case 1:
return "Sorceress"
case 2:
return "Necromancer"
case 3:
return "Paladin"
case 4:
return "Barbarian"
case 5:
return "Druid"
case 6:
return "Assassin"
case _:
# TODO: In 3.11 replace this with assert_never
assert False, f"{id} Should be unreachable"

View File

@@ -239,12 +239,3 @@ class ParseItemTest(unittest.TestCase):
self.assertAlmostEqual( self.assertAlmostEqual(
lookup_affix(item.suffixes[2], False)["name"], "of Charged Shield" lookup_affix(item.suffixes[2], False)["name"], "of Charged Shield"
) )
def test_cathans_rule(self):
# Cathan's Rule: Only source of fire-max stat
data = bytes.fromhex(
"10008000050054c90448ad74276f910150448c180afc24ff1348fd3f212d85b415d25a48fb0f"
)
data, item = parse_item(data)
self.assertEqual(data, b"")
self.assertEqual(str(item.stats[0]), "+10 to Maximum Fire Damage")