Files
my-botty/tools/audit_stash_pickit.py

119 lines
4.5 KiB
Python

"""
Audit the stash against the CURRENT pickit rules — find slots you can free.
Walks every stash page (0 = personal, 1-5 = shared), hovers each occupied
slot, OCRs the item tooltip, and evaluates it with the same should_keep()
the bot uses at stash time. Items that no longer match any keep rule (e.g.
early-season keeps after switching to an endgame pickit profile) are listed
as FREE-ABLE. Read-only: nothing is moved, sold or dropped.
Usage: be in a game with the STASH OPEN (stand at stash, open it), then:
<botty-env-python> tools/audit_stash_pickit.py [max_page]
Report goes to console and log/stash_audit_<timestamp>.txt.
Respects the active profile's pickit (profiles/pickit_profiles resolution).
"""
import os
import sys
import time
import ctypes
import itertools
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
ctypes.windll.user32.SetProcessDPIAware()
from config import Config
from logger import Logger
from screen import start_detecting_window, stop_detecting_window, grab, convert_screen_to_monitor
from utils.misc import wait, cut_roi
from input_layer import mouse
from inventory import common
from ui_manager import is_visible, ScreenObjects
import d2r_image
from bnip.actions import should_keep
STASH_COLS, STASH_ROWS = 10, 10
def _stash_slot_pos(col: int, row: int) -> tuple[int, int]:
x, y, _, _ = Config().ui_roi["left_inventory"]
w, h = Config().ui_pos["slot_width"], Config().ui_pos["slot_height"]
return (x + col * w + w // 2, y + row * h + h // 2)
def _slot_occupied(img, col: int, row: int) -> bool:
x, y, _, _ = Config().ui_roi["left_inventory"]
w, h = Config().ui_pos["slot_width"], Config().ui_pos["slot_height"]
ox, oy = int(w * 0.12), int(h * 0.12)
sx, sy = x + col * w, y + row * h
return common.slot_has_item(img[sy + oy:sy + h - oy, sx + ox:sx + w - ox])
def audit_page(page: int) -> list[dict]:
common.select_stash_page(page)
wait(0.5, 0.7)
img = grab(force_new=True)
results = []
seen_uids = set()
for col, row in itertools.product(range(STASH_COLS), range(STASH_ROWS)):
if not _slot_occupied(img, col, row):
continue
x_m, y_m = convert_screen_to_monitor(_stash_slot_pos(col, row))
mouse.move(x_m, y_m, randomize=4, delay_factor=[0.15, 0.25])
wait(0.15, 0.2)
hovered = grab(force_new=True)
try:
props, box = d2r_image.get_hovered_item(hovered)
except Exception:
continue
if box is None or props is None:
continue
name = getattr(box, "ocr_result", None)
name = (name.text.splitlines()[0] if name and name.text else "?").strip()
uid = name + str(getattr(props, "NTIPAliasType", ""))
if (name, col // 2, row // 2) in seen_uids: # multi-slot items rehover
continue
seen_uids.add((name, col // 2, row // 2))
try:
keep, expression = should_keep(props.as_dict())
except Exception as e:
keep, expression = None, f"eval failed: {e}"
results.append({"page": page, "slot": (col, row), "name": name,
"keep": keep, "rule": expression or ""})
return results
def main():
max_page = int(sys.argv[1]) if len(sys.argv) > 1 else 5
print("Stash must be OPEN in-game. Starting audit in 5s...")
time.sleep(5)
start_detecting_window()
wait(0.5)
all_results = []
try:
if not is_visible(ScreenObjects.GoldBtnStash, grab(force_new=True)):
raise SystemExit("Stash panel not detected — open the stash first.")
for page in range(0, max_page + 1):
page_results = audit_page(page)
all_results.extend(page_results)
print(f"page {page}: {len(page_results)} items")
finally:
stop_detecting_window()
freeable = [r for r in all_results if r["keep"] is False]
lines = [f"STASH AUDIT vs pickit ({time.strftime('%Y-%m-%d %H:%M')}) — "
f"{len(all_results)} items, {len(freeable)} FREE-ABLE\n"]
for r in all_results:
verdict = {True: "KEEP ", False: "FREE ", None: "UNKNOWN"}[r["keep"]]
lines.append(f" p{r['page']} {str(r['slot']):>8} {verdict} {r['name']}"
+ (f" [{r['rule']}]" if r["keep"] else ""))
report = "\n".join(lines)
print(report)
out = f"log/stash_audit_{time.strftime('%Y%m%d_%H%M%S')}.txt"
with open(out, "w", encoding="utf-8") as f:
f.write(report)
print(f"\nreport saved: {out}")
if __name__ == "__main__":
main()