From 2baa43db20de45977c59238f5d1b6eaea7cf0966 Mon Sep 17 00:00:00 2001 From: omicron Date: Thu, 2 Oct 2025 19:10:18 +0200 Subject: [PATCH] Add rudimentary grail tracker for sets and uniques --- d2warehouse/app/main.py | 85 +++++++++++++++++++++ d2warehouse/app/static/helpers.js | 7 ++ d2warehouse/app/static/style.css | 25 ++++++ d2warehouse/app/templates/grail.html | 28 +++++++ d2warehouse/app/templates/list_storage.html | 1 + d2warehouse/app/templates/menu.html | 1 + 6 files changed, 147 insertions(+) create mode 100644 d2warehouse/app/templates/grail.html diff --git a/d2warehouse/app/main.py b/d2warehouse/app/main.py index e90584f..8f88dfe 100644 --- a/d2warehouse/app/main.py +++ b/d2warehouse/app/main.py @@ -3,6 +3,7 @@ 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 @@ -14,6 +15,7 @@ import re from d2warehouse.stash import StashFullError + STASH_FILES = { "softcore": "SharedStashSoftCoreV2.d2i", "hardcore": "SharedStashHardCoreV2.d2i", @@ -357,3 +359,86 @@ 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/") +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, + ) diff --git a/d2warehouse/app/static/helpers.js b/d2warehouse/app/static/helpers.js index 3c8b73a..394f3ff 100644 --- a/d2warehouse/app/static/helpers.js +++ b/d2warehouse/app/static/helpers.js @@ -11,3 +11,10 @@ function toggleSelectAll(tabIndex) { cb.checked = !allSelected; }); } + +function toggleCollected() { + const collected = document.querySelectorAll('.collected'); + collected.forEach(item => { + item.classList.toggle('hidden'); + }); +} diff --git a/d2warehouse/app/static/style.css b/d2warehouse/app/static/style.css index 3a4c3e9..8917b78 100644 --- a/d2warehouse/app/static/style.css +++ b/d2warehouse/app/static/style.css @@ -123,3 +123,28 @@ 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; +} diff --git a/d2warehouse/app/templates/grail.html b/d2warehouse/app/templates/grail.html new file mode 100644 index 0000000..79f5d89 --- /dev/null +++ b/d2warehouse/app/templates/grail.html @@ -0,0 +1,28 @@ + + + + + Grail + + + + + {% include "menu.html" %} + +

Unique Items

+ Progress: {{uniques.count}}/{{uniques.total}} ({{uniques.progress | round(1)}}%) + +

Set Items

+ Progress: {{sets.count}}/{{sets.total}} ({{sets.progress | round(1)}}%) + + + + diff --git a/d2warehouse/app/templates/list_storage.html b/d2warehouse/app/templates/list_storage.html index 2597b24..5716432 100644 --- a/d2warehouse/app/templates/list_storage.html +++ b/d2warehouse/app/templates/list_storage.html @@ -19,6 +19,7 @@
+ There are {{ storage_items | length }} items. {% for db_id, item in storage_items.items() %}
diff --git a/d2warehouse/app/templates/menu.html b/d2warehouse/app/templates/menu.html index 40d6956..2e8bfcf 100644 --- a/d2warehouse/app/templates/menu.html +++ b/d2warehouse/app/templates/menu.html @@ -2,5 +2,6 @@