diff --git a/README.md b/README.md index ad1b6b8..3280fd2 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,7 @@ run_shenk=0 | id_items | Will identify items at cain before stashing them. Cain must be rescued for this to work.| | open_chests | Open up chests in some places. E.g. on dead ends of arcane. Note: currently bad runtime. | | fill_shared_stash_first | Fill stash tabs starting from right to left, filling personal stash last | +| gamble_items | List of items to gamble when stash fills with gold. Leave blank to disable. Supported items currently include circlet, ring, coronet, talon, amulet ### Builds | [sorceress] | Descriptions | diff --git a/assets/gamble/amulet.png b/assets/gamble/amulet.png new file mode 100644 index 0000000..af5dc69 Binary files /dev/null and b/assets/gamble/amulet.png differ diff --git a/assets/gamble/circlet.png b/assets/gamble/circlet.png new file mode 100644 index 0000000..72b4138 Binary files /dev/null and b/assets/gamble/circlet.png differ diff --git a/assets/gamble/coronet.png b/assets/gamble/coronet.png new file mode 100644 index 0000000..d00be55 Binary files /dev/null and b/assets/gamble/coronet.png differ diff --git a/assets/gamble/no_gold.png b/assets/gamble/no_gold.png new file mode 100644 index 0000000..600666d Binary files /dev/null and b/assets/gamble/no_gold.png differ diff --git a/assets/gamble/refresh.png b/assets/gamble/refresh.png new file mode 100644 index 0000000..01b2e03 Binary files /dev/null and b/assets/gamble/refresh.png differ diff --git a/assets/gamble/refresh_text.png b/assets/gamble/refresh_text.png new file mode 100644 index 0000000..3811fbb Binary files /dev/null and b/assets/gamble/refresh_text.png differ diff --git a/assets/gamble/ring.png b/assets/gamble/ring.png new file mode 100644 index 0000000..bfe69ce Binary files /dev/null and b/assets/gamble/ring.png differ diff --git a/assets/gamble/talon.png b/assets/gamble/talon.png new file mode 100644 index 0000000..7f71788 Binary files /dev/null and b/assets/gamble/talon.png differ diff --git a/assets/npc/action_btn/gamble.png b/assets/npc/action_btn/gamble.png new file mode 100644 index 0000000..c6fdf2a Binary files /dev/null and b/assets/npc/action_btn/gamble.png differ diff --git a/assets/npc/action_btn/gamble_blue.png b/assets/npc/action_btn/gamble_blue.png new file mode 100644 index 0000000..89a3064 Binary files /dev/null and b/assets/npc/action_btn/gamble_blue.png differ diff --git a/config/params.ini b/config/params.ini index 7cff58c..51046e3 100644 --- a/config/params.ini +++ b/config/params.ini @@ -89,6 +89,8 @@ always_repair=0 id_items=1 open_chests=1 fill_shared_stash_first=0 +; to gamble, add any/all of the following: circlet, ring, coronet, talon, amulet +gamble_items= ; =========================== ; ==== Builds: Sorceress ==== diff --git a/src/bot.py b/src/bot.py index b4123ae..78b5f34 100644 --- a/src/bot.py +++ b/src/bot.py @@ -298,6 +298,19 @@ class Bot: if not self._curr_loc: return self.trigger_or_stop("end_game", failed=True) + # Check if gambling is needed + gambling = self._ui_manager.gambling_needed() + if gambling: + for x in range (4): + self._curr_loc = self._town_manager.gamble(self._curr_loc) + self._ui_manager.gamble(self._item_finder) + if (x ==3): + self._curr_loc = self._town_manager.stash (self._curr_loc) + else: + self._curr_loc = self._town_manager.stash (self._curr_loc, gamble=gambling) + + self._ui_manager.set__gold_full (False) + # Start a new run started_run = False for key in self._do_runs: diff --git a/src/config.py b/src/config.py index 1e8d58e..859261a 100644 --- a/src/config.py +++ b/src/config.py @@ -28,6 +28,7 @@ class Config: # config data general = {} advanced_options = {} + gamble = {} ui_roi = {} ui_pos = {} dclone = {} @@ -241,8 +242,8 @@ class Config: "atk_len_cs_trashmobs": float(Config._select_val("char", "atk_len_cs_trashmobs")), "kill_cs_trash": float(Config._select_val("char", "kill_cs_trash")), "always_repair": bool(int(Config._select_val("char", "always_repair"))), + "gamble_items": Config._select_val("char", "gamble_items").replace(" ","").split(","), } - # Sorc base config sorc_base_cfg = dict(Config._config["sorceress"]) if "sorceress" in Config._custom: diff --git a/src/gamble.py b/src/gamble.py new file mode 100644 index 0000000..e69de29 diff --git a/src/npc_manager.py b/src/npc_manager.py index dbc8fdb..e01c825 100644 --- a/src/npc_manager.py +++ b/src/npc_manager.py @@ -166,6 +166,10 @@ class NpcManager: "trade": { "white": color_filter(self._template_finder.get_template("TRADE"), self._config.colors["white"])[1], "blue": color_filter(self._template_finder.get_template("TRADE_BLUE"), self._config.colors["blue"])[1], + }, + "gamble": { + "white": color_filter(self._template_finder.get_template("GAMBLE"), self._config.colors["white"])[1], + "blue": color_filter(self._template_finder.get_template("GAMBLE_BLUE"), self._config.colors["blue"])[1], } }, "roi": [337, 188, (733-337), (622-188)], diff --git a/src/template_finder.py b/src/template_finder.py index fb5b9dc..7d99f25 100644 --- a/src/template_finder.py +++ b/src/template_finder.py @@ -28,12 +28,7 @@ class TemplateFinder: to find these assets within another image IMPORTANT: This method must be thread safe! """ - def __init__( - self, - screen: Screen, - template_pathes: list[str] = ["assets\\templates", "assets\\npc", "assets\\item_properties", "assets\\chests"], - save_last_res: bool = False - ): + def __init__(self, screen: Screen, template_pathes: list[str] = ["assets\\templates", "assets\\npc", "assets\\item_properties", "assets\\chests", "assets\\gamble"], save_last_res: bool = False): self._screen = screen self._config = Config() self._save_last_res = save_last_res diff --git a/src/town/a4.py b/src/town/a4.py index a3997e8..a401f49 100644 --- a/src/town/a4.py +++ b/src/town/a4.py @@ -22,6 +22,7 @@ class A4(IAct): def can_resurrect(self) -> bool: return True def can_buy_pots(self) -> bool: return True def can_identify(self) -> bool: return True + def can_gamble(self) -> bool: return True def can_heal(self) -> bool: return True def can_stash(self) -> bool: return True def can_trade_and_repair(self) -> bool: return True @@ -53,6 +54,13 @@ class A4(IAct): self._npc_manager.press_npc_btn(Npc.CAIN, "identify") return Location.A4_TYRAEL_STASH return False + + def gamble (self, curr_loc: Location) -> Union[Location, bool]: + if not self._pather.traverse_nodes((curr_loc, Location.A4_JAMELLA), self._char, force_move=True): return False + if self._npc_manager.open_npc_menu(Npc.JAMELLA): + self._npc_manager.press_npc_btn(Npc.JAMELLA, "gamble") + return Location.A4_JAMELLA + return False def open_trade_menu(self, curr_loc: Location) -> Union[Location, bool]: if not self._pather.traverse_nodes((curr_loc, Location.A4_JAMELLA), self._char, force_move=True): return False diff --git a/src/town/i_act.py b/src/town/i_act.py index 0153cb5..fed5b8a 100644 --- a/src/town/i_act.py +++ b/src/town/i_act.py @@ -22,6 +22,7 @@ class IAct: def can_trade_and_repair(self) -> bool: return False # Is merc resurrection implemented for this Town? def can_identify(self) -> bool: return False + def can_gamble (self) -> bool: return False # If any of the above functions return True for the Town, the respective method must be implemented def open_trade_menu(self, curr_loc: Location) -> Union[Location, bool]: return False def heal(self, curr_loc: Location) -> Union[Location, bool]: return False @@ -29,3 +30,4 @@ class IAct: def open_stash(self, curr_loc: Location) -> Union[Location, bool]: return False def open_trade_and_repair_menu(self, curr_loc: Location) -> Union[Location, bool]: return False def identify(self, curr_loc: Location) -> Union[Location, bool]: return False + def gamble (self, curr_loc: Location) -> Union[Location, bool]: return False diff --git a/src/town/town_manager.py b/src/town/town_manager.py index 25f9e6c..1279748 100644 --- a/src/town/town_manager.py +++ b/src/town/town_manager.py @@ -125,8 +125,18 @@ class TownManager: new_loc = self.go_to_act(5, curr_loc) if not new_loc: return False return self._acts[Location.A5_TOWN_START].identify(new_loc) + + def gamble (self, curr_loc: Location) -> Union[Location, bool]: + curr_act = TownManager.get_act_from_location(curr_loc) + if curr_act is None: return False + # check if we can Identify in current act + if self._acts[curr_act].can_gamble(): + return self._acts[curr_act].gamble(curr_loc) + new_loc = self.go_to_act(4, curr_loc) + if not new_loc: return False + return self._acts[Location.A4_TOWN_START].gamble(new_loc) - def stash(self, curr_loc: Location) -> Union[Location, bool]: + def stash(self, curr_loc: Location, gamble=False) -> Union[Location, bool]: curr_act = TownManager.get_act_from_location(curr_loc) if curr_act is None: return False # check if we can stash in current act @@ -134,7 +144,7 @@ class TownManager: new_loc = self._acts[curr_act].open_stash(curr_loc) if not new_loc: return False wait(1.0) - self._ui_manager.stash_all_items(self._config.char["num_loot_columns"], self._item_finder) + self._ui_manager.stash_all_items(self._config.char["num_loot_columns"], self._item_finder, gamble) return new_loc new_loc = self.go_to_act(5, curr_loc) if not new_loc: return False diff --git a/src/ui/ui_manager.py b/src/ui/ui_manager.py index ef94524..9fb5a82 100644 --- a/src/ui/ui_manager.py +++ b/src/ui/ui_manager.py @@ -28,6 +28,8 @@ class UiManager(): self._messenger = Messenger() self._game_stats = game_stats self._screen = screen + self._gold_full = False + self._gambling_round = 1 self._curr_stash = { "items": 3 if self._config.char["fill_shared_stash_first"] else 0, "gold": 0 @@ -240,14 +242,14 @@ class UiManager(): center_pos = (int(slot[0] + (slot_width // 2)), int(slot[1] + (slot_height // 2))) return center_pos, slot_img - def _inventory_has_items(self, img, num_loot_columns: int) -> bool: + def _inventory_has_items(self, img, num_loot_columns: int, num_ignore_columns=0) -> bool: """ Check if Inventory has any items :param img: Img from screen.grab() with inventory open :param num_loot_columns: Number of columns to check from left :return: Bool if inventory still has items or not """ - for column, row in itertools.product(range(num_loot_columns), range(4)): + for column, row in itertools.product(range(num_ignore_columns, num_loot_columns), range(4)): _, slot_img = self.get_slot_pos_and_img(self._config, img, column, row) if self._slot_has_item(slot_img): return True @@ -372,7 +374,7 @@ class UiManager(): mouse.click(button="left") wait(0.3, 0.4) - def stash_all_items(self, num_loot_columns: int, item_finder: ItemFinder): + def stash_all_items(self, num_loot_columns: int, item_finder: ItemFinder, gamble = False): """ Stashing all items in inventory. Stash UI must be open when calling the function. :param num_loot_columns: Number of columns used for loot from left @@ -387,41 +389,51 @@ class UiManager(): Logger.error("Could not determine to be in stash menu. Continue...") return Logger.debug("Found inventory gold btn") - # stash gold - if self._config.char["stash_gold"]: - inventory_no_gold = self._template_finder.search("INVENTORY_NO_GOLD", self._screen.grab(), roi=self._config.ui_roi["inventory_gold"], threshold=0.83) - if inventory_no_gold.valid: - Logger.debug("Skipping gold stashing") - else: - Logger.debug("Stashing gold") - self._move_to_stash_tab(min(3, self._curr_stash["gold"])) - 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.0, 1.2) - # move cursor away from button to interfere with screen grab - mouse.move(-120, 0, absolute=False, randomize=15) + if not gamble: + # stash gold + if self._config.char["stash_gold"]: inventory_no_gold = self._template_finder.search("INVENTORY_NO_GOLD", self._screen.grab(), roi=self._config.ui_roi["inventory_gold"], threshold=0.83) - if not inventory_no_gold.valid: - Logger.info("Stash tab is full of gold, selecting next stash tab.") - self._curr_stash["gold"] += 1 - if self._config.general["info_screenshots"]: - cv2.imwrite("./info_screenshots/info_gold_stash_full_" + time.strftime("%Y%m%d_%H%M%S") + ".png", self._screen.grab()) - if self._curr_stash["gold"] > 3: - # turn off gold pickup - self._config.turn_off_goldpickup() - # inform user about it - Logger.info("All stash tabs and character are full of gold, turn of gold pickup") - if self._config.general["custom_message_hook"]: - self._messenger.send_gold() - else: - # move to next stash - wait(0.5, 0.6) - return self.stash_all_items(num_loot_columns, item_finder) + if inventory_no_gold.valid: + Logger.debug("Skipping gold stashing") + else: + Logger.debug("Stashing gold") + self._move_to_stash_tab(min(3, self._curr_stash["gold"])) + 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.0, 1.2) + # move cursor away from button to interfere with screen grab + mouse.move(-120, 0, absolute=False, randomize=15) + inventory_no_gold = self._template_finder.search("INVENTORY_NO_GOLD", self._screen.grab(), roi=self._config.ui_roi["inventory_gold"], threshold=0.83) + if not inventory_no_gold.valid: + Logger.info("Stash tab is full of gold, selecting next stash tab.") + self._curr_stash["gold"] += 1 + if self._config.general["info_screenshots"]: + cv2.imwrite("./info_screenshots/info_gold_stash_full_" + time.strftime("%Y%m%d_%H%M%S") + ".png", self._screen.grab()) + if self._curr_stash["gold"] > 3: + #decide if gold pickup should be disabled or gambling is active + if (self._config.char["gamble_items"]): + self._gold_full = True + else: + # turn off gold pickup + self._config.char["stash_gold"] = False + self._config.items["misc_gold"].pickit_type = 0 + item_finder.update_items_to_pick(self._config) + # inform user about it + msg = "All stash tabs and character are full of gold, turn of gold pickup" + Logger.info(msg) + if self._config.general["custom_message_hook"]: + self._messenger.send(msg=f"{self._config.general['name']}: {msg}") + else: + # move to next stash + wait(0.5, 0.6) + return self.stash_all_items(num_loot_columns, item_finder) + else: + self.transfer_shared_to_private_gold (self._gambling_round) # stash stuff self._move_to_stash_tab(self._curr_stash["items"]) center_m = self._screen.convert_abs_to_monitor((0, 0)) @@ -502,6 +514,34 @@ class UiManager(): wait(0.4, 0.5) keyboard.send("esc") + def transfer_shared_to_private_gold (self, count: int): + for x in range (3): + self._move_to_stash_tab (count) + stash_gold_btn = self._template_finder.search("INVENTORY_GOLD_BTN", self._screen.grab(), roi=self._config.ui_roi["gold_btn_stash"], threshold=0.83) + if stash_gold_btn.valid: + x,y = self._screen.convert_screen_to_monitor(stash_gold_btn.center) + mouse.move(x, y, randomize=4) + wait (0.4, 0.5) + mouse.press(button="left") + wait (0.1, 0.15) + mouse.release(button="left") + wait (0.1, 0.15) + keyboard.send ("Enter") + wait (0.1, 0.15) + self._move_to_stash_tab (0) + inventory_gold_btn = self._template_finder.search("INVENTORY_GOLD_BTN", self._screen.grab(), roi=self._config.ui_roi["gold_btn"], threshold=0.83) + if inventory_gold_btn.valid: + x,y = self._screen.convert_screen_to_monitor(inventory_gold_btn.center) + mouse.move(x, y, randomize=4) + wait (0.4, 0.5) + mouse.press(button="left") + wait (0.1, 0.15) + mouse.release(button="left") + wait (0.1, 0.15) + keyboard.send ("Enter") + wait (0.1, 0.15) + self._gambling_round += 1 + def should_stash(self, num_loot_columns: int): """ Check if there are items that need to be stashed in the inventory @@ -596,6 +636,69 @@ class UiManager(): use_grayscale=True) return template_match.valid + def gambling_needed(self) -> bool: + return self._gold_full + + def set__gold_full (self, bool: bool): + self._gold_full = bool + self._gambling_round = 1 + + def gamble(self, item_finder: ItemFinder): + gold = True + gamble_on = True + if self._config.char["num_loot_columns"]%2==0: + ignore_columns = self._config.char["num_loot_columns"]-1 + else: + ignore_columns = self._config.char["num_loot_columns"]-2 + template_match = self._template_finder.search_and_wait("REFRESH", threshold=0.79, time_out=4) + if template_match.valid: + #Gambling window is open. Starting to spent some coins + while (gamble_on and gold): + if (self._inventory_has_items (self._screen.grab(),self._config.char["num_loot_columns"], ignore_columns) and self._inventory_has_items (self._screen.grab(),2)): + gamble_on = False + self.close_vendor_screen () + break + for item in self._config.char["gamble_items"]: + template_match_item = self._template_finder.search (item.upper(), self._screen.grab(), roi=self._config.ui_roi["vendor_stash"], normalize_monitor=True) + while not template_match_item.valid: + #Refresh gambling screen + template_match = self._template_finder.search ("REFRESH", self._screen.grab(), normalize_monitor=True) + if (template_match.valid): + mouse.move(*template_match.center, randomize=12, delay_factor=[1.0, 1.5]) + wait(0.1, 0.15) + mouse.click(button="left") + wait(0.1, 0.15) + template_match_item = self._template_finder.search (item.upper(), self._screen.grab(), roi=self._config.ui_roi["vendor_stash"], normalize_monitor=True) + #item found in gambling menu + mouse.move(*template_match_item.center, randomize=12, delay_factor=[1.0, 1.5]) + wait(0.1, 0.15) + mouse.click(button="right") + wait(0.1, 0.15) + template_match = self._template_finder.search ("no_gold".upper(), self._screen.grab(), threshold= 0.90) + #check if gold is av + if template_match.valid: + gold = False + self.close_vendor_screen () + break + for column, row in itertools.product(range(self._config.char["num_loot_columns"]), range(4)): + img = self._screen.grab() + slot_pos, slot_img = self.get_slot_pos_and_img(self._config, img, column, row) + if self._slot_has_item(slot_img): + x_m, y_m = self._screen.convert_screen_to_monitor(slot_pos) + mouse.move(x_m, y_m, randomize=10, delay_factor=[1.0, 1.3]) + # check item again and discard it or stash it + wait(1.2, 1.4) + hovered_item = self._screen.grab() + if not self._keep_item(item_finder, hovered_item): + keyboard.send('ctrl', do_release=False) + wait(0.1, 0.15) + mouse.click (button="left") + wait(0.1, 0.15) + keyboard.send('ctrl', do_press=False) + #Stashing needed + else: + Logger.warning("gambling failed") + def enable_no_pickup(self) -> bool: """ Checks the best match between enabled and disabled an retrys if already set. @@ -658,4 +761,4 @@ if __name__ == "__main__": template_finder = TemplateFinder(screen) item_finder = ItemFinder() ui_manager = UiManager(screen, template_finder, game_stats) - ui_manager.stash_all_items(5, item_finder) + ui_manager.gamble (item_finder) \ No newline at end of file