SL2 Analyzer
Read a FromSoftware .sl2 save and report what is in it
Loading...
Searching...
No Matches
gen_sdt_lot_flags.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""Regenerate db_sdt/lot_flags.json: the flag each Sekiro item lot REALLY sets.
3
4`db_sdt/item_flags.json` files every world pickup under `50000000 + lot id`, and for
5most lots that is the flag the game writes. Not for all of them. The game's own
6`ItemLotParam` gives each lot a `getItemFlagId`, and a hundred rows in the table point
7somewhere else — including every prosthetic part, every esoteric text, every Gourd Seed
8and every world Prayer Bead in the nine areas the tool reads. Those lots set a GLOBAL
9flag (`6500` for the Shuriken Wheel, `6725` for the courtyard Gourd Seed), so reading
10the lot-shaped id reported them "still out there" on a save that had picked up all of
11them hours earlier.
12
13This joins the two and writes only the corrections, so `item_flags.json` itself is not
14hand-edited:
15
16 moved {row id: the flag the lot actually sets}, where the two differ
17 misfiled [[row id, name]] for rows whose lot hands out a DIFFERENT item, so the
18 row's id says nothing about the item it names
19
20A row counts as the lot's item when one of the lot's item names starts with the row's
21name — the game's own names carry suffixes the table drops (`Sabimaru Memo (Bought)`,
22`Sweet Rice Ball 2`). An id with no English name is no evidence either way and leaves
23the row alone.
24
25Input is `ItemLotParam` as `gen_sdt_from_regulation.py` extracts it, one row per lot:
26
27 python3 tools/gen_sdt_from_regulation.py --game-root ... --paramdex ... --out DIR
28 python3 tools/gen_sdt_lot_flags.py --lots DIR/item_flags.tsv [--write]
29
30Without --write it prints the delta and writes nothing.
31"""
32
33import argparse
34import csv
35import json
36import os
37import sys
38
39BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
40DB = os.path.join(BASE, "db_sdt")
41
42## The pickup family's id rule: a row id is this plus the lot id.
43LOT_FLAG_BASE = 50000000
44## ItemLotParam carries eight item slots per lot.
45LOT_SLOTS = 8
46
47
48def load_json(name):
49 with open(os.path.join(DB, name), encoding="utf-8") as f:
50 return json.load(f)
51
52
54 names = {}
55 for stem in ("weapons", "goods"):
56 for k, v in load_json(stem + ".json").items():
57 names.setdefault(int(k), []).append(v)
58 return names
59
60
61def load_lots(path):
62 with open(path, encoding="utf-8", newline="") as f:
63 return {int(r["id"]): r for r in csv.DictReader(f, delimiter="\t")}
64
65
66def lot_items(lot, names):
67 out = []
68 for i in range(1, LOT_SLOTS + 1):
69 iid = int(lot[f"lotItemId{i:02d}"])
70 if iid:
71 out += names.get(iid, [])
72 return out
73
74
75def build(lots, rows, names):
76 moved, misfiled = {}, []
77 for fid, name, _where in rows:
78 lot = lots.get(fid - LOT_FLAG_BASE)
79 if lot is None:
80 continue
81 items = lot_items(lot, names)
82 if items and not any(n.startswith(name) for n in items):
83 misfiled.append([fid, name])
84 continue
85 flag = int(lot["getItemFlagId"])
86 if flag > 0 and flag != fid:
87 moved[str(fid)] = flag
88 return {"moved": moved, "misfiled": misfiled}
89
90
91## House style for db_*/: one-space indent, a short pair kept on one line.
92def dump(table):
93 moved = ",\n".join(f' "{k}": {v}' for k, v in table["moved"].items())
94 misfiled = ",\n".join(
95 f" {json.dumps(r, ensure_ascii=False)}" for r in table["misfiled"]
96 )
97 return f'{{\n "moved": {{\n{moved}\n }},\n "misfiled": [\n{misfiled}\n ]\n}}\n'
98
99
100def main():
101 ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
102 ap.add_argument("--lots", required=True, help="ItemLotParam TSV (item_flags.tsv)")
103 ap.add_argument("--write", action="store_true", help="write db_sdt/lot_flags.json")
104 args = ap.parse_args()
105
106 rows = load_json("item_flags.json").get("item_pickup", [])
107 table = build(load_lots(args.lots), rows, item_names())
108 print(f"{len(table['moved'])} rows moved, {len(table['misfiled'])} misfiled")
109 for fid, name in table["misfiled"]:
110 print(f" misfiled {fid} {name}")
111 if args.write:
112 with open(os.path.join(DB, "lot_flags.json"), "w", encoding="utf-8") as f:
113 f.write(dump(table))
114 print("wrote db_sdt/lot_flags.json")
115 return 0
116
117
118if __name__ == "__main__":
119 sys.exit(main())
build(lots, rows, names)
dump(table)
House style for db_*/: one-space indent, a short pair kept on one line.