Compare commits

..
19 Commits
Author SHA1 Message Date
mgleed 1f16c13300 Bump to 0.7.2 (#652) 2022-03-19 20:59:05 -04:00
mgleed ee479fb526 Bugfix: Stash tab counts not tracking (#646)
* init

* saved stuff

* fix startup

* fix

* gdi
2022-03-19 12:02:14 -04:00
Legit d05ff50f76 Enhancement: Improved eld attack sequence for Blizz sorc (#608)
* improved eld attack sequence

added an extra attack sequences and added wait timers.

* Update blizz_sorc.py
2022-03-18 17:14:41 -04:00
Aimware0 927ade6108 Bugfix: added error handling for custom.ini when missing section header (#647) 2022-03-18 17:02:12 -04:00
Steve6030 5eda34ded0 Enhancement: Tweaked light sorc attack sequence (#644) 2022-03-18 16:45:04 -04:00
mgleedandVladimir Makaev d9b9d66e3b Feature: Use OCR for gold pickit (#643)
* init

* consolidate

* Update src/config.py

Co-authored-by: Vladimir Makaev <[email protected]>

Co-authored-by: Vladimir Makaev <[email protected]>
2022-03-17 21:31:48 -04:00
mgleed 1adeb3a32a Bugfix: Some NPC's don't open to misc tab (#642)
* init

* cleanup

* clean

* cleanup
2022-03-16 21:52:29 -04:00
mgleed 1abf57eae1 Bugfix/refactor: Safer screen transitions for laggy setups (#641)
* init

* update

* WIP. cleanup code. refactor stashing routines, etc. hopefully now closer to lag-proof

* more simplification, ready for testing

* change default normalize_monitor and adjust code accordingly. testing

* undo save/exit keyboard

* forgot to save a couple

* fix gold checking
2022-03-16 16:06:12 -04:00
Legit 65797eba8f Note for num loot (#637)
created note for num loot to avoid confusion. Per Sosa.
2022-03-14 17:25:34 -04:00
mgleed 03a6e8b05c Bugfix/refactor: Arcane run doesn't turn off health_monitor when in town 2022-03-14 16:01:07 -04:00
mgleed 2c09459bde Bugfix/enhancement: Optimize waits; implement wait_for_expiration(ScreenObject) function (#629)
* init

* simplify

* the

* init

* update

* init

* fix belt open

* rework waiting functions in ui_manager.py
2022-03-13 20:19:41 -04:00
aeon0anduidn4233 7564fea4b0 Remove: Codecov (#632)
* Remove: Codecov

* test commit

* revert test commit

Co-authored-by: uidn4233 <[email protected]>
2022-03-12 22:21:14 -05:00
mgleed 95b27f099f Bugfix/refactor: Search for open panels instead of skill icons (#631)
* init

* add town logic

* add additional close asset for HELP menu

* fin

* a
2022-03-11 17:46:30 -05:00
mgleed e96c042853 Bugfix: Stash tab gold was being read based on prior stash tab image (#628)
* init

* simplify

* the

* init

* update
2022-03-11 13:53:46 -05:00
mgleed 57bf7a0f7d Bugfix: log_item_keep without game_stats (#627)
* init

* simplify

* the
2022-03-11 11:57:32 -05:00
mgleed da892d9043 Bugfix: Laggy inventory loading (#625)
* init

* undo item add

* add msg

* curse you Vladimir for reviewing too carefully
2022-03-10 17:28:22 -05:00
mgleed 7fad5e9aff Bugfixes: Quantity: parse fail handling, if in-game icon not found during return_to_play() 2022-03-10 17:07:35 -05:00
mgleed 840ffcdf0c Update version.py (#621) 2022-03-10 17:06:58 -05:00
Ezro 1078563f2d Bugfix: Prevent join for NoneType and start_detecting_window() in main.py (#624)
* Prevent join for NoneType and start_detecting_window() in main.py

* Start and stop in the debugger toggle
2022-03-09 23:15:36 -05:00
64 changed files with 694 additions and 585 deletions
-5
View File
@@ -50,8 +50,3 @@ jobs:
run: |
C:\Miniconda\condabin\conda.bat activate botty
coverage xml
- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v2
with:
verbose: true
files: ./coverage.xml
+1 -1
View File
@@ -97,7 +97,7 @@ run_shenk=0
| battle_order | Hotkey for battle orders from cta (only needed if cta_available=1) |
| battle_command | Hotkey for battle command from cta (only needed if cta_available=1) |
| stash_gold | Bool value to stash gold each time when stashing items |
| gold_trav_only | Hacky config that will restrict gold pickup to trav only. misc_gold must be set to 1 for this to have any effect |
| min_gold_to_pick | Minimum quantity of gold to pickup (also must set misc_gold=1 in pickit config) |
| use_merc | Set to 1 for using merc. Set to 0 for not using merc (will not revive merc when dead), default = 1 |
| atk_len_arc | Attack length for hdin/sorc fighting arcane |
| atk_len_trav | Attack length for hdin fighting trav (note this atk length will be applied in 4 different spots each) |
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

-1
View File
@@ -1 +0,0 @@
comment: false
+4
View File
@@ -22,6 +22,7 @@ mana_globe=117,120,20,121,255,255
blue_slot=102,194,18,138,230,54
green_slot=33,181,18,87,258,69
red_slot=161,204,28,197,240,64
tab_text=0,0,125,180,255,255
[ui_pos]
screen_width=1280
@@ -130,6 +131,9 @@ right_panel_header=830,0,450,54
npc_dialogue=458,4,21,140
bind_skill=516,619,251,29
quest_skill_btn=284,455,710,121
left_inventory_tabs=31,62,385,28
tab_indicator=31,82,385,14
deposit_btn=454,365,186,43
[path]
; static pathes in format: x0,y0, x1,y1, x2,y2, ...
+2 -1
View File
@@ -38,6 +38,7 @@ casting_frames=10
show_items=alt
inventory_screen=i
tp=f9
;Note: loot columns are counted from left to right. Items on the left side of inventory will be considered lootable.
num_loot_columns=5
force_move=e
;Note: stand_still can not be the default "shift" as it would interfere with merc healing
@@ -57,7 +58,7 @@ battle_command=f8
; ==== Optional configs ====
; ==========================
stash_gold=1
gold_trav_only=0
min_gold_to_pick=1000
use_merc=1
; Attack length for barbarians should be as high as 8-10 and even 10-12 for trav/shenk
atk_len_arc=2.5
+25 -19
View File
@@ -8,9 +8,9 @@ import cv2
from copy import copy
from typing import Union
from collections import OrderedDict
from health_manager import set_pause_state
from transmute import Transmute
from utils.misc import wait
from game_stats import GameStats
from logger import Logger
from config import Config
@@ -27,7 +27,7 @@ from char.barbarian import Barbarian
from char.necro import Necro
from char.basic import Basic
from char.basic_ranged import Basic_Ranged
from ui_manager import wait_for_screen_object, detect_screen_object, ScreenObjects
from ui_manager import wait_until_hidden, wait_until_visible, ScreenObjects, is_visible
from ui import meters, skills, view, character_select, main_menu
from inventory import personal, vendor, belt, common, consumables
@@ -105,7 +105,6 @@ class Bot:
self._diablo = Diablo(self._pather, self._town_manager, self._char, self._pickit)
# Create member variables
self._pick_corpse = False
self._picked_up_items = False
self._curr_loc: Union[bool, Location] = None
self._use_id_tome = True
@@ -218,8 +217,7 @@ class Bot:
def on_create_game(self):
# Start a game from hero selection
m = wait_for_screen_object(ScreenObjects.MainMenu)
if m.valid:
if (m := wait_until_visible(ScreenObjects.MainMenu)).valid:
if "DARK" in m.name:
keyboard.send("esc")
main_menu.start_game()
@@ -253,17 +251,21 @@ class Bot:
self.trigger_or_stop("maintenance")
def on_maintenance(self):
# Pause health manager if not already paused
set_pause_state(True)
# Dismiss skill/quest/help/stats icon if they are on screen
if not view.dismiss_skills_icon():
view.return_to_play()
# Handle picking up corpse in case of death
self._pick_corpse = detect_screen_object(ScreenObjects.Corpse).valid
if self._pick_corpse:
if (corpse_present := is_visible(ScreenObjects.Corpse)):
self._previous_run_failed = True
time.sleep(1.6)
view.pickup_corpse()
wait(1.2, 1.5)
wait_until_hidden(ScreenObjects.Corpse)
belt.fill_up_belt_from_inventory(Config().char["num_loot_columns"])
wait(0.5)
self._char.discover_capabilities()
if self._pick_corpse and self._char.capabilities.can_teleport_with_charges and not self._char.select_tp():
if corpse_present and self._char.capabilities.can_teleport_with_charges and not self._char.select_tp():
keybind = self._char._skill_hotkeys["teleport"]
Logger.info(f"Teleport keybind is lost upon death. Rebinding teleport to '{keybind}'")
self._char.remap_right_skill_hotkey("TELE_ACTIVE", self._char._skill_hotkeys["teleport"])
@@ -281,7 +283,7 @@ class Bot:
# Update TP, ID, key needs
if self._game_stats._game_counter == 1:
self._use_id_tome = common.tome_state(img, 'id')[0] is not None
self._use_keys = detect_screen_object(ScreenObjects.Key, img).valid
self._use_keys = is_visible(ScreenObjects.Key, img)
if (self._game_stats._run_counter - 1) % 4 == 0 or self._previous_run_failed:
consumables.update_tome_key_needs(img, item_type = 'tp')
if self._use_id_tome:
@@ -323,7 +325,6 @@ class Bot:
items = result_items
sell_items = any([item.sell for item in items]) if items else None
Logger.debug(f"Needs: {consumables.get_needs()}")
wait(0.5, 0.8)
elif meters.get_health(img) < 0.6 or meters.get_mana(img) < 0.2:
Logger.info("Healing at next possible Vendor")
self._curr_loc = self._town_manager.heal(self._curr_loc)
@@ -340,10 +341,9 @@ class Bot:
if not self._curr_loc:
return self.trigger_or_stop("end_game", failed=True)
self._picked_up_items = False
wait(1.0)
# Check if we are out of tps or need repairing
need_repair = detect_screen_object(ScreenObjects.NeedRepair).valid
need_repair = is_visible(ScreenObjects.NeedRepair)
need_routine_repair = False if not Config().char["runs_per_repair"] else self._game_stats._run_counter % Config().char["runs_per_repair"] == 0
need_refill_teleport = self._char.capabilities.can_teleport_with_charges and (not self._char.select_tp() or self._char.is_low_on_teleport_charges())
if need_repair or need_routine_repair or need_refill_teleport or sell_items:
@@ -360,11 +360,9 @@ class Bot:
items = result_items
if not self._curr_loc:
return self.trigger_or_stop("end_game", failed=True)
wait(1.0)
# Check if merc needs to be revived
match = detect_screen_object(ScreenObjects.MercIcon)
if not match.valid and Config().char["use_merc"]:
if not is_visible(ScreenObjects.MercIcon) and Config().char["use_merc"]:
Logger.info("Resurrect merc")
self._game_stats.log_merc_death()
self._curr_loc = self._town_manager.resurrect(self._curr_loc)
@@ -399,11 +397,11 @@ class Bot:
self._curr_loc = False
self._pre_buffed = False
view.save_and_exit()
set_pause_state(True)
self._game_stats.log_end_game(failed=failed)
self._do_runs = copy(self._do_runs_reset)
if Config().general["randomize_runs"]:
self.shuffle_runs()
wait(0.2, 0.5)
self.trigger_or_stop("init")
def on_end_run(self):
@@ -413,9 +411,11 @@ class Bot:
if success:
self._curr_loc = self._town_manager.wait_for_tp(self._curr_loc)
if self._curr_loc:
set_pause_state(True)
return self.trigger_or_stop("maintenance")
if not skills.has_tps():
consumables.set_needs("tp", 20)
set_pause_state(True)
self.trigger_or_stop("end_game", failed=True)
# All the runs go here
@@ -441,6 +441,7 @@ class Bot:
self._game_stats.update_location("Pin" if Config().general['discord_status_condensed'] else "Pindle")
self._curr_loc = self._pindle.approach(self._curr_loc)
if self._curr_loc:
set_pause_state(False)
res = self._pindle.battle(not self._pre_buffed)
self._ending_run_helper(res)
@@ -449,6 +450,7 @@ class Bot:
self._do_runs["run_shenk"] = False
self._curr_loc = self._shenk.approach(self._curr_loc)
if self._curr_loc:
set_pause_state(False)
res = self._shenk.battle(Config().routes["run_shenk"], not self._pre_buffed, self._game_stats)
self._ending_run_helper(res)
@@ -458,6 +460,7 @@ class Bot:
self._game_stats.update_location("Trav" if Config().general['discord_status_condensed'] else "Travincal")
self._curr_loc = self._trav.approach(self._curr_loc)
if self._curr_loc:
set_pause_state(False)
res = self._trav.battle(not self._pre_buffed)
self._ending_run_helper(res)
@@ -467,6 +470,7 @@ class Bot:
self._game_stats.update_location("Nihl" if Config().general['discord_status_condensed'] else "Nihlathak")
self._curr_loc = self._nihlathak.approach(self._curr_loc)
if self._curr_loc:
set_pause_state(False)
res = self._nihlathak.battle(not self._pre_buffed)
self._ending_run_helper(res)
@@ -476,6 +480,7 @@ class Bot:
self._game_stats.update_location("Arc" if Config().general['discord_status_condensed'] else "Arcane")
self._curr_loc = self._arcane.approach(self._curr_loc)
if self._curr_loc:
set_pause_state(False)
res = self._arcane.battle(not self._pre_buffed)
self._ending_run_helper(res)
@@ -485,5 +490,6 @@ class Bot:
self._game_stats.update_location("Dia" if Config().general['discord_status_condensed'] else "Diablo")
self._curr_loc = self._diablo.approach(self._curr_loc)
if self._curr_loc:
set_pause_state(False)
res = self._diablo.battle(not self._pre_buffed)
self._ending_run_helper(res)
+7 -7
View File
@@ -93,8 +93,8 @@ class Barbarian(IChar):
if not self._do_pre_move:
# keyboard.send(self._skill_hotkeys["concentration"])
# wait(0.05, 0.15)
self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, time_out=1.0, do_pre_move=self._do_pre_move)
self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, time_out=0.1)
self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, timeout=1.0, do_pre_move=self._do_pre_move)
self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, timeout=0.1)
self._cast_war_cry(Config().char["atk_len_pindle"])
wait(0.1, 0.15)
self._do_hork(4)
@@ -104,7 +104,7 @@ class Barbarian(IChar):
if self.capabilities.can_teleport_natively:
self._pather.traverse_nodes_fixed("eldritch_end", self)
else:
self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, time_out=1.0, do_pre_move=self._do_pre_move)
self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, timeout=1.0, do_pre_move=self._do_pre_move)
wait(0.05, 0.1)
self._cast_war_cry(Config().char["atk_len_eldritch"])
wait(0.1, 0.15)
@@ -112,7 +112,7 @@ class Barbarian(IChar):
return True
def kill_shenk(self):
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, time_out=1.0, do_pre_move=self._do_pre_move)
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=1.0, do_pre_move=self._do_pre_move)
wait(0.05, 0.1)
self._cast_war_cry(Config().char["atk_len_shenk"])
wait(0.1, 0.15)
@@ -123,14 +123,14 @@ class Barbarian(IChar):
# Check out the node screenshot in assets/templates/trav/nodes to see where each node is at
atk_len = Config().char["atk_len_trav"]
# Go inside and war cry a bit
self._pather.traverse_nodes([228, 229], self, time_out=2.5, force_tp=True)
self._pather.traverse_nodes([228, 229], self, timeout=2.5, force_tp=True)
self._cast_war_cry(atk_len)
# Move a bit back and another round
self._move_and_attack((40, 20), atk_len)
# Here we have two different attack sequences depending if tele is available or not
if self.capabilities.can_teleport_natively:
# Back to center stairs and more war cry
self._pather.traverse_nodes([226], self, time_out=2.5, force_tp=True)
self._pather.traverse_nodes([226], self, timeout=2.5, force_tp=True)
self._cast_war_cry(atk_len)
# move a bit to the top
self._move_and_attack((65, -30), atk_len)
@@ -143,7 +143,7 @@ class Barbarian(IChar):
def kill_nihlathak(self, end_nodes: list[int]) -> bool:
# Move close to nihlathak
self._pather.traverse_nodes(end_nodes, self, time_out=0.8, do_pre_move=False)
self._pather.traverse_nodes(end_nodes, self, timeout=0.8, do_pre_move=False)
# move mouse to center (leftover from hammerdin)
pos_m = convert_abs_to_monitor((0, 0))
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
+7 -7
View File
@@ -75,8 +75,8 @@ class Basic(IChar):
if not self._do_pre_move:
# keyboard.send(self._skill_hotkeys["concentration"])
# wait(0.05, 0.15)
self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, time_out=1.0, do_pre_move=self._do_pre_move)
self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, time_out=0.1)
self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, timeout=1.0, do_pre_move=self._do_pre_move)
self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, timeout=0.1)
self._cast_attack_pattern(Config().char["atk_len_pindle"])
wait(0.1, 0.15)
return True
@@ -88,7 +88,7 @@ class Basic(IChar):
if not self._do_pre_move:
# keyboard.send(self._skill_hotkeys["concentration"])
# wait(0.05, 0.15)
self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, time_out=1.0, do_pre_move=self._do_pre_move)
self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, timeout=1.0, do_pre_move=self._do_pre_move)
wait(0.05, 0.1)
self._cast_attack_pattern(Config().char["atk_len_eldritch"])
return True
@@ -97,7 +97,7 @@ class Basic(IChar):
# if not self._do_pre_move:
# keyboard.send(self._skill_hotkeys["concentration"])
# wait(0.05, 0.15)
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, time_out=1.0, do_pre_move=self._do_pre_move)
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=1.0, do_pre_move=self._do_pre_move)
wait(0.05, 0.1)
self._cast_attack_pattern(Config().char["atk_len_shenk"])
wait(0.1, 0.15)
@@ -107,14 +107,14 @@ class Basic(IChar):
# Check out the node screenshot in assets/templates/trav/nodes to see where each node is at
atk_len = Config().char["atk_len_trav"]
# Go inside and war cry a bit
self._pather.traverse_nodes([228, 229], self, time_out=2.5, force_tp=True)
self._pather.traverse_nodes([228, 229], self, timeout=2.5, force_tp=True)
self._cast_attack_pattern(atk_len)
# Move a bit back and another round
self._move_and_attack((40, 20), atk_len)
# Here we have two different attack sequences depending if tele is available or not
if self.capabilities.can_teleport_natively:
# Back to center stairs and more war cry
self._pather.traverse_nodes([226], self, time_out=2.5, force_tp=True)
self._pather.traverse_nodes([226], self, timeout=2.5, force_tp=True)
self._cast_attack_pattern(atk_len)
# move a bit to the top
self._move_and_attack((65, -30), atk_len)
@@ -126,7 +126,7 @@ class Basic(IChar):
def kill_nihlathak(self, end_nodes: list[int]) -> bool:
# Move close to nihlathak
self._pather.traverse_nodes(end_nodes, self, time_out=0.8, do_pre_move=False)
self._pather.traverse_nodes(end_nodes, self, timeout=0.8, do_pre_move=False)
# move mouse to center (leftover from hammerdin)
pos_m = convert_abs_to_monitor((0, 0))
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
+6 -6
View File
@@ -72,7 +72,7 @@ class Basic_Ranged(IChar):
keyboard.send(Config().char["stand_still"], do_press=False)
wait(self._cast_duration, self._cast_duration + 0.2)
# Move to items
self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, time_out=1.4, force_tp=True)
self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, timeout=1.4, force_tp=True)
return True
@@ -91,7 +91,7 @@ class Basic_Ranged(IChar):
keyboard.send(Config().char["stand_still"], do_press=False)
wait(self._cast_duration, self._cast_duration + 0.2)
# Move to items
self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, time_out=1.4, force_tp=True)
self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, timeout=1.4, force_tp=True)
return True
def kill_shenk(self) -> bool:
@@ -111,7 +111,7 @@ class Basic_Ranged(IChar):
keyboard.send(Config().char["stand_still"], do_press=False)
wait(self._cast_duration, self._cast_duration + 0.2)
# Move to items
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, time_out=1.4, force_tp=True)
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=1.4, force_tp=True)
return True
def kill_council(self) -> bool:
@@ -119,7 +119,7 @@ class Basic_Ranged(IChar):
# Check out the node screenshot in assets/templates/trav/nodes to see where each node is at
# Go inside cast stuff in general direction
self._pather.offset_node(229, [250, 130])
self._pather.traverse_nodes([228, 229], self, time_out=2.5, force_tp=True)
self._pather.traverse_nodes([228, 229], self, timeout=2.5, force_tp=True)
self._pather.offset_node(229, [-250, -130])
atk_pos_abs = self._pather.find_abs_node_pos(230, grab())
if atk_pos_abs is None:
@@ -149,7 +149,7 @@ class Basic_Ranged(IChar):
self._left_attack(cast_pos_abs, spray=11)
# Move outside
# Move a bit back and another round
self._pather.traverse_nodes([226], self, time_out=2.5, force_tp=True)
self._pather.traverse_nodes([226], self, timeout=2.5, force_tp=True)
cast_pos_abs = np.array([-300, -100])
for _ in range(atk_len_trav_2):
if skills.is_right_skill_active():
@@ -183,7 +183,7 @@ class Basic_Ranged(IChar):
self._left_attack(cast_pos_abs, spray=11)
# Move to items
wait(self._cast_duration, self._cast_duration + 0.2)
self._pather.traverse_nodes(end_nodes, self, time_out=0.8)
self._pather.traverse_nodes(end_nodes, self, timeout=0.8)
return True
+56 -56
View File
@@ -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.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, time_out=1.0, do_pre_move=self._do_pre_move)
self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, timeout=1.0, do_pre_move=self._do_pre_move)
self._cast_hammers(Config().char["atk_len_pindle"])
wait(0.1, 0.15)
self._cast_hammers(1.6, "redemption")
@@ -95,7 +95,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.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, time_out=1.0, do_pre_move=self._do_pre_move, force_tp=True, use_tp_charge=True)
self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, timeout=1.0, do_pre_move=self._do_pre_move, force_tp=True, use_tp_charge=True)
wait(0.05, 0.1)
self._cast_hammers(Config().char["atk_len_eldritch"])
wait(0.1, 0.15)
@@ -106,7 +106,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.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, time_out=1.0, do_pre_move=self._do_pre_move, force_tp=True, use_tp_charge=True)
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=1.0, do_pre_move=self._do_pre_move, force_tp=True, use_tp_charge=True)
wait(0.05, 0.1)
self._cast_hammers(Config().char["atk_len_shenk"])
wait(0.1, 0.15)
@@ -120,14 +120,14 @@ class Hammerdin(IChar):
# Check out the node screenshot in assets/templates/trav/nodes to see where each node is at
atk_len = Config().char["atk_len_trav"]
# Go inside and hammer a bit
self._pather.traverse_nodes([228, 229], self, time_out=2.5, force_tp=True, use_tp_charge=True)
self._pather.traverse_nodes([228, 229], self, timeout=2.5, force_tp=True, use_tp_charge=True)
self._cast_hammers(atk_len)
# Move a bit back and another round
self._move_and_attack((40, 20), atk_len)
# Here we have two different attack sequences depending if tele is available or not
if self.capabilities.can_teleport_natively or self.capabilities.can_teleport_with_charges:
# Back to center stairs and more hammers
self._pather.traverse_nodes([226], self, time_out=2.5, force_tp=True, use_tp_charge=True)
self._pather.traverse_nodes([226], self, timeout=2.5, force_tp=True, use_tp_charge=True)
self._cast_hammers(atk_len)
# move a bit to the top
self._move_and_attack((65, -30), atk_len)
@@ -140,7 +140,7 @@ class Hammerdin(IChar):
def kill_nihlathak(self, end_nodes: list[int]) -> bool:
# Move close to nihlathak
self._pather.traverse_nodes(end_nodes, self, time_out=0.8, do_pre_move=False)
self._pather.traverse_nodes(end_nodes, self, timeout=0.8, do_pre_move=False)
# move mouse to center, otherwise hammers sometimes dont fly, not sure why
pos_m = convert_abs_to_monitor((0, 0))
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
@@ -200,7 +200,7 @@ class Hammerdin(IChar):
elif location == "rof_01": #node 603 - outside CS in ROF
### APPROACH ###
if not self._pather.traverse_nodes([603], self, time_out=3): return False #calibrate after static path
if not self._pather.traverse_nodes([603], self, timeout=3): return False #calibrate after static path
pos_m = convert_abs_to_monitor((0, 0))
### ATTACK ###
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
@@ -221,7 +221,7 @@ class Hammerdin(IChar):
elif location == "rof_02": #node 604 - inside ROF
### APPROACH ###
if not self._pather.traverse_nodes([604], self, time_out=3): return False #threshold=0.8 (ex 601)
if not self._pather.traverse_nodes([604], self, timeout=3): return False #threshold=0.8 (ex 601)
### ATTACK ###
pos_m = convert_abs_to_monitor((0, 0))
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
@@ -298,7 +298,7 @@ class Hammerdin(IChar):
wait(0.3, 0.6)
### LOOT ###
self._picked_up_items |= self._pickit.pick_up_items(self)
if not self._pather.traverse_nodes([673], self): return False # , time_out=3): # Re-adjust itself and continues to attack
if not self._pather.traverse_nodes([673], self): return False # , timeout=3): # Re-adjust itself and continues to attack
elif location == "entrance1_02": #node 673
### APPROACH ###
@@ -317,7 +317,7 @@ class Hammerdin(IChar):
### LOOT ###
self._picked_up_items |= self._pickit.pick_up_items(self)
self._pather.traverse_nodes_fixed("diablo_entrance_1_1", self) # Moves char to postion close to node 674 continues to attack
if not self._pather.traverse_nodes([674], self): return False#, time_out=3)
if not self._pather.traverse_nodes([674], self): return False#, timeout=3)
elif location == "entrance1_03": #node 674
### APPROACH ###
@@ -336,9 +336,9 @@ class Hammerdin(IChar):
### LOOT ###
self._picked_up_items |= self._pickit.pick_up_items(self)
self._picked_up_items |= self._pickit.pick_up_items(self)
if not self._pather.traverse_nodes([675], self): return False#, time_out=3) # Re-adjust itself
if not self._pather.traverse_nodes([675], self): return False#, timeout=3) # Re-adjust itself
self._pather.traverse_nodes_fixed("diablo_entrance_1_1", self) #static path to get to be able to spot 676
if not self._pather.traverse_nodes([676], self): return False#, time_out=3)
if not self._pather.traverse_nodes([676], self): return False#, timeout=3)
elif location == "entrance1_04": #node 676- Hall3
### APPROACH ###
@@ -379,10 +379,10 @@ class Hammerdin(IChar):
elif location == "entrance2_02": #node 682
### APPROACH ###
#if not self._pather.traverse_nodes([682], self): return False # , time_out=3):
#if not self._pather.traverse_nodes([682], self): return False # , timeout=3):
self._pather.traverse_nodes_fixed("diablo_trash_b_hall2_605_right", self) #pull mobs from the right
wait (0.2, 0.5)
if not self._pather.traverse_nodes([605], self): return False#, time_out=3)
if not self._pather.traverse_nodes([605], self): return False#, timeout=3)
### ATTACK ###
pos_m = convert_abs_to_monitor((0, 0))
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
@@ -400,13 +400,13 @@ class Hammerdin(IChar):
elif location == "entrance2_03": #node 683
### APPROACH ###
#if not self._pather.traverse_nodes([682], self): return False # , time_out=3):
#if not self._pather.traverse_nodes([682], self): return False # , timeout=3):
#self._pather.traverse_nodes_fixed("diablo_entrance2_1", self)
#if not self._pather.traverse_nodes([683], self): return False # , time_out=3):
#if not self._pather.traverse_nodes([683], self): return False # , timeout=3):
self._pather.traverse_nodes_fixed("diablo_trash_b_hall2_605_top1", self) #pull mobs from top
wait (0.2, 0.5)
self._pather.traverse_nodes_fixed("diablo_trash_b_hall2_605_top2", self) #pull mobs from top
if not self._pather.traverse_nodes([605], self): return False#, time_out=3)
if not self._pather.traverse_nodes([605], self): return False#, timeout=3)
### ATTACK ###
pos_m = convert_abs_to_monitor((0, 0))
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
@@ -424,14 +424,14 @@ class Hammerdin(IChar):
elif location == "entrance2_04": #node 686 - Hall3
### APPROACH ###
if not self._pather.traverse_nodes([605], self): return False#, time_out=3)
#if not self._pather.traverse_nodes([683,684], self): return False#, time_out=3)
if not self._pather.traverse_nodes([605], self): return False#, timeout=3)
#if not self._pather.traverse_nodes([683,684], self): return False#, timeout=3)
#self._pather.traverse_nodes_fixed("diablo_entrance2_2", self)
#if not self._pather.traverse_nodes([685,686], self): return False#, time_out=3)
#if not self._pather.traverse_nodes([685,686], self): return False#, timeout=3)
self._pather.traverse_nodes_fixed("diablo_trash_b_hall2_605_hall3", self)
if not self._pather.traverse_nodes([609], self): return False#, time_out=3)
if not self._pather.traverse_nodes([609], self): return False#, timeout=3)
self._pather.traverse_nodes_fixed("diablo_trash_b_hall3_pull_609", self)
if not self._pather.traverse_nodes([609], self): return False#, time_out=3)
if not self._pather.traverse_nodes([609], self): return False#, timeout=3)
### ATTACK ###
pos_m = convert_abs_to_monitor((0, 0))
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
@@ -450,9 +450,9 @@ class Hammerdin(IChar):
### LOOT ###
self._picked_up_items |= self._pickit.pick_up_items(self)
self._picked_up_items |= self._pickit.pick_up_items(self)
if not self._pather.traverse_nodes([609], self): return False#, time_out=3)
if not self._pather.traverse_nodes([609], self): return False#, timeout=3)
self._picked_up_items |= self._pickit.pick_up_items(self)
if not self._pather.traverse_nodes([609], self): return False#, time_out=3)
if not self._pather.traverse_nodes([609], self): return False#, timeout=3)
####################
# PENT TRASH TO SEAL
@@ -605,7 +605,7 @@ class Hammerdin(IChar):
elif location == "A1-L_01": #node 611 seal layout A1-L: safe_dist
### APPROACH ###
if not self._pather.traverse_nodes([611], self): return False # , time_out=3):
if not self._pather.traverse_nodes([611], self): return False # , timeout=3):
### ATTACK ###
pos_m = convert_abs_to_monitor((0, 0))
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
@@ -623,7 +623,7 @@ class Hammerdin(IChar):
elif location == "A1-L_02": #node 612 seal layout A1-L: center
### APPROACH ###
if not self._pather.traverse_nodes([612], self): return False # , time_out=3):
if not self._pather.traverse_nodes([612], self): return False # , timeout=3):
### ATTACK ###
if self._skill_hotkeys["redemption"]:
keyboard.send(self._skill_hotkeys["redemption"])
@@ -646,7 +646,7 @@ class Hammerdin(IChar):
elif location == "A1-L_03": #node 613 seal layout A1-L: fake_seal
### APPROACH ###
if not self._pather.traverse_nodes([613], self): return False # , time_out=3):
if not self._pather.traverse_nodes([613], self): return False # , timeout=3):
### ATTACK ###
pos_m = convert_abs_to_monitor((0, 0))
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
@@ -677,7 +677,7 @@ class Hammerdin(IChar):
elif location == "A1-L_seal2": #node 614 seal layout A1-L: boss_seal
### APPROACH ###
if not self._pather.traverse_nodes([613, 615], self): return False # , time_out=3):
if not self._pather.traverse_nodes([613, 615], self): return False # , timeout=3):
### ATTACK ###
if self._skill_hotkeys["redemption"]:
keyboard.send(self._skill_hotkeys["redemption"])
@@ -693,7 +693,7 @@ class Hammerdin(IChar):
### APPROACH ###
if not self._pather.traverse_nodes_fixed("dia_a2y_hop_622", self): return False
Logger.info("A2-Y: Hop!")
#if not self._pather.traverse_nodes([622], self): return False # , time_out=3):
#if not self._pather.traverse_nodes([622], self): return False # , timeout=3):
if not self._pather.traverse_nodes([622], self): return False
if self._skill_hotkeys["redemption"]:
keyboard.send(self._skill_hotkeys["redemption"])
@@ -743,7 +743,7 @@ class Hammerdin(IChar):
### ATTACK ###
### LOOT ###
# we loot at boss
if not self._pather.traverse_nodes([625], self): return False # , time_out=3):
if not self._pather.traverse_nodes([625], self): return False # , timeout=3):
if self._skill_hotkeys["redemption"]:
keyboard.send(self._skill_hotkeys["redemption"])
wait(0.3, 0.6)
@@ -785,7 +785,7 @@ class Hammerdin(IChar):
elif location == "B1-S_seal2": #B only has 1 seal, which is the boss seal = seal2
### APPROACH ###
if not self._pather.traverse_nodes([634], self): return False # , time_out=3):
if not self._pather.traverse_nodes([634], self): return False # , timeout=3):
### ATTACK ###
if self._skill_hotkeys["redemption"]:
keyboard.send(self._skill_hotkeys["redemption"])
@@ -821,7 +821,7 @@ class Hammerdin(IChar):
elif location == "B2-U_seal2": #B only has 1 seal, which is the boss seal = seal2
### APPROACH ###
self._pather.traverse_nodes_fixed("dia_b2u_bold_seal", self)
if not self._pather.traverse_nodes([644], self): return False # , time_out=3):
if not self._pather.traverse_nodes([644], self): return False # , timeout=3):
### ATTACK ###
### LOOT ###
# we loot at boss
@@ -860,7 +860,7 @@ class Hammerdin(IChar):
wait(0.1,0.3)
self._pather.traverse_nodes_fixed("dia_c1f_hop_fakeseal", self)
wait(0.1,0.3)
if not self._pather.traverse_nodes([655], self): return False # , time_out=3):
if not self._pather.traverse_nodes([655], self): return False # , timeout=3):
### ATTACK ###
pos_m = convert_abs_to_monitor((0, 0))
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
@@ -875,7 +875,7 @@ class Hammerdin(IChar):
wait(0.3, 0.6)
### LOOT ###
self._picked_up_items |= self._pickit.pick_up_items(self)
if not self._pather.traverse_nodes([655], self): return False # , time_out=3):
if not self._pather.traverse_nodes([655], self): return False # , timeout=3):
if self._skill_hotkeys["redemption"]:
keyboard.send(self._skill_hotkeys["redemption"])
wait(0.3, 0.6)
@@ -883,7 +883,7 @@ class Hammerdin(IChar):
elif location == "C1-F_seal2":
### APPROACH ###
self._pather.traverse_nodes_fixed("dia_c1f_654_651", self)
if not self._pather.traverse_nodes([652], self): return False # , time_out=3):
if not self._pather.traverse_nodes([652], self): return False # , timeout=3):
### ATTACK ###
pos_m = convert_abs_to_monitor((0, 0))
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
@@ -898,7 +898,7 @@ class Hammerdin(IChar):
wait(0.3, 0.6)
### LOOT ###
self._picked_up_items |= self._pickit.pick_up_items(self)
if not self._pather.traverse_nodes([652], self): return False # , time_out=3):
if not self._pather.traverse_nodes([652], self): return False # , timeout=3):
if self._skill_hotkeys["redemption"]:
keyboard.send(self._skill_hotkeys["redemption"])
wait(0.3, 0.6)
@@ -930,7 +930,7 @@ class Hammerdin(IChar):
elif location == "C2-G_seal1":
### APPROACH ###
#if not self._pather.traverse_nodes([663, 662], self): return False # , time_out=3): #caused 7% failed runs, replaced by static path.
#if not self._pather.traverse_nodes([663, 662], self): return False # , timeout=3): #caused 7% failed runs, replaced by static path.
self._pather.traverse_nodes_fixed("dia_c2g_lc_661", self)
### ATTACK ###
### LOOT ###
@@ -976,7 +976,7 @@ class Hammerdin(IChar):
if self._skill_hotkeys["redemption"]:
keyboard.send(self._skill_hotkeys["redemption"])
wait(0.3, 0.6)
if not self._pather.traverse_nodes([664, 665], self): return False # , time_out=3):
if not self._pather.traverse_nodes([664, 665], self): return False # , timeout=3):
else:
### APPROACH ###
@@ -1002,7 +1002,7 @@ class Hammerdin(IChar):
def kill_vizier(self, seal_layout:str) -> bool:
if seal_layout == "A1-L":
### APPROACH ###
if not self._pather.traverse_nodes([612], self): return False # , time_out=3):
if not self._pather.traverse_nodes([612], self): return False # , timeout=3):
### ATTACK ###
Logger.debug(seal_layout + ": Attacking Vizier at position 1/2")
pos_m = convert_abs_to_monitor((0, 0))
@@ -1011,7 +1011,7 @@ class Hammerdin(IChar):
self._move_and_attack((-30, -15), Config().char["atk_len_diablo_vizier"] * 0.5)
self._cast_hammers(1, "redemption")
Logger.debug(seal_layout + ": Attacking Vizier at position 2/2")
self._pather.traverse_nodes([611], self, time_out=3)
self._pather.traverse_nodes([611], self, timeout=3)
self._move_and_attack((30, 15), Config().char["atk_len_diablo_vizier"] * 0.5)
self._move_and_attack((-30, -15), Config().char["atk_len_diablo_vizier"]) # no factor, so merc is not reset by teleport and he his some time to move & kill stray bosses
self._cast_hammers(1, "redemption")
@@ -1024,16 +1024,16 @@ class Hammerdin(IChar):
wait(0.3, 1.2)
### LOOT ###
self._picked_up_items |= self._pickit.pick_up_items(self)
if not self._pather.traverse_nodes([612], self): return False # , time_out=3):
if not self._pather.traverse_nodes([612], self): return False # , timeout=3):
if self._skill_hotkeys["redemption"]:
keyboard.send(self._skill_hotkeys["redemption"])
wait(0.3, 0.6)
self._picked_up_items |= self._pickit.pick_up_items(self)
if not self._pather.traverse_nodes([612], self): return False # , time_out=3): # recalibrate after loot
if not self._pather.traverse_nodes([612], self): return False # , timeout=3): # recalibrate after loot
elif seal_layout == "A2-Y":
### APPROACH ###
if not self._pather.traverse_nodes([627, 622], self): return False # , time_out=3):
if not self._pather.traverse_nodes([627, 622], self): return False # , timeout=3):
### ATTACK ###
Logger.debug(seal_layout + ": Attacking Vizier at position 1/2")
pos_m = convert_abs_to_monitor((0, 0))
@@ -1042,7 +1042,7 @@ class Hammerdin(IChar):
self._move_and_attack((-30, -15), Config().char["atk_len_diablo_vizier"] * 0.5)
self._cast_hammers(1, "redemption")
Logger.debug(seal_layout + ": Attacking Vizier at position 2/2")
self._pather.traverse_nodes([623], self, time_out=3)
self._pather.traverse_nodes([623], self, timeout=3)
self._move_and_attack((30, 15), Config().char["atk_len_diablo_vizier"] * 0.5)
self._move_and_attack((-30, -15), Config().char["atk_len_diablo_vizier"] * 0.5)
self._cast_hammers(1, "redemption")
@@ -1064,12 +1064,12 @@ class Hammerdin(IChar):
if self._skill_hotkeys["redemption"]:
keyboard.send(self._skill_hotkeys["redemption"])
wait(0.3, 0.6)
if not self._pather.traverse_nodes([622], self): return False #, time_out=3):
if not self._pather.traverse_nodes([622], self): return False #, timeout=3):
if self._skill_hotkeys["redemption"]:
keyboard.send(self._skill_hotkeys["redemption"])
wait(0.3, 0.6)
self._picked_up_items |= self._pickit.pick_up_items(self)
if not self._pather.traverse_nodes([622], self): return False # , time_out=3): #recalibrate after loot
if not self._pather.traverse_nodes([622], self): return False # , timeout=3): #recalibrate after loot
else:
Logger.debug(seal_layout + ": Invalid location for kill_deseis("+ seal_layout +"), should not happen.")
@@ -1093,16 +1093,16 @@ class Hammerdin(IChar):
self._move_and_attack((-30, -15), Config().char["atk_len_diablo_deseis"] * 0.2)
self._cast_hammers(1, "redemption")
Logger.debug(seal_layout + ": Attacking De Seis at position 2/4")
self._pather.traverse_nodes(nodes1, self, time_out=3)
self._pather.traverse_nodes(nodes1, self, timeout=3)
self._move_and_attack((30, 15), Config().char["atk_len_diablo_deseis"] * 0.2)
self._move_and_attack((-30, -15), Config().char["atk_len_diablo_deseis"] * 0.2)
self._cast_hammers(1, "redemption")
Logger.debug(seal_layout + ": Attacking De Seis at position 3/4")
self._pather.traverse_nodes(nodes2, self, time_out=3)
self._pather.traverse_nodes(nodes2, self, timeout=3)
self._move_and_attack((0, 0), Config().char["atk_len_diablo_deseis"] * 0.5)
self._cast_hammers(1, "redemption")
Logger.debug(seal_layout + ": Attacking De Seis at position 4/4")
self._pather.traverse_nodes(nodes3, self, time_out=3)
self._pather.traverse_nodes(nodes3, self, timeout=3)
self._move_and_attack((0, 0), Config().char["atk_len_diablo_deseis"]) # no factor, so merc is not reset by teleport and he his some time to move & kill stray bosses
wait(0.1, 0.2)
self._cast_hammers(2, "redemption")
@@ -1130,16 +1130,16 @@ class Hammerdin(IChar):
self._move_and_attack((-30, -15), Config().char["atk_len_diablo_deseis"] * 0.2)
self._cast_hammers(1, "redemption")
Logger.debug(seal_layout + ": Attacking De Seis at position 2/4")
self._pather.traverse_nodes(nodes1, self, time_out=3)
self._pather.traverse_nodes(nodes1, self, timeout=3)
self._move_and_attack((30, 15), Config().char["atk_len_diablo_deseis"] * 0.2)
self._move_and_attack((-30, -15), Config().char["atk_len_diablo_deseis"] * 0.2)
self._cast_hammers(1, "redemption")
Logger.debug(seal_layout + ": Attacking De Seis at position 3/4")
self._pather.traverse_nodes(nodes2, self, time_out=3)
self._pather.traverse_nodes(nodes2, self, timeout=3)
self._move_and_attack((0, 0), Config().char["atk_len_diablo_deseis"] * 0.5)
self._cast_hammers(1, "redemption")
Logger.debug(seal_layout + ": Attacking De Seis at position 4/4")
self._pather.traverse_nodes(nodes3, self, time_out=3)
self._pather.traverse_nodes(nodes3, self, timeout=3)
self._move_and_attack((0, 0), Config().char["atk_len_diablo_deseis"]) # no factor, so merc is not reset by teleport and he his some time to move & kill stray bosses
wait(0.1, 0.2)
self._cast_hammers(2, "redemption")
@@ -1150,11 +1150,11 @@ class Hammerdin(IChar):
#if Config().general["info_screenshots"]: cv2.imwrite(f"./info_screenshots/info_check_deseis_dead" + seal_layout + "_" + time.strftime("%Y%m%d_%H%M%S") + ".png", grab())
### LOOT ###
self._picked_up_items |= self._pickit.pick_up_items(self)
if not self._pather.traverse_nodes([641], self): return False # , time_out=3):
if not self._pather.traverse_nodes([646], self): return False # , time_out=3):
if not self._pather.traverse_nodes([641], self): return False # , timeout=3):
if not self._pather.traverse_nodes([646], self): return False # , timeout=3):
self._picked_up_items |= self._pickit.pick_up_items(self)
if not self._pather.traverse_nodes([646], self): return False # , time_out=3):
if not self._pather.traverse_nodes([640], self): return False # , time_out=3):
if not self._pather.traverse_nodes([646], self): return False # , timeout=3):
if not self._pather.traverse_nodes([640], self): return False # , timeout=3):
self._picked_up_items |= self._pickit.pick_up_items(self)
else:
+7 -10
View File
@@ -7,7 +7,7 @@ from inventory import consumables
import keyboard
import numpy as np
from char.capabilities import CharacterCapabilities
from ui_manager import wait_for_screen_object
from ui_manager import is_visible, wait_until_visible
from ui import skills
from utils.custom_mouse import mouse
from utils.misc import wait, cut_roi, is_in_roi, color_filter
@@ -69,7 +69,7 @@ class IChar:
self,
template_type: Union[str, List[str]],
success_func: Callable = None,
time_out: float = 8,
timeout: float = 8,
threshold: float = 0.68,
telekinesis: bool = False
) -> bool:
@@ -77,17 +77,16 @@ class IChar:
Finds any template from the template finder and interacts with it
:param template_type: Strings or list of strings of the templates that should be searched for
:param success_func: Function that will return True if the interaction is successful e.g. return True when loading screen is reached, defaults to None
:param time_out: Timeout for the whole template selection, defaults to None
:param timeout: Timeout for the whole template selection, defaults to None
:param threshold: Threshold which determines if a template is found or not. None will use default form .ini files
:return: True if success. False otherwise
"""
if type(template_type) == list and "A5_STASH" in template_type:
# sometimes waypoint is opened and stash not found because of that, check for that
match = detect_screen_object(ScreenObjects.WaypointLabel)
if match.valid:
if is_visible(ScreenObjects.WaypointLabel):
keyboard.send("esc")
start = time.time()
while time_out is None or (time.time() - start) < time_out:
while timeout is None or (time.time() - start) < timeout:
template_match = TemplateFinder().search(template_type, grab(), threshold=threshold, normalize_monitor=True)
if template_match.valid:
Logger.debug(f"Select {template_match.name} ({template_match.score*100:.1f}% confidence)")
@@ -203,16 +202,14 @@ class IChar:
mouse.click(button="right")
consumables.increment_need("tp", 1)
wait(0.8, 1.3) # takes quite a while for tp to be visible
template_match = detect_screen_object(ScreenObjects.TownPortal)
if template_match.valid:
if (template_match := detect_screen_object(ScreenObjects.TownPortal)).valid:
pos = template_match.center
pos = (pos[0], pos[1] + 30)
# Note: Template is top of portal, thus move the y-position a bit to the bottom
mouse.move(*pos, randomize=6, delay_factor=[0.9, 1.1])
wait(0.08, 0.15)
mouse.click(button="left")
match = wait_for_screen_object(ScreenObjects.Loading, 2)
if match.valid:
if wait_until_visible(ScreenObjects.Loading, 2).valid:
return True
# move mouse away to not overlay with the town portal if mouse is in center
pos_screen = convert_monitor_to_screen(mouse.get_position())
+15 -19
View File
@@ -13,7 +13,7 @@ from pather import Location, Pather
import numpy as np
import time
import os
from ui_manager import wait_for_screen_object, ScreenObjects
from ui_manager import wait_until_visible, ScreenObjects
class Necro(IChar):
def __init__(self, skill_hotkeys: dict, pather: Pather):
@@ -491,7 +491,7 @@ class Necro(IChar):
if self.capabilities.can_teleport_natively:
self._pather.traverse_nodes_fixed("eldritch_end", self)
else:
self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, time_out=0.6, force_tp=True)
self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, timeout=0.6, force_tp=True)
self.bone_armor()
#get some more summons out for elite packs
@@ -532,7 +532,7 @@ class Necro(IChar):
self._summon_stat()
#continue to shenk fight
self._pather.traverse_nodes(([ 146, 147, 148]), self, time_out=1.4, force_tp=True)
self._pather.traverse_nodes(([ 146, 147, 148]), self, timeout=1.4, force_tp=True)
shenk_pos_abs = self._pather.find_abs_node_pos(149, grab())
if shenk_pos_abs is None:
@@ -566,7 +566,7 @@ class Necro(IChar):
# Move to items
#wait(self._cast_duration, self._cast_duration + 0.2)
self._pather.traverse_nodes(([148,149]), self, time_out=3.4, force_tp=True)
self._pather.traverse_nodes(([148,149]), self, timeout=3.4, force_tp=True)
for _ in range(30):
self._summon_count()
if self._skeletons_count < 10:
@@ -678,11 +678,8 @@ class Necro(IChar):
wait(0.08, 0.15)
mouse.click(button="left")
Logger.debug("enter durance lv 1")
match = wait_for_screen_object(ScreenObjects.Loading, 2)
if match.valid:
return True
else:
return False
return wait_until_visible(ScreenObjects.Loading, 2).valid
else:
Logger.debug("cant find durance!, trying fixed loc")
return False
@@ -719,8 +716,7 @@ class Necro(IChar):
wait(0.08, 0.15)
mouse.click(button="left")
Logger.debug("entering trav...")
match = wait_for_screen_object(ScreenObjects.Loading, 2)
if match.valid:
if wait_until_visible(ScreenObjects.Loading, 2).valid:
return True
return False
@@ -728,7 +724,7 @@ class Necro(IChar):
def kill_council(self) -> bool:
''' kill the council '''
result = self._pather.traverse_nodes((901,902,903,904,905,906,226,228,300), self, force_move=True,time_out = 2.5,threshold=.55)
result = self._pather.traverse_nodes((901,902,903,904,905,906,226,228,300), self, force_move=True,timeout = 2.5,threshold=.55)
#this is gross but the skeletons I think cause pathing issues
while result is False:
@@ -745,22 +741,22 @@ class Necro(IChar):
if self._pather.find_abs_node_pos(904, grab()):
#try again
result = self._pather.traverse_nodes((904,905,906,226,228,300), self, force_move=True,time_out = 2.5)
result = self._pather.traverse_nodes((904,905,906,226,228,300), self, force_move=True,timeout = 2.5)
elif self._pather.find_abs_node_pos(905, grab()):
#try again
result = self._pather.traverse_nodes((905,906,226,228,300), self, force_move=True,time_out = 2.5)
result = self._pather.traverse_nodes((905,906,226,228,300), self, force_move=True,timeout = 2.5)
elif self._pather.find_abs_node_pos(906, grab()):
#try again
result = self._pather.traverse_nodes((906,226,228,300), self, force_move=True,time_out = 2.5)
result = self._pather.traverse_nodes((906,226,228,300), self, force_move=True,timeout = 2.5)
elif self._pather.find_abs_node_pos(226, grab()):
#try again
result = self._pather.traverse_nodes((226,228,300), self, force_move=True,time_out = 2.5)
result = self._pather.traverse_nodes((226,228,300), self, force_move=True,timeout = 2.5)
elif self._pather.find_abs_node_pos(901, grab()):
#try again
result = self._pather.traverse_nodes((901,902,903,904,905,906,226,228,300), self, force_move=True,time_out = 2.5)
result = self._pather.traverse_nodes((901,902,903,904,905,906,226,228,300), self, force_move=True,timeout = 2.5)
elif self._pather.find_abs_node_pos(902, grab()):
#try again
result = self._pather.traverse_nodes((902,903,904,905,906,226,228,300), self, force_move=True,time_out = 2.5)
result = self._pather.traverse_nodes((902,903,904,905,906,226,228,300), self, force_move=True,timeout = 2.5)
self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=4,cast_v_div=3,cast_spell='amp_dmg',delay=3.0)
@@ -864,7 +860,7 @@ class Necro(IChar):
self._corpse_explosion(corpse_exp_pos, 240, cast_count=18)
#self._pather.traverse_nodes([230, 229 ,228], self, time_out=2.5, force_tp=True)
#self._pather.traverse_nodes([230, 229 ,228], self, timeout=2.5, force_tp=True)
Logger.info('\033[92m'+"atk cycle end"+'\033[0m')
+17 -10
View File
@@ -73,26 +73,33 @@ class BlizzSorc(Sorceress):
self.move(pos_m, force_move=True)
self._blizzard((-50, -50), spray=10)
self._cast_static()
wait(0.75)
#move down
pos_m = convert_abs_to_monitor((0, 85))
self.pre_move()
self.move(pos_m, force_move=True)
wait(0.70)
self._blizzard((-170, -350), spray=10)
self._cast_static()
#move down
wait(0.75)
pos_m = convert_abs_to_monitor((0, 75))
self.pre_move()
self.move(pos_m, force_move=True)
self._blizzard((100, -300), spray=10)
self._cast_static()
wait(0.75)
pos_m = convert_abs_to_monitor((0, 55))
self.pre_move()
self.move(pos_m, force_move=True)
wait(1.0)
self._blizzard((-50, -130), spray=10)
self._cast_static()
wait(3.0)
pos_m = convert_abs_to_monitor((0, -100))
self.pre_move()
self.move(pos_m, force_move=True)
self._blizzard((-50, -130), spray=10)
self._cast_static()
wait(1.75)
self._pather.traverse_nodes_fixed("eldritch_end", self)
return True
@@ -101,7 +108,7 @@ class BlizzSorc(Sorceress):
self.pre_move()
self.move(pos_m, force_move=True)
#lower left posistion
self._pather.traverse_nodes([151], self, time_out=2.5, force_tp=False)
self._pather.traverse_nodes([151], self, timeout=2.5, force_tp=False)
self._cast_static()
self._blizzard((-250, 100), spray=10)
self._ice_blast((60, 70), spray=60)
@@ -130,14 +137,14 @@ class BlizzSorc(Sorceress):
self._cast_static()
self._blizzard((100, -50), spray=10)
# Move to items
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, time_out=1.4, force_tp=True)
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=1.4, force_tp=True)
return True
def kill_council(self) -> bool:
# Move inside to the right
self._pather.traverse_nodes_fixed([(1110, 120)], self)
self._pather.offset_node(300, (80, -110))
self._pather.traverse_nodes([300], self, time_out=5.5, force_tp=True)
self._pather.traverse_nodes([300], self, timeout=5.5, force_tp=True)
self._pather.offset_node(300, (-80, 110))
# Attack to the left
self._blizzard((-150, 10), spray=80)
@@ -154,7 +161,7 @@ class BlizzSorc(Sorceress):
wait(0.5)
# Move to far left
self._pather.offset_node(301, (-80, -50))
self._pather.traverse_nodes([301], self, time_out=2.5, force_tp=True)
self._pather.traverse_nodes([301], self, timeout=2.5, force_tp=True)
self._pather.offset_node(301, (80, 50))
# Attack to RIGHT
self._blizzard((100, 150), spray=80)
@@ -167,7 +174,7 @@ class BlizzSorc(Sorceress):
for p in [(450, 100), (-190, 200)]:
pos_m = convert_abs_to_monitor(p)
self.move(pos_m, force_move=True)
self._pather.traverse_nodes([304], self, time_out=2.5, force_tp=True)
self._pather.traverse_nodes([304], self, timeout=2.5, force_tp=True)
# Attack to center of stairs
self._blizzard((-175, -200), spray=30)
self._ice_blast((30, -60), spray=30)
@@ -176,7 +183,7 @@ class BlizzSorc(Sorceress):
wait(1.0)
# Move back inside
self._pather.traverse_nodes_fixed([(1110, 15)], self)
self._pather.traverse_nodes([300], self, time_out=2.5, force_tp=False)
self._pather.traverse_nodes([300], self, timeout=2.5, force_tp=False)
# Attack to center
self._blizzard((-100, 0), spray=10)
self._cast_static()
@@ -208,7 +215,7 @@ class BlizzSorc(Sorceress):
self._cast_static()
self._ice_blast((-30, 50), spray=10)
# Move outside since the trav.py expects to start searching for items there if char can teleport
self._pather.traverse_nodes([226], self, time_out=2.5, force_tp=True)
self._pather.traverse_nodes([226], self, timeout=2.5, force_tp=True)
return True
def kill_nihlathak(self, end_nodes: list[int]) -> bool:
@@ -233,7 +240,7 @@ class BlizzSorc(Sorceress):
self._blizzard(cast_pos_abs, spray=15)
# Move to items
wait(1.3)
self._pather.traverse_nodes(end_nodes, self, time_out=0.8)
self._pather.traverse_nodes(end_nodes, self, timeout=0.8)
return True
def kill_summoner(self) -> bool:
+22 -20
View File
@@ -57,9 +57,9 @@ class LightSorc(Sorceress):
def kill_pindle(self) -> bool:
pindle_pos_abs = convert_screen_to_abs(Config().path["pindle_end"][0])
cast_pos_abs = [pindle_pos_abs[0] * 0.9, pindle_pos_abs[1] * 0.9]
self._lightning(cast_pos_abs, spray=11)
for _ in range(int(Config().char["atk_len_pindle"])):
self._chain_lightning(cast_pos_abs, spray=11)
self._lightning(cast_pos_abs, spray=11)
wait(self._cast_duration, self._cast_duration + 0.2)
# Move to items
self._pather.traverse_nodes_fixed("pindle_end", self)
@@ -68,9 +68,9 @@ class LightSorc(Sorceress):
def kill_eldritch(self) -> bool:
eld_pos_abs = convert_screen_to_abs(Config().path["eldritch_end"][0])
cast_pos_abs = [eld_pos_abs[0] * 0.9, eld_pos_abs[1] * 0.9]
self._lightning(cast_pos_abs, spray=50)
for _ in range(int(Config().char["atk_len_eldritch"])):
self._chain_lightning(cast_pos_abs, spray=90)
self._lightning(cast_pos_abs, spray=50)
# Move to items
wait(self._cast_duration, self._cast_duration + 0.2)
self._pather.traverse_nodes_fixed("eldritch_end", self)
@@ -81,36 +81,37 @@ class LightSorc(Sorceress):
if shenk_pos_abs is None:
shenk_pos_abs = convert_screen_to_abs(Config().path["shenk_end"][0])
cast_pos_abs = [shenk_pos_abs[0] * 0.9, shenk_pos_abs[1] * 0.9]
for _ in range(int(Config().char["atk_len_shenk"] * 0.5)):
self._chain_lightning(cast_pos_abs, spray=90)
pos_m = convert_abs_to_monitor((150, 50))
self.pre_move()
self.move(pos_m, force_move=True)
shenk_pos_abs = convert_screen_to_abs(Config().path["shenk_end"][0])
cast_pos_abs = [shenk_pos_abs[0] * 0.9, shenk_pos_abs[1] * 0.9]
for _ in range(int(Config().char["atk_len_shenk"] * 0.5)):
self._chain_lightning(cast_pos_abs, spray=90)
self._lightning(cast_pos_abs, spray=60)
for _ in range(int(Config().char["atk_len_shenk"] * 0.5)):
self._chain_lightning(cast_pos_abs, spray=90)
pos_m = convert_abs_to_monitor((150, 50))
self.pre_move()
self.move(pos_m, force_move=True)
shenk_pos_abs = convert_screen_to_abs(Config().path["shenk_end"][0])
cast_pos_abs = [shenk_pos_abs[0] * 0.9, shenk_pos_abs[1] * 0.9]
self._lightning(cast_pos_abs, spray=60)
for _ in range(int(Config().char["atk_len_shenk"] * 0.5)):
self._chain_lightning(cast_pos_abs, spray=90)
pos_m = convert_abs_to_monitor((150, 50))
self.pre_move()
self.move(pos_m, force_move=True)
shenk_pos_abs = convert_screen_to_abs(Config().path["shenk_end"][0])
cast_pos_abs = [shenk_pos_abs[0] * 0.9, shenk_pos_abs[1] * 0.9]
self._lightning(cast_pos_abs, spray=60)
for _ in range(int(Config().char["atk_len_shenk"])):
self._chain_lightning(cast_pos_abs, spray=90)
self._lightning(cast_pos_abs, spray=60)
self.pre_move()
self.move(pos_m, force_move=True)
# Move to items
wait(self._cast_duration, self._cast_duration + 0.2)
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, time_out=1.4, force_tp=True)
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=1.4, force_tp=True)
return True
def kill_council(self) -> bool:
# Move inside to the right
self._pather.traverse_nodes_fixed([(1110, 120)], self)
self._pather.offset_node(300, (80, -110))
self._pather.traverse_nodes([300], self, time_out=1.0, force_tp=True)
self._pather.traverse_nodes([300], self, timeout=1.0, force_tp=True)
self._pather.offset_node(300, (-80, 110))
wait(0.5)
self._frozen_orb((-150, -10), spray=10)
@@ -126,7 +127,7 @@ class LightSorc(Sorceress):
self.move(pos_m, force_move=True)
wait(0.5)
self._pather.offset_node(226, (-80, 60))
self._pather.traverse_nodes([226], self, time_out=1.0, force_tp=True)
self._pather.traverse_nodes([226], self, timeout=1.0, force_tp=True)
self._pather.offset_node(226, (80, -60))
wait(0.5)
self._frozen_orb((-150, -130), spray=10)
@@ -134,7 +135,7 @@ class LightSorc(Sorceress):
self._chain_lightning((-170, -150), spray=20)
wait(0.5)
self._pather.traverse_nodes_fixed([(1110, 15)], self)
self._pather.traverse_nodes([300], self, time_out=1.0, force_tp=True)
self._pather.traverse_nodes([300], self, timeout=1.0, force_tp=True)
pos_m = convert_abs_to_monitor((300, 150))
self.pre_move()
self.move(pos_m, force_move=True)
@@ -149,7 +150,7 @@ class LightSorc(Sorceress):
self.pre_move()
self.move(pos_m, force_move=True)
self._pather.offset_node(304, (0, -80))
self._pather.traverse_nodes([304], self, time_out=1.0, force_tp=True)
self._pather.traverse_nodes([304], self, timeout=1.0, force_tp=True)
self._pather.offset_node(304, (0, 80))
wait(0.5)
self._frozen_orb((175, -170), spray=40)
@@ -184,7 +185,7 @@ class LightSorc(Sorceress):
self.pre_move()
self.move(pos_m, force_move=True)
# Move outside since the trav.py expects to start searching for items there if char can teleport
self._pather.traverse_nodes([226], self, time_out=2.5, force_tp=True)
self._pather.traverse_nodes([226], self, timeout=2.5, force_tp=True)
return True
def kill_nihlathak(self, end_nodes: list[int]) -> bool:
@@ -204,6 +205,7 @@ class LightSorc(Sorceress):
if nihlathak_pos_abs is not None:
cast_pos_abs = np.array([nihlathak_pos_abs[0] * 0.9, nihlathak_pos_abs[1] * 0.9])
self._lightning(cast_pos_abs, spray=60)
self._chain_lightning(cast_pos_abs, delay, 90)
# Do some tele "dancing" after each sequence
if i < atk_len - 1:
@@ -220,7 +222,7 @@ class LightSorc(Sorceress):
# Move to items
wait(self._cast_duration, self._cast_duration + 0.2)
self._pather.traverse_nodes(end_nodes, self, time_out=0.8)
self._pather.traverse_nodes(end_nodes, self, timeout=0.8)
return True
def kill_summoner(self) -> bool:
@@ -229,8 +231,8 @@ class LightSorc(Sorceress):
pos_m = convert_abs_to_monitor((-20, 20))
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
for _ in range(int(Config().char["atk_len_arc"])):
self._chain_lightning(cast_pos_abs, spray=11)
self._lightning(cast_pos_abs, spray=11)
self._chain_lightning(cast_pos_abs, spray=11)
wait(self._cast_duration, self._cast_duration + 0.2)
return True
+4 -4
View File
@@ -48,7 +48,7 @@ class NovaSorc(Sorceress):
return True
def kill_shenk(self) -> bool:
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, time_out=1.0)
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=1.0)
self._cast_static(0.6)
self._nova(Config().char["atk_len_shenk"])
return True
@@ -61,13 +61,13 @@ class NovaSorc(Sorceress):
self._pather.offset_node(229, offset_229)
def clear_inside():
self._pather.traverse_nodes_fixed([(1110, 120)], self)
self._pather.traverse_nodes([229], self, time_out=0.8, force_tp=True)
self._pather.traverse_nodes([229], self, timeout=0.8, force_tp=True)
self._nova(atk_len)
self._move_and_attack((-40, -20), atk_len)
self._move_and_attack((40, 20), atk_len)
self._move_and_attack((40, 20), atk_len)
def clear_outside():
self._pather.traverse_nodes([226], self, time_out=0.8, force_tp=True)
self._pather.traverse_nodes([226], self, timeout=0.8, force_tp=True)
self._nova(atk_len)
self._move_and_attack((45, -20), atk_len)
self._move_and_attack((-45, 20), atk_len)
@@ -82,7 +82,7 @@ class NovaSorc(Sorceress):
def kill_nihlathak(self, end_nodes: list[int]) -> bool:
atk_len = Config().char["atk_len_nihlathak"] * 0.3
# Move close to nihlathak
self._pather.traverse_nodes(end_nodes, self, time_out=0.8, do_pre_move=False)
self._pather.traverse_nodes(end_nodes, self, timeout=0.8, do_pre_move=False)
# move mouse to center
pos_m = convert_abs_to_monitor((0, 0))
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
+6 -7
View File
@@ -10,7 +10,7 @@ import time
from typing import Tuple
from pather import Pather
from config import Config
from ui_manager import detect_screen_object, ScreenObjects
from ui_manager import ScreenObjects, is_visible
class Sorceress(IChar):
def __init__(self, skill_hotkeys: dict, pather: Pather):
@@ -38,20 +38,19 @@ class Sorceress(IChar):
self,
template_type: Union[str, List[str]],
success_func: Callable = None,
time_out: float = 8,
timeout: float = 8,
threshold: float = 0.68,
telekinesis: bool = False
) -> bool:
# In case telekinesis is False or hotkey is not set, just call the base implementation
if not self._skill_hotkeys["telekinesis"] or not telekinesis:
return super().select_by_template(template_type, success_func, time_out, threshold)
return super().select_by_template(template_type, success_func, timeout, threshold)
if type(template_type) == list and "A5_STASH" in template_type:
# sometimes waypoint is opened and stash not found because of that, check for that
match = detect_screen_object(ScreenObjects.WaypointLabel)
if match.valid:
if is_visible(ScreenObjects.WaypointLabel):
keyboard.send("esc")
start = time.time()
while time_out is None or (time.time() - start) < time_out:
while timeout is None or (time.time() - start) < timeout:
template_match = TemplateFinder().search(template_type, grab(), threshold=threshold, normalize_monitor=True)
if template_match.valid:
keyboard.send(self._skill_hotkeys["telekinesis"])
@@ -65,7 +64,7 @@ class Sorceress(IChar):
if success_func is None or success_func():
return True
# In case telekinesis fails, try again with the base implementation
return super().select_by_template(template_type, success_func, time_out, threshold)
return super().select_by_template(template_type, success_func, timeout, threshold)
def pre_buff(self):
if Config().char["cta_available"]:
+3 -3
View File
@@ -96,7 +96,7 @@ class Trapsin(IChar):
if self.capabilities.can_teleport_natively:
self._pather.traverse_nodes_fixed("eldritch_end", self)
else:
self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, time_out=0.6, force_tp=True)
self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, timeout=0.6, force_tp=True)
return True
def kill_shenk(self) -> bool:
@@ -110,7 +110,7 @@ class Trapsin(IChar):
self._left_attack(cast_pos_abs, 90)
# Move to items
wait(self._cast_duration, self._cast_duration + 0.2)
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, time_out=1.4, force_tp=True)
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=1.4, force_tp=True)
return True
def kill_nihlathak(self, end_nodes: list[int]) -> bool:
@@ -132,7 +132,7 @@ class Trapsin(IChar):
self.move(pos_m)
# Move to items
wait(self._cast_duration, self._cast_duration + 0.2)
self._pather.traverse_nodes(end_nodes, self, time_out=0.8)
self._pather.traverse_nodes(end_nodes, self, timeout=0.8)
return True
+2 -2
View File
@@ -23,12 +23,12 @@ class Chest:
chest = filename[:-4].upper()
self._templates.append(chest)
def open_up_chests(self, time_out: float = 8.0, threshold: float = 0.73) -> bool:
def open_up_chests(self, timeout: float = 8.0, threshold: float = 0.73) -> bool:
Logger.debug("Open chests")
templates = self._templates
found_chest = True
start = time.time()
while time.time() - start < time_out:
while time.time() - start < timeout:
template_match = TemplateFinder().search(templates, grab(), roi=Config().ui_roi["reduce_to_center"], threshold=threshold, use_grayscale=True, best_match=True, normalize_monitor=True)
# search for at least 1.5 second, if no chest found, break
if not template_match.valid:
+5 -2
View File
@@ -180,7 +180,10 @@ class Config:
self._shop_config.read('config/shop.ini')
self._custom = configparser.ConfigParser()
if os.environ.get('RUN_ENV') != "test" and os.path.exists('config/custom.ini'):
self._custom.read('config/custom.ini')
try:
self._custom.read('config/custom.ini')
except configparser.MissingSectionHeaderError:
Logger.error("custom.ini missing section header, defaulting to params.ini")
self.general = {
"saved_games_folder": self._select_val("general", "saved_games_folder"),
@@ -238,7 +241,7 @@ class Config:
"belt_hp_columns": int(self._select_val("char", "belt_hp_columns")),
"belt_mp_columns": int(self._select_val("char", "belt_mp_columns")),
"stash_gold": bool(int(self._select_val("char", "stash_gold"))),
"gold_trav_only": bool(int(self._select_val("char", "gold_trav_only"))),
"min_gold_to_pick": int(_default_iff(self._select_val("char", "min_gold_to_pick"), '', 0)),
"use_merc": bool(int(self._select_val("char", "use_merc"))),
"id_items": bool(int(self._select_val("char", "id_items"))),
"open_chests": bool(int(self._select_val("char", "open_chests"))),
+5 -8
View File
@@ -1,14 +1,12 @@
from utils.misc import color_filter, cut_roi, wait
from template_finder import TemplateFinder
from screen import convert_screen_to_monitor, grab
from utils.misc import wait
from screen import grab
from config import Config
from utils.custom_mouse import mouse
import keyboard
import cv2
from logger import Logger
import time
from ui import view
from ui_manager import ScreenObjects, detect_screen_object
from ui_manager import ScreenObjects, is_visible
class DeathManager:
@@ -36,8 +34,7 @@ class DeathManager:
def handle_death_screen(self):
img = grab()
template_match = detect_screen_object(ScreenObjects.YouHaveDied, img)
if template_match.valid:
if is_visible(ScreenObjects.YouHaveDied, img):
Logger.warning("You have died!")
if Config().general["info_screenshots"]:
self._last_death_screenshot = "./info_screenshots/info_debug_death_" + time.strftime("%Y%m%d_%H%M%S") + ".png"
@@ -55,7 +52,7 @@ class DeathManager:
wait(0.1, 0.2)
mouse.release(button="left")
time.sleep(1)
if detect_screen_object(ScreenObjects.MainMenu).valid:
if is_visible(ScreenObjects.MainMenu):
# in this case chicken executed and left the game, but we were still dead.
return True
keyboard.send("esc")
-1
View File
@@ -42,7 +42,6 @@ class GameController:
do_restart = False
messenger = Messenger()
while 1:
self.health_manager.update_location(self.bot.get_curr_location())
max_game_length_reached = self.game_stats.get_current_game_length() > Config().general["max_game_length_s"]
max_consecutive_fails_reached = False if not Config().general["max_consecutive_fails"] else self.game_stats.get_consecutive_runs_failed() >= Config().general["max_consecutive_fails"]
if max_game_length_reached or max_consecutive_fails_reached or self.death_manager.died() or self.health_manager.did_chicken():
+5 -6
View File
@@ -1,9 +1,8 @@
from screen import grab
from config import Config
from death_manager import DeathManager
import time
import keyboard
from ui_manager import detect_screen_object, ScreenObjects
from ui_manager import ScreenObjects, is_visible
from ui import view, loading
from utils.misc import set_d2r_always_on_top
@@ -23,22 +22,22 @@ class GameRecovery:
# make sure we are not on loading screen
is_loading = loading.check_for_black_screen()
while is_loading:
is_loading = detect_screen_object(ScreenObjects.Loading).valid
is_loading = is_visible(ScreenObjects.Loading)
is_loading |= loading.check_for_black_screen()
time.sleep(0.5)
# lets just see if you might already be at hero selection
if detect_screen_object(ScreenObjects.MainMenu).valid:
if is_visible(ScreenObjects.MainMenu):
return True
# would have been too easy, maybe we have died?
if self._death_manager.handle_death_screen():
time.sleep(1)
continue
# check for save/exit button
if detect_screen_object(ScreenObjects.SaveAndExit).valid:
if is_visible(ScreenObjects.SaveAndExit):
view.save_and_exit()
continue
# maybe we are in-game in stash/inventory, press escape
elif detect_screen_object(ScreenObjects.InGame).valid:
elif is_visible(ScreenObjects.InGame):
keyboard.send("esc")
time.sleep(1)
return False
+33 -21
View File
@@ -9,8 +9,23 @@ from logger import Logger
from screen import grab
import time
from config import Config
from inventory import common
from ui import view, meters
from ui_manager import detect_screen_object, ScreenObjects
from ui_manager import ScreenObjects, is_visible
pause_state = True
def get_pause_state():
global pause_state
return pause_state
def set_pause_state(state: bool):
global pause_state
prev = get_pause_state()
if prev != state:
debug_str = "pausing" if state else "active"
Logger.info(f"Health Manager is now {debug_str}")
pause_state = state
class HealthManager:
def __init__(self):
@@ -21,8 +36,8 @@ class HealthManager:
self._last_mana = time.time()
self._last_merc_heal = time.time()
self._callback = None
self._pausing = True
self._last_chicken_screenshot = None
self._count_panel_detects = 0
def stop_monitor(self):
self._do_monitor = False
@@ -35,16 +50,7 @@ class HealthManager:
def reset_chicken_flag(self):
self._did_chicken = False
self._pausing = True
def update_location(self, loc: Location):
if loc is not None and type(loc) == str:
bosses = ["shenk", "eldritch", "pindle", "nihlathak", "trav", "arc", "diablo"]
prev_value = self._pausing
self._pausing = not any(substring in loc for substring in bosses)
if self._pausing != prev_value:
debug_str = "pausing" if self._pausing else "active"
Logger.info(f"Health Manager is now {debug_str}")
set_pause_state(True)
def _do_chicken(self, img):
if self._callback is not None:
@@ -64,7 +70,7 @@ class HealthManager:
time.sleep(0.01)
view.save_and_exit()
self._did_chicken = True
self._pausing = True
set_pause_state(True)
def start_monitor(self):
Logger.info("Start health monitoring")
@@ -74,9 +80,9 @@ class HealthManager:
while self._do_monitor:
time.sleep(0.1)
# Wait until the flag is reset by main.py
if self._did_chicken or self._pausing: continue
if self._did_chicken or get_pause_state(): continue
img = grab()
if detect_screen_object(ScreenObjects.InGame, img).valid:
if is_visible(ScreenObjects.InGame, img):
health_percentage = meters.get_health(img)
mana_percentage = meters.get_mana(img)
# check rejuv
@@ -103,7 +109,7 @@ class HealthManager:
belt.drink_potion("mana", stats=[health_percentage, mana_percentage])
self._last_mana = time.time()
# check merc
if detect_screen_object(ScreenObjects.MercIcon, img).valid:
if is_visible(ScreenObjects.MercIcon, img):
merc_health_percentage = meters.get_merc_health(img)
last_drink = time.time() - self._last_merc_heal
if merc_health_percentage < Config().char["merc_chicken"]:
@@ -115,20 +121,26 @@ class HealthManager:
elif merc_health_percentage < Config().char["heal_merc"] and last_drink > 7.0:
belt.drink_potion("health", merc=True, stats=[merc_health_percentage])
self._last_merc_heal = time.time()
if detect_screen_object(ScreenObjects.QuestSkillBtn, img).valid:
Logger.warning(f"Skill, stats, or quest button appeared--quit to avoid accidental allocations.")
self._do_chicken(img)
if is_visible(ScreenObjects.LeftPanel, img) or is_visible(ScreenObjects.RightPanel, img):
Logger.warning(f"Found an open inventory / quest / skill / stats page. Close it.")
self._count_panel_detects += 1
if self._count_panel_detects >= 2:
self._count_panel_detects = 0
Logger.warning(f"Found an open inventory / quest / skill / stats page again. Chicken to dismiss.")
self._do_chicken(img)
common.close()
Logger.debug("Stop health monitoring")
# Testing: Start dying or lossing mana and see if it works
# Testing: Start dying or losing mana and see if it works
if __name__ == "__main__":
import threading
import keyboard
import os
from health_manager import set_pause_state
keyboard.add_hotkey('f12', lambda: Logger.info('Exit Health Manager') or os._exit(1))
manager = HealthManager()
manager._pausing = False
set_pause_state(True)
Logger.info("Press f12 to exit health manager")
health_monitor_thread = threading.Thread(target=manager.start_monitor)
health_monitor_thread.start()
+25 -14
View File
@@ -3,14 +3,33 @@ from logger import Logger
from typing import List
import numpy as np
from template_finder import TemplateFinder
from inventory import common, consumables
from inventory.consumables import item_consumables_map
from inventory import common, consumables, personal
from ui import view
from ui_manager import is_visible, wait_until_visible, ScreenObjects, wait_until_hidden
from utils.custom_mouse import mouse
from utils.misc import cut_roi, wait, color_filter
from config import Config
from screen import convert_abs_to_monitor, convert_monitor_to_screen, convert_screen_to_monitor, grab
import keyboard
def open(img: np.ndarray = None) -> np.ndarray:
img = grab() if img is None else img
if is_visible(ScreenObjects.BeltExpandable, img) and Config().char["belt_rows"] > 1:
keyboard.send(Config().char["show_belt"])
if not wait_until_hidden(ScreenObjects.BeltExpandable, 1):
return None
img = grab()
return img
def close(img: np.ndarray = None) -> np.ndarray:
img = grab() if img is None else img
if not is_visible(ScreenObjects.BeltExpandable, img):
keyboard.send("esc")
if not wait_until_visible(ScreenObjects.BeltExpandable, 2).valid:
success = view.return_to_play()
if not success:
return None
return img
def _potion_type(img: np.ndarray) -> str:
"""
@@ -80,10 +99,8 @@ def update_pot_needs():
if screen_mouse_pos[1] > Config().ui_pos["screen_height"] * 0.72:
center_m = convert_abs_to_monitor((-200, -120))
mouse.move(*center_m, randomize=100)
keyboard.send(Config().char["show_belt"])
wait(0.5)
img = open()
# first clean up columns that might be too much
img = grab()
for column in range(4):
potion_type = _potion_type(_cut_potion_img(img, column, 0))
if potion_type != "empty":
@@ -112,21 +129,17 @@ def update_pot_needs():
break
elif current_column is not None and potion_type == "empty":
pot_needs[current_column] += 1
wait(0.2)
#Logger.debug(f"Will pickup: {pot_needs}")
keyboard.send(Config().char["show_belt"])
consumables.set_needs("health", pot_needs["health"])
consumables.set_needs("mana", pot_needs["mana"])
consumables.set_needs("rejuv", pot_needs["rejuv"])
close(img)
def fill_up_belt_from_inventory(num_loot_columns: int):
"""
Fill up your belt with pots from the inventory e.g. after death. It will open and close invetory by itself!
:param num_loot_columns: Number of columns used for loot from left
"""
keyboard.send(Config().char["inventory_screen"])
wait(0.7, 1.0)
img = grab()
img = personal.open()
pot_positions = []
for column, row in itertools.product(range(num_loot_columns), range(4)):
center_pos, slot_img = common.get_slot_pos_and_img(img, column, row)
@@ -141,6 +154,4 @@ def fill_up_belt_from_inventory(num_loot_columns: int):
mouse.click(button="left")
wait(0.3, 0.4)
keyboard.release("shift")
wait(0.2, 0.25)
keyboard.send(Config().char["inventory_screen"])
wait(0.5)
common.close(img)
+108 -28
View File
@@ -5,15 +5,15 @@ import keyboard
import time
from utils.custom_mouse import mouse
from template_finder import TemplateFinder
from ui_manager import detect_screen_object, ScreenObjects, center_mouse
from ui_manager import detect_screen_object, ScreenObjects, center_mouse, is_visible, wait_until_hidden
from utils.misc import wait, trim_black, color_filter, cut_roi
from inventory import consumables, personal
from ui import view
from screen import grab
from screen import convert_screen_to_monitor, grab
from dataclasses import dataclass
from logger import Logger
from ocr import Ocr
from template_finder import TemplateMatch
@dataclass
class BoxInfo:
@@ -65,10 +65,9 @@ def slot_has_item(slot_img: np.ndarray) -> bool:
def close(img: np.ndarray = None) -> np.ndarray:
img = grab() if img is None else img
if detect_screen_object(ScreenObjects.RightPanel, img).valid:
if is_visible(ScreenObjects.RightPanel, img) or is_visible(ScreenObjects.LeftPanel, img):
keyboard.send("esc")
wait(0.1, 0.2)
if detect_screen_object(ScreenObjects.RightPanel).valid:
if not wait_until_hidden(ScreenObjects.RightPanel, 1) and not wait_until_hidden(ScreenObjects.LeftPanel, 1):
success = view.return_to_play()
if not success:
return None
@@ -113,13 +112,13 @@ def id_item_with_tome(item_location: list, id_tome_location: list):
wait(0.1)
mouse.click(button="left")
consumables.increment_need("id", 1)
wait(0.2, 0.4)
wait(0.1)
def transfer_items(items: list, action: str = "drop") -> list:
#requires open inventory / stash / vendor
img = grab()
filtered = []
left_panel_open = detect_screen_object(ScreenObjects.LeftPanel, img).valid
left_panel_open = is_visible(ScreenObjects.LeftPanel, img)
if action == "drop":
filtered = [ item for item in items if item.keep == False and item.sell == False ]
elif action == "sell":
@@ -127,7 +126,7 @@ def transfer_items(items: list, action: str = "drop") -> list:
if not left_panel_open:
Logger.error(f"transfer_items: Can't perform, vendor is not open")
elif action == "stash":
if detect_screen_object(ScreenObjects.GoldBtnStash, img).valid:
if is_visible(ScreenObjects.GoldBtnStash, img):
filtered = [ item for item in items if item.keep == True ]
else:
Logger.error(f"transfer_items: Can't perform, stash is not open")
@@ -143,16 +142,15 @@ def transfer_items(items: list, action: str = "drop") -> list:
keyboard.send('ctrl', do_release=False)
wait(0.2, 0.4)
for item in filtered:
attempts = 0
attempts = 1
prev_gold_img = cut_roi(grab(), roi=Config().ui_roi["inventory_gold_digits"])
while attempts < 2:
while attempts <= 2:
# move to item position and left click
mouse.move(*item.pos, randomize=4, delay_factor=[0.2, 0.4])
wait(0.2, 0.4)
mouse.press(button="left")
wait(0.2, 0.4)
mouse.release(button="left")
wait(0.2, 0.4)
# if dropping, drag item to middle if vendor/stash is open
if action == "drop" and left_panel_open:
center_mouse()
@@ -160,28 +158,33 @@ def transfer_items(items: list, action: str = "drop") -> list:
mouse.press(button="left")
wait(0.2, 0.3)
mouse.release(button="left")
wait(0.8, 1)
# check if item is still there
img=grab()
slot_img = get_slot_pos_and_img(img, item.column, item.row)[1]
if not slot_has_item(slot_img):
item_remains = True
start = time.time()
while item_remains and (time.time() - start) < 3:
slot_img = get_slot_pos_and_img(grab(), item.column, item.row)[1]
item_remains = slot_has_item(slot_img)
if not item_remains:
# item successfully transferred, delete from list
for cnt, o_item in enumerate(items):
if o_item.pos == item.pos:
items.pop(cnt)
break
# check and see if inventory gold count changed
new_gold_img = cut_roi(img, roi=Config().ui_roi["inventory_gold_digits"])
if prev_gold_img.shape == new_gold_img.shape and not(np.bitwise_xor(prev_gold_img, new_gold_img).any()):
Logger.info("Inventory gold is full, force stash")
personal.set_inventory_gold_full(True)
else:
personal.set_inventory_gold_full(False)
if action == "sell":
# check and see if inventory gold count changed
start = time.time()
gold_unchanged = True
while gold_unchanged and (time.time() - start) < 2:
new_gold_img = cut_roi(grab(), roi=Config().ui_roi["inventory_gold_digits"])
gold_unchanged = prev_gold_img.shape == new_gold_img.shape and not(np.bitwise_xor(prev_gold_img, new_gold_img).any())
if gold_unchanged:
Logger.info("Inventory gold is full, force stash")
personal.set_inventory_gold_full(gold_unchanged)
break
else:
# item is still there, try again
attempts += 1
if attempts > 1:
if attempts > 2:
Logger.error(f"transfer_items: could not stash in position {item.pos}")
if (action == "drop" and not left_panel_open) or action in ["sell", "stash"]:
keyboard.send('ctrl', do_press=False)
@@ -215,15 +218,92 @@ def read_gold(img: np.ndarray = None, type: str = "inventory"):
Logger.debug(f"{type.upper()} gold: {number}")
return number
def wait_for_left_inventory(timeout: int = 5):
start=time.time()
while time.time() - start < timeout:
if left_inventory_ready(grab()):
Logger.debug("Vendor/stash inventory ready")
return True
wait(0.1)
Logger.error("wait_for_left_inventory: Vendor/stash inventory not detected")
return False
def left_inventory_ready(img = np.ndarray):
# on laggy PC's or online the vendor may take longer to have all of its inventory ready
if is_visible(ScreenObjects.LeftPanel, img):
# check for tab text
text, _ = color_filter(img, Config().colors["tab_text"])
text = cut_roi(text, Config().ui_roi["left_inventory_tabs"])
# check for red slots in inventory space
red, _ = color_filter(img, Config().colors["red_slot"])
red = cut_roi(red, Config().ui_roi["left_inventory"])
# check for blue slots in inventory space
blue, _ = color_filter(img, Config().colors["blue_slot"])
blue = cut_roi(blue, Config().ui_roi["left_inventory"])
# if none of the above are true, then inventory is empty and there are no tabs (not loaded yet)
return any(np.sum(i) > 0 for i in [text, red, blue])
return False
def tab_properties(idx: int = 0) -> dict[int, int, tuple]:
tab_width = round(int(Config().ui_roi["tab_indicator"][2]) / 4)
x_start = int(Config().ui_roi["tab_indicator"][0])
left = idx * tab_width + x_start
right = (idx + 1) * tab_width + x_start
x_center = (left + right) / 2
y_center = int(Config().ui_roi["tab_indicator"][1]) - 5
return {
"left": round(left),
"right": round(right),
"center": (x_center, y_center)
}
def indicator_location_to_tab_count(pos: tuple) -> int:
for i in range(3):
tab = tab_properties(i)
if tab["left"] <= pos[0] < tab["right"]:
return i
def get_active_tab(indicator: TemplateMatch = None) -> int:
indicator = detect_screen_object(ScreenObjects.TabIndicator) if indicator is None else indicator
if indicator.valid:
return indicator_location_to_tab_count(indicator.center)
else:
Logger.error("common/get_active_tab(): Error finding tab indicator")
return False
def select_tab(idx: int):
# stash or vendor must be open
# indices start from 0
if not get_active_tab() == idx:
tab = tab_properties(idx)
pos = convert_screen_to_monitor(tab["center"])
mouse.move(*pos)
wait(0.2, 0.3)
mouse.click("left")
wait(0.2, 0.3)
if __name__ == "__main__":
import os
import keyboard
from config import Config
from screen import start_detecting_window, stop_detecting_window
from utils.misc import color_filter
start_detecting_window()
keyboard.add_hotkey('f12', lambda: os._exit(1))
keyboard.add_hotkey('f12', lambda: Logger.info('Force Exit (f12)') or stop_detecting_window() or os._exit(1))
print("Move to d2r window and press f11")
keyboard.wait("f11")
img = grab()
print(read_gold(img, "inventory"))
stop_detecting_window()
#select_tab(0)
color = Config().colors["tab_text"]
while 1:
# img = cv2.imread("")
img = grab()
a = cut_roi(img, Config().ui_roi["deposit_ok"])
b, _ = color_filter(img, Config().colors["tab_text"])
print(np.sum(b))
cv2.imshow('test', b)
key = cv2.waitKey(1)
+10 -9
View File
@@ -1,15 +1,13 @@
from config import Config
import cv2
import numpy as np
import keyboard
import time
import parse
from utils.custom_mouse import mouse
from template_finder import TemplateFinder
from ui_manager import detect_screen_object, ScreenObjects
from inventory import personal, common
from inventory import personal
from utils.misc import wait
from screen import grab, convert_screen_to_monitor
from screen import grab
from dataclasses import dataclass
from logger import Logger
from item import ItemCropper
@@ -136,11 +134,14 @@ def update_tome_key_needs(img: np.ndarray = None, item_type: str = "tp") -> bool
# get the item description box
item_box = ItemCropper().crop_item_descr(hovered_item, model="engd2r_inv_th_fast")
if item_box.valid:
result = parse.search("Quantity: {:d}", item_box.ocr_result.text).fixed[0]
if item_type.lower() in ["tp", "id"]:
set_needs(item_type, 20 - result)
if item_type.lower() == "key":
set_needs(item_type, 12 - result)
try:
result = parse.search("Quantity: {:d}", item_box.ocr_result.text).fixed[0]
if item_type.lower() in ["tp", "id"]:
set_needs(item_type, 20 - result)
if item_type.lower() == "key":
set_needs(item_type, 12 - result)
except Exception as e:
Logger.error(f"update_tome_key_needs: unable to parse quantity for {item_type}. Exception: {e}")
else:
Logger.error(f"update_tome_key_needs: Failed to capture item description box for {item_type}")
if Config().general["info_screenshots"]:
+51 -52
View File
@@ -3,7 +3,7 @@ from game_stats import GameStats
from item import ItemFinder, Item
from item.item_cropper import ItemText
from logger import Logger
from screen import convert_abs_to_monitor, grab, convert_screen_to_monitor
from screen import grab, convert_screen_to_monitor
import keyboard
import cv2
import time
@@ -15,7 +15,7 @@ from utils.misc import wait, is_in_roi, mask_by_roi
from utils.custom_mouse import mouse
from inventory import stash, common, vendor
from ui import view
from ui_manager import detect_screen_object, wait_for_screen_object, ScreenObjects, center_mouse
from ui_manager import detect_screen_object, is_visible, select_screen_object_match, wait_until_visible, ScreenObjects, center_mouse
from item import ItemCropper
from messages import Messenger
@@ -58,68 +58,64 @@ def stash_all_items(items: list = None):
global messenger
if items is None:
Logger.debug("No items to stash, skip")
return False
return None
center_mouse()
# Wait till gold btn is found
Logger.debug("Searching for inventory gold btn...")
if not (gold_btn := wait_for_screen_object(ScreenObjects.GoldBtnInventory, time_out = 20)).valid:
Logger.error("Could not determine to be in stash menu. Continue...")
return
Logger.debug("Found inventory gold btn")
# Wait for stash to fully load
if not common.wait_for_left_inventory():
Logger.error("stash_all_items(): Could not determine to be in stash menu. Continue...")
return items
# stash gold
if Config().char["stash_gold"]:
img = grab()
if detect_screen_object(ScreenObjects.GoldNone, img).valid:
Logger.debug("No gold to stash")
else:
if not is_visible(ScreenObjects.GoldNone):
Logger.debug("Stashing gold")
stash.move_to_stash_tab(min(3, stash.curr_stash["gold"]))
try:
# Try to read gold count with OCR
stash_full_of_gold = common.read_gold(img, "stash") == 2500000
except:
stash_full_of_gold = False
common.select_tab(min(3, stash.get_curr_stash()["gold"]))
wait(0.7, 1)
stash_full_of_gold = False
# Try to read gold count with OCR
try: stash_full_of_gold = common.read_gold(grab(), "stash") == 2500000
except: pass
if not stash_full_of_gold:
# If gold read by OCR fails, fallback to old method
mouse.move(*gold_btn.center, randomize=4)
wait(0.1, 0.15)
mouse.press(button="left")
wait(0.25, 0.35)
mouse.release(button="left")
wait(0.4, 0.6)
keyboard.send("enter") #if stash already full of gold just nothing happens -> gold stays on char -> no popup window
wait(1.3, 1.6)
gold_btn = detect_screen_object(ScreenObjects.GoldBtnInventory)
select_screen_object_match(gold_btn)
if wait_until_visible(ScreenObjects.DepositBtn, 3).valid:
keyboard.send("enter") #if stash already full of gold just nothing happens -> gold stays on char -> no popup window
else:
Logger.error("stash_all_items(): deposit button not detected, failed to stash gold")
# move cursor away from button to interfere with screen grab
mouse.move(-120, 0, absolute=False, randomize=15)
stash_full_of_gold = not detect_screen_object(ScreenObjects.GoldNone).valid
mouse.move(-120, 0, absolute=False, randomize=15, delay_factor=[0.3, 0.5])
# if 0 gold becomes visible in personal inventory then the stash tab still has room for gold
stash_full_of_gold = not wait_until_visible(ScreenObjects.GoldNone, 1.5).valid
if stash_full_of_gold:
Logger.info("Stash tab is full of gold, selecting next stash tab.")
stash.curr_stash["gold"] += 1
Logger.debug("Stash tab is full of gold, selecting next stash tab.")
stash.set_curr_stash(gold = (stash.get_curr_stash()["gold"] + 1))
if Config().general["info_screenshots"]:
cv2.imwrite("./info_screenshots/info_gold_stash_full_" + time.strftime("%Y%m%d_%H%M%S") + ".png", grab())
if stash.curr_stash["gold"] > 3:
if stash.get_curr_stash()["gold"] > 3:
#decide if gold pickup should be disabled or gambling is active
vendor.set_gamble_status(True)
else:
# move to next stash
wait(0.5, 0.6)
# move to next stash tab
return stash_all_items(items=items)
else:
set_inventory_gold_full(False)
# check if stash tab is completely full (no empty slots)
stash.move_to_stash_tab(stash.curr_stash["items"])
while stash.curr_stash["items"] <= 3:
common.select_tab(stash.get_curr_stash()["items"])
while stash.get_curr_stash()["items"] <= 3:
img = grab()
if detect_screen_object(ScreenObjects.EmptyStashSlot, img).valid:
if is_visible(ScreenObjects.EmptyStashSlot, img):
break
else:
Logger.info(f"Stash tab completely full, advance to next")
Logger.debug(f"Stash tab completely full, advance to next")
if Config().general["info_screenshots"]:
cv2.imwrite("./info_screenshots/stash_tab_completely_full_" + time.strftime("%Y%m%d_%H%M%S") + ".png", img)
stash.curr_stash["items"] += -1 if Config().char["fill_shared_stash_first"] else 1
if (Config().char["fill_shared_stash_first"] and stash.curr_stash["items"] < 0) or stash.curr_stash["items"] > 3:
if Config().char["fill_shared_stash_first"]:
stash.set_curr_stash(items = (stash.get_curr_stash()["items"] - 1))
else:
stash.set_curr_stash(items = (stash.get_curr_stash()["items"] + 1))
if (Config().char["fill_shared_stash_first"] and stash.get_curr_stash()["items"] < 0) or stash.get_curr_stash()["items"] > 3:
stash.stash_full()
stash.move_to_stash_tab(stash.curr_stash["items"])
common.select_tab(stash.get_curr_stash()["items"])
# stash stuff
while True:
items = common.transfer_items(items, "stash")
@@ -128,10 +124,13 @@ def stash_all_items(items: list = None):
Logger.debug("Wanted to stash item, but it's still in inventory. Assumes full stash. Move to next.")
if Config().general["info_screenshots"]:
cv2.imwrite("./info_screenshots/debug_info_inventory_not_empty_" + time.strftime("%Y%m%d_%H%M%S") + ".png", grab())
stash.curr_stash["items"] += -1 if Config().char["fill_shared_stash_first"] else 1
if (Config().char["fill_shared_stash_first"] and stash.curr_stash["items"] < 0) or stash.curr_stash["items"] > 3:
if Config().char["fill_shared_stash_first"]:
stash.set_curr_stash(items = (stash.get_curr_stash()["items"] - 1))
else:
stash.set_curr_stash(items = (stash.get_curr_stash()["items"] + 1))
if (Config().char["fill_shared_stash_first"] and stash.get_curr_stash()["items"] < 0) or stash.get_curr_stash()["items"] > 3:
stash.stash_full()
stash.move_to_stash_tab(stash.curr_stash["items"])
common.select_tab(stash.get_curr_stash()["items"])
else:
break
Logger.debug("Done stashing")
@@ -144,7 +143,6 @@ def keep_item(item_box: ItemText = None, found_item: Item = None, do_logging: bo
:param do_logging: Bool value to turn on/off logging for items that are found and should be kept
:return: Bool if item should be kept
"""
wait(0.2, 0.3)
ymax = 50 if item_box.data.shape[0] < 50 else item_box.data.shape[0]
img = item_box.data[0:ymax,:]
@@ -162,7 +160,7 @@ def keep_item(item_box: ItemText = None, found_item: Item = None, do_logging: bo
and any(item_type in found_item.name for item_type in ["uniq", "magic", "rare", "set"])
)
# items that are checked to be kept should all be identified unless an error occurred or user preferred unidentified
or detect_screen_object(ScreenObjects.Unidentified, item_box.data).valid
or is_visible(ScreenObjects.Unidentified, item_box.data)
):
include_props = False
exclude_props = False
@@ -259,13 +257,14 @@ def specific_inventory_roi(desired: str = "reserved"):
def open(img: np.ndarray = None) -> np.ndarray:
img = grab() if img is None else img
if not detect_screen_object(ScreenObjects.RightPanel, img).valid:
if not is_visible(ScreenObjects.RightPanel, img):
keyboard.send(Config().char["inventory_screen"])
if not wait_for_screen_object(ScreenObjects.RightPanel, 1).valid:
if not wait_until_visible(ScreenObjects.RightPanel, 1).valid:
if not view.return_to_play():
return None
keyboard.send(Config().char["inventory_screen"])
if not wait_for_screen_object(ScreenObjects.RightPanel, 1).valid:
if not wait_until_visible(ScreenObjects.RightPanel, 1).valid:
Logger.error(f"personal.open(): Failed to open inventory")
return None
img = grab()
return img
@@ -277,7 +276,7 @@ def inspect_items(inp_img: np.ndarray = None, close_window: bool = True, game_st
"""
center_mouse()
img = open(inp_img)
vendor_open = detect_screen_object(ScreenObjects.GoldBtnVendor, inp_img).valid
vendor_open = is_visible(ScreenObjects.GoldBtnVendor, inp_img)
slots = []
# check which slots have items
for column, row in itertools.product(range(Config().char["num_loot_columns"]), range(4)):
@@ -341,7 +340,7 @@ def inspect_items(inp_img: np.ndarray = None, close_window: bool = True, game_st
implied_no_id = not (Config().items[found_item.name].include or Config().items[found_item.name].exclude)
implied_no_id |= not any(item_type in found_item.name for item_type in ["uniq", "magic", "rare", "set"])
if not implied_no_id:
if (is_unidentified := detect_screen_object(ScreenObjects.Unidentified, item_box.data).valid):
if (is_unidentified := is_visible(ScreenObjects.Unidentified, item_box.data)):
need_id = True
center_mouse()
tome_state, tome_pos = common.tome_state(grab(), tome_type = "id", roi = specific_inventory_roi("reserved"))
@@ -394,7 +393,7 @@ def inspect_items(inp_img: np.ndarray = None, close_window: bool = True, game_st
common.transfer_items([box], action = "sell")
continue
# if item is to be kept and is already ID'd or doesn't need ID, log and stash
if keep and not need_id:
if game_stats is not None and (keep and not need_id):
game_stats.log_item_keep(found_item.name, Config().items[found_item.name].pickit_type == 2, item_box.data, item_box.ocr_result.text)
# if item is to be kept or still needs to be sold or identified, append to list
if keep or sell or need_id:
+10 -19
View File
@@ -12,28 +12,19 @@ curr_stash = {
"gold": 0
}
def get_curr_stash() -> dict:
global curr_stash
return curr_stash
def set_curr_stash(items: int = None, gold: int = None):
global curr_stash
if items is not None: curr_stash["items"] = items
if gold is not None: curr_stash["gold"] = gold
def stash_full():
Logger.error("All stash is full, quitting")
if Config().general["custom_message_hook"]:
Messenger().send_stash()
os.system("taskkill /f /im D2R.exe")
wait(1.0, 1.5)
os._exit(0)
def move_to_stash_tab(stash_idx: int):
"""Move to a specifc tab in the stash
:param stash_idx: idx of the stash starting at 0 (personal stash)
"""
str_to_idx_map = {"STASH_0_ACTIVE": 0, "STASH_1_ACTIVE": 1, "STASH_2_ACTIVE": 2, "STASH_3_ACTIVE": 3}
template_match = TemplateFinder().search([*str_to_idx_map], grab(), threshold=0.7, best_match=True, roi=Config().ui_roi["stash_btn_roi"])
curr_active_stash = str_to_idx_map[template_match.name] if template_match.valid else -1
if curr_active_stash != stash_idx:
# select the start stash
personal_stash_pos = (Config().ui_pos["stash_personal_btn_x"], Config().ui_pos["stash_personal_btn_y"])
stash_btn_width = Config().ui_pos["stash_btn_width"]
next_stash_pos = (personal_stash_pos[0] + stash_btn_width * stash_idx, personal_stash_pos[1])
x_m, y_m = convert_screen_to_monitor(next_stash_pos)
mouse.move(x_m, y_m, randomize=[30, 7], delay_factor=[1.0, 1.5])
wait(0.2, 0.3)
mouse.click(button="left")
wait(0.3, 0.4)
os._exit(0)
+12 -14
View File
@@ -7,8 +7,8 @@ from utils.misc import wait
from screen import grab
from logger import Logger
from utils.custom_mouse import mouse
from ui_manager import detect_screen_object, wait_for_screen_object, ScreenObjects
from inventory import personal, common
from ui_manager import is_visible, select_screen_object_match, wait_until_visible, ScreenObjects
from inventory import personal, common, stash
gamble_count = 0
gamble_status = False
@@ -39,21 +39,17 @@ def repair() -> bool:
Repair and fills up TP buy selling tome and buying. Vendor inventory needs to be open!
:return: Bool if success
"""
repair_btn = wait_for_screen_object(ScreenObjects.RepairBtn, time_out=4)
if not repair_btn.valid:
if not (repair_btn := wait_until_visible(ScreenObjects.RepairBtn, timeout=4)).valid:
return False
mouse.move(*repair_btn.center, randomize=12, delay_factor=[1.0, 1.5])
wait(0.1, 0.15)
mouse.click(button="left")
wait(0.1, 0.15)
if detect_screen_object(ScreenObjects.NotEnoughGold).valid:
select_screen_object_match(repair_btn)
if wait_until_visible(ScreenObjects.NotEnoughGold, 1).valid:
Logger.warning("Couldn't repair--out of gold. Continue.")
keyboard.send("esc")
return False
return True
def gamble():
if (refresh_btn := TemplateFinder().search_and_wait("REFRESH", threshold=0.79, time_out=4, normalize_monitor=True)).valid:
if (refresh_btn := TemplateFinder().search_and_wait("REFRESH", threshold=0.79, timeout=4, normalize_monitor=True)).valid:
#Gambling window is open. Starting to spent some coins
max_gamble_count = floor(2000000/188000) # leave about 500k gold and assume buying coronets at ~188k
while get_gamble_status() and get_gamble_count() < max_gamble_count:
@@ -72,7 +68,7 @@ def gamble():
wait(0.4, 0.6)
img=grab()
# make sure the "not enough gold" message doesn't exist
if detect_screen_object(ScreenObjects.NotEnoughGold, img).valid:
if is_visible(ScreenObjects.NotEnoughGold, img):
Logger.warning(f"Out of gold, stop gambling")
keyboard.send("esc")
set_gamble_status(False)
@@ -92,8 +88,10 @@ def gamble():
if new_count >= max_gamble_count:
break
Logger.debug(f"Finish gambling")
stash.set_curr_stash(gold = 0)
personal.set_inventory_gold_full(False)
set_gamble_status(False)
if get_gamble_status():
set_gamble_status(False)
common.close()
return None
else:
@@ -118,7 +116,7 @@ def buy_item(template_name: str, quantity: int = 1, img: np.ndarray = None, shif
wait(0.5, 0.8)
mouse.click(button="right")
wait(0.4, 0.6)
if detect_screen_object(ScreenObjects.NotEnoughGold).valid:
if is_visible(ScreenObjects.NotEnoughGold):
Logger.warning(f"Out of gold, could not purchase {template_name}")
keyboard.send("esc")
return False
@@ -129,7 +127,7 @@ def buy_item(template_name: str, quantity: int = 1, img: np.ndarray = None, shif
for _ in range(quantity):
mouse.click(button="right")
wait(0.9, 1.1)
if detect_screen_object(ScreenObjects.NotEnoughGold).valid:
if is_visible(ScreenObjects.NotEnoughGold):
Logger.warning(f"Out of gold, could not purchase {template_name}")
keyboard.send("esc")
return False
+6 -1
View File
@@ -10,7 +10,7 @@ from config import Config
from utils.misc import color_filter, cut_roi
from item import ItemCropper
from template_finder import TemplateFinder
from ocr import OcrResult
from ocr import OcrResult, Ocr
@dataclass
@@ -115,6 +115,11 @@ class ItemFinder:
hist = cv2.calcHist([cropped_item], [0, 1, 2], mask, [8, 8, 8], [0, 256, 0, 256, 0, 256])
hist_result = cv2.compareHist(template.hist, hist, cv2.HISTCMP_CORREL)
same_type = hist_result > 0.65 and hist_result is not np.inf
# if ocr_during_pickit is off, min_gold_to_pick is set, and matched template is gold, OCR the image
if not Config().advanced_options['ocr_during_pickit'] \
and Config().char['min_gold_to_pick'] and 'misc_gold' == key:
results = Ocr().image_to_text([cluster["clean_img"]], model = "engd2r_inv_th_fast", psm = 7)
setattr(cluster, "ocr_result", results[0])
if same_type:
item = Item()
item.center = (int(max_loc[0] + x + int(template.data.shape[1] * 0.5)), int(max_loc[1] + y + int(template.data.shape[0] * 0.5)))
+18 -12
View File
@@ -1,9 +1,8 @@
import time
from inventory import belt
import keyboard
import cv2
from operator import itemgetter
from ui_manager import ScreenObjects, detect_screen_object
from ui_manager import ScreenObjects, is_visible
from utils.custom_mouse import mouse
from config import Config
from logger import Logger
@@ -11,6 +10,7 @@ from screen import grab, convert_abs_to_monitor, convert_screen_to_monitor
from item import ItemFinder, Item
from char import IChar
from inventory import consumables
import parse
class PickIt:
@@ -18,11 +18,10 @@ class PickIt:
self._item_finder = item_finder
self._last_closest_item: Item = None
def pick_up_items(self, char: IChar, is_at_trav: bool = False) -> bool:
def pick_up_items(self, char: IChar) -> bool:
"""
Pick up all items with specified char
:param char: The character used to pick up the item
:param is_at_trav: Dirty hack to reduce gold pickup only to trav area, should be removed once we can determine the amount of gold reliably
:return: Bool if any items were picked up or not. (Does not account for picking up scrolls and pots)
"""
found_nothing = 0
@@ -35,7 +34,7 @@ class PickIt:
cv2.imwrite("./loot_screenshots/info_debug_drop_" + time.strftime("%Y%m%d_%H%M%S") + ".png", img)
Logger.debug("Took a screenshot of current loot")
start = prev_cast_start = time.time()
time_out = False
timeout = False
picked_up_items = []
skip_items = []
curr_item_to_pick: Item = None
@@ -43,9 +42,9 @@ class PickIt:
did_force_move = False
done_ocr=False
while not time_out:
while not timeout:
if (time.time() - start) > 28:
time_out = True
timeout = True
Logger.warning("Got stuck during pickit, skipping it this time...")
break
img = grab()
@@ -83,9 +82,16 @@ class PickIt:
if needs["key"] <= 0:
item_list = [x for x in item_list if "misc_key" != x.name]
# TODO: Hacky solution for trav only gold pickup, hope we can soon read gold ammount and filter by that...
if Config().char["gold_trav_only"] and not is_at_trav:
item_list = [x for x in item_list if "misc_gold" not in x.name]
# filter out gold less than desired quantity
if (min_gold := Config().char['min_gold_to_pick']):
for item in item_list[:]:
if "misc_gold" == item.name:
try:
ocr_gold = int(parse.search("{:d} GOLD", item.ocr_result.text).fixed[0])
except:
ocr_gold = 0
if ocr_gold < min_gold:
item_list.remove(item)
if len(item_list) == 0:
# if twice no item was found, break
@@ -145,9 +151,9 @@ class PickIt:
if not char.capabilities.can_teleport_natively:
time.sleep(0.2)
if detect_screen_object(ScreenObjects.Overburdened).valid:
if is_visible(ScreenObjects.Overburdened):
found_items = True
Logger.warning("Inventory full, skipping pickit!")
Logger.warning("Inventory full, terminating pickit!")
# TODO: Could think about sth like: Go back to town, stash, come back picking up stuff
break
else:
+3 -1
View File
@@ -26,14 +26,16 @@ def start_or_pause_bot(controllers: Controllers):
else:
# Kill any other controllers and start botty
controllers.debugger.stop()
controllers.game.start()
screen.start_detecting_window()
controllers.game.start()
def start_or_stop_graphic_debugger(controllers: Controllers):
if controllers.debugger.is_running:
controllers.debugger.stop()
screen.stop_detecting_window()
else:
# Kill any other controller and start debugger
screen.start_detecting_window()
controllers.game.stop()
controllers.debugger.start()
+4 -3
View File
@@ -5,7 +5,7 @@ import keyboard
from template_finder import TemplateFinder
from config import Config
from screen import grab
from ui_manager import detect_screen_object, ScreenObjects, center_mouse
from ui_manager import ScreenObjects, center_mouse, is_visible, wait_until_hidden
from utils.misc import color_filter, wait
from logger import Logger
from utils.custom_mouse import mouse
@@ -220,9 +220,10 @@ npcs = {
def escape_dialogue(img) -> np.ndarray:
while detect_screen_object(ScreenObjects.NPCDialogue, img).valid:
while is_visible(ScreenObjects.NPCDialogue, img):
keyboard.send("esc")
wait(0.2)
if wait_until_hidden(ScreenObjects.NPCDialogue, 0.5):
break
img = grab()
return img
+9 -17
View File
@@ -6,7 +6,6 @@ import random
from typing import Tuple, Union, List
import cv2
import numpy as np
from item.pickit import PickIt
from utils.custom_mouse import mouse
from utils.misc import wait # for stash/shrine tele cancel detection in traverse node
from utils.misc import is_in_roi
@@ -15,7 +14,7 @@ from logger import Logger
from screen import convert_screen_to_monitor, convert_abs_to_screen, convert_abs_to_monitor, convert_screen_to_abs, grab, stop_detecting_window
from template_finder import TemplateFinder
from char import IChar
from ui_manager import detect_screen_object, ScreenObjects
from ui_manager import detect_screen_object, ScreenObjects, is_visible, select_screen_object_match
class Location:
# A5 Town
@@ -594,7 +593,7 @@ class Pather:
self,
path: Union[tuple[Location, Location], list[int]],
char: IChar,
time_out: float = 5,
timeout: float = 5,
force_tp: bool = False,
do_pre_move: bool = True,
force_move: bool = False,
@@ -604,7 +603,7 @@ class Pather:
"""Traverse from one location to another
:param path: Either a list of node indices or a tuple with (start_location, end_location)
: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 timeout: 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 successful or False if it got stuck
"""
@@ -641,17 +640,17 @@ class Pather:
while not continue_to_next_node:
img = grab()
# Handle timeout
if (time.time() - last_move) > time_out:
if detect_screen_object(ScreenObjects.WaypointLabel).valid:
if (time.time() - last_move) > timeout:
if is_visible(ScreenObjects.WaypointLabel):
# sometimes bot opens waypoint menu, close it to find templates again
Logger.debug("Opened wp, closing it again")
keyboard.send("esc")
last_move = time.time()
else:
# This is a bit hacky, but for moving into a boss location we set time_out usually quite low
# This is a bit hacky, but for moving into a boss location we set timeout usually quite low
# because of all the spells and monsters it often can not determine the final template
# Don't want to spam the log with errors in this case because it most likely worked out just fine
if time_out > 3.1:
if timeout > 3.1:
if Config().general["info_screenshots"]:
cv2.imwrite("./info_screenshots/info_pather_got_stuck_" + time.strftime("%Y%m%d_%H%M%S") + ".png", img)
Logger.error("Got stuck exit pather")
@@ -671,18 +670,11 @@ class Pather:
# Sometimes we get stuck at a Shrine or Stash, after a few seconds check if the screen was different, if force a left click.
if (teleport_count + 1) % 30 == 0:
Logger.debug("Longer-than-expected traverse: Check for an occluding shrine")
if detect_screen_object(ScreenObjects.ShrineArea).valid:
if (match := detect_screen_object(ScreenObjects.ShrineArea)).valid:
if Config().general["info_screenshots"]: cv2.imwrite(f"./info_screenshots/info_shrine_check_before" + time.strftime("%Y%m%d_%H%M%S") + ".png", grab())
Logger.debug(f"Shrine found, activating it")
x_m, y_m = convert_abs_to_monitor((0, -130)) #above head
mouse.move(x_m, y_m)
wait(0.1, 0.15)
mouse.click(button="left")
select_screen_object_match(match)
if Config().general["info_screenshots"]: cv2.imwrite(f"./info_screenshots/info_shrine_check_after" + time.strftime("%Y%m%d_%H%M%S") + ".png", grab())
# we might need a check if she moved after the sequence here was executed to confirm it was successful? Otherwise we just loop again :)
else:
Logger.debug("Shrine not found.")
teleport_count = 0
break
teleport_count += 1
+10 -11
View File
@@ -10,6 +10,7 @@ from utils.misc import wait
from dataclasses import dataclass
from chest import Chest
from ui import waypoint
from health_manager import set_pause_state
class Arcane:
def __init__(
@@ -17,17 +18,17 @@ class Arcane:
pather: Pather,
town_manager: TownManager,
char: IChar,
pickit: PickIt
pickit: PickIt,
):
self._pather = pather
self._town_manager = town_manager
self._char = char
self._pickit = pickit
self._chest = Chest(self._char, 'arcane')
self.used_tps = 0
def approach(self, start_loc: Location) -> Union[bool, Location]:
Logger.info("Run Arcane")
set_pause_state(True)
if not self._char.capabilities.can_teleport_natively:
raise ValueError("Arcane requires teleport")
if not self._town_manager.open_wp(start_loc):
@@ -41,19 +42,19 @@ class Arcane:
# Check if we arrived at platform
templates_platform = ["ARC_PLATFORM_1", "ARC_PLATFORM_2", "ARC_PLATFORM_3", "ARC_CENTER"]
tempaltes_summoner = ["ARC_ALTAR", "ARC_ALTAR3", "ARC_END_STAIRS", "ARC_END_STAIRS_2"]
match_platform = TemplateFinder().search_and_wait(templates_platform, threshold=0.55, time_out=0.5, use_grayscale=True, take_ss=False)
match_summoner = TemplateFinder().search_and_wait(tempaltes_summoner, threshold=0.79, time_out=0.5, use_grayscale=True, take_ss=False)
match_platform = TemplateFinder().search_and_wait(templates_platform, threshold=0.55, timeout=0.5, use_grayscale=True, take_ss=False)
match_summoner = TemplateFinder().search_and_wait(tempaltes_summoner, threshold=0.79, timeout=0.5, use_grayscale=True, take_ss=False)
if not match_platform.valid and not match_summoner.valid:
# We might have arrived at summoner, move up stairs with static traverse
self._pather.traverse_nodes_fixed(traverse_to_summoner, self._char)
# try to match summoner again
match_summoner = TemplateFinder().search_and_wait(tempaltes_summoner, threshold=0.79, time_out=1.0, use_grayscale=True, take_ss=False)
match_summoner = TemplateFinder().search_and_wait(tempaltes_summoner, threshold=0.79, timeout=1.0, use_grayscale=True, take_ss=False)
if match_summoner.valid:
if self._pather.traverse_nodes([461], self._char, time_out=2.2, force_tp=True):
if self._pather.traverse_nodes([461], self._char, timeout=2.2, force_tp=True):
return True
else:
# Traverse to center of platform
self._pather.traverse_nodes([462], self._char, time_out=1.3, force_tp=True)
self._pather.traverse_nodes([462], self._char, timeout=1.3, force_tp=True)
return False
def battle(self, do_pre_buff: bool) -> Union[bool, tuple[Location, bool]]:
@@ -72,12 +73,11 @@ class Arcane:
]
picked_up_items = False
self.used_tps = 0
for i, data in enumerate(path_arr):
set_pause_state(False)
if do_pre_buff:
self._char.pre_buff()
# calibrating at start and moving towards the end of the arm
self._pather.traverse_nodes([data.calib_node_start], self._char, force_tp=True)
if not self._pather.traverse_nodes_fixed(data.static_path_forward, self._char):
@@ -94,9 +94,8 @@ class Arcane:
# Open TP and return back to town, walk to wp and start over
if not self._char.tp_town():
Logger.warning("TP to town failed, cancel run")
self.used_tps += 20
return False
self.used_tps += 1
set_pause_state(True)
if not self._town_manager.wait_for_tp(Location.A2_TP):
return False
if not self.approach(Location.A2_FARA_STASH):
+18 -19
View File
@@ -78,10 +78,9 @@ class Diablo:
self._curr_loc = self._town_manager.buy_pots(self._curr_loc, pot_needs["health"], pot_needs["mana"])
Logger.debug(location + ": Done in town, now going back to portal...")
# Move from Act 4 NPC Jamella towards WP where we can see the Blue Portal
if not self._pather.traverse_nodes([164, 163], self._char, time_out=2): return False
if not self._pather.traverse_nodes([164, 163], self._char, timeout=2): return False
wait(0.22, 0.28)
template_match = detect_screen_object(ScreenObjects.TownPortalReduced)
if template_match.valid:
if (template_match := detect_screen_object(ScreenObjects.TownPortalReduced)).valid:
pos = template_match.center
pos = (pos[0], pos[1] + 30)
Logger.debug(location + ": Going through portal...")
@@ -111,9 +110,9 @@ class Diablo:
i = 0
while i < 4:
Logger.debug(seal_layout + ": trying to open (try #" + str(i+1)+")")
self._char.select_by_template(seal_closedtemplates, threshold=0.5, time_out=0.1, telekinesis=True)
self._char.select_by_template(seal_closedtemplates, threshold=0.5, timeout=0.1, telekinesis=True)
wait(i*0.5)
found = TemplateFinder().search_and_wait(seal_opentemplates, threshold=0.75, time_out=0.1, best_match=True, take_ss=False).valid
found = TemplateFinder().search_and_wait(seal_opentemplates, threshold=0.75, timeout=0.1, best_match=True, take_ss=False).valid
if found:
Logger.debug(seal_layout +": is open - "+'\033[92m'+" open"+'\033[0m')
break
@@ -143,7 +142,7 @@ class Diablo:
templates = ["DIA_NEW_PENT_TP", "DIA_NEW_PENT_0", "DIA_NEW_PENT_1", "DIA_NEW_PENT_2"]
start_time = time.time()
while not found and time.time() - start_time < 15:
found = TemplateFinder().search_and_wait(templates, threshold=0.83, time_out=0.1, best_match=True, take_ss=False).valid
found = TemplateFinder().search_and_wait(templates, threshold=0.83, timeout=0.1, best_match=True, take_ss=False).valid
if not found: self._pather.traverse_nodes_fixed(path, self._char)
if not found:
if Config().general["info_screenshots"]: cv2.imwrite(f"./info_screenshots/info_failed_loop_pentagram_" + path + "_" + time.strftime("%Y%m%d_%H%M%S") + ".png", grab())
@@ -161,10 +160,10 @@ class Diablo:
if not self._pather.traverse_nodes([605], self._char): return False
templates = ["DIABLO_ENTRANCE_53", "DIABLO_ENTRANCE_51","DIABLO_ENTRANCE_50", "DIABLO_ENTRANCE_52", "DIABLO_ENTRANCE_54", "DIABLO_ENTRANCE_55"]
if TemplateFinder().search_and_wait(templates, threshold=0.8, time_out=0.1, best_match=False, take_ss=False).valid:
if TemplateFinder().search_and_wait(templates, threshold=0.8, timeout=0.1, best_match=False, take_ss=False).valid:
Logger.debug("CS Trash (A): Layout_check step 1/2: Layout A templates found")
templates = ["DIABLO_ENTRANCE2_55", "DIABLO_ENTRANCE2_50", "DIABLO_ENTRANCE2_51", "DIABLO_ENTRANCE2_52","DIABLO_ENTRANCE2_53","DIABLO_ENTRANCE2_54","DIABLO_ENTRANCE2_15","DIABLO_ENTRANCE2_56"]
if not TemplateFinder().search_and_wait(templates, threshold=0.8, time_out=0.5, best_match=True, take_ss=False).valid:
if not TemplateFinder().search_and_wait(templates, threshold=0.8, timeout=0.5, best_match=True, take_ss=False).valid:
Logger.debug("CS Trash (A): Layout_check step 2/2: Layout B templates NOT found - "+'\033[95m'+"all fine, proceeding with Layout A"+'\033[0m')
entrance1_layout = "CS Trash (A):"
#if Config().general["info_screenshots"]: cv2.imwrite(f"./info_screenshots/info_" + entrance1_layout + "_" + time.strftime("%Y%m%d_%H%M%S") + ".png", grab())
@@ -185,7 +184,7 @@ class Diablo:
else:
Logger.debug("CS Trash (B): Layout_check step 1/2: Layout A templates NOT found")
templates = ["DIABLO_ENTRANCE2_55", "DIABLO_ENTRANCE2_50", "DIABLO_ENTRANCE2_51", "DIABLO_ENTRANCE2_52","DIABLO_ENTRANCE2_53","DIABLO_ENTRANCE2_54","DIABLO_ENTRANCE2_15","DIABLO_ENTRANCE2_56"]
if TemplateFinder().search_and_wait(templates, threshold=0.8, time_out=0.1, best_match=False, take_ss=False).valid:
if TemplateFinder().search_and_wait(templates, threshold=0.8, timeout=0.1, best_match=False, take_ss=False).valid:
Logger.debug("CS Trash (B): Layout_check step 2/2: Layout B templates found - "+'\033[96m'+"all fine, proceeding with Layout B"+'\033[0m')
entrance2_layout = "CS Trash (B):"
#if Config().general["info_screenshots"]: cv2.imwrite(f"./info_screenshots/info_" + entrance2_layout + "_" + time.strftime("%Y%m%d_%H%M%S") + ".png", grab())
@@ -214,7 +213,7 @@ class Diablo:
templates = ["DIA_NEW_PENT_0", "DIA_NEW_PENT_1", "DIA_NEW_PENT_2"]
start_time = time.time()
while not found and time.time() - start_time < 10:
found = TemplateFinder().search_and_wait(templates, threshold=0.8, time_out=0.1, best_match=True, take_ss=False).valid
found = TemplateFinder().search_and_wait(templates, threshold=0.8, timeout=0.1, best_match=True, take_ss=False).valid
if not found:
self._pather.traverse_nodes_fixed("diablo_wp_pentagram_loop", self._char)
if not found:
@@ -235,7 +234,7 @@ class Diablo:
templates = ["DIABLO_CS_ENTRANCE_0", "DIABLO_CS_ENTRANCE_2", "DIABLO_CS_ENTRANCE_3"]
start_time = time.time()
while not found and time.time() - start_time < 10:
found = TemplateFinder().search_and_wait(templates, threshold=0.8, time_out=0.1, best_match=True, take_ss=False).valid
found = TemplateFinder().search_and_wait(templates, threshold=0.8, timeout=0.1, best_match=True, take_ss=False).valid
if not found:
self._pather.traverse_nodes_fixed("diablo_wp_entrance_loop", self._char)
if not found:
@@ -251,7 +250,7 @@ class Diablo:
templates = ["DIA_NEW_PENT_TP", "DIA_NEW_PENT_0", "DIA_NEW_PENT_1", "DIA_NEW_PENT_2"]
start_time = time.time()
while not found and time.time() - start_time < 15:
found = TemplateFinder().search_and_wait(templates, threshold=0.83, time_out=0.1, best_match=True, take_ss=False).valid
found = TemplateFinder().search_and_wait(templates, threshold=0.83, timeout=0.1, best_match=True, take_ss=False).valid
if not found: self._pather.traverse_nodes_fixed("diablo_wp_pentagram_loop", self._char)
if not found:
if Config().general["info_screenshots"]: cv2.imwrite(f"./info_screenshots/info_failed_loop_pentagram_diablo_wp_pentagram_loop_" + time.strftime("%Y%m%d_%H%M%S") + ".png", grab())
@@ -277,23 +276,23 @@ class Diablo:
"""
#CLEAR TRASH BETWEEN PENTAGRAM & LAYOUT CHECK (clear_trash=1): NEW METHOD, BUT ONLY 50% EFFICACY
def _trash_seals(self, seal:str, path:str, node_calibration:str, loop_path:str, threshold:float) -> bool:
if not self._pather.traverse_nodes([602], self._char, time_out=2): return False
if not self._pather.traverse_nodes([602], self._char, timeout=2): return False
if not self._pather.traverse_nodes_fixed(path, self._char): return False
Logger.info("CS TRASH: " + seal + " Pent to LC")
self._char.kill_cs_trash(path)
if not self._pather.traverse_nodes(node_calibration, self._char, time_out=2, threshold=threshold): return False
if not self._pather.traverse_nodes(node_calibration, self._char, timeout=2, threshold=threshold): return False
Logger.info("CS TRASH: " + str(seal) + " looping to PENTAGRAM")
#if not self._loop_pentagram(loop_path): return False
found = False
templates = ["DIA_NEW_PENT_TP", "DIA_NEW_PENT_0", "DIA_NEW_PENT_1", "DIA_NEW_PENT_2"]
start_time = time.time()
while not found and time.time() - start_time < 15:
found = TemplateFinder().search_and_wait(templates, threshold=0.83, time_out=0.1, best_match=True, take_ss=False).valid
found = TemplateFinder().search_and_wait(templates, threshold=0.83, timeout=0.1, best_match=True, take_ss=False).valid
if not found: self._pather.traverse_nodes_fixed(loop_path, self._char)
if not found:
if Config().general["info_screenshots"]: cv2.imwrite(f"./info_screenshots/info_failed_loop_pentagram_" + path + "_" + time.strftime("%Y%m%d_%H%M%S") + ".png", grab())
return False
if not self._pather.traverse_nodes([602], self._char, time_out=2): return False
if not self._pather.traverse_nodes([602], self._char, timeout=2): return False
Logger.info("CS TRASH: " + str(seal) + " calibrated at PENTAGRAM")
return True
"""
@@ -370,12 +369,12 @@ class Diablo:
if not self._pather.traverse_nodes(calibration_node, self._char, threshold=calibration_threshold,): return False
#if Config().general["info_screenshots"]: cv2.imwrite(f"./info_screenshots/info_LC_" + sealname + "_" + time.strftime("%Y%m%d_%H%M%S") + ".png", grab())
#check1 using primary templates
if not TemplateFinder().search_and_wait(templates_primary, threshold =threshold_primary, time_out=0.1, best_match=True, take_ss=False).valid:
if not TemplateFinder().search_and_wait(templates_primary, threshold =threshold_primary, timeout=0.1, best_match=True, take_ss=False).valid:
Logger.debug(f"{seal_layout1}: Layout_check step 1/2 - templates NOT found for "f"{seal_layout2}")
#cross-check for confirmation
if not confirmation_node == None:
if not self._pather.traverse_nodes(confirmation_node, self._char, threshold=calibration_threshold,): return False
if not TemplateFinder().search_and_wait(templates_confirmation, threshold=threshold_confirmation, time_out=0.1, best_match=True, take_ss=False).valid:
if not TemplateFinder().search_and_wait(templates_confirmation, threshold=threshold_confirmation, timeout=0.1, best_match=True, take_ss=False).valid:
Logger.warning(f"{seal_layout2}: Layout_check failure - could not determine the seal Layout at" f"{sealname} ("f"{boss}) - "+'\033[91m'+"aborting run"+'\033[0m')
if Config().general["info_screenshots"]: cv2.imwrite(f"./info_screenshots/info_" + seal_layout1 + "_LC_fail" + time.strftime("%Y%m%d_%H%M%S") + ".png", grab())
return False
@@ -387,7 +386,7 @@ class Diablo:
#cross-check for confirmation
if not confirmation_node2 == None:
if not self._pather.traverse_nodes(confirmation_node2, self._char, threshold=calibration_threshold,): return False
if not TemplateFinder().search_and_wait(templates_confirmation, threshold=threshold_confirmation2, time_out=0.1, best_match=True, take_ss=False).valid:
if not TemplateFinder().search_and_wait(templates_confirmation, threshold=threshold_confirmation2, timeout=0.1, best_match=True, take_ss=False).valid:
Logger.debug(f"{seal_layout2}: Layout_check step 2/2 - templates NOT found for "f"{seal_layout1} - "+'\033[94m'+"all fine, proceeding with "f"{seal_layout2}"+'\033[0m')
if not self._seal(*params_seal2): return False
else:
+8 -8
View File
@@ -40,7 +40,7 @@ class Nihlathak:
def battle(self, do_pre_buff: bool) -> Union[bool, tuple[Location, bool]]:
# TODO: We might need a second template for each option as merc might run into the template and we dont find it then
# Let's check which layout ("NI1_A = bottom exit" , "NI1_B = large room", "NI1_C = small room")
template_match = TemplateFinder().search_and_wait(["NI1_A", "NI1_B", "NI1_C"], threshold=0.65, time_out=20)
template_match = TemplateFinder().search_and_wait(["NI1_A", "NI1_B", "NI1_C"], threshold=0.65, timeout=20)
if not template_match.valid:
return False
if do_pre_buff:
@@ -50,16 +50,16 @@ class Nihlathak:
# Its xpects that the static routes defined in game.ini are named: "ni1_a", "ni1_b", "ni1_c"
self._pather.traverse_nodes_fixed(template_match.name.lower(), self._char)
found_loading_screen_func = lambda: loading.wait_for_loading_screen(2.0) or \
TemplateFinder().search_and_wait(["NI2_SEARCH_0", "NI2_SEARCH_1"], threshold=0.8, time_out=0.5).valid
TemplateFinder().search_and_wait(["NI2_SEARCH_0", "NI2_SEARCH_1"], threshold=0.8, timeout=0.5).valid
# look for stairs
if not self._char.select_by_template(["NI1_STAIRS", "NI1_STAIRS_2", "NI1_STAIRS_3", "NI1_STAIRS_4"], found_loading_screen_func, threshold=0.63, time_out=4):
if not self._char.select_by_template(["NI1_STAIRS", "NI1_STAIRS_2", "NI1_STAIRS_3", "NI1_STAIRS_4"], found_loading_screen_func, threshold=0.63, timeout=4):
# do a random tele jump and try again
pos_m = convert_abs_to_monitor((random.randint(-70, 70), random.randint(-70, 70)))
self._char.move(pos_m, force_move=True)
if not self._char.select_by_template(["NI1_STAIRS", "NI1_STAIRS_2", "NI1_STAIRS_3", "NI1_STAIRS_4"], found_loading_screen_func, threshold=0.63, time_out=4):
if not self._char.select_by_template(["NI1_STAIRS", "NI1_STAIRS_2", "NI1_STAIRS_3", "NI1_STAIRS_4"], found_loading_screen_func, threshold=0.63, timeout=4):
return False
# Wait until templates in lvl 2 entrance are found
if not TemplateFinder().search_and_wait(["NI2_SEARCH_0", "NI2_SEARCH_1", "NI2_SEARCH_2"], threshold=0.8, time_out=20).valid:
if not TemplateFinder().search_and_wait(["NI2_SEARCH_0", "NI2_SEARCH_1", "NI2_SEARCH_2"], threshold=0.8, timeout=20).valid:
return False
wait(1.0) # wait to make sure the red writing is gone once we check for the eye
@dataclass
@@ -82,11 +82,11 @@ class Nihlathak:
# Move to spot where eye would be visible
self._pather.traverse_nodes_fixed(data.circle_static_path_key, self._char)
# Search for eye
template_match = TemplateFinder().search_and_wait(data.template_name, threshold=0.7, best_match=True, time_out=3)
template_match = TemplateFinder().search_and_wait(data.template_name, threshold=0.7, best_match=True, timeout=3)
# If it is found, move down that hallway
if template_match.valid and template_match.name.endswith("_SAFE_DIST"):
self._pather.traverse_nodes_fixed(data.destination_static_path_key, self._char)
self._pather.traverse_nodes(data.save_dist_nodes, self._char, time_out=2, do_pre_move=False)
self._pather.traverse_nodes(data.save_dist_nodes, self._char, timeout=2, do_pre_move=False)
end_nodes = data.end_nodes
break
@@ -94,7 +94,7 @@ class Nihlathak:
if end_nodes is None:
self._pather.traverse_nodes_fixed("ni2_circle_back_to_a", self._char)
self._pather.traverse_nodes_fixed(check_arr[0].destination_static_path_key, self._char)
self._pather.traverse_nodes(check_arr[0].save_dist_nodes, self._char, time_out=2, do_pre_move=False)
self._pather.traverse_nodes(check_arr[0].save_dist_nodes, self._char, timeout=2, do_pre_move=False)
end_nodes = check_arr[0].end_nodes
# Attack & Pick items
+1 -1
View File
@@ -38,7 +38,7 @@ class Pindle:
def battle(self, do_pre_buff: bool) -> Union[bool, tuple[Location, bool]]:
# Kill Pindle
if not TemplateFinder().search_and_wait(["PINDLE_0", "PINDLE_1"], threshold=0.65, time_out=20).valid:
if not TemplateFinder().search_and_wait(["PINDLE_0", "PINDLE_1"], threshold=0.65, timeout=20).valid:
return False
if do_pre_buff:
self._char.pre_buff()
+1 -1
View File
@@ -35,7 +35,7 @@ class ShenkEld:
def battle(self, do_shenk: bool, do_pre_buff: bool, game_stats) -> Union[bool, tuple[Location, bool]]:
# Eldritch
game_stats.update_location("Eld" if Config().general['discord_status_condensed'] else "Eldritch")
if not TemplateFinder().search_and_wait(["ELDRITCH_0", "ELDRITCH_START"], threshold=0.65, time_out=20).valid:
if not TemplateFinder().search_and_wait(["ELDRITCH_0", "ELDRITCH_START"], threshold=0.65, timeout=20).valid:
return False
if do_pre_buff:
self._char.pre_buff()
+6 -6
View File
@@ -35,7 +35,7 @@ class Trav:
def battle(self, do_pre_buff: bool) -> Union[bool, tuple[Location, bool]]:
# Kill Council
if not TemplateFinder().search_and_wait(["TRAV_0", "TRAV_1", "TRAV_20"], threshold=0.65, time_out=20).valid:
if not TemplateFinder().search_and_wait(["TRAV_0", "TRAV_1", "TRAV_20"], threshold=0.65, timeout=20).valid:
return False
if do_pre_buff:
self._char.pre_buff()
@@ -45,13 +45,13 @@ class Trav:
if not self._pather.traverse_nodes((Location.A3_TRAV_START, Location.A3_TRAV_CENTER_STAIRS), self._char, force_move=True):
return False
self._char.kill_council()
picked_up_items = self._pickit.pick_up_items(self._char, is_at_trav=True)
picked_up_items = self._pickit.pick_up_items(self._char)
wait(0.2, 0.3)
# If we can teleport we want to move back inside and also check loot there
if self._char.capabilities.can_teleport_natively or self._char.capabilities.can_teleport_with_charges:
if not self._pather.traverse_nodes([229], self._char, time_out=2.5, use_tp_charge=True):
self._pather.traverse_nodes([228, 229], self._char, time_out=2.5, use_tp_charge=True)
picked_up_items |= self._pickit.pick_up_items(self._char, is_at_trav=True)
if not self._pather.traverse_nodes([229], self._char, timeout=2.5, use_tp_charge=True):
self._pather.traverse_nodes([228, 229], self._char, timeout=2.5, use_tp_charge=True)
picked_up_items |= self._pickit.pick_up_items(self._char)
# Make sure we go back to the center to not hide the tp
self._pather.traverse_nodes([230], self._char, time_out=2.5)
self._pather.traverse_nodes([230], self._char, timeout=2.5)
return (Location.A3_TRAV_CENTER_STAIRS, picked_up_items)
+2 -1
View File
@@ -62,7 +62,8 @@ def set_window_position(offset_x: int, offset_y: int):
def stop_detecting_window():
global detect_window, detect_window_thread
detect_window = False
detect_window_thread.join()
if detect_window_thread:
detect_window_thread.join()
def grab() -> np.ndarray:
global monitor_roi
+3 -3
View File
@@ -28,9 +28,9 @@ def exit(run_obj):
os._exit(0)
def wait_for_loading_screen(time_out):
def wait_for_loading_screen(timeout):
start = time.time()
while time.time() - start < time_out:
while time.time() - start < timeout:
img = grab()
is_loading_black_roi = np.average(img[:700, 0:250]) < 4.0
if is_loading_black_roi:
@@ -221,7 +221,7 @@ class AnyaShopper:
def select_by_template(self, template_type: str) -> bool:
Logger.debug(f"Select {template_type}")
template_match = TemplateFinder(True).search_and_wait(template_type, time_out=10, normalize_monitor=True)
template_match = TemplateFinder(True).search_and_wait(template_type, timeout=10, normalize_monitor=True)
if template_match.valid:
mouse.move(*template_match.center)
wait(0.1, 0.2)
+4 -4
View File
@@ -204,7 +204,7 @@ class TemplateFinder:
self,
ref: Union[str, list[str]],
roi: list[float] = None,
time_out: float = None,
timeout: float = None,
threshold: float = 0.68,
normalize_monitor: bool = False,
best_match: bool = False,
@@ -215,7 +215,7 @@ class TemplateFinder:
) -> TemplateMatch:
"""
Helper function that will loop and keep searching for a template
:param time_out: After this amount of time the search will stop and it will return [False, None]
:param timeout: After this amount of time the search will stop and it will return [False, None]
:param take_ss: Bool value to take screenshot on timeout or not (flag must still be set in params!)
Other params are the same as for TemplateFinder.search()
"""
@@ -232,9 +232,9 @@ class TemplateFinder:
if template_match.valid:
Logger.debug(f"Found Match: {template_match.name} ({template_match.score*100:.1f}% confidence)")
return template_match
if time_out is not None and (time.time() - start) > time_out:
if timeout is not None and (time.time() - start) > timeout:
if Config().general["info_screenshots"] and take_ss:
cv2.imwrite(f"./info_screenshots/info_wait_for_{ref}_time_out_" + time.strftime("%Y%m%d_%H%M%S") + ".png", img)
cv2.imwrite(f"./info_screenshots/info_wait_for_{ref}_timeout_" + time.strftime("%Y%m%d_%H%M%S") + ".png", img)
if take_ss:
Logger.debug(f"Could not find any of the above templates")
return template_match
+5 -5
View File
@@ -6,7 +6,7 @@ from npc_manager import Npc, open_npc_menu, press_npc_btn
from pather import Pather, Location
from typing import Union
from template_finder import TemplateFinder
from ui_manager import detect_screen_object, ScreenObjects
from ui_manager import ScreenObjects, is_visible
from utils.misc import wait
@@ -38,12 +38,12 @@ class A1(IAct):
curr_loc = Location.A1_WP_SOUTH
if not self._pather.traverse_nodes((curr_loc, Location.A1_WP_NORTH), self._char, force_move=True): return False
wait(0.5, 0.7)
found_wp_func = lambda: detect_screen_object(ScreenObjects.WaypointLabel).valid
found_wp_func = lambda: is_visible(ScreenObjects.WaypointLabel)
# decreased threshold because we sometimes walk "over" it during pathing
return self._char.select_by_template(["A1_WP"], found_wp_func, threshold=0.62)
def wait_for_tp(self) -> Union[Location, bool]:
success = TemplateFinder().search_and_wait(["A1_TOWN_7", "A1_TOWN_9"], time_out=20).valid
success = TemplateFinder().search_and_wait(["A1_TOWN_7", "A1_TOWN_9"], timeout=20).valid
if not self._pather.traverse_nodes([Location.A1_TOWN_TP, Location.A1_KASHYA_CAIN], self._char, force_move=True): return False
if success:
return Location.A1_KASHYA_CAIN
@@ -68,8 +68,8 @@ class A1(IAct):
wait(0.5, 0.6)
def stash_is_open_func():
img = grab()
found = detect_screen_object(ScreenObjects.GoldBtnInventory, img).valid
found |= detect_screen_object(ScreenObjects.GoldBtnStash, img).valid
found = is_visible(ScreenObjects.GoldBtnInventory, img)
found |= is_visible(ScreenObjects.GoldBtnStash, img)
return found
if not self._char.select_by_template(["A1_TOWN_0"], stash_is_open_func):
return False
+5 -6
View File
@@ -1,13 +1,12 @@
from char import IChar
from town.i_act import IAct
from screen import grab
from config import Config
from npc_manager import Npc, open_npc_menu, press_npc_btn
from pather import Pather, Location
from typing import Union
from template_finder import TemplateFinder
from utils.misc import wait
from ui_manager import detect_screen_object, ScreenObjects
from ui_manager import ScreenObjects, is_visible
class A2(IAct):
def __init__(self, pather: Pather, char: IChar):
@@ -33,8 +32,8 @@ class A2(IAct):
wait(0.3)
def stash_is_open_func():
img = grab()
found = detect_screen_object(ScreenObjects.GoldBtnInventory, img).valid
found |= detect_screen_object(ScreenObjects.GoldBtnStash, img).valid
found = is_visible(ScreenObjects.GoldBtnInventory, img)
found |= is_visible(ScreenObjects.GoldBtnStash, img)
return found
if not self._char.select_by_template(["A2_STASH_LIGHT", "A2_STASH_DARK"], stash_is_open_func, telekinesis=True):
return False
@@ -64,11 +63,11 @@ class A2(IAct):
def open_wp(self, curr_loc: Location) -> bool:
if not self._pather.traverse_nodes((curr_loc, Location.A2_WP), self._char, force_move=True): return False
wait(0.5, 0.7)
found_wp_func = lambda: detect_screen_object(ScreenObjects.WaypointLabel).valid
found_wp_func = lambda: is_visible(ScreenObjects.WaypointLabel)
return self._char.select_by_template(["A2_WP_LIGHT", "A2_WP_DARK"], found_wp_func, telekinesis=True)
def wait_for_tp(self) -> Union[Location, bool]:
template_match = TemplateFinder().search_and_wait(["A2_TOWN_21", "A2_TOWN_22", "A2_TOWN_20", "A2_TOWN_19"], time_out=20)
template_match = TemplateFinder().search_and_wait(["A2_TOWN_21", "A2_TOWN_22", "A2_TOWN_20", "A2_TOWN_19"], timeout=20)
if template_match.valid:
self._pather.traverse_nodes((Location.A2_TP, Location.A2_FARA_STASH), self._char, force_move=True)
return Location.A2_FARA_STASH
+5 -6
View File
@@ -1,13 +1,12 @@
from char import IChar
from town.i_act import IAct
from screen import grab
from config import Config
from npc_manager import Npc, open_npc_menu, press_npc_btn
from pather import Pather, Location
from typing import Union
from template_finder import TemplateFinder
from utils.misc import wait
from ui_manager import detect_screen_object, ScreenObjects
from ui_manager import ScreenObjects, is_visible
class A3(IAct):
@@ -40,8 +39,8 @@ class A3(IAct):
wait(0.3)
def stash_is_open_func():
img = grab()
found = detect_screen_object(ScreenObjects.GoldBtnInventory, img).valid
found |= detect_screen_object(ScreenObjects.GoldBtnStash, img).valid
found = is_visible(ScreenObjects.GoldBtnInventory, img)
found |= is_visible(ScreenObjects.GoldBtnStash, img)
return found
if not self._char.select_by_template("A3_STASH", stash_is_open_func, telekinesis=True):
return False
@@ -50,11 +49,11 @@ class A3(IAct):
def open_wp(self, curr_loc: Location) -> bool:
if not self._pather.traverse_nodes((curr_loc, Location.A3_STASH_WP), self._char, force_move=True): return False
wait(0.5, 0.7)
found_wp_func = lambda: detect_screen_object(ScreenObjects.WaypointLabel).valid
found_wp_func = lambda: is_visible(ScreenObjects.WaypointLabel)
return self._char.select_by_template("A3_WP", found_wp_func, telekinesis=True)
def wait_for_tp(self) -> Union[Location, bool]:
template_match = TemplateFinder().search_and_wait("A3_TOWN_10", time_out=20)
template_match = TemplateFinder().search_and_wait("A3_TOWN_10", timeout=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
+5 -6
View File
@@ -1,13 +1,12 @@
from char import IChar
from town.i_act import IAct
from screen import grab
from config import Config
from npc_manager import Npc, open_npc_menu, press_npc_btn
from pather import Pather, Location
from typing import Union
from template_finder import TemplateFinder
from utils.misc import wait
from ui_manager import detect_screen_object, ScreenObjects
from ui_manager import ScreenObjects, is_visible
class A4(IAct):
@@ -35,12 +34,12 @@ class A4(IAct):
def open_wp(self, curr_loc: Location) -> bool:
if not self._pather.traverse_nodes((curr_loc, Location.A4_WP), self._char, force_move=True): return False
wait(0.5, 0.7)
found_wp_func = lambda: detect_screen_object(ScreenObjects.WaypointLabel).valid
found_wp_func = lambda: is_visible(ScreenObjects.WaypointLabel)
# decreased threshold because we sometimes walk "over" it during pathing
return self._char.select_by_template(["A4_WP", "A4_WP_2"], found_wp_func, threshold=0.62, telekinesis=False)
def wait_for_tp(self) -> Union[Location, bool]:
success = TemplateFinder().search_and_wait(["A4_TOWN_4", "A4_TOWN_5", "A4_TOWN_6"], time_out=20).valid
success = TemplateFinder().search_and_wait(["A4_TOWN_4", "A4_TOWN_5", "A4_TOWN_6"], timeout=20).valid
if success:
return Location.A4_TOWN_START
return False
@@ -72,8 +71,8 @@ class A4(IAct):
wait(0.5, 0.6)
def stash_is_open_func():
img = grab()
found = detect_screen_object(ScreenObjects.GoldBtnInventory, img).valid
found |= detect_screen_object(ScreenObjects.GoldBtnStash, img).valid
found = is_visible(ScreenObjects.GoldBtnInventory, img)
found |= is_visible(ScreenObjects.GoldBtnStash, img)
return found
if not self._char.select_by_template(["A4_TOWN_2"], stash_is_open_func, telekinesis=True):
return False
+5 -6
View File
@@ -1,13 +1,12 @@
from char import IChar
from town.i_act import IAct
from screen import grab
from config import Config
from npc_manager import Npc, open_npc_menu, press_npc_btn
from pather import Pather, Location
from typing import Union
from template_finder import TemplateFinder
from utils.misc import wait
from ui_manager import detect_screen_object, ScreenObjects
from ui_manager import ScreenObjects, is_visible
class A5(IAct):
@@ -56,8 +55,8 @@ class A5(IAct):
wait(0.5, 0.6)
def stash_is_open_func():
img = grab()
found = detect_screen_object(ScreenObjects.GoldBtnInventory, img).valid
found |= detect_screen_object(ScreenObjects.GoldBtnStash, img).valid
found = is_visible(ScreenObjects.GoldBtnInventory, img)
found |= is_visible(ScreenObjects.GoldBtnStash, img)
return found
if not self._char.select_by_template(["A5_STASH", "A5_STASH_2"], stash_is_open_func, telekinesis=True):
return False
@@ -72,11 +71,11 @@ class A5(IAct):
def open_wp(self, curr_loc: Location) -> bool:
if not self._pather.traverse_nodes((curr_loc, Location.A5_WP), self._char, force_move=True): return False
wait(0.5, 0.7)
found_wp_func = lambda: detect_screen_object(ScreenObjects.WaypointLabel).valid
found_wp_func = lambda: is_visible(ScreenObjects.WaypointLabel)
return self._char.select_by_template("A5_WP", found_wp_func, telekinesis=True)
def wait_for_tp(self) -> Union[Location, bool]:
success = TemplateFinder().search_and_wait(["A5_TOWN_1", "A5_TOWN_0"], time_out=20).valid
success = TemplateFinder().search_and_wait(["A5_TOWN_1", "A5_TOWN_0"], timeout=20).valid
if success:
return Location.A5_TOWN_START
return False
+1 -1
View File
@@ -21,7 +21,7 @@ class IAct:
# Is trade/repair implemented in this Town?
def can_trade_and_repair(self) -> bool: return False
# Is merc resurrection implemented for this Town?
def can_identify(self) -> bool: return False
def can_identify(self) -> bool: return False
def can_gamble (self) -> bool: return False
# If any of the above functions return True for the Town, the respective method must be implemented
def open_trade_menu(self, curr_loc: Location) -> Union[Location, bool]: return False
+6 -5
View File
@@ -43,12 +43,12 @@ class TownManager:
location = Location.A1_TOWN_START
return location
def wait_for_town_spawn(self, time_out: float = None) -> Location:
def wait_for_town_spawn(self, timeout: 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
:param timeout: 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 timeout time
"""
template_match = TemplateFinder().search_and_wait(TOWN_MARKERS, best_match=True, time_out=time_out)
template_match = TemplateFinder().search_and_wait(TOWN_MARKERS, best_match=True, timeout=timeout)
if template_match.valid:
return TownManager.get_act_from_location(template_match.name)
return None
@@ -97,7 +97,8 @@ class TownManager:
# check if we can buy pots in current act
if self._acts[curr_act].can_buy_pots():
new_loc = self._acts[curr_act].open_trade_menu(curr_loc)
if not new_loc: return False, items
if not (new_loc and common.wait_for_left_inventory()): return False, items
common.select_tab(3)
img=grab()
# Buy HP pots
if consumables.get_needs("health") > 0:
+12 -21
View File
@@ -1,7 +1,7 @@
import itertools
from random import randint
from config import Config
from ui_manager import detect_screen_object, wait_for_screen_object, ScreenObjects
from ui_manager import detect_screen_object, select_screen_object_match, wait_until_visible, ScreenObjects
from .inventory_collection import InventoryCollection
from .stash import Stash
from .gem_picking import SimpleGemPicking
@@ -15,8 +15,7 @@ from template_finder import TemplateFinder
import numpy as np
import keyboard
import cv2
from inventory import personal
from inventory.stash import move_to_stash_tab
from inventory import personal, common
FLAWLESS_GEMS = [
"INVENTORY_TOPAZ_FLAWLESS",
@@ -66,11 +65,9 @@ class Transmute:
self._wait()
def open_cube(self):
move_to_stash_tab(0)
match = detect_screen_object(ScreenObjects.CubeInventory)
if match.valid:
x, y = convert_screen_to_monitor(match.center)
mouse.move(x, y)
common.select_tab(0)
if (match := detect_screen_object(ScreenObjects.CubeInventory)).valid:
mouse.move(*match.center)
self._wait()
mouse.click("right")
self._wait()
@@ -78,13 +75,8 @@ class Transmute:
Logger.error(f"Can't find cube: {match.score}")
def transmute(self):
match = detect_screen_object(ScreenObjects.CubeOpened)
if match.valid:
x, y = convert_screen_to_monitor(match.center)
mouse.move(x, y)
self._wait()
mouse.click("left")
self._wait()
if (match := detect_screen_object(ScreenObjects.CubeOpened)).valid:
select_screen_object_match(match)
def close_cube(self):
self._wait()
@@ -100,7 +92,7 @@ class Transmute:
return self.pick_from_area(column, row, Config().ui_roi["right_inventory"])
def pick_from_stash_at(self, index, column, row):
move_to_stash_tab(index)
common.select_tab(index)
return self.pick_from_area(column, row, Config().ui_roi["left_inventory"])
def inspect_area(self, total_rows, total_columns, roi, known_items) -> InventoryCollection:
@@ -134,7 +126,7 @@ class Transmute:
def inspect_stash(self) -> Stash:
stash = Stash()
for i in range(4):
move_to_stash_tab(i)
common.select_tab(i)
wait(0.4, 0.5)
tab = self.inspect_area(
10, 10, Config().ui_roi["left_inventory"], FLAWLESS_GEMS)
@@ -148,14 +140,14 @@ class Transmute:
while flawless_gems.count_by(gem) > 0:
pick.append((randint(0, 3), *flawless_gems.pop(gem)))
for tab, x, y in sorted(pick, key=lambda x: x[0]):
move_to_stash_tab(tab)
common.select_tab(tab)
self.pick_from_inventory_at(x, y)
def select_tab_with_enough_space(self, s: Stash) -> None:
tabs_priority = Config()._transmute_config["stash_destination"]
for tab in tabs_priority:
if s.get_empty_on_tab(tab) > 0:
move_to_stash_tab(tab)
common.select_tab(tab)
break
def put_back_all_gems(self, s: Stash) -> None:
@@ -176,8 +168,7 @@ class Transmute:
return self._game_stats._game_counter - self._last_game >= int(every_x_game)
def run_transmutes(self, force=False) -> None:
gold_btn = wait_for_screen_object(ScreenObjects.GoldBtnStash, time_out = 20)
if not gold_btn.valid:
if not wait_until_visible(ScreenObjects.GoldBtnStash, timeout = 8).valid:
Logger.error("Could not determine to be in stash menu. Continue...")
return
if not force and not self.should_transmute():
+4 -8
View File
@@ -30,8 +30,7 @@ def has_char_template_saved():
return last_char_template is not None
def save_char_online_status():
match = detect_screen_object(ScreenObjects.OnlineStatus)
if match.valid:
if (match := detect_screen_object(ScreenObjects.OnlineStatus)).valid:
online_status = online_active(match)
Logger.debug(f"Saved online status. Online={online_status}")
else:
@@ -45,8 +44,7 @@ def online_active(match) -> bool:
def save_char_template():
img = grab()
match = detect_screen_object(ScreenObjects.SelectedCharacter)
if match.valid:
if (match := detect_screen_object(ScreenObjects.SelectedCharacter)).valid:
x, y, w, h = Config().ui_roi["character_name_sub_roi"]
x, y = x + match.region[0], y + match.region[1]
char_template = cut_roi(img, [x, y, w, h])
@@ -74,8 +72,7 @@ def save_char_template():
def select_char():
if last_char_template is not None:
img = grab()
match = detect_screen_object(ScreenObjects.OnlineStatus, img)
if match.valid:
if (match := detect_screen_object(ScreenObjects.OnlineStatus, img)).valid:
if online_active(match) and (not online_character):
select_online_tab(match.region, match.center)
img = grab()
@@ -86,8 +83,7 @@ def select_char():
else:
Logger.error("select_char: Could not find online/offline tabs")
return
match = detect_screen_object(ScreenObjects.SelectedCharacter, img)
if not match.valid:
if not (match := detect_screen_object(ScreenObjects.SelectedCharacter, img)).valid:
Logger.error("select_char: Could not find highlighted profile")
return
scrolls_attempts = 0
+1 -2
View File
@@ -5,8 +5,7 @@ import keyboard
def handle_error() -> bool:
Logger.warning("Server connection issue. waiting 20s")
match = detect_screen_object(ScreenObjects.ServerError)
if match.valid:
if (match := detect_screen_object(ScreenObjects.ServerError)).valid:
select_screen_object_match(match)
wait(1, 2)
keyboard.send("esc")
+3 -3
View File
@@ -7,14 +7,14 @@ def check_for_black_screen() -> bool:
img = grab()
return np.average(img[:, 0:Config().ui_roi["loading_left_black"][2]]) < 1.5
def wait_for_loading_screen(time_out: float = 10.0) -> bool:
def wait_for_loading_screen(timeout: float = 10.0) -> bool:
"""
Waits until loading screen apears
:param time_out: Maximum time to search for a loading screen
:param timeout: 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 time.time() - start < timeout:
if check_for_black_screen():
return True
return False
+5 -9
View File
@@ -3,7 +3,7 @@ from config import Config
from utils.misc import wait
from logger import Logger
from ui import error_screens
from ui_manager import detect_screen_object, select_screen_object_match, ScreenObjects
from ui_manager import detect_screen_object, is_visible, select_screen_object_match, ScreenObjects
def start_game() -> bool:
"""
@@ -13,8 +13,7 @@ def start_game() -> bool:
Logger.debug("Wait for Play button")
start = time.time()
while True:
m = detect_screen_object(ScreenObjects.PlayBtn)
if m.valid:
if (m := detect_screen_object(ScreenObjects.PlayBtn)).valid:
if play_active(m):
# found active play button
Logger.debug(f"Found Play Btn")
@@ -38,20 +37,17 @@ def start_game() -> bool:
start = time.time()
while True:
#look for difficulty select
m = detect_screen_object(Difficulty)
if m.valid:
if (m := detect_screen_object(Difficulty)).valid:
select_screen_object_match(m)
break
#check for loading screen
m = detect_screen_object(ScreenObjects.Loading)
if m.valid:
if is_visible(ScreenObjects.Loading):
Logger.debug("Found loading screen / creating game screen rather than difficulty select, normal difficulty")
break
else:
wait(1,2)
# check for server issue
m = detect_screen_object(ScreenObjects.ServerError)
if m.valid:
if is_visible(ScreenObjects.ServerError):
error_screens.handle_error()
return start_game()
+5 -8
View File
@@ -1,4 +1,3 @@
from typing import List
import keyboard
from logger import Logger
import cv2
@@ -8,10 +7,9 @@ from utils.misc import cut_roi, color_filter, wait
from screen import grab
from config import Config
from template_finder import TemplateFinder
from ui_manager import wait_for_screen_object, ScreenObjects
from inventory import consumables
from ui_manager import wait_until_visible, ScreenObjects
def is_left_skill_selected(template_list: List[str]) -> bool:
def is_left_skill_selected(template_list: list[str]) -> bool:
"""
:return: Bool if skill is currently the selected skill on the left skill slot.
"""
@@ -27,12 +25,11 @@ def has_tps() -> bool:
"""
if Config().char["tp"]:
keyboard.send(Config().char["tp"])
template_match = wait_for_screen_object(ScreenObjects.TownPortalSkill, time_out=4)
if not template_match.valid:
if not (tps_remain := wait_until_visible(ScreenObjects.TownPortalSkill, timeout=4).valid):
Logger.warning("You are out of tps")
if Config().general["info_screenshots"]:
cv2.imwrite("./info_screenshots/debug_out_of_tps_" + time.strftime("%Y%m%d_%H%M%S") + ".png", grab())
return template_match.valid
return tps_remain
else:
return False
@@ -57,7 +54,7 @@ def is_right_skill_active() -> bool:
avg = np.average(img)
return avg > 75.0
def is_right_skill_selected(template_list: List[str]) -> bool:
def is_right_skill_selected(template_list: list[str]) -> bool:
"""
:return: Bool if skill is currently the selected skill on the right skill slot.
"""
+34 -11
View File
@@ -5,7 +5,8 @@ import keyboard
from utils.custom_mouse import mouse
from logger import Logger
from utils.misc import wait
from ui_manager import wait_for_screen_object, detect_screen_object, select_screen_object_match, ScreenObjects, list_visible_objects
from ui_manager import wait_until_hidden, wait_until_visible, detect_screen_object, select_screen_object_match, ScreenObjects, list_visible_objects, is_visible
from inventory import common
from screen import convert_screen_to_monitor
def enable_no_pickup() -> bool:
@@ -18,8 +19,7 @@ def enable_no_pickup() -> bool:
keyboard.write('/nopickup',delay=.20)
keyboard.send('enter')
wait(0.1, 0.25)
item_pickup_text = wait_for_screen_object(ScreenObjects.ItemPickupText, time_out=3)
if not item_pickup_text.valid:
if not (item_pickup_text := wait_until_visible(ScreenObjects.ItemPickupText, timeout=1.3)).valid:
return False
if item_pickup_text.name == "ITEM_PICKUP_DISABLED":
return True
@@ -36,13 +36,34 @@ def save_and_exit() -> bool:
Performes save and exit action from within game
:return: Bool if action was successful
"""
start=time.time()
while not (exit_button := detect_screen_object(ScreenObjects.SaveAndExit)).valid:
keyboard.send("esc")
wait(0.1)
if time.time() - start > 5:
# if exit button isn't detected already, press escape
attempts = 1
success = False
while attempts <= 2 and not success:
if not (exit_button := detect_screen_object(ScreenObjects.SaveAndExit)).valid:
keyboard.send("esc")
# wait for exit button to appear
exit_button = wait_until_visible(ScreenObjects.SaveAndExit, 3)
# if exit button is found, double click it to be sure
if exit_button.valid:
select_screen_object_match(exit_button, delay_factor=(0.1, 0.3))
wait(0.1, 0.2)
mouse.click(button="left")
# if center icon on player bar disappears then save/exit was successful
success = wait_until_hidden(ScreenObjects.InGame, 3)
attempts += 1
if not success:
Logger.debug("Failed to find or click save/exit button")
return success
def dismiss_skills_icon() -> bool:
start = time.time()
while (match := detect_screen_object(ScreenObjects.QuestSkillBtn)).valid:
select_screen_object_match(match)
wait(0.6)
common.close()
if (time.time() - start) > 15:
return False
select_screen_object_match(exit_button, delay_factor=(0.1, 0.3))
return True
def pickup_corpse():
@@ -62,7 +83,8 @@ def return_to_play() -> bool:
start=time.time()
while (elapsed := (time.time() - start) < 5):
need_escape = False
if "DARK" in detect_screen_object(ScreenObjects.InGame, img).name:
match = detect_screen_object(ScreenObjects.InGame, img)
if match.valid and "DARK" in match.name:
need_escape = True
if not need_escape:
for substring in substrings:
@@ -70,11 +92,12 @@ def return_to_play() -> bool:
break
if need_escape:
keyboard.send("esc")
wait(0.6, 1)
wait(1)
img=grab()
else:
break
if not elapsed:
Logger.error("return_to_play(): failed to return to neutral play screen")
return False
return True
+2 -5
View File
@@ -1,11 +1,9 @@
import time
import numpy as np
import re
from utils.custom_mouse import mouse
from logger import Logger
from config import Config
from screen import convert_screen_to_monitor, grab
from screen import convert_screen_to_monitor
from utils.misc import wait
from ui import loading
from ui_manager import detect_screen_object, ScreenObjects
@@ -66,8 +64,7 @@ def use_wp(label: str = None, act: int = None, idx: int = None) -> bool:
if label:
act = _WAYPOINTS[label][0]
idx = _WAYPOINTS[label][1]
match = detect_screen_object(ScreenObjects.WaypointTabs)
if match.valid:
if (match := detect_screen_object(ScreenObjects.WaypointTabs)).valid:
curr_active_act = get_active_act_from_match(match)
else:
Logger.error("Could not find waypoint tabs")
+54 -42
View File
@@ -2,6 +2,7 @@ import keyboard
import os
import numpy as np
import time
from typing import Union, TypeVar, Callable
from utils.custom_mouse import mouse
from utils.misc import wait
from logger import Logger
@@ -14,6 +15,7 @@ from game_stats import GameStats
messenger = Messenger()
game_stats = GameStats()
T = TypeVar('T')
@dataclass
class ScreenObject:
@@ -21,12 +23,13 @@ class ScreenObject:
ref: list[str]
inp_img: np.ndarray = None
roi: list[float] = None
time_out: float = 30
timeout: float = 30
threshold: float = 0.68
normalize_monitor: bool = False
normalize_monitor: bool = True
best_match: bool = False
use_grayscale: bool = False
color_match: list[np.array] = None
suppress_debug: bool = False
def __call__(self, cls):
cls._screen_object = self
@@ -110,12 +113,14 @@ class ScreenObjects:
OnlineStatus=ScreenObject(
ref=["CHARACTER_STATE_ONLINE", "CHARACTER_STATE_OFFLINE"],
roi="character_online_status",
best_match=True
best_match=True,
normalize_monitor=False
)
SelectedCharacter=ScreenObject(
ref=["CHARACTER_ACTIVE"],
roi="character_select",
threshold=0.8
threshold=0.8,
normalize_monitor=False
)
ServerError=ScreenObject(
ref=["SERVER_ISSUES"]
@@ -142,35 +147,29 @@ class ScreenObjects:
TownPortal=ScreenObject(
ref="BLUE_PORTAL",
threshold=0.8,
roi="tp_search",
normalize_monitor=True
roi="tp_search"
)
TownPortalReduced=ScreenObject(
ref="BLUE_PORTAL",
threshold=0.8,
roi="reduce_to_center",
normalize_monitor=True
roi="reduce_to_center"
)
GoldBtnInventory=ScreenObject(
ref="INVENTORY_GOLD_BTN",
roi="gold_btn",
normalize_monitor=True
roi="gold_btn"
)
GoldBtnStash=ScreenObject(
ref="INVENTORY_GOLD_BTN",
roi="gold_btn_stash",
normalize_monitor=True
roi="gold_btn_stash"
)
GoldBtnVendor=ScreenObject(
ref="VENDOR_GOLD",
roi="gold_btn_stash",
normalize_monitor=True
roi="gold_btn_stash"
)
GoldNone=ScreenObject(
ref="INVENTORY_NO_GOLD",
roi="inventory_gold",
threshold=0.83,
use_grayscale=True
threshold=0.83
)
TownPortalSkill=ScreenObject(
ref=["TP_ACTIVE", "TP_INACTIVE"],
@@ -181,7 +180,6 @@ class ScreenObjects:
RepairBtn=ScreenObject(
ref="REPAIR_BTN",
roi="repair_btn",
normalize_monitor=True,
use_grayscale=True
)
YouHaveDied=ScreenObject(
@@ -224,7 +222,7 @@ class ScreenObjects:
use_grayscale=True
)
RightPanel=ScreenObject(
ref="CLOSE_PANEL",
ref=["CLOSE_PANEL", "CLOSE_PANEL_2"],
roi="right_panel_header",
threshold=0.8,
use_grayscale=True
@@ -248,8 +246,7 @@ class ScreenObjects:
)
Key=ScreenObject(
ref="INV_KEY",
threshold=0.8,
normalize_monitor=True
threshold=0.8
)
EmptyStashSlot=ScreenObject(
ref="STASH_EMPTY_SLOT",
@@ -268,43 +265,59 @@ class ScreenObjects:
use_grayscale=True,
roi="quest_skill_btn"
)
TabIndicator=ScreenObject(
ref="TAB_INDICATOR",
roi="tab_indicator",
normalize_monitor=False
)
DepositBtn=ScreenObject(
ref=["DEPOSIT_BTN", "DEPOSIT_BTN_BRIGHT"],
threshold=0.8,
roi="deposit_btn",
)
def detect_screen_object(screen_object: ScreenObject, img: np.ndarray = None) -> TemplateMatch:
roi = Config().ui_roi[screen_object.roi] if screen_object.roi else None
img = grab() if img is None else img
match = TemplateFinder().search(
return TemplateFinder().search(
ref = screen_object.ref,
inp_img = img,
threshold = screen_object.threshold,
roi = roi,
best_match = screen_object.best_match,
use_grayscale = screen_object.use_grayscale,
normalize_monitor = screen_object.normalize_monitor)
if match.valid:
return match
return match
normalize_monitor = screen_object.normalize_monitor
)
def select_screen_object_match(match: TemplateMatch, delay_factor: tuple[float, float] = (0.9, 1.1)) -> None:
mouse.move(*convert_screen_to_monitor(match.center), delay_factor=delay_factor)
def select_screen_object_match(match: TemplateMatch, delay_factor: tuple[float, float] = (0.9, 1.1), normalize_monitor: bool = False) -> None:
pos = match.center if not normalize_monitor else convert_screen_to_monitor(match.center)
mouse.move(*pos, delay_factor=delay_factor)
wait(0.05, 0.09)
mouse.click("left")
wait(0.05, 0.09)
def wait_for_screen_object(screen_object: ScreenObject, time_out: int = None) -> TemplateMatch:
roi = Config().ui_roi[screen_object.roi] if screen_object.roi else None
time_out = time_out if time_out else 30
match = TemplateFinder().search_and_wait(
ref = screen_object.ref,
time_out = time_out,
threshold = screen_object.threshold,
roi = roi,
best_match = screen_object.best_match,
use_grayscale = screen_object.use_grayscale,
normalize_monitor = screen_object.normalize_monitor)
if match.valid:
return match
def is_visible(screen_object: ScreenObject, img: np.ndarray = None) -> bool:
return detect_screen_object(screen_object, img).valid
def wait_until_visible(screen_object: ScreenObject, timeout: float = 30) -> TemplateMatch:
if not (match := wait_until(lambda: detect_screen_object(screen_object), lambda match: match.valid, timeout)[0]).valid:
Logger.debug(f"{screen_object.ref} not found after {timeout} seconds")
return match
def wait_until_hidden(screen_object: ScreenObject, timeout: float = 3) -> bool:
if not (hidden := wait_until(lambda: detect_screen_object(screen_object).valid, lambda res: not res, timeout)[1]):
Logger.debug(f"{screen_object.ref} still found after {timeout} seconds")
return hidden
def wait_until(func: Callable[[], T], is_success: Callable[[T], bool], timeout = None) -> Union[T, None]:
start = time.time()
while (time.time() - start) < timeout:
res = func()
if (success := is_success(res)):
break
wait(0.05)
return res, success
def hover_over_screen_object_match(match) -> None:
mouse.move(*convert_screen_to_monitor(match.center))
wait(0.2, 0.4)
@@ -313,8 +326,7 @@ def list_visible_objects(img: np.ndarray = None) -> list:
img = grab() if img is None else img
visible=[]
for pair in [a for a in vars(ScreenObjects).items() if not a[0].startswith('__') and a[1] is not None]:
if (match := detect_screen_object(pair[1], img)).valid:
# visible.append(match)
if is_visible(pair[1], img):
visible.append(pair[0])
return visible
+1 -1
View File
@@ -1 +1 @@
__version__ = '0.7.1'
__version__ = '0.7.2'