Connect all towns (#220)

This commit is contained in:
aeon0
2021-12-06 18:15:58 +01:00
committed by GitHub
parent 0b6b1bfa2a
commit bbda12709f
21 changed files with 511 additions and 167 deletions
+1
View File
@@ -104,6 +104,7 @@ run_shenk=0
| show_items | Hotkey for "show |
| inventory_screen | Hotkey to open up |
| stand_still | Hotkey for "stand still". Note this can not be the default shift key as it would interfere with the merc healing routine. |
| force_move | Hotkey for "force move" |
| tp | Hotkey for using a town |
| belt_rows | Integer value of how many rows the char's belt has |
| show_belt | Hotkey for "show |
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

+1
View File
@@ -96,6 +96,7 @@ go_btn=533,627,213,67
difficulty_select=556,278,168,142
gold_btn=927,491,267,100
inventory_gold=980,510,150,40
gold_btn_stash=150,480,40,65
health_globe=160,580,240,140
mana_globe=887,580,240,140
cut_skill_bar=0,0,1280,653
+1
View File
@@ -42,6 +42,7 @@ show_items=alt
inventory_screen=i
;note: stand_still can not be the default "shift" as it would interfere with merc healing
stand_still=capslock
force_move=e
tp=f9
belt_rows=4
;note: this is different from the default hotkey as "~" is for many keyboards not reachable without also pressing altgr
+77 -104
View File
@@ -5,6 +5,7 @@ from template_finder import TemplateFinder
from item_finder import ItemFinder
from screen import Screen
from ui_manager import UiManager
from npc_manager import NpcManager
from belt_manager import BeltManager
from pather import Pather, Location
from logger import Logger
@@ -13,7 +14,7 @@ from char.i_char import IChar
from config import Config
from health_manager import HealthManager
from death_manager import DeathManager
from npc_manager import NpcManager, Npc
from town import TownManager, A3, A4, A5
from pickit import PickIt
from game_stats import GameStats
from utils.misc import wait
@@ -34,7 +35,6 @@ class Bot:
self._ui_manager = UiManager(self._screen, self._template_finder)
self._belt_manager = BeltManager(self._screen, self._template_finder)
self._pather = Pather(self._screen, self._template_finder)
self._npc_manager = NpcManager(self._screen, self._template_finder)
self._pickit = PickIt(self._screen, self._item_finder, self._ui_manager, self._belt_manager, self._game_stats)
if self._config.char["type"] == "sorceress":
self._char: IChar = Sorceress(self._config.sorceress, self._config.char, self._screen, self._template_finder, self._ui_manager, self._pather)
@@ -43,6 +43,11 @@ class Bot:
else:
Logger.error(f'{self._config.char["type"]} is not supported! Closing down bot.')
os._exit(1)
npc_manager = NpcManager(screen, self._template_finder)
a5 = A5(self._screen, self._template_finder, self._pather, self._char, npc_manager)
a4 = A4(self._screen, self._template_finder, self._pather, self._char, npc_manager)
a3 = A3(self._screen, self._template_finder, self._pather, self._char, npc_manager)
self._town_manager = TownManager(self._template_finder, self._ui_manager, a3, a4, a5)
self._route_config = self._config.routes
if self._route_config["run_shenk"] and not self._route_config["run_eldritch"]:
Logger.error("Running shenk without eldtritch is not supported. Either run none, both or eldritch only.")
@@ -54,8 +59,7 @@ class Bot:
if self._config.general["randomize_runs"]:
self.shuffle_runs()
self._picked_up_items = False
self._tp_is_up = False
self._curr_location: Location = None
self._curr_loc: Location = None
self._tps_left = 20
self._pre_buffed = 0
self._stopping = False
@@ -64,17 +68,17 @@ class Bot:
self._no_stash_counter = 0
self._ran_no_pickup = False
self._states=['hero_selection', 'a5_town', 'pindle', 'shenk']
self._states=['hero_selection', 'town', 'pindle', 'shenk']
self._transitions = [
{ 'trigger': 'create_game', 'source': 'hero_selection', 'dest': 'a5_town', 'before': "on_create_game"},
{ 'trigger': 'create_game', 'source': 'hero_selection', 'dest': 'town', 'before': "on_create_game"},
# Tasks within town
{ 'trigger': 'maintenance', 'source': 'a5_town', 'dest': 'a5_town', 'before': "on_maintenance"},
{ 'trigger': 'maintenance', 'source': 'town', 'dest': 'town', 'before': "on_maintenance"},
# Different runs
{ 'trigger': 'run_pindle', 'source': 'a5_town', 'dest': 'pindle', 'before': "on_run_pindle"},
{ 'trigger': 'run_shenk', 'source': 'a5_town', 'dest': 'shenk', 'before': "on_run_shenk"},
{ 'trigger': 'run_pindle', 'source': 'town', 'dest': 'pindle', 'before': "on_run_pindle"},
{ 'trigger': 'run_shenk', 'source': 'town', 'dest': 'shenk', 'before': "on_run_shenk"},
# End run / game
{ 'trigger': 'end_run', 'source': ['shenk', 'pindle'], 'dest': 'a5_town', 'before': "on_end_run"},
{ 'trigger': 'end_game', 'source': ['a5_town', 'shenk', 'pindle', 'end_run'], 'dest': 'hero_selection', 'before': "on_end_game"},
{ 'trigger': 'end_run', 'source': ['shenk', 'pindle'], 'dest': 'town', 'before': "on_end_run"},
{ 'trigger': 'end_game', 'source': ['town', 'shenk', 'pindle', 'end_run'], 'dest': 'hero_selection', 'before': "on_end_game"},
]
self.machine = Machine(model=self, states=self._states, initial="hero_selection", transitions=self._transitions, queued=True)
@@ -85,7 +89,7 @@ class Bot:
self.machine.get_graph().draw('my_state_diagram.png', prog='dot')
def get_curr_location(self):
return self._curr_location
return self._curr_loc
def start(self):
self.trigger('create_game')
@@ -127,96 +131,64 @@ class Bot:
return not found_unfinished_run
def on_create_game(self):
keyboard.release(self._config.char["stand_still"])
# Start a game from hero selection
self._game_stats.log_start_game()
template_match = self._template_finder.search_and_wait("D2_LOGO_HS", roi=self._config.ui_roi["hero_selection_logo"])
if template_match.valid:
Logger.debug(f"Found {template_match.name}")
if not self._ui_manager.start_game():
return
self._template_finder.search_and_wait(["A5_TOWN_1", "A5_TOWN_0"])
self._tp_is_up = False
self._curr_location = Location.A5_TOWN_START
# Make sure these keys are released
keyboard.release(self._config.char["stand_still"])
wait(0.05, 0.15)
keyboard.release(self._config.char["show_items"])
wait(0.05, 0.15)
if not self._ui_manager.start_game(): return
self._curr_loc = self._town_manager.wait_for_town_spawn()
# Run /nopickup command to avoid picking up stuff on accident
if not self._ran_no_pickup:
if self._ui_manager.enable_no_pickup():
self._ran_no_pickup = True
Logger.info("Enabled No Item Pickup.")
Logger.info("Enabled No Item Pickup")
self.trigger_or_stop("maintenance")
def on_maintenance(self):
if self._pick_corpse:
self._pick_corpse = False
time.sleep(0.6)
# TODO: Test corpse pickup in A3 (and A4)
DeathManager.pick_up_corpse(self._config, self._screen)
wait(1.2, 1.5)
self._belt_manager.fill_up_belt_from_inventory(self._config.char["num_loot_columns"])
self._belt_manager.update_pot_needs()
# Check if healing is needed, TODO: add shoping e.g. for potions
img = self._screen.grab()
# TODO: If tp is up we always go back into the portal...
if not self._tp_is_up and (HealthManager.get_health(self._config, img) < 0.6 or HealthManager.get_mana(self._config, img) < 0.3):
Logger.info("Need some healing first. Going to Malah.")
if not self._pather.traverse_nodes(self._curr_location, Location.A5_MALAH, self._char):
self.trigger_or_stop("end_game")
return
self._curr_location = Location.A5_MALAH
self._npc_manager.open_npc_menu(Npc.MALAH)
if not self._pather.traverse_nodes(self._curr_location, Location.A5_TOWN_START, self._char):
self.trigger_or_stop("end_game")
return
self._curr_location = Location.A5_TOWN_START
# Stash stuff, either when item was picked up or after 4 runs without stashing (so unwanted loot will not cause inventory full)
# but should not happen much with /nopickup set
self._no_stash_counter += 1
if self._picked_up_items or (self._no_stash_counter > 4 and self._ui_manager.should_stash(self._config.char["num_loot_columns"])):
self._no_stash_counter = 0
Logger.info("Stashing picked up items.")
if not self._pather.traverse_nodes(self._curr_location, Location.A5_STASH, self._char):
self.trigger_or_stop("end_game")
return
self._curr_location = Location.A5_STASH
time.sleep(0.3)
stash_is_open_func = lambda: self._template_finder.search("INVENTORY_GOLD_BTN", self._screen.grab(), roi=self._config.ui_roi["gold_btn"]).valid
if self._char.select_by_template("A5_STASH", stash_is_open_func):
self._ui_manager.stash_all_items(self._config.char["num_loot_columns"], self._item_finder)
self._picked_up_items = False
time.sleep(1.2) # otherwise next grab of screen will still have inventory
else:
Logger.warning("Could not find stash, continue...")
if self._tps_left < 3 or (self._config.char["tp"] and not self._ui_manager.has_tps()):
Logger.info("Repairing and buying TPs at Larzuk.")
if not self._pather.traverse_nodes(self._curr_location, Location.A5_LARZUK, self._char):
self.trigger_or_stop("end_game")
return
self._curr_location = Location.A5_LARZUK
self._npc_manager.open_npc_menu(Npc.LARZUK)
self._npc_manager.press_npc_btn(Npc.LARZUK, "trade_repair")
if self._ui_manager.repair_and_fill_up_tp():
wait(0.1, 0.2)
self._ui_manager.close_vendor_screen()
self._tps_left = 20
wait(0.5)
self._belt_manager.update_pot_needs()
# Do some healing TODO: If tp is up we always go back into the portal... could use force move here?
img = self._screen.grab()
hp = HealthManager.get_health(self._config, img)
mp = HealthManager.get_mana(self._config, img)
if hp < 0.6 or mp < 0.2:
Logger.info("Healing at next possible Vendor")
self._curr_loc = self._town_manager.heal(self._curr_loc)
if not self._curr_loc:
return self.trigger_or_stop("end_game")
# Stash stuff, either when item was picked up or after 4 runs without stashing (so unwanted loot will not cause inventory full)
self._no_stash_counter += 1
force_stash = self._no_stash_counter > 4 and self._ui_manager.should_stash(self._config.char["num_loot_columns"])
if self._picked_up_items or force_stash:
self._no_stash_counter = 0
Logger.info("Stashing items")
self._curr_loc = self._town_manager.stash(self._curr_loc)
if not self._curr_loc:
return self.trigger_or_stop("end_game")
wait(1.0)
# Check if we are out of tps
if self._tps_left < 3 or (self._config.char["tp"] and not self._ui_manager.has_tps()):
Logger.info("Repairing and buying TPs at next Vendor")
self._curr_loc = self._town_manager.repair_and_fill_tps(self._curr_loc)
if not self._curr_loc:
return self.trigger_or_stop("end_game")
wait(1.0)
# Check if merc needs to be revived
merc_alive = self._template_finder.search("MERC", self._screen.grab(), threshold=0.9, roi=[0, 0, 200, 200]).valid
if not merc_alive and self._config.char["use_merc"]:
Logger.info("Reviving merc.")
if not self._pather.traverse_nodes(self._curr_location, Location.A5_QUAL_KEHK, self._char):
self.trigger_or_stop("end_game")
return
self._curr_location = Location.A5_QUAL_KEHK
if self._npc_manager.open_npc_menu(Npc.QUAL_KEHK):
self._npc_manager.press_npc_btn(Npc.QUAL_KEHK, "resurrect")
time.sleep(1.2)
Logger.info("Resurrect merc")
self._curr_loc = self._town_manager.resurrect(self._curr_loc)
if not self._curr_loc:
return self.trigger_or_stop("end_game")
# Start a new run
started_run = False
for key in self._do_runs:
@@ -230,12 +202,15 @@ class Bot:
def on_run_pindle(self):
def do_it() -> bool:
Logger.info("Run Pindle")
if not self._pather.traverse_nodes(self._curr_location, Location.A5_NIHLATHAK_PORTAL, self._char): return False
self._curr_location = Location.A5_NIHLATHAK_PORTAL
wait(0.3, 0.4) # otherwise will often missclick because still moving
self._curr_loc = self._town_manager.go_to_act(5, self._curr_loc)
if not self._curr_loc:
return False
if not self._pather.traverse_nodes(self._curr_loc, Location.A5_NIHLATHAK_PORTAL, self._char): return False
self._curr_loc = Location.A5_NIHLATHAK_PORTAL
wait(0.4)
found_loading_screen_func = lambda: self._ui_manager.wait_for_loading_screen(2.0)
if not self._char.select_by_template(["A5_RED_PORTAL", "A5_RED_PORTAL_TEXT"], found_loading_screen_func): return False
self._curr_location = Location.PINDLE_START
self._curr_loc = Location.A5_PINDLE_START
if not self._template_finder.search_and_wait(["PINDLE_0", "PINDLE_1"], threshold=0.65, time_out=20).valid: return False
if not self._pre_buffed:
self._char.pre_buff()
@@ -243,9 +218,10 @@ class Bot:
if self._config.char["static_path_pindle"]:
self._pather.traverse_nodes_fixed("pindle_save_dist", self._char)
else:
if not self._pather.traverse_nodes(Location.PINDLE_START, Location.PINDLE_SAVE_DIST, self._char): return False
if not self._pather.traverse_nodes(Location.A5_PINDLE_START, Location.A5_PINDLE_SAVE_DIST, self._char): return False
self._char.kill_pindle()
self._picked_up_items = self._pickit.pick_up_items(self._char)
self._curr_loc = Location.A5_PINDLE_END
return True
self._do_runs["run_pindle"] = False
@@ -258,14 +234,11 @@ class Bot:
def on_run_shenk(self):
def do_it():
Logger.info("Run Eldritch")
if not self._pather.traverse_nodes(self._curr_location, Location.A5_WP, self._char): return False
self._curr_location = Location.A5_WP
wait(0.5, 0.7) # otherwise will missclick because still moving
found_wp_func = lambda: self._template_finder.search("WAYPOINT_MENU", self._screen.grab()).valid
if not self._char.select_by_template("A5_WP", found_wp_func): return False
self._ui_manager.use_wp(4, 1)
self._curr_loc = self._town_manager.open_wp(self._curr_loc)
wait(0.4)
self._ui_manager.use_wp(5, 1)
# eldritch
self._curr_location = Location.ELDRITCH_START
self._curr_loc = Location.A5_ELDRITCH_START
if not self._template_finder.search_and_wait(["ELDRITCH_0", "ELDRITCH_START"], threshold=0.65, time_out=20).valid: return False
if not self._pre_buffed:
self._char.pre_buff()
@@ -273,17 +246,19 @@ class Bot:
if self._config.char["static_path_eldritch"]:
self._pather.traverse_nodes_fixed("eldritch_save_dist", self._char)
else:
if not self._pather.traverse_nodes(Location.ELDRITCH_START, Location.ELDRITCH_SAVE_DIST, self._char): return False
if not self._pather.traverse_nodes(Location.A5_ELDRITCH_START, Location.A5_ELDRITCH_SAVE_DIST, self._char): return False
self._char.kill_eldritch()
self._curr_loc = Location.A5_ELDRITCH_END
self._picked_up_items = self._pickit.pick_up_items(self._char)
# shenk
if self._route_config["run_shenk"]:
Logger.info("Run Shenk")
self._curr_location = Location.SHENK_START
if not self._pather.traverse_nodes(Location.SHENK_START, Location.SHENK_SAVE_DIST, self._char): return False
self._curr_loc = Location.A5_SHENK_START
if not self._pather.traverse_nodes(Location.A5_SHENK_START, Location.A5_SHENK_SAVE_DIST, self._char): return False
self._char.kill_shenk()
wait(1.9, 2.4) # sometimes merc needs some time to kill shenk...
wait(1.9, 2.4) # sometimes merc needs some more time to kill shenk...
self._picked_up_items |= self._pickit.pick_up_items(self._char)
self._curr_loc = Location.A5_SHENK_END
return True
self._do_runs["run_shenk"] = False
@@ -310,10 +285,8 @@ class Bot:
success = self._char.tp_town()
if success:
self._tps_left -= 1
success = self._template_finder.search_and_wait(["A5_TOWN_1", "A5_TOWN_0"], time_out=10).valid
if success:
self._tp_is_up = True
self._curr_location = Location.A5_TOWN_START
self._curr_loc = self._town_manager.wait_for_tp(self._curr_loc)
if self._curr_loc:
self.trigger_or_stop("maintenance")
else:
self.trigger_or_stop("end_game")
@@ -329,6 +302,6 @@ if __name__ == "__main__":
config = Config()
screen = Screen(config.general["monitor"])
bot = Bot(screen)
bot.state = "a5_town"
bot._curr_location = Location.A5_TOWN_START
bot.state = "town"
bot._curr_loc = Location.A5_TOWN_START
bot.on_maintenance()
+5 -5
View File
@@ -65,10 +65,10 @@ class Hammerdin(IChar):
if not self._do_pre_move:
keyboard.send(self._skill_hotkeys["concentration"])
wait(0.05, 0.15)
self._pather.traverse_nodes(Location.PINDLE_SAVE_DIST, Location.PINDLE_END, self, time_out=1.0, do_pre_move=self._do_pre_move)
self._pather.traverse_nodes(Location.A5_PINDLE_SAVE_DIST, Location.A5_PINDLE_END, self, time_out=1.0, do_pre_move=self._do_pre_move)
self._cast_hammers(1)
# pindle sometimes knocks back, get back in
self._pather.traverse_nodes(Location.PINDLE_SAVE_DIST, Location.PINDLE_END, self, time_out=0.1)
self._pather.traverse_nodes(Location.A5_PINDLE_SAVE_DIST, Location.A5_PINDLE_END, self, time_out=0.1)
self._cast_hammers(max(1, self._char_config["atk_len_pindle"] - 1))
wait(0.1, 0.15)
self._do_redemption()
@@ -81,7 +81,7 @@ class Hammerdin(IChar):
if not self._do_pre_move:
keyboard.send(self._skill_hotkeys["concentration"])
wait(0.05, 0.15)
self._pather.traverse_nodes(Location.ELDRITCH_SAVE_DIST, Location.ELDRITCH_END, self, time_out=1.0, do_pre_move=self._do_pre_move)
self._pather.traverse_nodes(Location.A5_ELDRITCH_SAVE_DIST, Location.A5_ELDRITCH_END, self, time_out=1.0, do_pre_move=self._do_pre_move)
wait(0.05, 0.1)
self._cast_hammers(self._char_config["atk_len_eldritch"])
wait(0.05, 0.15)
@@ -92,7 +92,7 @@ class Hammerdin(IChar):
if not self._do_pre_move:
keyboard.send(self._skill_hotkeys["concentration"])
wait(0.05, 0.15)
self._pather.traverse_nodes(Location.SHENK_SAVE_DIST, Location.SHENK_END, self, time_out=1.0, do_pre_move=self._do_pre_move)
self._pather.traverse_nodes(Location.A5_SHENK_SAVE_DIST, Location.A5_SHENK_END, self, time_out=1.0, do_pre_move=self._do_pre_move)
wait(0.05, 0.1)
self._cast_hammers(self._char_config["atk_len_shenk"])
wait(0.05, 0.15)
@@ -124,5 +124,5 @@ if __name__ == "__main__":
ui_manager = UiManager(screen, t_finder)
char = Hammerdin(config.hammerdin, config.char, screen, t_finder, ui_manager, pather)
char.pre_buff()
pather.traverse_nodes(Location.ELDRITCH_START, Location.ELDRITCH_SAVE_DIST, char)
pather.traverse_nodes(Location.A5_ELDRITCH_START, Location.A5_ELDRITCH_SAVE_DIST, char)
char.kill_eldritch()
+5 -2
View File
@@ -72,7 +72,7 @@ class IChar:
keyboard.send(self._skill_hotkeys["teleport"])
wait(0.15, 0.25)
def move(self, pos_monitor: Tuple[float, float], force_tp: bool = False):
def move(self, pos_monitor: Tuple[float, float], force_tp: bool = False, force_move: bool = False):
factor = self._config.advanced_options["pathing_delay_factor"]
if self._skill_hotkeys["teleport"] and (force_tp or self._ui_manager.is_right_skill_active()):
mouse.move(pos_monitor[0], pos_monitor[1], randomize=3, delay_factor=[factor*0.1, factor*0.14])
@@ -91,7 +91,10 @@ class IChar:
x, y = self._screen.convert_abs_to_monitor(pos_abs)
mouse.move(x, y, randomize=5, delay_factor=[factor*0.1, factor*0.14])
wait(0.012, 0.02)
mouse.click(button="left")
if force_move:
keyboard.send(self._config.char["force_move"])
else:
mouse.click(button="left")
def tp_town(self):
if not self._ui_manager.has_tps():
+3 -3
View File
@@ -96,7 +96,7 @@ class Sorceress(IChar):
if self._config.char["static_path_pindle"]:
self._pather.traverse_nodes_fixed("pindle_end", self)
else:
self._pather.traverse_nodes(Location.PINDLE_SAVE_DIST, Location.PINDLE_END, self, force_tp=True)
self._pather.traverse_nodes(Location.A5_PINDLE_SAVE_DIST, Location.A5_PINDLE_END, self, force_tp=True)
return True
return False
@@ -117,7 +117,7 @@ class Sorceress(IChar):
if self._config.char["static_path_eldritch"]:
self._pather.traverse_nodes_fixed("eldritch_end", self)
else:
self._pather.traverse_nodes(Location.ELDRITCH_SAVE_DIST, Location.ELDRITCH_END, self, time_out=0.6, force_tp=True)
self._pather.traverse_nodes(Location.A5_ELDRITCH_SAVE_DIST, Location.A5_ELDRITCH_END, self, time_out=0.6, force_tp=True)
return True
return False
@@ -135,7 +135,7 @@ class Sorceress(IChar):
self._left_attack(cast_pos_abs, delay, 90)
wait(self._cast_duration, self._cast_duration + 0.2)
# Move to items
self._pather.traverse_nodes(Location.SHENK_SAVE_DIST, Location.SHENK_END, self, time_out=1.4, force_tp=True)
self._pather.traverse_nodes(Location.A5_SHENK_SAVE_DIST, Location.A5_SHENK_END, self, time_out=1.4, force_tp=True)
return True
return False
+1
View File
@@ -51,6 +51,7 @@ class Config:
"show_items": self._select_val("char", "show_items"),
"inventory_screen": self._select_val("char", "inventory_screen"),
"stand_still": self._select_val("char", "stand_still"),
"force_move": self._select_val("char", "force_move"),
"num_loot_columns": int(self._select_val("char", "num_loot_columns")),
"take_health_potion": float(self._select_val("char", "take_health_potion")),
"take_mana_potion": float(self._select_val("char", "take_mana_potion")),
+2 -3
View File
@@ -84,6 +84,5 @@ if __name__ == "__main__":
keyboard.wait("f11")
config = Config()
screen = Screen(config.general["monitor"])
template_finder = TemplateFinder(screen)
manager = DeathManager(screen, template_finder)
manager.start_monitor(None)
manager = DeathManager(screen)
manager.pick_up_corpse(config, screen)
+2 -2
View File
@@ -44,8 +44,8 @@ class HealthManager:
self._pausing = True
def update_location(self, loc: Location):
if loc is not None:
bosses = ["shenk", "eldritch", "pindle"]
if loc is not None and type(loc) == str:
bosses = ["shenk", "eldritch", "pindle", "trav"]
self._pausing = not any(substring in loc for substring in bosses)
@staticmethod
+51 -30
View File
@@ -20,23 +20,24 @@ class Location:
A5_TOWN_START = "a5_town_start"
A5_STASH = "a5_stash"
A5_WP = "a5_wp"
A5_QUAL_KEHK = "qual_kehk"
A5_MALAH = "malah"
A5_NIHLATHAK_PORTAL = "nihlathak_portal"
A5_LARZUK = "larzuk"
A5_QUAL_KEHK = "a5_qual_kehk"
A5_MALAH = "a5_malah"
A5_NIHLATHAK_PORTAL = "a5_nihlathak_portal"
A5_LARZUK = "a5_larzuk"
# Pindle
PINDLE_START = "pindle_start"
PINDLE_SAVE_DIST = "pindle_save_dist"
PINDLE_END = "pindle_end"
A5_PINDLE_START = "a5_pindle_start"
A5_PINDLE_SAVE_DIST = "a5_pindle_save_dist"
A5_PINDLE_END = "a5_pindle_end"
# Eldritch
ELDRITCH_START = "eldritch_start"
ELDRITCH_SAVE_DIST = "eldritch_save_dist"
ELDRITCH_END = "eldritch_end"
A5_ELDRITCH_START = "a5_eldritch_start"
A5_ELDRITCH_SAVE_DIST = "a5_eldritch_save_dist"
A5_ELDRITCH_END = "a5_eldritch_end"
# Shenk
SHENK_START = "shenk_start"
SHENK_SAVE_DIST = "shenk_save_dist"
SHENK_END = "shenk_end"
A5_SHENK_START = "a5_shenk_start"
A5_SHENK_SAVE_DIST = "a5_shenk_save_dist"
A5_SHENK_END = "a5_shenk_end"
# A4 Town
A4_TOWN_START = "a4_town_start"
A4_WP = "a4_town_wp"
A4_TYRAEL_STASH = "a4_tyrael_stash"
A4_VENDOR = "a4_vendor"
@@ -44,11 +45,11 @@ class Location:
A3_TOWN_START = "a3_town_start"
A3_ORMUS = "a3_ormus"
A3_STASH_WP = "a3_stash_wp"
A3_ASHEARA = "ar_asheara"
A3_ASHEARA = "a3_asheara"
# Trav
TRAV_START = "trav_start"
TRAV_SAVE_DIST = "trav_save_dist"
TRAV_END = "trav_end"
A3_TRAV_START = "a3_trav_start"
A3_TRAV_SAVE_DIST = "a3_trav_save_dist"
A3_TRAV_END = "a3_trav_end"
class Pather:
"""
@@ -145,32 +146,37 @@ class Pather:
(Location.A5_STASH, Location.A5_QUAL_KEHK): [5, 6, 10, 11, 12],
(Location.A5_STASH, Location.A5_LARZUK): [13, 14],
(Location.A5_STASH, Location.A5_WP): [],
(Location.A5_WP, Location.A5_STASH): [],
(Location.A5_WP, Location.A5_NIHLATHAK_PORTAL): [6, 8, 9],
(Location.A5_QUAL_KEHK, Location.A5_NIHLATHAK_PORTAL): [12, 11, 10, 6, 8, 9],
(Location.A5_QUAL_KEHK, Location.A5_WP): [12, 11, 10, 6],
(Location.A5_LARZUK, Location.A5_QUAL_KEHK): [13, 14, 5, 6, 10, 11, 12],
(Location.A5_LARZUK, Location.A5_NIHLATHAK_PORTAL): [14, 13, 5, 6, 8, 9],
(Location.A5_LARZUK, Location.A5_WP): [14, 13, 5],
# Pindle
(Location.PINDLE_START, Location.PINDLE_SAVE_DIST): [100, 101, 102, 103],
(Location.PINDLE_SAVE_DIST, Location.PINDLE_END): [104],
(Location.A5_PINDLE_START, Location.A5_PINDLE_SAVE_DIST): [100, 101, 102, 103],
(Location.A5_PINDLE_SAVE_DIST, Location.A5_PINDLE_END): [104],
# Eldritch
(Location.ELDRITCH_START, Location.ELDRITCH_SAVE_DIST): [120, 121, 122],
(Location.ELDRITCH_SAVE_DIST, Location.ELDRITCH_END): [123],
(Location.A5_ELDRITCH_START, Location.A5_ELDRITCH_SAVE_DIST): [120, 121, 122],
(Location.A5_ELDRITCH_SAVE_DIST, Location.A5_ELDRITCH_END): [123],
# Shenk
(Location.SHENK_START, Location.SHENK_SAVE_DIST): [140, 141, 142, 143, 144, 145, 146, 147, 148],
(Location.SHENK_SAVE_DIST, Location.SHENK_END): [149],
(Location.A5_SHENK_START, Location.A5_SHENK_SAVE_DIST): [140, 141, 142, 143, 144, 145, 146, 147, 148],
(Location.A5_SHENK_SAVE_DIST, Location.A5_SHENK_END): [149],
# A4 Town
(Location.A4_WP, Location.A4_TYRAEL_STASH): [160, 161],
(Location.A4_TYRAEL_STASH, Location.A4_WP): [161, 160],
(Location.A4_TOWN_START, Location.A4_WP): [162],
# A3 Town
(Location.A3_TOWN_START, Location.A3_STASH_WP): [180, 181, 182, 183, 184, 185, 186, 187, 188],
(Location.A3_TOWN_START, Location.A3_ORMUS): [180, 181, 182, 183, 184, 185],
(Location.A3_ORMUS, Location.A3_STASH_WP): [186, 187, 188],
(Location.A3_ORMUS, Location.A3_ASHEARA): [189, 190, 191, 192],
(Location.A3_ASHEARA, Location.A3_STASH_WP): [191, 190, 189, 185, 186, 187, 188],
(Location.A3_STASH_WP, Location.A3_STASH_WP): [188],
(Location.A3_STASH_WP, Location.A3_ORMUS): [187, 186, 185],
# Trav
(Location.TRAV_START, Location.TRAV_SAVE_DIST): [220, 221, 222, 223, 224, 225, 226, 227],
(Location.TRAV_SAVE_DIST, Location.TRAV_END): [228],
(Location.A3_TRAV_START, Location.A3_TRAV_SAVE_DIST): [220, 221, 222, 223, 224, 225, 226, 227],
(Location.A3_TRAV_SAVE_DIST, Location.A3_TRAV_END): [228],
}
def _get_node(self, key: int, template: str):
@@ -234,17 +240,32 @@ class Pather:
return node_pos_abs
return None
def traverse_nodes(self, start_location: Location, end_location: Location, char: IChar, time_out: float = 7, force_tp: bool = False, do_pre_move: bool = True) -> bool:
def traverse_nodes(
self,
start_location: Location,
end_location: Location,
char: IChar,#
time_out: float = 7,
force_tp: bool = False,
do_pre_move: bool = True,
force_move: bool = False) -> bool:
"""
Traverse from one location to another
:param start_location: Location the char is starting at
:param end_location: Location the char is supposed to end up
:param char: Char that is traversing the nodes
:param time_out: Timeout in second. If no more move was found in that time it will cancel traverse
:param force_move: Bool value if force move should be used for pathing
:return: Bool if traversed succesfull or False if it got stuck
"""
Logger.debug(f"Traverse from {start_location} to {end_location}")
path = self._paths[(start_location, end_location)]
try:
path = self._paths[(start_location, end_location)]
except KeyError:
if start_location == end_location:
return True
Logger.error(f"Don't know how to traverse from {start_location} to {end_location}")
return False
if do_pre_move:
char.pre_move()
for i, node_idx in enumerate(path):
@@ -276,7 +297,7 @@ class Pather:
else:
# Move the char
x_m, y_m = self._screen.convert_abs_to_monitor(node_pos_abs)
char.move((x_m, y_m), force_tp=force_tp)
char.move((x_m, y_m), force_tp=force_tp, force_move=force_move)
last_move = time.time()
return True
@@ -331,6 +352,6 @@ if __name__ == "__main__":
ui_manager = UiManager(screen, t_finder)
char = Hammerdin(config.hammerdin, config.char, screen, t_finder, ui_manager, pather)
# pather.traverse_nodes_fixed("pindle_save_dist", char)
pather.traverse_nodes(Location.TRAV_START, Location.TRAV_SAVE_DIST, char)
pather.traverse_nodes(Location.TRAV_SAVE_DIST, Location.TRAV_END, char)
# pather.traverse_nodes(Location.A3_TRAV_START, Location.A3_TRAV_SAVE_DIST, char)
pather.traverse_nodes(Location.A3_STASH_WP, Location.A3_STASH_WP, char)
# display_all_nodes(pather, filter="TRAV")
+5
View File
@@ -0,0 +1,5 @@
from .a3 import A3
from .a4 import A4
from .a5 import A5
from .i_act import IAct
from .town_manager import TownManager
+52
View File
@@ -0,0 +1,52 @@
from char.i_char import IChar
from town.i_act import IAct
from screen import Screen
from config import Config
from npc_manager import NpcManager, Npc
from pather import Pather, Location
from typing import Union
from template_finder import TemplateFinder
from utils.misc import wait
from utils.custom_mouse import mouse
class A3(IAct):
def __init__(self, screen: Screen, template_finder: TemplateFinder, pather: Pather, char: IChar, npc_manager: NpcManager):
self._config = Config()
self._screen = screen
self._pather = pather
self._char = char
self._npc_manager = npc_manager
self._template_finder = template_finder
def get_wp_location(self) -> Location: return Location.A3_STASH_WP
def can_heal(self) -> bool: return True
def can_stash(self) -> bool: return True
def heal(self, curr_loc: Location) -> Union[Location, bool]:
if not self._pather.traverse_nodes(curr_loc, Location.A3_ORMUS, self._char): return False
self._npc_manager.open_npc_menu(Npc.ORMUS)
return Location.A3_ORMUS
def open_stash(self, curr_loc: Location) -> Union[Location, bool]:
if not self._pather.traverse_nodes(curr_loc, Location.A3_STASH_WP, self._char): return False
wait(0.3)
def stash_is_open_func():
found = self._template_finder.search("INVENTORY_GOLD_BTN", self._screen.grab(), roi=self._config.ui_roi["gold_btn"]).valid
found |= self._template_finder.search("INVENTORY_GOLD_BTN", self._screen.grab(), roi=self._config.ui_roi["gold_btn_stash"]).valid
return found
if not self._char.select_by_template("A3_STASH", stash_is_open_func): return False
return Location.A3_STASH_WP
def open_wp(self, curr_loc: Location) -> bool:
if not self._pather.traverse_nodes(curr_loc, Location.A3_STASH_WP, self._char): return False
wait(0.5, 0.7)
found_wp_func = lambda: self._template_finder.search("WAYPOINT_MENU", self._screen.grab()).valid
return self._char.select_by_template("A3_WP", found_wp_func)
def wait_for_tp(self) -> Union[Location, bool]:
template_match = self._template_finder.search_and_wait("A3_TOWN_10", time_out=20)
if template_match.valid:
self._pather.traverse_nodes(Location.A3_STASH_WP, Location.A3_STASH_WP, self._char, force_move=True)
return Location.A3_STASH_WP
return False
+41
View File
@@ -0,0 +1,41 @@
from char.i_char import IChar
from town.i_act import IAct
from screen import Screen
from config import Config
from npc_manager import NpcManager, Npc
from pather import Pather, Location
from typing import Union
from template_finder import TemplateFinder
from utils.misc import wait
class A4(IAct):
def __init__(self, screen: Screen, template_finder: TemplateFinder, pather: Pather, char: IChar, npc_manager: NpcManager):
self._config = Config()
self._screen = screen
self._pather = pather
self._char = char
self._npc_manager = npc_manager
self._template_finder = template_finder
def get_wp_location(self) -> Location: return Location.A4_WP
def can_resurrect(self) -> bool: return True
def resurrect(self, curr_loc: Location) -> Union[Location, bool]:
if not self._pather.traverse_nodes(curr_loc, Location.A4_TYRAEL_STASH, self._char): return False
if self._npc_manager.open_npc_menu(Npc.TYRAEL):
self._npc_manager.press_npc_btn(Npc.TYRAEL, "resurrect")
return Location.A4_TYRAEL_STASH
return False
def open_wp(self, curr_loc: Location) -> bool:
if not self._pather.traverse_nodes(curr_loc, Location.A4_WP, self._char): return False
wait(0.5, 0.7)
found_wp_func = lambda: self._template_finder.search("WAYPOINT_MENU", self._screen.grab()).valid
return self._char.select_by_template("A4_WP", found_wp_func)
def wait_for_tp(self) -> Union[Location, bool]:
success = self._template_finder.search_and_wait(["A4_TOWN_4", "A4_TOWN_5", "A4_TOWN_6"], time_out=20).valid
if success:
return Location.A4_TOWN_START
return False
+67
View File
@@ -0,0 +1,67 @@
from char.i_char import IChar
from town.i_act import IAct
from screen import Screen
from config import Config
from npc_manager import NpcManager, Npc
from pather import Pather, Location
from typing import Union
from template_finder import TemplateFinder
from utils.misc import wait
class A5(IAct):
def __init__(self, screen: Screen, template_finder: TemplateFinder, pather: Pather, char: IChar, npc_manager: NpcManager):
self._config = Config()
self._screen = screen
self._pather = pather
self._char = char
self._npc_manager = npc_manager
self._template_finder = template_finder
def get_wp_location(self) -> Location: return Location.A5_WP
def can_heal(self) -> bool: return True
def can_resurrect(self) -> bool: return True
def can_stash(self) -> bool: return True
def can_trade_and_repair(self) -> bool: return True
def heal(self, curr_loc: Location) -> Union[Location, bool]:
if not self._pather.traverse_nodes(curr_loc, Location.A5_MALAH, self._char, force_move=True): return False
self._npc_manager.open_npc_menu(Npc.MALAH)
if not self._pather.traverse_nodes(Location.A5_MALAH, Location.A5_TOWN_START, self._char, force_move=True): return False
return Location.A5_TOWN_START
def resurrect(self, curr_loc: Location) -> Union[Location, bool]:
if not self._pather.traverse_nodes(curr_loc, Location.A5_QUAL_KEHK, self._char): return False
if self._npc_manager.open_npc_menu(Npc.QUAL_KEHK):
self._npc_manager.press_npc_btn(Npc.QUAL_KEHK, "resurrect")
return Location.A5_QUAL_KEHK
return False
def open_stash(self, curr_loc: Location) -> Union[Location, bool]:
if not self._pather.traverse_nodes(curr_loc, Location.A5_STASH, self._char): return False
wait(0.3)
def stash_is_open_func():
found = self._template_finder.search("INVENTORY_GOLD_BTN", self._screen.grab(), roi=self._config.ui_roi["gold_btn"]).valid
found |= self._template_finder.search("INVENTORY_GOLD_BTN", self._screen.grab(), roi=self._config.ui_roi["gold_btn_stash"]).valid
return found
if not self._char.select_by_template("A5_STASH", stash_is_open_func): return False
return Location.A5_STASH
def open_trade_and_repair_menu(self, curr_loc: Location) -> Union[Location, bool]:
if not self._pather.traverse_nodes(curr_loc, Location.A5_LARZUK, self._char): return
self._npc_manager.open_npc_menu(Npc.LARZUK)
self._npc_manager.press_npc_btn(Npc.LARZUK, "trade_repair")
return Location.A5_LARZUK
def open_wp(self, curr_loc: Location) -> bool:
if not self._pather.traverse_nodes(curr_loc, Location.A5_WP, self._char): return False
wait(0.5, 0.7)
found_wp_func = lambda: self._template_finder.search("WAYPOINT_MENU", self._screen.grab()).valid
return self._char.select_by_template("A5_WP", found_wp_func)
def wait_for_tp(self) -> Union[Location, bool]:
success = self._template_finder.search_and_wait(["A5_TOWN_1", "A5_TOWN_0"], time_out=20).valid
if success:
return Location.A5_TOWN_START
return False
+24
View File
@@ -0,0 +1,24 @@
from typing import Union
from pather import Location
class IAct:
# Open waypoint menu
def open_wp(self, curr_loc: Location) -> bool: return False
# Get Location that is closest to waypoint to continue pathing from there
def get_wp_location(self) -> Location: pass
# Wait until we arrive in town after using town portal (by searching for templates close it)
def wait_for_tp(self) -> Union[Location, bool]: return False
# Is healing implemented for this Town?
def can_heal(self) -> bool: return False
# Is merc resurrection implemented for this Town?
def can_resurrect(self) -> bool: return False
# Is stashing implemented in this Town?
def can_stash(self) -> bool: return False
# Is trade/repair implemented in this Town?
def can_trade_and_repair(self) -> bool: return False
# If any of the above functions return True for the Town, the respective method must be implemented
def heal(self, curr_loc: Location) -> Union[Location, bool]: return False
def resurrect(self, curr_loc: Location) -> Union[Location, bool]: return False
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
+149
View File
@@ -0,0 +1,149 @@
from typing import Union
from item_finder import ItemFinder
from template_finder import TemplateFinder
from config import Config
from pather import Location
from logger import Logger
from ui_manager import UiManager
from town import IAct, A3, A4, A5
from utils.misc import wait
class TownManager:
def __init__(self, template_finder: TemplateFinder, ui_manager: UiManager, a3: A3, a4: A4, a5: A5):
self._template_finder = template_finder
self._ui_manager = ui_manager
self._item_finder = ItemFinder()
self._config = Config()
self._acts: dict[Location, IAct] = {
Location.A3_TOWN_START: a3,
Location.A4_TOWN_START: a4,
Location.A5_TOWN_START: a5
}
@staticmethod
def get_act_from_location(loc: Location) -> Location:
location = None
if loc.upper().startswith("A5_"):
location = Location.A5_TOWN_START
elif loc.upper().startswith("A4_"):
location = Location.A4_TOWN_START
elif loc.upper().startswith("A3_"):
location = Location.A3_TOWN_START
return location
def wait_for_town_spawn(self, time_out: float = None) -> Location:
"""Wait for the char to spawn in town after starting a new game
:param time_out: Optional float value for time out in seconds, defaults to None
:return: Location of the town (e.g. Location.A4_TOWN_START) or None if nothing was found within time_out time
"""
template_match = self._template_finder.search_and_wait([
"A5_TOWN_0", "A5_TOWN_1",
"A4_TOWN_0", "A4_TOWN_1",
"A3_TOWN_0", "A3_TOWN_1"
], time_out=time_out)
if template_match.valid:
return TownManager.get_act_from_location(template_match.name)
return None
def wait_for_tp(self, curr_loc: Location):
curr_act = TownManager.get_act_from_location(curr_loc)
if curr_act is None: return False
return self._acts[curr_act].wait_for_tp()
def open_wp(self, curr_loc: Location):
curr_act = TownManager.get_act_from_location(curr_loc)
if curr_act is None: return False
return self._acts[curr_act].open_wp(curr_loc)
def go_to_act(self, act_idx: int, 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 already are in the desired act
if act_idx == 3: act = Location.A3_TOWN_START
elif act_idx == 4: act = Location.A4_TOWN_START
elif act_idx == 5: act = Location.A5_TOWN_START
else:
Logger.error(f"Act {act_idx} is not supported")
return False
if curr_act == act:
return curr_loc
# if not, move to the desired act via waypoint
if not self._acts[curr_act].open_wp(curr_loc): return False
self._ui_manager.use_wp(act_idx, 0)
return self._acts[act].get_wp_location()
def heal(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 heal in current act
if self._acts[curr_act].can_heal():
return self._acts[curr_act].heal(curr_loc)
Logger.warning(f"Could not heal in {curr_act}. Continue without healing")
return curr_loc
def resurrect(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 resurrect in current act
if self._acts[curr_act].can_resurrect():
return self._acts[curr_act].resurrect(curr_loc)
new_loc = self.go_to_act(4, curr_loc)
if not new_loc: return False
return self._acts[Location.A4_TOWN_START].resurrect(new_loc)
def stash(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 stash in current act
if self._acts[curr_act].can_stash():
new_loc = self._acts[curr_act].open_stash(curr_loc)
if not new_loc: return False
self._ui_manager.stash_all_items(self._config.char["num_loot_columns"], self._item_finder)
return new_loc
new_loc = self.go_to_act(5, curr_loc)
if not new_loc: return False
new_loc = self._acts[Location.A5_TOWN_START].open_stash(new_loc)
if not new_loc: return False
self._ui_manager.stash_all_items(self._config.char["num_loot_columns"], self._item_finder)
return new_loc
def repair_and_fill_tps(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 rapair in current act
if self._acts[curr_act].can_trade_and_repair():
new_loc = self._acts[curr_act].open_trade_and_repair_menu(curr_loc)
if not new_loc: return False
if self._ui_manager.repair_and_fill_up_tp():
wait(0.1, 0.2)
self._ui_manager.close_vendor_screen()
return new_loc
# TODO: Got to Act 5 for repair and fill up tps
Logger.warning(f"Could not repair in {curr_act}. Continue without repairing")
return curr_loc
# Test: Move to desired location in d2r and run any town action you want to test from there
if __name__ == "__main__":
import keyboard
import os
keyboard.add_hotkey('f12', lambda: Logger.info('Force Exit (f12)') or os._exit(1))
print("Move to d2r window and press f11")
keyboard.wait("f11")
from char.hammerdin import Hammerdin
from pather import Pather
from screen import Screen
from npc_manager import NpcManager
config = Config()
screen = Screen(config.general["monitor"])
template_finder = TemplateFinder(screen)
npc_manager = NpcManager(screen, template_finder)
pather = Pather(screen, template_finder)
ui_manager = UiManager(screen, template_finder)
char = Hammerdin(config.hammerdin, config.char, screen, template_finder, ui_manager, pather)
a5 = A5(screen, template_finder, pather, char, npc_manager)
a4 = A4(screen, template_finder, pather, char, npc_manager)
a3 = A3(screen, template_finder, pather, char, npc_manager)
town_manager = TownManager(template_finder, ui_manager, a3, a4, a5)
print(town_manager.wait_for_tp(Location.A3_TRAV_END))
+24 -18
View File
@@ -27,20 +27,25 @@ class UiManager():
def use_wp(self, act: int, idx: int):
"""
Use Waypoint. The menu must be opened when calling the function.
:param act: Index of the act from left. Note that it start at 0. e.g. Act5 -> act=4
:param idx: Index of the waypoint from top. Note that it start at 0.
:param act: Index of the desired act starting at 1 [A1 = 1, A2 = 2, A3 = 3, ...]
:param idx: Index of the waypoint from top. Note that it start at 0!
"""
# Note: We are currently only in act 5, thus no need to click here.
# pos_act_btn = (self._config.ui_pos["wp_act_i_btn_x"] + self._config.ui_pos["wp_act_btn_width"] * act, self._config.ui_pos["wp_act_i_btn_y"])
# x, y = self._screen.convert_screen_to_monitor(pos_act_btn)
# mouse.move(x, y, randomize=8)
# mouse.click(button="left")
# wait(0.3, 0.4)
# TODO: Optimize by checking if wp already has desired act active
pos_act_btn = (self._config.ui_pos["wp_act_i_btn_x"] + self._config.ui_pos["wp_act_btn_width"] * (act - 1), self._config.ui_pos["wp_act_i_btn_y"])
x, y = self._screen.convert_screen_to_monitor(pos_act_btn)
mouse.move(x, y, randomize=8)
mouse.click(button="left")
wait(0.3, 0.4)
pos_wp_btn = (self._config.ui_pos["wp_first_btn_x"], self._config.ui_pos["wp_first_btn_y"] + self._config.ui_pos["wp_btn_height"] * idx)
x, y = self._screen.convert_screen_to_monitor(pos_wp_btn)
mouse.move(x, y, randomize=[110, 20], delay_factor=[0.9, 1.4])
mouse.move(x, y, randomize=[60, 9], delay_factor=[0.9, 1.4])
wait(0.4, 0.5)
mouse.click(button="left")
# wait till loading screen is over
if self.wait_for_loading_screen():
while 1:
if not self.wait_for_loading_screen(0.2):
return
def is_right_skill_active(self) -> bool:
"""
@@ -80,19 +85,20 @@ class UiManager():
return True
return False
def wait_for_loading_screen(self, time_out: float) -> bool:
def wait_for_loading_screen(self, time_out: float = None) -> bool:
"""
Waits until loading screen apears
:param time_out: Maximum time to search for a loading screen
:return: True if loading screen was found within the timeout. False otherwise
"""
start = time.time()
while time.time() - start < time_out:
while True:
img = self._screen.grab()
is_loading_black_roi = np.average(img[:700, 0:250]) < 3.5
if is_loading_black_roi:
return True
return False
if time_out is not None and time.time() - start > time_out:
return False
def save_and_exit(self, does_chicken: bool = False) -> bool:
"""
@@ -265,6 +271,10 @@ class UiManager():
# TODO: Do not stash portal scrolls and potions but throw them out of inventory on the ground!
# then the pickit check for potions and belt free can also be removed
Logger.debug("Searching for inventory gold btn...")
# Move cursor to center
x, y = self._screen.convert_abs_to_monitor((0, 0))
mouse.move(x, y, randomize=[40, 200], delay_factor=[1.0, 1.5])
# Wait till gold btn is found
gold_btn = self._template_finder.search_and_wait("INVENTORY_GOLD_BTN", roi=self._config.ui_roi["gold_btn"], time_out=20)
if not gold_btn.valid:
Logger.error("Could not determine to be in stash menu. Continue...")
@@ -318,9 +328,7 @@ class UiManager():
time.sleep(0.3)
curr_pos = mouse.get_position()
# move mouse away from inventory, for some reason it was sometimes included in the grabed img
top_left_slot = (self._config.ui_pos["inventory_top_left_slot_x"], self._config.ui_pos["inventory_top_left_slot_y"])
move_to = (top_left_slot[0] - 300, top_left_slot[1] - 200)
x, y = self._screen.convert_screen_to_monitor(move_to)
x, y = self._screen.convert_abs_to_monitor((0, 0))
mouse.move(x, y, randomize=[40, 200], delay_factor=[1.0, 1.5])
item_check_img = self._screen.grab()
mouse.move(*curr_pos, randomize=2)
@@ -341,9 +349,7 @@ class UiManager():
Logger.debug("Check if stash is full")
time.sleep(0.6)
# move mouse away from inventory, for some reason it was sometimes included in the grabed img
top_left_slot = (self._config.ui_pos["inventory_top_left_slot_x"], self._config.ui_pos["inventory_top_left_slot_y"])
move_to = (top_left_slot[0] - 300, top_left_slot[1] - 200)
x, y = self._screen.convert_screen_to_monitor(move_to)
x, y = self._screen.convert_abs_to_monitor((0, 0))
mouse.move(x, y, randomize=[40, 200], delay_factor=[1.0, 1.5])
img = self._screen.grab()
if self._inventory_has_items(img, num_loot_columns):