Compare commits
2 Commits
webui_grai
...
7c96e47da2
| Author | SHA1 | Date | |
|---|---|---|---|
| 7c96e47da2 | |||
| 813e875cdf |
@@ -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")
|
||||
@@ -3,7 +3,6 @@ from flask import Flask, redirect, abort, render_template, request
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
import psutil
|
||||
from d2warehouse.item import Item, Quality, lookup_basetype
|
||||
@@ -15,7 +14,6 @@ import re
|
||||
|
||||
from d2warehouse.stash import StashFullError
|
||||
|
||||
|
||||
STASH_FILES = {
|
||||
"softcore": "SharedStashSoftCoreV2.d2i",
|
||||
"hardcore": "SharedStashHardCoreV2.d2i",
|
||||
@@ -94,26 +92,22 @@ def storage_count(item: Item, stash: str) -> int | str:
|
||||
db = get_stash_db(stash)
|
||||
if item.is_simple:
|
||||
return db.execute(
|
||||
"SELECT COUNT(id) FROM item "
|
||||
"WHERE code = ? AND deleted IS NULL AND socketed_into IS NULL",
|
||||
"SELECT COUNT(id) FROM item WHERE code = ? AND deleted IS NULL",
|
||||
(item.code,),
|
||||
).fetchone()[0]
|
||||
elif item.quality == Quality.UNIQUE:
|
||||
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 AND socketed_into IS NULL",
|
||||
"SELECT COUNT(id) FROM item INNER JOIN item_extra ON id = item_id WHERE code = ? AND unique_id = ? AND deleted IS NULL",
|
||||
(item.code, item.unique_id),
|
||||
).fetchone()[0]
|
||||
elif item.quality == Quality.SET:
|
||||
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 AND socketed_into IS NULL",
|
||||
"SELECT COUNT(id) FROM item INNER JOIN item_extra ON id = item_id WHERE code = ? AND set_id = ? AND deleted IS NULL",
|
||||
(item.code, item.set_id),
|
||||
).fetchone()[0]
|
||||
elif item.is_runeword:
|
||||
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 and socketed_into IS NULL",
|
||||
"SELECT COUNT(id) FROM item INNER JOIN item_extra ON id = item_id WHERE code = ? AND runeword_id = ? AND deleted IS NULL",
|
||||
(item.code, item.runeword_id),
|
||||
).fetchone()[0]
|
||||
else:
|
||||
@@ -223,9 +217,7 @@ def list_storage(stash_name: str):
|
||||
|
||||
db = get_stash_db(stash_name)
|
||||
items = {}
|
||||
rows = db.execute(
|
||||
"SELECT id FROM item WHERE deleted IS NULL and socketed_into IS NULL"
|
||||
).fetchall()
|
||||
rows = db.execute("SELECT id FROM item WHERE deleted IS NULL").fetchall()
|
||||
for row in rows:
|
||||
items[row["id"]] = Item.load_from_db(row["id"], db=db)
|
||||
|
||||
@@ -246,21 +238,16 @@ def list_storage_category(stash_name: str, category: str):
|
||||
|
||||
if category == "uniques":
|
||||
q = db.execute(
|
||||
"SELECT id FROM item INNER JOIN item_extra ON id = item_id "
|
||||
"WHERE deleted IS NULL AND socketed_into IS NULL AND quality = ?",
|
||||
"SELECT id FROM item INNER JOIN item_extra ON id = item_id WHERE deleted IS NULL AND quality = ?",
|
||||
(int(Quality.UNIQUE),),
|
||||
)
|
||||
elif category == "sets":
|
||||
q = db.execute(
|
||||
"SELECT id FROM item INNER JOIN item_extra ON id = item_id "
|
||||
"WHERE deleted IS NULL AND socketed_into IS NULL AND quality = ?",
|
||||
"SELECT id FROM item INNER JOIN item_extra ON id = item_id WHERE deleted IS NULL AND quality = ?",
|
||||
(int(Quality.SET),),
|
||||
)
|
||||
elif category == "misc":
|
||||
q = db.execute(
|
||||
"SELECT id FROM item "
|
||||
"WHERE deleted IS NULL AND socketed_into IS NULL AND is_simple = TRUE"
|
||||
)
|
||||
q = db.execute("SELECT id FROM item WHERE deleted IS NULL AND is_simple = TRUE")
|
||||
else:
|
||||
return "Unexpected category", 400
|
||||
|
||||
@@ -273,7 +260,6 @@ def list_storage_category(stash_name: str, category: str):
|
||||
"list_storage.html",
|
||||
stash_name=stash_name,
|
||||
storage_items=items,
|
||||
category=category,
|
||||
storage_count=lambda x: storage_count(x, stash_name),
|
||||
)
|
||||
|
||||
@@ -339,16 +325,14 @@ def storage_currency_counts(item_codes: list[str], stash_name: str) -> dict:
|
||||
for code in item_codes:
|
||||
currencies[code] = {
|
||||
"count": db.execute(
|
||||
"SELECT COUNT(id) FROM item "
|
||||
"WHERE code = ? AND deleted IS NULL AND socketed_into IS NULL",
|
||||
(code,),
|
||||
"SELECT COUNT(id) FROM item WHERE code = ?", (code,)
|
||||
).fetchone()[0],
|
||||
"name": lookup_basetype(code)["name"],
|
||||
}
|
||||
return currencies
|
||||
|
||||
|
||||
@app.route("/currency/<stash_name>")
|
||||
@app.route("/storage/<stash_name>/currency")
|
||||
def storage_currency(stash_name: str):
|
||||
if stash_name not in DB_FILES:
|
||||
abort(404)
|
||||
@@ -359,86 +343,3 @@ def storage_currency(stash_name: str):
|
||||
return render_template(
|
||||
"currency.html", runes=runes, gems=gems, keys=keys, essences=essences
|
||||
)
|
||||
|
||||
|
||||
def load_uniques_data():
|
||||
"""Return a sorted dictionary of unique item ids indexed by names."""
|
||||
uniques_path = Path(__file__).resolve().parent.parent / "data/uniques.json"
|
||||
with uniques_path.open() as f:
|
||||
data = json.load(f)
|
||||
name_to_id = {v["name"]: int(k) for k, v in data.items()}
|
||||
del name_to_id["Amulet of the Viper"]
|
||||
del name_to_id["Staff of Kings"]
|
||||
del name_to_id["Horadric Staff"]
|
||||
del name_to_id["Khalim's Flail"]
|
||||
del name_to_id["Khalim's Will"]
|
||||
del name_to_id["Hell Forge Hammer"]
|
||||
return {name: name_to_id[name] for name in sorted(name_to_id.keys())}
|
||||
|
||||
|
||||
def load_sets_data():
|
||||
"""Return a sorted dictionary of unique item ids indexed by names."""
|
||||
uniques_path = Path(__file__).resolve().parent.parent / "data/sets.json"
|
||||
with uniques_path.open() as f:
|
||||
data = json.load(f)
|
||||
name_to_id = {v["name"]: int(k) for k, v in data.items()}
|
||||
return {name: name_to_id[name] for name in sorted(name_to_id.keys())}
|
||||
|
||||
|
||||
all_uniques = load_uniques_data()
|
||||
all_sets = load_sets_data()
|
||||
|
||||
|
||||
def get_found_uniques(db):
|
||||
rows = db.execute(
|
||||
"SELECT DISTINCT unique_id "
|
||||
"FROM item INNER JOIN item_extra ON id = item_id "
|
||||
"WHERE quality = 7 AND deleted IS NULL AND socketed_into IS NULL",
|
||||
).fetchall()
|
||||
return {row["unique_id"]: True for row in rows}
|
||||
|
||||
|
||||
def get_found_sets(db):
|
||||
rows = db.execute(
|
||||
"SELECT DISTINCT set_id "
|
||||
"FROM item INNER JOIN item_extra ON id = item_id "
|
||||
"WHERE quality = 5 AND deleted IS NULL AND socketed_into IS NULL",
|
||||
).fetchall()
|
||||
return {row["set_id"]: True for row in rows}
|
||||
|
||||
|
||||
@app.route("/grail/<stash_name>")
|
||||
def storage_grail(stash_name: str):
|
||||
if stash_name not in DB_FILES:
|
||||
abort(404)
|
||||
db = get_stash_db(stash_name)
|
||||
|
||||
# Unique progress
|
||||
found = get_found_uniques(db)
|
||||
items = {name: found.get(id, False) for name, id in all_uniques.items()}
|
||||
count = len(found)
|
||||
total = len(all_uniques)
|
||||
uniques = {
|
||||
"list": items,
|
||||
"count": count,
|
||||
"total": total,
|
||||
"progress": count / total * 100,
|
||||
}
|
||||
|
||||
# Set progress
|
||||
found = get_found_sets(db)
|
||||
items = {name: found.get(id, False) for name, id in all_sets.items()}
|
||||
count = len(found)
|
||||
total = len(all_sets)
|
||||
sets = {
|
||||
"list": items,
|
||||
"count": count,
|
||||
"total": total,
|
||||
"progress": count / total * 100,
|
||||
}
|
||||
|
||||
return render_template(
|
||||
"grail.html",
|
||||
uniques=uniques,
|
||||
sets=sets,
|
||||
)
|
||||
|
||||
@@ -11,10 +11,3 @@ function toggleSelectAll(tabIndex) {
|
||||
cb.checked = !allSelected;
|
||||
});
|
||||
}
|
||||
|
||||
function toggleCollected() {
|
||||
const collected = document.querySelectorAll('.collected');
|
||||
collected.forEach(item => {
|
||||
item.classList.toggle('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,25 +5,6 @@ body {
|
||||
color: rgb(240, 240, 240);
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
nav a {
|
||||
display: block;
|
||||
padding: 1rem 1.5rem;
|
||||
text-decoration: none;
|
||||
color: rgb(240, 240, 240);
|
||||
}
|
||||
|
||||
nav a:hover {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
.stash-tab {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
@@ -123,28 +104,3 @@ input[type="checkbox"]:checked + label {
|
||||
background-color: #444;
|
||||
color: rgb(240, 240, 240);
|
||||
}
|
||||
|
||||
ul.grail {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem 2rem;
|
||||
}
|
||||
|
||||
ul.grail li {
|
||||
flex: 0 0 15rem;
|
||||
padding: 0.5rem;
|
||||
width: 15rem;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.collected {
|
||||
background-color: #343;
|
||||
}
|
||||
.uncollected {
|
||||
background-color: #433;
|
||||
}
|
||||
|
||||
@@ -2,19 +2,10 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Currency</title>
|
||||
<title>Shared Stash</title>
|
||||
<link rel="stylesheet" href="/static/style.css" />
|
||||
<head>
|
||||
<body>
|
||||
{% include "menu.html" %}
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/storage/{{stash_name or 'softcore'}}">All</a></li>
|
||||
<li><a href="/storage/{{stash_name or 'softcore'}}/uniques">Uniques</a></li>
|
||||
<li><a href="/storage/{{stash_name or 'softcore'}}/sets">Sets</a></li>
|
||||
<li><a href="/storage/{{stash_name or 'softcore'}}/currency">Currency</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="currencies">
|
||||
<div>
|
||||
<table>
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Grail</title>
|
||||
<link rel="stylesheet" href="/static/style.css" />
|
||||
<script src="/static/helpers.js"></script>
|
||||
<head>
|
||||
<body>
|
||||
{% include "menu.html" %}
|
||||
<button type="button" onclick="toggleCollected()">Toggle collected</button>
|
||||
<h1>Unique Items</h1>
|
||||
Progress: {{uniques.count}}/{{uniques.total}} ({{uniques.progress | round(1)}}%)
|
||||
<ul class="grail">
|
||||
{% for name,collected in uniques.list.items() %}
|
||||
<li class="{{ 'collected' if collected else 'uncollected' }}">{{name}}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<h1>Set Items</h1>
|
||||
Progress: {{sets.count}}/{{sets.total}} ({{sets.progress | round(1)}}%)
|
||||
<ul class="grail">
|
||||
{% for name,collected in sets.list.items() %}
|
||||
<li class="{{ 'collected' if collected else 'uncollected' }}">{{name}}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
<script src="/static/helpers.js"></script>
|
||||
<head>
|
||||
<body>
|
||||
{% include "menu.html" %}
|
||||
<form action="/stash/{{stash_name}}/store" method="POST">
|
||||
{% for tab in stash.tabs %}
|
||||
{% set tabloop = loop %}
|
||||
|
||||
@@ -2,23 +2,13 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Storage - {{category or 'all'}}</title>
|
||||
<title>Storage</title>
|
||||
<link rel="stylesheet" href="/static/style.css" />
|
||||
<head>
|
||||
<body>
|
||||
{% include "menu.html" %}
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/storage/{{stash_name or 'softcore'}}">All</a></li>
|
||||
<li><a href="/storage/{{stash_name or 'softcore'}}/uniques">Uniques</a></li>
|
||||
<li><a href="/storage/{{stash_name or 'softcore'}}/sets">Sets</a></li>
|
||||
<li><a href="/storage/{{stash_name or 'softcore'}}/misc">Misc</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
<form action="/storage/{{stash_name}}/take" method="POST">
|
||||
<div>
|
||||
<!-- TODO: Include item.html -->
|
||||
There are {{ storage_items | length }} items.
|
||||
{% for db_id, item in storage_items.items() %}
|
||||
<div class="storage-item-entry">
|
||||
<input type="checkbox" name="item_{{db_id}}" id="item_{{db_id}}" value="take" />
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/stash/{{stash_name or 'softcore'}}">Stash</a></li>
|
||||
<li><a href="/storage/{{stash_name or 'softcore'}}">Storage</a></li>
|
||||
<li><a href="/grail/{{stash_name or 'softcore'}}">Grail</a></li>
|
||||
<li><a href="/currency/{{stash_name or 'softcore'}}">Currency</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -31,7 +31,6 @@ _unique_map = None
|
||||
_set_item_map = None
|
||||
_runeword_map = None
|
||||
_affix_map = None
|
||||
_skills_map = None
|
||||
|
||||
|
||||
class Quality(IntEnum):
|
||||
@@ -78,18 +77,8 @@ class Stat:
|
||||
for val in self.values:
|
||||
subst_text = subst_text.replace("#", str(val), 1)
|
||||
if param:
|
||||
subst_text = self.try_add_skill_text(subst_text)
|
||||
subst_text = re.sub(r"\[[^\]]*\]", str(param), subst_text, 1)
|
||||
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:
|
||||
@@ -557,57 +546,3 @@ def lookup_affix(id: int, prefix: bool) -> dict:
|
||||
with open(os.path.join(_data_path, "affixes.json")) as f:
|
||||
_affix_map = json.load(f)
|
||||
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"
|
||||
|
||||
Reference in New Issue
Block a user