188 lines
7.0 KiB
Python
188 lines
7.0 KiB
Python
# Stash inventory scanner - reads all stash tabs and prints contents.
|
|
# Run from project root: python scripts/stash_inventory.py
|
|
import sys
|
|
import os
|
|
import time
|
|
import json
|
|
import numpy as np
|
|
import itertools
|
|
|
|
_script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
_root = os.path.dirname(_script_dir)
|
|
_src = os.path.join(_root, "src")
|
|
if _src not in sys.path:
|
|
sys.path.insert(0, _src)
|
|
os.chdir(_root)
|
|
|
|
import ctypes
|
|
ctypes.windll.user32.SetProcessDPIAware()
|
|
|
|
import ssl
|
|
def _patch_ssl():
|
|
_orig = ssl.SSLContext.load_default_certs
|
|
def _safe(self, purpose=ssl.Purpose.CLIENT_AUTH):
|
|
try:
|
|
_orig(self, purpose)
|
|
except ssl.SSLError:
|
|
try:
|
|
import certifi
|
|
self.load_verify_locations(certifi.where())
|
|
except Exception:
|
|
pass
|
|
ssl.SSLContext.load_default_certs = _safe
|
|
_patch_ssl()
|
|
|
|
from logger import Logger
|
|
from config import Config
|
|
from screen import grab, convert_screen_to_monitor, start_detecting_window, stop_detecting_window
|
|
from ui_manager import is_visible, ScreenObjects, wait_until_visible
|
|
from input_layer import mouse, keyboard
|
|
from inventory import common, stash
|
|
from d2r_image import processing as d2r_image
|
|
from utils.misc import wait
|
|
from utils.log_rotation import safe_imwrite
|
|
|
|
STASH_COLS = 10
|
|
STASH_ROWS = 10 # stash grid is 10x10, not 10x4 (that's the character inventory)
|
|
TOTAL_PAGES = 6 # 0=personal, 1-5=shared
|
|
|
|
def _stash_origin():
|
|
# Stash contents are in the LEFT panel. left_inventory ROI = [x, y, w, h].
|
|
roi = Config().ui_roi["left_inventory"]
|
|
slot_w = Config().ui_pos["slot_width"]
|
|
slot_h = Config().ui_pos["slot_height"]
|
|
return int(roi[0]), int(roi[1]), slot_w, slot_h
|
|
|
|
def get_stash_slot_pos(col, row):
|
|
x0, y0, sw, sh = _stash_origin()
|
|
return (x0 + sw * col, y0 + sh * row)
|
|
|
|
def stash_slot_has_item(img, col, row):
|
|
x0, y0, sw, sh = _stash_origin()
|
|
x = x0 + sw * col
|
|
y = y0 + sh * row
|
|
margin_x = int(sw * 0.15)
|
|
margin_y = int(sh * 0.15)
|
|
slot_img = img[y + margin_y:y + sh - margin_y, x + margin_x:x + sw - margin_x]
|
|
return np.mean(slot_img) > 12 # slightly higher threshold to skip empty stash cell backgrounds
|
|
|
|
def scan_page(page_idx):
|
|
print(f"\n--- Stash page {page_idx} ---")
|
|
common.select_stash_page(page_idx)
|
|
wait(0.5, 0.8)
|
|
img = grab(True)
|
|
items = []
|
|
# Track which (col, row) slots are already claimed by a multi-slot item so we
|
|
# don't double-count Large Charms (1x2) or Grand Charms (1x3).
|
|
claimed = set()
|
|
for col in range(STASH_COLS):
|
|
for row in range(STASH_ROWS):
|
|
if (col, row) in claimed:
|
|
continue
|
|
if not stash_slot_has_item(img, col, row):
|
|
continue
|
|
slot_pos = get_stash_slot_pos(col, row)
|
|
x_m, y_m = convert_screen_to_monitor(slot_pos)
|
|
mouse.move(x_m, y_m, randomize=3, delay_factor=[0.1, 0.15])
|
|
wait(0.15, 0.2)
|
|
hover_img = grab(True)
|
|
try:
|
|
item_props, item_box = d2r_image.get_hovered_item(hover_img)
|
|
if item_box and item_box.ocr_result:
|
|
text = item_box.ocr_result.text.strip()
|
|
name = text.splitlines()[0] if text.splitlines() else "Unknown"
|
|
items.append({"col": col, "row": row, "name": name, "full_text": text})
|
|
print(f" [{col},{row}] {name}")
|
|
# Mark lower rows in this column as claimed based on item height.
|
|
# get_hovered_item may return BaseItem with dimensions; fall back to
|
|
# scanning down until slot_has_item stops or name changes.
|
|
if item_props and hasattr(item_props, 'BaseItem') and item_props.BaseItem and "dimensions" in item_props.BaseItem:
|
|
dims = item_props.BaseItem["dimensions"]
|
|
h = dims[0] if isinstance(dims, list) else dims.get("height", 1)
|
|
for r in range(row + 1, row + h):
|
|
claimed.add((col, r))
|
|
else:
|
|
# Peek next rows: if same tooltip name, claim them
|
|
for r in range(row + 1, STASH_ROWS):
|
|
if stash_slot_has_item(img, col, r):
|
|
claimed.add((col, r))
|
|
else:
|
|
break
|
|
else:
|
|
print(f" [{col},{row}] (no tooltip)")
|
|
except Exception as e:
|
|
print(f" [{col},{row}] (OCR error: {e})")
|
|
return items
|
|
|
|
def ensure_stash_open():
|
|
img = grab()
|
|
if is_visible(ScreenObjects.GoldBtnStash, img):
|
|
print("Stash UI already open")
|
|
return True
|
|
print("Stash not open — navigating to stash chest...")
|
|
import template_finder
|
|
from ui import main_menu
|
|
from pather import Pather
|
|
from item.pickit import PickIt
|
|
from town import TownManager, A1, A2, A3, A4, A5
|
|
pather = Pather()
|
|
if Config().char["type"] in ("hammerdin", "paladin"):
|
|
from char.paladin.hammerdin import Hammerdin
|
|
char = Hammerdin(Config().hammerdin, pather, PickIt())
|
|
else:
|
|
from char.basic import Basic
|
|
char = Basic(Config().basic, pather)
|
|
char.discover_capabilities()
|
|
tm = TownManager(A1(pather, char), A2(pather, char), A3(pather, char),
|
|
A4(pather, char), A5(pather, char))
|
|
if template_finder.search(main_menu.MAIN_MENU_MARKERS, grab(), best_match=True).valid:
|
|
print("On main menu — creating a game first...")
|
|
if not main_menu.start_game():
|
|
print("ERROR: Could not create a game")
|
|
return False
|
|
act = tm.wait_for_town_spawn()
|
|
else:
|
|
act = tm.detect_current_act()
|
|
if act is None:
|
|
print("ERROR: No town marker found. Is the character in town?")
|
|
return False
|
|
if not tm.open_stash(act):
|
|
print("ERROR: Could not open stash chest")
|
|
return False
|
|
return common.wait_for_left_inventory()
|
|
|
|
def main():
|
|
Logger.info("=== STASH INVENTORY SCANNER ===")
|
|
start_detecting_window()
|
|
try:
|
|
_run()
|
|
finally:
|
|
stop_detecting_window()
|
|
|
|
def _run():
|
|
if not ensure_stash_open():
|
|
return
|
|
all_items = {}
|
|
for page in range(TOTAL_PAGES):
|
|
items = scan_page(page)
|
|
all_items[page] = items
|
|
print("\n" + "=" * 50)
|
|
print("STASH SUMMARY")
|
|
print("=" * 50)
|
|
total = sum(len(v) for v in all_items.values())
|
|
print(f"Total items: {total}")
|
|
for page, items in all_items.items():
|
|
label = "GOLD" if page == 0 else f"Page {page}"
|
|
print(f" {label} ({len(items)} items):")
|
|
for item in items:
|
|
print(f" [{item['row']},{item['col']}] {item['name']}")
|
|
output = "log/stash_inventory.json"
|
|
with open(output, "w") as f:
|
|
json.dump(all_items, f, indent=2)
|
|
print(f"\nSaved to {output}")
|
|
safe_imwrite(f"log/screenshots/info/stash_scan_{time.strftime('%Y%m%d_%H%M%S')}.png", grab())
|
|
print("Screenshot saved")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|