Connect all towns (#220)
This commit is contained in:
@@ -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 |
@@ -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
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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
@@ -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():
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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")),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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")
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user