SL2 Analyzer
Read a FromSoftware .sl2 save and report what is in it
Loading...
Searching...
No Matches
itemdb.py
Go to the documentation of this file.
1"""Item-name databases. Three id schemes across the four games — see the db_*/
2folders and CLAUDE.md for why DS2 is id-keyed and the others are not.
3"""
4
5import json
6import os
7
8## @brief DS2 tables: filename stem to category. Ids are unique across categories.
9# Categories are finer than the game's raw tabs so the output can mirror the
10# in-game menu: consumables, trade goods, emotes and boss souls each stand alone.
11DS2_DB_FILES = {
12 "weapons": "weapons",
13 "armors": "armors",
14 "rings": "rings",
15 "spells": "spells",
16 "key": "keys",
17 "bolts": "bolts",
18 "upgrade": "upgrade",
19 "consumables": "consumables",
20 "online": "online",
21 "emotes": "emotes",
22 "bosssouls": "bosssouls",
23}
24
25
26## @brief DS1 (DSR and PtDE) tables. Ids repeat across categories, so lookups stay
27# category-scoped and the slot type decides which table to use.
28# Spells are stored as ordinary goods by the game (Soul Arrow is good 3000), but they
29# are kept in their own file so they render under their own heading like DS2/DS3 —
30# the slot type cannot tell them apart, the id range can.
31DS1_DB_FILES = {
32 "MeleeWeapons": "weapons",
33 "Armor": "armors",
34 "Rings": "rings",
35 "Consumables": "goods",
36 "Spells": "spells",
37}
38
39
40##
41# @brief Ids for one table entry. A name whose value is a LIST owns several ids.
42# @details The tables are name-keyed, so one name can normally hold one id — which
43# silently dropped every duplicate the game really ships (DS3 has one "Cinders of a
44# Lord" per lord, DS1 has a base and an alternate-path row under one name). A list
45# value keeps them all; a bare value is the ordinary single-id case.
46def _ids(value):
47 return [int(v) for v in (value if isinstance(value, list) else [value])]
48
49
50##
51# @brief Load item-name tables for a game family.
52# @param db_dir Folder holding the JSON tables.
53# @param flat True for DS2 (one id-to-(name,category) dict); False for DS1
54# (a dict per category).
55# @param files The filename-stem to category mapping to load.
56# @return The lookup structure, or an empty container if the folder is missing.
57def load_item_db(db_dir, flat, files):
58 if not os.path.isdir(db_dir):
59 return {} if flat else {}
60 if flat:
61 # DS2 tables are id-keyed ({"<little-endian-hex>": name}), not name-keyed:
62 # the game gives one item name several ids (base + reinforced/infused/variant
63 # forms), which a name-keyed file cannot hold without dropping all but one.
64 db = {}
65 for stem, cat in files.items():
66 path = os.path.join(db_dir, stem + ".json")
67 if os.path.exists(path):
68 for hx, name in json.load(open(path, encoding="utf-8")).items():
69 db.setdefault(
70 int.from_bytes(bytes.fromhex(hx), "little"), (name, cat)
71 )
72 return db
73 db = {}
74 for stem, cat in files.items():
75 path = os.path.join(db_dir, stem + ".json")
76 if os.path.exists(path):
77 table = db.setdefault(cat, {})
78 for name, value in json.load(open(path, encoding="utf-8")).items():
79 for iid in _ids(value):
80 table[iid] = name
81 return db
82
83
84##
85# @brief Collapse duplicate stackable items into one line, summing counts, in
86# first-seen order.
87# @param items A list of @c (name, qty) pairs.
88# @return A list of @c (name, total_qty).
89def merge_qty(items):
90 order, agg = [], {}
91 for name, q in items:
92 if name not in agg:
93 agg[name] = 0
94 order.append(name)
95 agg[name] += q
96 return [(n, agg[n]) for n in order]
97
98
99##
100# @brief Load an id-scan database: per-category JSON of @c {name: id}, flattened
101# to @c {id: (name, category)}.
102# @param db_dir Folder of category JSON files.
103# @param files Filename-stem to category mapping.
104# @param refine Optional @c (id, cat) -> cat hook, used to split DS3's one goods
105# file into the finer categories the render prints.
106# @return The flat lookup, or {} if the folder is absent.
107def load_scan_db(db_dir, files, refine=None):
108 if not os.path.isdir(db_dir):
109 return {}
110 db = {}
111 for stem, cat in files.items():
112 path = os.path.join(db_dir, stem + ".json")
113 if os.path.exists(path):
114 for name, value in json.load(open(path, encoding="utf-8")).items():
115 for iid in _ids(value):
116 db.setdefault(iid, (name, refine(iid, cat) if refine else cat))
117 return db
_ids(value)
Ids for one table entry.
Definition itemdb.py:46