Files
my-botty/src/inventory/personal.py
T
mgleed 2c09459bde Bugfix/enhancement: Optimize waits; implement wait_for_expiration(ScreenObject) function (#629)
* init

* simplify

* the

* init

* update

* init

* fix belt open

* rework waiting functions in ui_manager.py
2022-03-13 20:19:41 -04:00

415 lines
19 KiB
Python

import itertools
from game_stats import GameStats
from item import ItemFinder, Item
from item.item_cropper import ItemText
from logger import Logger
from screen import convert_abs_to_monitor, grab, convert_screen_to_monitor
import keyboard
import cv2
import time
import numpy as np
from template_finder import TemplateFinder
from config import Config
from utils.misc import wait, is_in_roi, mask_by_roi
from utils.custom_mouse import mouse
from inventory import stash, common, vendor
from ui import view
from ui_manager import detect_screen_object, wait_until_visible, ScreenObjects, center_mouse
from item import ItemCropper
from messages import Messenger
inv_gold_full = False
messenger = Messenger()
item_finder = ItemFinder()
nontradable_items = ["key of ", "essense of", "wirt's", "jade figurine"]
def get_inventory_gold_full():
global inv_gold_full
return inv_gold_full
def set_inventory_gold_full(bool):
global inv_gold_full
inv_gold_full = bool
def inventory_has_items(img: np.ndarray = None, close_window = False) -> bool:
"""
Check if Inventory has any items
:param img: Img from screen.grab() with inventory open
:return: Bool if inventory still has items or not
"""
img = open(img)
items=False
for column, row in itertools.product(range(0, Config().char["num_loot_columns"]), range(4)):
_, slot_img = common.get_slot_pos_and_img(img, column, row)
if common.slot_has_item(slot_img):
items=True
break
if close_window:
common.close()
if items:
return True
return False
def stash_all_items(items: list = None):
"""
Stashing all items in inventory. Stash UI must be open when calling the function.
"""
global messenger
if items is None:
Logger.debug("No items to stash, skip")
return False
center_mouse()
# Wait till gold btn is found
Logger.debug("Searching for inventory gold btn...")
if not (gold_btn := wait_until_visible(ScreenObjects.GoldBtnInventory, timeout = 20)).valid:
Logger.error("Could not determine to be in stash menu. Continue...")
return
Logger.debug("Found inventory gold btn")
# stash gold
if Config().char["stash_gold"]:
if wait_until_visible(ScreenObjects.GoldNone, 0.5).valid:
Logger.debug("No gold to stash")
else:
Logger.debug("Stashing gold")
stash.move_to_stash_tab(min(3, stash.curr_stash["gold"]))
wait(0.7, 1)
stash_full_of_gold = False
# Try to read gold count with OCR
try: stash_full_of_gold = common.read_gold(grab(), "stash") == 2500000
except: pass
if not stash_full_of_gold:
# If gold read by OCR fails, fallback to old method
mouse.move(*gold_btn.center, randomize=4)
wait(0.1, 0.15)
mouse.press(button="left")
wait(0.25, 0.35)
mouse.release(button="left")
wait(0.4, 0.6)
keyboard.send("enter") #if stash already full of gold just nothing happens -> gold stays on char -> no popup window
wait(1.3, 1.6)
# move cursor away from button to interfere with screen grab
mouse.move(-120, 0, absolute=False, randomize=15)
stash_full_of_gold = not detect_screen_object(ScreenObjects.GoldNone).valid
if stash_full_of_gold:
Logger.info("Stash tab is full of gold, selecting next stash tab.")
stash.curr_stash["gold"] += 1
if Config().general["info_screenshots"]:
cv2.imwrite("./info_screenshots/info_gold_stash_full_" + time.strftime("%Y%m%d_%H%M%S") + ".png", grab())
if stash.curr_stash["gold"] > 3:
#decide if gold pickup should be disabled or gambling is active
vendor.set_gamble_status(True)
else:
# move to next stash
return stash_all_items(items=items)
else:
set_inventory_gold_full(False)
# check if stash tab is completely full (no empty slots)
stash.move_to_stash_tab(stash.curr_stash["items"])
while stash.curr_stash["items"] <= 3:
img = grab()
if detect_screen_object(ScreenObjects.EmptyStashSlot, img).valid:
break
else:
Logger.info(f"Stash tab completely full, advance to next")
if Config().general["info_screenshots"]:
cv2.imwrite("./info_screenshots/stash_tab_completely_full_" + time.strftime("%Y%m%d_%H%M%S") + ".png", img)
stash.curr_stash["items"] += -1 if Config().char["fill_shared_stash_first"] else 1
if (Config().char["fill_shared_stash_first"] and stash.curr_stash["items"] < 0) or stash.curr_stash["items"] > 3:
stash.stash_full()
stash.move_to_stash_tab(stash.curr_stash["items"])
# stash stuff
while True:
items = common.transfer_items(items, "stash")
if items and any([item.keep for item in items]):
# could not stash all items, stash tab is likely full
Logger.debug("Wanted to stash item, but it's still in inventory. Assumes full stash. Move to next.")
if Config().general["info_screenshots"]:
cv2.imwrite("./info_screenshots/debug_info_inventory_not_empty_" + time.strftime("%Y%m%d_%H%M%S") + ".png", grab())
stash.curr_stash["items"] += -1 if Config().char["fill_shared_stash_first"] else 1
if (Config().char["fill_shared_stash_first"] and stash.curr_stash["items"] < 0) or stash.curr_stash["items"] > 3:
stash.stash_full()
stash.move_to_stash_tab(stash.curr_stash["items"])
else:
break
Logger.debug("Done stashing")
return items
def keep_item(item_box: ItemText = None, found_item: Item = None, do_logging: bool = True) -> bool:
"""
Check if an item should be kept, the item should be hovered and in own inventory when function is called
:param img: Image in which the item is searched (item details should be visible)
:param do_logging: Bool value to turn on/off logging for items that are found and should be kept
:return: Bool if item should be kept
"""
ymax = 50 if item_box.data.shape[0] < 50 else item_box.data.shape[0]
img = item_box.data[0:ymax,:]
if any(x in found_item.name for x in ["potion", "misc_scroll"]) or found_item.name == "misc_key" or (Config().items[found_item.name].pickit_type == 0):
return False
setattr(found_item, "ocr_result", item_box["ocr_result"])
include_props = Config().items[found_item.name].include
exclude_props = Config().items[found_item.name].exclude
if (
#Disable include/exclude params for uniq, rare, magical if ident is disabled in params.ini
(
not Config().char["id_items"]
and any(item_type in found_item.name for item_type in ["uniq", "magic", "rare", "set"])
)
# items that are checked to be kept should all be identified unless an error occurred or user preferred unidentified
or detect_screen_object(ScreenObjects.Unidentified, item_box.data).valid
):
include_props = False
exclude_props = False
if not (include_props or exclude_props):
if do_logging:
Logger.debug(f"{found_item.name}: Stashing")
return True
include = True
include_logic_type = Config().items[found_item.name].include_type
if include_props:
include = False
found_props=[]
for prop in include_props:
if len(prop)>1:
found_subprops=[]
for subprop in prop:
try:
template_match = TemplateFinder().search(subprop, img, threshold=0.95)
except:
Logger.error(f"{found_item.name}: can't find template file for required {prop}, ignore just in case")
template_match = lambda: None; template_match.valid = True
if template_match.valid:
if include_logic_type == "OR":
found_subprops.append(True)
else:
found_props.append (True)
break
else:
found_subprops.append(False)
break
if (len(found_subprops) > 0 and all(found_subprops)):
include = True
break
else:
try:
template_match = TemplateFinder().search(prop, img, threshold=0.95)
except:
Logger.error(f"{found_item.name}: can't find template file for required {prop}, ignore just in case")
template_match = lambda: None; template_match.valid = True
if template_match.valid:
if include_logic_type == "AND":
found_props.append(True)
else:
include = True
break
else:
found_props.append(False)
if include_logic_type == "AND" and len(found_props) > 0 and all(found_props):
include = True
if not include:
if do_logging:
Logger.debug(f"{found_item.name}: Discarding. Required {include_logic_type}({include_props})={include}")
return False
exclude = False
exclude_logic_type = Config().items[found_item.name].exclude_type
if exclude_props:
found_props = []
for prop in exclude_props:
try:
template_match = TemplateFinder().search(prop, img, threshold=0.97)
except:
Logger.error(f"{found_item.name}: can't find template file for exclusion {prop}, ignore just in case")
template_match = lambda: None; template_match.valid = False
if template_match.valid:
if exclude_logic_type == "AND":
found_props.append(True)
else:
exclude = True
break
else:
found_props.append(False)
if exclude_logic_type == "AND" and len(exclude_props) > 0 and all(found_props):
exclude = True
if include and not exclude:
if do_logging:
Logger.debug(f"{found_item.name}: Stashing. Required {include_logic_type}({include_props})={include}, exclude {exclude_logic_type}({exclude_props})={exclude}")
return True
return False
def specific_inventory_roi(desired: str = "reserved"):
#roi spec: left, top, W, H
roi = Config().ui_roi["right_inventory"].copy()
open_width = Config().ui_pos["slot_width"] * Config().char["num_loot_columns"]
if desired == "reserved":
roi[0]=roi[0] + open_width
roi[2]=roi[2] - open_width
elif desired == "open":
roi[2]=open_width
else:
Logger.error(f"set_inventory_rois: unsupported desired={desired}")
return None
return roi
def open(img: np.ndarray = None) -> np.ndarray:
img = grab() if img is None else img
if not detect_screen_object(ScreenObjects.RightPanel, img).valid:
keyboard.send(Config().char["inventory_screen"])
if not wait_until_visible(ScreenObjects.RightPanel, 1).valid:
if not view.return_to_play():
return None
keyboard.send(Config().char["inventory_screen"])
if not wait_until_visible(ScreenObjects.RightPanel, 1).valid:
return None
img = grab()
return img
def inspect_items(inp_img: np.ndarray = None, close_window: bool = True, game_stats: GameStats = None):
"""
Iterate over all picked items in inventory--ID items and decide which to stash
:param img: Image in which the item is searched (item details should be visible)
"""
center_mouse()
img = open(inp_img)
vendor_open = detect_screen_object(ScreenObjects.GoldBtnVendor, inp_img).valid
slots = []
# check which slots have items
for column, row in itertools.product(range(Config().char["num_loot_columns"]), range(4)):
slot_pos, slot_img = common.get_slot_pos_and_img(img, column, row)
if common.slot_has_item(slot_img):
slots.append([slot_pos, row, column])
boxes = []
# iterate over slots with items
item_rois = []
for count, slot in enumerate(slots):
failed = False
# ignore this slot if it lies within in a previous item's ROI
if any(is_in_roi(item_roi, slot[0]) for item_roi in item_rois): continue
img = grab()
x_m, y_m = convert_screen_to_monitor(slot[0])
delay = [0.2, 0.3] if count else [1, 1.3]
mouse.move(x_m, y_m, randomize = 10, delay_factor = delay)
wait(0.1, 0.2)
hovered_item = grab()
# get the item description box
item_box = ItemCropper().crop_item_descr(hovered_item)
if item_box.valid:
# determine the item's ROI in inventory
cnt=0
while True:
pre = mask_by_roi(img, specific_inventory_roi("open"))
post = mask_by_roi(hovered_item, specific_inventory_roi("open"))
# will sometimes have equivalent diff if mouse ends up in an inconvenient place.
if not np.array_equal(pre, post):
break
Logger.debug(f"inspect_items: pre=post, try again. slot {slot[0]}")
center_mouse()
img = grab()
mouse.move(x_m, y_m, randomize = 10, delay_factor = delay)
wait(0.3, 0.5)
hovered_item = grab()
cnt += 1
if cnt >= 2:
Logger.error(f"inspect_items: Unable to get item's inventory ROI, slot {slot[0]}")
break
item_name = item_box.ocr_result.text.splitlines()[0] if not vendor_open else item_box.ocr_result.text.splitlines()[1]
extend_roi = item_box.roi[:]
extend_roi[3] = extend_roi[3] + 30
item_roi = common.calc_item_roi(mask_by_roi(pre, extend_roi, type = "inverse"), mask_by_roi(post, extend_roi, type = "inverse"))
if item_roi:
item_rois.append(item_roi)
# determine whether the item can be sold
item_can_be_traded = not any(substring in item_name for substring in nontradable_items)
sell = Config().char["sell_junk"] and item_can_be_traded
# check and see if item exists in pickit
try:
found_item = item_finder.search(item_box.data)[0]
except:
Logger.debug(f"inspect_items: item_finder returned None or error for {item_name}, likely an accidental pick")
Logger.debug(f"Dropping {item_name}")
found_item = False
# attempt to identify item
need_id = False
if Config().char["id_items"] and found_item:
# if this item has no include or exclude properties, leave it unidentified
implied_no_id = not (Config().items[found_item.name].include or Config().items[found_item.name].exclude)
implied_no_id |= not any(item_type in found_item.name for item_type in ["uniq", "magic", "rare", "set"])
if not implied_no_id:
if (is_unidentified := detect_screen_object(ScreenObjects.Unidentified, item_box.data).valid):
need_id = True
center_mouse()
tome_state, tome_pos = common.tome_state(grab(), tome_type = "id", roi = specific_inventory_roi("reserved"))
if is_unidentified and tome_state is not None and tome_state == "ok":
common.id_item_with_tome([x_m, y_m], tome_pos)
need_id = False
# recapture box after ID
mouse.move(x_m, y_m, randomize = 4, delay_factor = delay)
wait(0.2, 0.3)
hovered_item = grab()
item_box = ItemCropper().crop_item_descr(hovered_item)
if item_box.valid:
Logger.debug(f"OCR ITEM DESCR: Mean conf: {item_box.ocr_result.mean_confidence}")
for i, line in enumerate(list(filter(None, item_box.ocr_result.text.splitlines()))):
Logger.debug(f"OCR LINE{i}: {line}")
if Config().general["loot_screenshots"]:
timestamp = time.strftime("%Y%m%d_%H%M%S")
found_low_confidence = False
for cnt, x in enumerate(item_box.ocr_result['word_confidences']):
if x <= 88:
try:
Logger.debug(f"Low confidence word #{cnt}: {item_box.ocr_result['original_text'].split()[cnt]} -> {item_box.ocr_result['text'].split()[cnt]}, Conf: {x}, save screenshot")
found_low_confidence = True
except: pass
if found_low_confidence:
cv2.imwrite(f"./loot_screenshots/ocr_box_{timestamp}_o.png", item_box.ocr_result['original_img'])
cv2.imwrite(f"./loot_screenshots/ocr_box_{timestamp}_n.png", item_box.ocr_result['processed_img'])
# decide whether to keep item
keep = False
if not need_id:
keep = keep_item(item_box, found_item) if found_item else False
if keep:
sell = need_id = False
box = common.BoxInfo(
img = item_box.data,
pos = (x_m, y_m),
column = slot[2],
row = slot[1],
need_id = need_id,
sell = sell,
keep = keep
)
# sell if not keeping item, vendor is open, and item type can be traded
if not (keep or need_id) and vendor_open and item_can_be_traded:
Logger.debug(f"Selling {item_name}")
box.sell = True
common.transfer_items([box], action = "sell")
continue
# if item is to be kept and is already ID'd or doesn't need ID, log and stash
if game_stats is not None and (keep and not need_id):
game_stats.log_item_keep(found_item.name, Config().items[found_item.name].pickit_type == 2, item_box.data, item_box.ocr_result.text)
# if item is to be kept or still needs to be sold or identified, append to list
if keep or sell or need_id:
# save item info
boxes.append(box)
else:
# if item isn't going to be kept (or sold if vendor window not open), trash it
Logger.debug(f"Dropping {item_name}")
common.transfer_items([box], action = "drop")
wait(0.3, 0.5)
else:
failed = True
else:
failed = True
if failed:
Logger.error(f"item_cropper failed for slot_pos: {slot[0]}")
if Config().general["info_screenshots"]:
cv2.imwrite("./info_screenshots/failed_item_box_" + time.strftime("%Y%m%d_%H%M%S") + ".png", hovered_item)
if close_window:
common.close()
return boxes