A2 Pathing + Arcane run + Static for Sorc + Static Tele robustification + Chest class (#348)

This commit is contained in:
aeon0
2022-01-01 14:15:13 +01:00
committed by GitHub
parent 0fe0b55661
commit d8942f92b4
108 changed files with 492 additions and 52 deletions
+21 -7
View File
@@ -28,8 +28,8 @@ from char.sorceress import LightSorc, BlizzSorc, NovaSorc
from char.trapsin import Trapsin
from char.hammerdin import Hammerdin
from char.barbarian import Barbarian
from run import Pindle, ShenkEld, Trav, Nihlatak
from town import TownManager, A3, A4, A5
from run import Pindle, ShenkEld, Trav, Nihlatak, Arcane
from town import TownManager, A2, A3, A4, A5
# Added for dclone ip hunt
from messenger import Messenger
@@ -70,7 +70,8 @@ class Bot:
a5 = A5(self._screen, self._template_finder, self._pather, self._char, npc_manager)
a4 = A4(self._screen, self._template_finder, self._pather, self._char, npc_manager)
a3 = A3(self._screen, self._template_finder, self._pather, self._char, npc_manager)
self._town_manager = TownManager(self._template_finder, self._ui_manager, self._item_finder, a3, a4, a5)
a2 = A2(self._screen, self._template_finder, self._pather, self._char, npc_manager)
self._town_manager = TownManager(self._template_finder, self._ui_manager, self._item_finder, a2, a3, a4, a5)
self._route_config = self._config.routes
self._route_order = self._config.routes_order
@@ -83,6 +84,7 @@ class Bot:
"run_pindle": self._route_config["run_pindle"],
"run_shenk": self._route_config["run_shenk"] or self._route_config["run_eldritch"],
"run_nihlatak": self._route_config["run_nihlatak"],
"run_arcane": self._route_config["run_arcane"],
}
# Adapt order to the config
self._do_runs = OrderedDict((k, self._do_runs[k]) for k in self._route_order if k in self._do_runs and self._do_runs[k])
@@ -94,6 +96,7 @@ class Bot:
self._shenk = ShenkEld(self._template_finder, self._pather, self._town_manager, self._ui_manager, self._char, self._pickit)
self._trav = Trav(self._template_finder, self._pather, self._town_manager, self._ui_manager, self._char, self._pickit)
self._nihlatak = Nihlatak(self._screen, self._template_finder, self._pather, self._town_manager, self._ui_manager, self._char, self._pickit)
self._arcane = Arcane(self._template_finder, self._pather, self._town_manager, self._ui_manager, self._char, self._pickit)
# Create member variables
self._pick_corpse = pick_corpse
@@ -108,7 +111,7 @@ class Bot:
self._ran_no_pickup = False
# Create State Machine
self._states=['hero_selection', 'town', 'pindle', 'shenk', 'trav', 'nihlatak']
self._states=['hero_selection', 'town', 'pindle', 'shenk', 'trav', 'nihlatak', 'arcane']
self._transitions = [
{ 'trigger': 'create_game', 'source': 'hero_selection', 'dest': 'town', 'before': "on_create_game"},
# Tasks within town
@@ -118,9 +121,10 @@ class Bot:
{ 'trigger': 'run_shenk', 'source': 'town', 'dest': 'shenk', 'before': "on_run_shenk"},
{ 'trigger': 'run_trav', 'source': 'town', 'dest': 'trav', 'before': "on_run_trav"},
{ 'trigger': 'run_nihlatak', 'source': 'town', 'dest': 'nihlatak', 'before': "on_run_nihlatak"},
{ 'trigger': 'run_arcane', 'source': 'town', 'dest': 'arcane', 'before': "on_run_arcane"},
# End run / game
{ 'trigger': 'end_run', 'source': ['shenk', 'pindle', 'nihlatak','trav'], 'dest': 'town', 'before': "on_end_run"},
{ 'trigger': 'end_game', 'source': ['town', 'shenk', 'pindle', 'nihlatak', 'trav', 'end_run'], 'dest': 'hero_selection', 'before': "on_end_game"},
{ 'trigger': 'end_run', 'source': ['shenk', 'pindle', 'nihlatak', 'trav', 'arcane'], 'dest': 'town', 'before': "on_end_run"},
{ 'trigger': 'end_game', 'source': ['town', 'shenk', 'pindle', 'nihlatak', 'trav', 'arcane', 'end_run'], 'dest': 'hero_selection', 'before': "on_end_game"},
]
self.machine = Machine(model=self, states=self._states, initial="hero_selection", transitions=self._transitions, queued=True)
@@ -258,7 +262,7 @@ class Bot:
# Check if we are out of tps or need repairing
need_repair = self._ui_manager.repair_needed()
if self._tps_left < random.randint(2, 5) or need_repair or self._config.char["always_repair"]:
if self._tps_left < random.randint(3, 5) or need_repair or self._config.char["always_repair"]:
if need_repair: Logger.info("Repair needed. Gear is about to break")
else: Logger.info("Repairing and buying TPs at next Vendor")
self._curr_loc = self._town_manager.repair_and_fill_tps(self._curr_loc)
@@ -360,3 +364,13 @@ class Bot:
if self._curr_loc:
res = self._nihlatak.battle(not self._pre_buffed)
self._ending_run_helper(res)
def on_run_arcane(self):
res = False
self._do_runs["run_arcane"] = False
self._game_stats.update_location("Arc" if self._config.general['discord_status_condensed'] else "Arcane")
self._curr_loc = self._arcane.approach(self._curr_loc)
if self._curr_loc:
res = self._arcane.battle(not self._pre_buffed)
self._tps_left -= self._arcane.used_tps
self._ending_run_helper(res)
+14
View File
@@ -143,6 +143,20 @@ class Hammerdin(IChar):
self._cast_hammers(1.2, "redemption")
return True
def kill_summoner(self) -> bool:
# move mouse to below altar
pos_m = self._screen.convert_abs_to_monitor((0, 20))
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
# Attack
self._cast_hammers(self._char_config["atk_len_arc"])
wait(0.1, 0.15)
self._cast_hammers(1.6, "redemption")
# Move a bit back and another round
self._move_and_attack((0, 80), self._char_config["atk_len_arc"] * 0.5)
wait(0.1, 0.15)
self._cast_hammers(1.6, "redemption")
return True
if __name__ == "__main__":
import os
+5 -2
View File
@@ -42,7 +42,8 @@ class IChar:
template_type: Union[str, List[str]],
success_func: Callable = None,
time_out: float = 8,
threshold: float = 0.68
threshold: float = 0.68,
telekinesis: bool = False
) -> bool:
"""
Finds any template from the template finder and interacts with it
@@ -192,7 +193,6 @@ class IChar:
Logger.warning("Failed to switch weapon, try again")
wait(0.5)
def pre_buff(self):
pass
@@ -210,3 +210,6 @@ class IChar:
def kill_nihlatak(self, end_nodes: list[int]) -> bool:
raise ValueError("Nihlatak is not implemented!")
def kill_summoner(self) -> bool:
raise ValueError("Arcane is not implemented!")
+13 -1
View File
@@ -220,7 +220,19 @@ class BlizzSorc(Sorceress):
self._blizzard((0, 0), spray=10)
return True
def kill_summoner(self) -> bool:
# Attack
delay = [0.2, 0.3]
cast_pos_abs = np.array([0, 0])
pos_m = self._screen.convert_abs_to_monitor((-20, 20))
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
for _ in range(int(self._char_config["atk_len_arc"])):
self._right_attack(cast_pos_abs, delay, 11)
self._left_attack(cast_pos_abs, delay, 11)
wait(self._cast_duration, self._cast_duration + 0.2)
return True
if __name__ == "__main__":
import os
import keyboard
+11
View File
@@ -145,6 +145,17 @@ class LightSorc(Sorceress):
self._pather.traverse_nodes(end_nodes, self, time_out=0.8)
return True
def kill_summoner(self) -> bool:
# Attack
delay = [0.2, 0.3]
cast_pos_abs = np.array([0, 0])
pos_m = self._screen.convert_abs_to_monitor((-20, 20))
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
for _ in range(int(self._char_config["atk_len_arc"])):
self._right_attack(cast_pos_abs, delay, 11)
self._left_attack(cast_pos_abs, delay, 11)
wait(self._cast_duration, self._cast_duration + 0.2)
return True
if __name__ == "__main__":
import os
+14 -4
View File
@@ -41,15 +41,13 @@ class NovaSorc(Sorceress):
def kill_eldritch(self) -> bool:
self._pather.traverse_nodes_fixed([(675, 30)], self)
self._cast_static(0.6)
atk_len = max(1.3, self._char_config["atk_len_eldritch"] - 0.7)
self._nova(atk_len)
self._nova(self._char_config["atk_len_eldritch"])
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._cast_static(0.6)
atk_len = max(1.5, self._char_config["atk_len_shenk"] - 1.0)
self._nova(atk_len)
self._nova(self._char_config["atk_len_shenk"])
return True
def kill_council(self) -> bool:
@@ -84,6 +82,18 @@ class NovaSorc(Sorceress):
self._move_and_attack((-70, -35), atk_len)
return True
def kill_summoner(self) -> bool:
# move mouse to below altar
pos_m = self._screen.convert_abs_to_monitor((0, 20))
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
# Attack
self._nova(self._char_config["atk_len_arc"])
# Move a bit back and another round
self._move_and_attack((0, 80), self._char_config["atk_len_arc"] * 0.5)
wait(0.1, 0.15)
self._nova(self._char_config["atk_len_arc"] * 0.5)
return True
if __name__ == "__main__":
import os
+34
View File
@@ -1,4 +1,5 @@
import keyboard
from typing import Dict, Tuple, Union, List, Callable
from utils.custom_mouse import mouse
from char import IChar
from template_finder import TemplateFinder
@@ -33,6 +34,39 @@ class Sorceress(IChar):
else:
return super().pick_up_item(pos, item_name, prev_cast_start)
def select_by_template(
self,
template_type: Union[str, List[str]],
success_func: Callable = None,
time_out: 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"] and telekinesis:
return super().select_by_template(template_type, success_func, time_out, 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
if self._template_finder.search("WAYPOINT_MENU", self._screen.grab()).valid:
keyboard.send("esc")
start = time.time()
while time_out is None or (time.time() - start) < time_out:
template_match = self._template_finder.search(template_type, self._screen.grab(), threshold=threshold)
if template_match.valid:
x_m, y_m = self._screen.convert_screen_to_monitor(template_match.position)
keyboard.send(self._skill_hotkeys["telekinesis"])
wait(0.1, 0.2)
mouse.move(x_m, y_m)
wait(0.2, 0.3)
mouse.click(button="right")
# check the successfunction for 2 sec, if not found, try again
check_success_start = time.time()
while time.time() - check_success_start < 2:
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)
def pre_buff(self):
if self._char_config["cta_available"]:
self._pre_buff_cta()
-1
View File
@@ -150,4 +150,3 @@ if __name__ == "__main__":
pather = Pather(screen, t_finder)
ui_manager = UiManager(screen, t_finder)
char = Trapsin(config.trapsin, config.char, screen, t_finder, ui_manager, pather)
+80
View File
@@ -0,0 +1,80 @@
import time
import os
from logger import Logger
from template_finder import TemplateFinder
from screen import Screen
from char import IChar
from config import Config
from utils.custom_mouse import mouse
from utils.misc import wait
class Chest:
def __init__(self, char: IChar, template_finder: TemplateFinder, template: str = None):
self._config = Config()
self._screen = Screen(self._config.general["monitor"])
self._char = char
self._template_finder = template_finder
self._folder_name = "chests"
# load all templates
self._templates = []
for filename in os.listdir(f'assets/{self._folder_name}/{template}'):
filename = filename.lower()
if filename.endswith('.png'):
chest = filename[:-4].upper()
self._templates.append(chest)
def open_up_chests(self, time_out: 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:
template_match = self._template_finder.search(templates, self._screen.grab(), roi=self._config.ui_roi["reduce_to_center"], threshold=threshold, use_grayscale=True, best_match=True)
# search for at least 1.5 second, if no chest found, break
if not template_match.valid:
if time.time() - start > 1.5:
break
else:
found_chest = True
x_m, y_m = self._screen.convert_screen_to_monitor(template_match.position)
# move mouse and check for label
mouse.move(x_m, y_m, delay_factor=[0.4, 0.6])
wait(0.13, 0.16)
chest_label = self._template_finder.search("CHEST_LABEL", self._screen.grab(), threshold=0.85)
if chest_label.valid:
Logger.debug(f"Opening {template_match.name} ({template_match.score*100:.1f}% confidence)")
# TODO: Act as picking up a potion to support telekinesis. This workaround needs a proper solution.
self._char.pick_up_item([x_m, y_m], 'potion')
wait(0.13, 0.16)
locked_chest = self._template_finder.search("LOCKED", self._screen.grab(), threshold=0.85)
if locked_chest.valid:
templates.remove(template_match.name)
Logger.debug("No more keys, removing locked chest template")
continue
else:
templates.remove(template_match.name)
Logger.debug("No chests left")
return found_chest
if __name__ == "__main__":
import keyboard
import os
keyboard.add_hotkey('f12', lambda: Logger.info('Force Exit (f12)') or os._exit(1))
print("Move to d2r window and press f11")
keyboard.wait("f11")
from char.hammerdin import Hammerdin
from screen import Screen
from pather import Pather
from config import Config
from ui import UiManager
config = Config()
screen = Screen(config.general["monitor"])
template_finder = TemplateFinder(screen)
pather = Pather(screen, template_finder)
ui_manager = UiManager(screen, template_finder)
char = Hammerdin(config.hammerdin, config.char, screen, template_finder, ui_manager, pather)
chest = Chest(char, template_finder, 'arcane')
chest.open_up_chests(threshold=0.8)
+3 -1
View File
@@ -104,13 +104,15 @@ class Config:
"stash_gold": bool(int(self._select_val("char", "stash_gold"))),
"gold_trav_only": bool(int(self._select_val("char", "gold_trav_only"))),
"use_merc": bool(int(self._select_val("char", "use_merc"))),
"id_items": bool(int(self._select_val("char", "ID_items"))),
"id_items": bool(int(self._select_val("char", "id_items"))),
"open_chests": bool(int(self._select_val("char", "open_chests"))),
"pre_buff_every_run": bool(int(self._select_val("char", "pre_buff_every_run"))),
"cta_available": bool(int(self._select_val("char", "cta_available"))),
"weapon_switch": self._select_val("char", "weapon_switch"),
"battle_orders": self._select_val("char", "battle_orders"),
"battle_command": self._select_val("char", "battle_command"),
"casting_frames": int(self._select_val("char", "casting_frames")),
"atk_len_arc": float(self._select_val("char", "atk_len_arc")),
"atk_len_trav": float(self._select_val("char", "atk_len_trav")),
"atk_len_pindle": float(self._select_val("char", "atk_len_pindle")),
"atk_len_eldritch": float(self._select_val("char", "atk_len_eldritch")),
+1 -1
View File
@@ -48,7 +48,7 @@ class HealthManager:
def update_location(self, loc: Location):
if loc is not None and type(loc) == str:
bosses = ["shenk", "eldritch", "pindle", "nihlatak", "trav"]
bosses = ["shenk", "eldritch", "pindle", "nihlatak", "trav", "arc"]
prev_value = self._pausing
self._pausing = not any(substring in loc for substring in bosses)
if self._pausing != prev_value:
+1 -1
View File
@@ -145,4 +145,4 @@ if __name__ == "__main__":
# cv2.putText(img, item.name, item.center, cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 1, cv2.LINE_AA)
# img = cv2.resize(img, None, fx=0.5, fy=0.5)
cv2.imshow('test', img)
cv2.waitKey(1)
cv2.waitKey(1)
+12
View File
@@ -16,6 +16,7 @@ class Npc:
ANYA = "anya"
TYRAEL = "tyrael"
ORMUS = "ormus"
FARA = "fara"
CAIN = "cain"
class NpcManager:
@@ -90,6 +91,17 @@ class NpcManager:
},
"template_group": ["ORMUS_0", "ORMUS_1", "ORMUS_2", "ORMUS_3", "ORMUS_4", "ORMUS_5"]
},
Npc.FARA: {
"name_tag_white": color_filter(self._template_finder.get_template("FARA_NAME_TAG_WHITE"), self._config.colors["white"])[1],
"name_tag_gold": color_filter(self._template_finder.get_template("FARA_NAME_TAG_GOLD"), self._config.colors["gold"])[1],
"action_btns": {
"trade_repair": {
"white": color_filter(self._template_finder.get_template("TRADE_REPAIR"), self._config.colors["white"])[1],
"blue": color_filter(self._template_finder.get_template("TRADE_REPAIR_BLUE"), self._config.colors["blue"])[1],
}
},
"template_group": ["FARA_LIGHT_1", "FARA_LIGHT_2", "FARA_LIGHT_3", "FARA_LIGHT_4", "FARA_LIGHT_5", "FARA_LIGHT_6", "FARA_LIGHT_7", "FARA_LIGHT_8", "FARA_LIGHT_9", "FARA_MEDIUM_1", "FARA_MEDIUM_2", "FARA_MEDIUM_3", "FARA_MEDIUM_4", "FARA_MEDIUM_5", "FARA_MEDIUM_6", "FARA_MEDIUM_7", "FARA_DARK_1", "FARA_DARK_2", "FARA_DARK_3", "FARA_DARK_4", "FARA_DARK_5", "FARA_DARK_6", "FARA_DARK_7"]
},
# Currently just nametag added to avoid having him focused when wanting to talk to tyrael or qual
#added in the ability to ID items at cain.
Npc.CAIN: {
+43 -7
View File
@@ -46,12 +46,21 @@ class Location:
A3_ORMUS = "a3_ormus"
A3_STASH_WP = "a3_stash_wp"
A3_ASHEARA = "a3_asheara"
# A2 Town
A2_TOWN_START = "a2_town_start"
A2_WP = "a2_wp"
A2_TP = "a2_tp"
A2_FARA_STASH = "a2_fara_stash"
# Trav
A3_TRAV_START = "a3_trav_start"
A3_TRAV_CENTER_STAIRS = "a3_trav_center_stairs"
# Nihalatk
A5_NIHLATAK_START = "a5_nihlatak_lvl1_start"
A5_NIHLATAK_END = "a5_nihlatak_lvl2_end"
# Arcane
A2_ARC_START = "a2_arc_start"
A2_ARC_CHECKPOINT = "a2_arc_checkpoint"
A2_ARC_END = "a2_arc_end"
class Pather:
@@ -126,6 +135,15 @@ class Pather:
190: {"A3_TOWN_12": (27, -198), "A3_TOWN_16": (831, 258), "A3_TOWN_14": (-318, 185), "A3_TOWN_15": (411, -9), "A3_TOWN_5": (-301, -334), "A3_TOWN_13": (-543, -10), "A3_TOWN_17": (707, 541)},
191: {"A3_TOWN_15": (-79, -256), "A3_TOWN_16": (341, 11), "A3_TOWN_17": (217, 294), "A3_TOWN_18": (542, 408), "A3_TOWN_19": (779, 171)},
192: {"A3_TOWN_17": (-187, 78), "A3_TOWN_16": (-63, -205), "A3_TOWN_18": (138, 192), "A3_TOWN_19": (375, -45)},
# A2 town
400: {"A2_TOWN_2": (-169, 160), "A2_TOWN_0": (290, -114), "A2_TOWN_3": (-348, 225), "A2_TOWN_1": (-345, -374)},
401: {"A2_TOWN_3": (45, -202), "A2_TOWN_7": (-40, 266), "A2_TOWN_2": (224, -267), "A2_TOWN_6": (-505, 26), "A2_TOWN_5": (116, 203)},
402: {"A2_TOWN_8": (182, 121), "A2_TOWN_6": (103, -348), "A2_TOWN_11": (128, 357), "A2_TOWN_9": (-324, 223), "A2_TOWN_12": (-213, 500)},
403: {"A2_TOWN_11": (161, -18), "A2_TOWN_12": (-180, 125), "A2_TOWN_9": (-291, -152), "A2_TOWN_8": (215, -254), "A2_TOWN_13": (149, 392)},
404: {"A2_TOWN_14": (79, 190), "A2_TOWN_15": (244, -12), "A2_TOWN_13": (-270, 123), "A2_TOWN_11": (-258, -287), "A2_TOWN_12": (-599, -143)},
405: {"A2_TOWN_10": (65, -175), "A2_TOWN_17": (-108, 164), "A2_TOWN_16": (-304, -11), "A2_TOWN_9": (319, -68), "A2_TOWN_18": (-415, -284)},
406: {"A2_TOWN_18": (108, -143), "A2_TOWN_16": (219, 129), "A2_TOWN_19": (-293, 21), "A2_TOWN_17": (415, 304), "A2_TOWN_10": (588, -34)},
408: {"A2_TOWN_20": (-26, -109), "A2_TOWN_22": (-82, 278), "A2_TOWN_19": (344, 38), "A2_TOWN_21": (-518, -299), "A2_TOWN_18": (745, -125)},
# Trav
220: {"TRAV_0": (445, 384), "TRAV_20": (-259, 267), "TRAV_1": (-248, -139), "TRAV_2": (-682, 21), "TRAV_21": (25, 180)},
221: {"TRAV_2": (-153, -101), "TRAV_3": (-125, 201), "TRAV_20": (270, 145), "TRAV_1": (281, -261), "TRAV_4": (-459, 122)},
@@ -142,6 +160,13 @@ class Pather:
301: {"TRAV_V3_7": (63, -113), "TRAV_V3_6": (55, 77), "TRAV_V3_0": (-27, -315), "TRAV_V3_5": (-450, -175), "TRAV_V3_8": (329, -188)},
302: {"TRAV_V3_0": (-18, 6), "TRAV_V3_7": (73, 208), "TRAV_V3_8": (339, 133), "TRAV_V3_6": (65, 398), "TRAV_V3_5": (-440, 146)},
304: {"TRAV_V2_4": (125, -148), "TRAV_V2_3": (-187, 55), "TRAV_V2_1": (-207, 59), "TRAV_V2_2": (267, 183), "TRAV_V2_0": (-159, 403)},
# Arcane
450: {"ARC_START": (49, 62)},
453: {"ARC_START": (-259, 62)},
456: {"ARC_START": (145, 264)},
459: {"ARC_START": (-356, 258)},
461: {"ARC_ALTAR": (60, 120), "ARC_ALTAR3": (-272, 200), "ARC_CENTER_2": (67, 41), "ARC_END_STAIRS": (76, -344), "ARC_END_STAIRS_2": (60, -160)},
462: {"ARC_PLATFORM_1": (0, -100), "ARC_PLATFORM_2": (50, -100), "ARC_PLATFORM_3": (70, 0)},
# Nil - End of Arm A
500: {"NIL2A_0": (-200, 1), "NIL2A_2": (-181, -102), "NIL2A_1": (329, 146), "NIL2A_4": (835, 24), "NIL2A_5": (-384, -20), "NIL2A_6": (-600, 410)},
501: {"NIL2A_4": (270+20, 259-50), "NIL2A_1": (-236+20, 381-50), "NIL2A_2": (-746+20, 133-50), "NIL2A_0": (-765+20, 236-50)},
@@ -176,7 +201,7 @@ class Pather:
(Location.A5_QUAL_KEHK, Location.A5_NIHLATHAK_PORTAL): [12, 11, 10, 6, 8, 9],
(Location.A5_QUAL_KEHK, Location.A5_WP): [12, 11, 10, 6],
(Location.A5_QUAL_KEHK, Location.A5_STASH): [12, 11, 10, 6, 5],
(Location.A5_LARZUK, Location.A5_QUAL_KEHK): [13, 14, 5, 6, 10, 11, 12],
(Location.A5_LARZUK, Location.A5_QUAL_KEHK): [13, 14, 5, 6, 10, 11, 12],
(Location.A5_LARZUK, Location.A5_NIHLATHAK_PORTAL): [14, 13, 5, 6, 8, 9],
(Location.A5_LARZUK, Location.A5_WP): [14, 13, 5],
# Pindle
@@ -201,6 +226,12 @@ class Pather:
(Location.A3_ASHEARA, Location.A3_STASH_WP): [191, 190, 189, 185, 186, 187, 188],
(Location.A3_STASH_WP, Location.A3_STASH_WP): [188],
(Location.A3_STASH_WP, Location.A3_ORMUS): [187, 186, 185],
# A2 Town
(Location.A2_TOWN_START, Location.A2_WP): [400, 401, 402, 403, 404],
(Location.A2_TOWN_START, Location.A2_FARA_STASH): [400, 401, 402, 405],
(Location.A2_FARA_STASH, Location.A2_WP): [403, 404],
(Location.A2_TP, Location.A2_FARA_STASH): [408, 406, 405],
(Location.A2_WP, Location.A2_FARA_STASH): [404, 403, 405],
# Trav
(Location.A3_TRAV_START, Location.A3_TRAV_CENTER_STAIRS): [220, 221, 222, 223, 224, 225, 226],
}
@@ -226,7 +257,7 @@ class Pather:
def _convert_rel_to_abs(rel_loc: Tuple[float, float], pos_abs: Tuple[float, float]) -> Tuple[float, float]:
return (rel_loc[0] + pos_abs[0], rel_loc[1] + pos_abs[1])
def traverse_nodes_fixed(self, key: Union[str, List[Tuple[float, float]]], char: IChar):
def traverse_nodes_fixed(self, key: Union[str, List[Tuple[float, float]]], char: IChar) -> bool:
if not char.can_teleport():
error_msg = "Teleport is requiered for static pathing"
Logger.error(error_msg)
@@ -237,6 +268,7 @@ class Pather:
else:
path = key
i = 0
stuck_count = 0
while i < len(path):
x_m, y_m = self._screen.convert_screen_to_monitor(path[i])
x_m += int(random.random() * 6 - 3)
@@ -252,9 +284,13 @@ class Pather:
if score > 0.15:
i += 1
else:
stuck_count += 1
Logger.debug(f"Teleport cancel detected. Try same teleport action again. ({score:.4f})")
if stuck_count >= 5:
return False
# if type(key) == str and ("_save_dist" in key or "_end" in key):
# cv2.imwrite(f"./info_screenshots/nil_path_{key}_" + time.strftime("%Y%m%d_%H%M%S") + ".png", self._screen.grab())
return True
def _adjust_abs_range_to_screen(self, abs_pos: Tuple[float, float]) -> Tuple[float, float]:
"""
@@ -367,7 +403,7 @@ class Pather:
Logger.error("Got stuck exit pather")
return False
# Sometimes we get stuck at rocks and stuff, after 2.5 seconds force a move into the last know direction
# Sometimes we get stuck at rocks and stuff, after a few seconds force a move into the last know direction
if not did_force_move and time.time() - last_move > 3.1:
pos_abs = (0, 150)
if last_direction is not None:
@@ -425,7 +461,7 @@ if __name__ == "__main__":
x, y = pather._screen.convert_abs_to_screen(ref_pos_abs)
cv2.circle(display_img, (x, y), 5, (0, 255, 0), 3)
cv2.putText(display_img, template_type, (x, y), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, cv2.LINE_AA)
# display_img = cv2.resize(display_img, None, fx=0.5, fy=0.5)
display_img = cv2.resize(display_img, None, fx=0.5, fy=0.5)
cv2.imshow("debug", display_img)
cv2.waitKey(1)
@@ -441,7 +477,7 @@ if __name__ == "__main__":
t_finder = TemplateFinder(screen)
pather = Pather(screen, t_finder)
display_all_nodes(pather, "ELD")
display_all_nodes(pather, "A2_TOWN")
# # changing node pos and generating new code
# code = ""
@@ -455,5 +491,5 @@ if __name__ == "__main__":
ui_manager = UiManager(screen, t_finder)
char = Hammerdin(config.hammerdin, config.char, screen, t_finder, ui_manager, pather)
pather.traverse_nodes_fixed("trav_safe_dist", char)
# pather.traverse_nodes([226, 228, 229], char)
# pather.traverse_nodes([452], char)
pather.traverse_nodes_fixed("arc_top_right", char)
+1
View File
@@ -2,3 +2,4 @@ from .pindle import Pindle
from .shenk_eld import ShenkEld
from .trav import Trav
from .nihlatak import Nihlatak
from .arcane import Arcane
+125
View File
@@ -0,0 +1,125 @@
from char.i_char import IChar
from config import Config
from logger import Logger
from pather import Location, Pather
from typing import Union
from item.pickit import PickIt
from template_finder import TemplateFinder
from town.town_manager import TownManager
from ui import UiManager
from utils.misc import wait
from dataclasses import dataclass
from chest import Chest
class Arcane:
def __init__(
self,
template_finder: TemplateFinder,
pather: Pather,
town_manager: TownManager,
ui_manager: UiManager,
char: IChar,
pickit: PickIt
):
self._config = Config()
self._template_finder = template_finder
self._pather = pather
self._town_manager = town_manager
self._ui_manager = ui_manager
self._char = char
self._pickit = pickit
self._chest = Chest(self._char, self._template_finder, 'arcane')
self.used_tps = 0
def approach(self, start_loc: Location) -> Union[bool, Location]:
Logger.info("Run Arcane")
if not self._char.can_teleport():
raise ValueError("Arcane requires teleport")
if not self._town_manager.open_wp(start_loc):
return False
wait(0.4)
self._ui_manager.use_wp(2, 7)
return Location.A2_ARC_START
def _find_summoner(self, traverse_to_summoner: list[tuple[float, float]]) -> bool:
# 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 = self._template_finder.search_and_wait(templates_platform, threshold=0.55, time_out=0.5, use_grayscale=True, take_ss=False)
match_summoner = self._template_finder.search_and_wait(tempaltes_summoner, threshold=0.79, time_out=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 = self._template_finder.search_and_wait(tempaltes_summoner, threshold=0.79, time_out=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):
return True
else:
# Traverse to center of platform
self._pather.traverse_nodes([462], self._char, time_out=1.3, force_tp=True)
return False
def battle(self, do_pre_buff: bool) -> Union[bool, tuple[Location, bool]]:
picked_up_items = False
@dataclass
class PathData:
calib_node_start: int
static_path_forward: str
jump_to_summoner: list[tuple[float, float]]
path_arr = [
PathData(450, "arc_top_right", [(500, 40)]),
PathData(453, "arc_top_left", [(500, 40)]),
PathData(456, "arc_bottom_right", [(500, 40)]),
PathData(459, "arc_bottom_left", [(500, 20), (700, 100)])
]
picked_up_items = False
self.used_tps = 0
if do_pre_buff:
self._char.pre_buff()
for i, data in enumerate(path_arr):
# 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):
return False
found = self._find_summoner(data.jump_to_summoner)
# Kill the summoner or trash mob
self._char.kill_summoner()
if self._config.char["open_chests"]:
self._chest.open_up_chests()
picked_up_items |= self._pickit.pick_up_items(self._char)
if found:
return (Location.A2_ARC_END, picked_up_items)
elif i < len(path_arr) - 1:
# 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
if not self._town_manager.wait_for_tp(Location.A2_TP):
return False
if not self.approach(Location.A2_FARA_STASH):
return False
return False
if __name__ == "__main__":
from screen import Screen
import keyboard
from game_stats import GameStats
import os
keyboard.add_hotkey('f12', lambda: os._exit(1))
keyboard.wait("f11")
from config import Config
from ui import UiManager
from bot import Bot
config = Config()
screen = Screen(config.general["monitor"])
game_stats = GameStats()
bot = Bot(screen, game_stats, False)
bot._arcane._find_summoner([(500, 40)])
+1 -1
View File
@@ -107,4 +107,4 @@ class Nihlatak:
self._char.kill_nihlatak(end_nodes)
wait(0.2, 0.3)
picked_up_items = self._pickit.pick_up_items(self._char)
return (Location.A5_NIHLATAK_END, picked_up_items)
return (Location.A5_NIHLATAK_END, picked_up_items)
+1 -1
View File
@@ -117,7 +117,7 @@ if __name__ == "__main__":
p1 = (x, y)
p2 = (x+w, y+h)
cv2.rectangle(test_img, p1, p2, (0, 255, 0), 2)
cv2.putText(test_img, roi_key, (p1[0], p1[1]+20), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 1, cv2.LINE_AA)
cv2.putText(test_img, roi_key, (p1[0], p1[1]+20), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 1, cv2.LINE_AA)
if show_pt:
pass
+6 -5
View File
@@ -22,7 +22,7 @@ class TemplateFinder:
Loads images from assets/templates and assets/npc and provides search functions
to find these assets within another image
"""
def __init__(self, screen: Screen, template_pathes: list[str] = ["assets\\templates", "assets\\npc", "assets\\item_properties"]):
def __init__(self, screen: Screen, template_pathes: list[str] = ["assets\\templates", "assets\\npc", "assets\\item_properties", "assets\\chests"]):
self._screen = screen
self._config = Config()
self.last_res = None
@@ -185,18 +185,19 @@ if __name__ == "__main__":
config = Config()
screen = Screen(config.general["monitor"])
template_finder = TemplateFinder(screen)
search_templates = ["REPAIR_NEEDED"]
search_templates = ["ARC_ALTAR", "ARC_ALTAR2"]
while 1:
# img = cv2.imread("")
img = screen.grab()
display_img = img.copy()
start = time.time()
for key in search_templates:
template_match = template_finder.search(key, img, best_match=True, threshold=0.35, use_grayscale=True)
template_match = template_finder.search(key, img, best_match=True, threshold=0.5, use_grayscale=True)
if template_match.valid:
cv2.putText(display_img, str(template_match.name), template_match.position, cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2, cv2.LINE_AA)
cv2.putText(display_img, str(template_match.name), template_match.position, cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, cv2.LINE_AA)
cv2.circle(display_img, template_match.position, 7, (255, 0, 0), thickness=5)
print(f"Name: {template_match.name} Pos: {template_match.position}, Score: {template_match.score}")
x, y = template_match.position
print(f"Name: {template_match.name} Pos: {template_match.position}, Dist: {625-x, 360-y}, Score: {template_match.score}")
# print(time.time() - start)
display_img = cv2.resize(display_img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_NEAREST)
cv2.imshow('test', display_img)
+1
View File
@@ -1,3 +1,4 @@
from .a2 import A2
from .a3 import A3
from .a4 import A4
from .a5 import A5
+62
View File
@@ -0,0 +1,62 @@
from char import IChar
from town.i_act import IAct
from screen import Screen
from config import Config
from npc_manager import NpcManager, Npc
from pather import Pather, Location
from typing import Union
from template_finder import TemplateFinder
from utils.misc import wait
class A2(IAct):
def __init__(self, screen: Screen, template_finder: TemplateFinder, pather: Pather, char: IChar, npc_manager: NpcManager):
self._config = Config()
self._screen = screen
self._pather = pather
self._char = char
self._npc_manager = npc_manager
self._template_finder = template_finder
def get_wp_location(self) -> Location: return Location.A2_WP
def can_stash(self) -> bool: return True
def can_buy_pots(self) -> bool: return False
def can_heal(self) -> bool: return True
def can_trade_and_repair(self) -> bool: return True
def heal(self, curr_loc: Location) -> Union[Location, bool]:
if not self._pather.traverse_nodes((curr_loc, Location.A2_FARA_STASH), self._char): return False
self._npc_manager.open_npc_menu(Npc.FARA)
return Location.A2_FARA_STASH
def open_stash(self, curr_loc: Location) -> Union[Location, bool]:
if not self._pather.traverse_nodes((curr_loc, Location.A2_FARA_STASH), self._char):
return False
wait(0.3)
def stash_is_open_func():
img = self._screen.grab()
found = self._template_finder.search("INVENTORY_GOLD_BTN", img, roi=self._config.ui_roi["gold_btn"]).valid
found |= self._template_finder.search("INVENTORY_GOLD_BTN", img, roi=self._config.ui_roi["gold_btn_stash"]).valid
return found
if not self._char.select_by_template(["A2_STASH_LIGHT", "A2_STASH_DARK"], stash_is_open_func, telekinesis=True):
return False
return Location.A2_FARA_STASH
def open_trade_and_repair_menu(self, curr_loc: Location) -> Union[Location, bool]:
if not self._pather.traverse_nodes((curr_loc, Location.A2_FARA_STASH), self._char): return
self._npc_manager.open_npc_menu(Npc.FARA)
self._npc_manager.press_npc_btn(Npc.FARA, "trade_repair")
return Location.A2_FARA_STASH
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: self._template_finder.search("WAYPOINT_MENU", self._screen.grab()).valid
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 = self._template_finder.search_and_wait(["A2_TOWN_21", "A2_TOWN_22", "A2_TOWN_20", "A2_TOWN_19"], time_out=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
return False
+2 -3
View File
@@ -7,7 +7,6 @@ from pather import Pather, Location
from typing import Union
from template_finder import TemplateFinder
from utils.misc import wait
from utils.custom_mouse import mouse
class A3(IAct):
@@ -44,7 +43,7 @@ class A3(IAct):
found = self._template_finder.search("INVENTORY_GOLD_BTN", img, roi=self._config.ui_roi["gold_btn"]).valid
found |= self._template_finder.search("INVENTORY_GOLD_BTN", img, roi=self._config.ui_roi["gold_btn_stash"]).valid
return found
if not self._char.select_by_template("A3_STASH", stash_is_open_func):
if not self._char.select_by_template("A3_STASH", stash_is_open_func, telekinesis=True):
return False
return Location.A3_STASH_WP
@@ -52,7 +51,7 @@ class A3(IAct):
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: self._template_finder.search("WAYPOINT_MENU", self._screen.grab()).valid
return self._char.select_by_template("A3_WP", found_wp_func)
return self._char.select_by_template("A3_WP", found_wp_func, telekinesis=True)
def wait_for_tp(self) -> Union[Location, bool]:
template_match = self._template_finder.search_and_wait("A3_TOWN_10", time_out=20)
+1 -1
View File
@@ -34,7 +34,7 @@ class A4(IAct):
wait(0.5, 0.7)
found_wp_func = lambda: self._template_finder.search("WAYPOINT_MENU", self._screen.grab()).valid
# 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)
return self._char.select_by_template(["A4_WP", "A4_WP_2"], found_wp_func, threshold=0.62, telekinesis=True)
def wait_for_tp(self) -> Union[Location, bool]:
success = self._template_finder.search_and_wait(["A4_TOWN_4", "A4_TOWN_5", "A4_TOWN_6"], time_out=20).valid
+2 -2
View File
@@ -62,7 +62,7 @@ class A5(IAct):
found = self._template_finder.search("INVENTORY_GOLD_BTN", img, roi=self._config.ui_roi["gold_btn"]).valid
found |= self._template_finder.search("INVENTORY_GOLD_BTN", img, roi=self._config.ui_roi["gold_btn_stash"]).valid
return found
if not self._char.select_by_template(["A5_STASH", "A5_STASH_2"], stash_is_open_func):
if not self._char.select_by_template(["A5_STASH", "A5_STASH_2"], stash_is_open_func, telekinesis=True):
return False
return Location.A5_STASH
@@ -76,7 +76,7 @@ class A5(IAct):
if not self._pather.traverse_nodes((curr_loc, Location.A5_WP), self._char): return False
wait(0.5, 0.7)
found_wp_func = lambda: self._template_finder.search("WAYPOINT_MENU", self._screen.grab()).valid
return self._char.select_by_template("A5_WP", found_wp_func)
return self._char.select_by_template("A5_WP", found_wp_func, telekinesis=True)
def wait_for_tp(self) -> Union[Location, bool]:
success = self._template_finder.search_and_wait(["A5_TOWN_1", "A5_TOWN_0"], time_out=20).valid
+12 -6
View File
@@ -5,17 +5,18 @@ from config import Config
from pather import Location
from logger import Logger
from ui import UiManager
from town import IAct, A3, A4, A5
from town import IAct, A2, A3, A4, A5
from utils.misc import wait
class TownManager:
def __init__(self, template_finder: TemplateFinder, ui_manager: UiManager, item_finder: ItemFinder, a3: A3, a4: A4, a5: A5):
def __init__(self, template_finder: TemplateFinder, ui_manager: UiManager, item_finder: ItemFinder, a2: A2, a3: A3, a4: A4, a5: A5):
self._config = Config()
self._template_finder = template_finder
self._ui_manager = ui_manager
self._item_finder = item_finder
self._acts: dict[Location, IAct] = {
Location.A2_TOWN_START: a2,
Location.A3_TOWN_START: a3,
Location.A4_TOWN_START: a4,
Location.A5_TOWN_START: a5
@@ -30,6 +31,8 @@ class TownManager:
location = Location.A4_TOWN_START
elif loc.upper().startswith("A3_"):
location = Location.A3_TOWN_START
elif loc.upper().startswith("A2_"):
location = Location.A2_TOWN_START
return location
def wait_for_town_spawn(self, time_out: float = None) -> Location:
@@ -40,7 +43,8 @@ class TownManager:
template_match = self._template_finder.search_and_wait([
"A5_TOWN_0", "A5_TOWN_1",
"A4_TOWN_4", "A4_TOWN_5",
"A3_TOWN_0", "A3_TOWN_1"
"A3_TOWN_0", "A3_TOWN_1",
"A2_TOWN_0", "A2_TOWN_1", "A2_TOWN_10"
], best_match=True, time_out=time_out)
if template_match.valid:
return TownManager.get_act_from_location(template_match.name)
@@ -60,7 +64,8 @@ class TownManager:
curr_act = TownManager.get_act_from_location(curr_loc)
if curr_act is None: return False
# check if we already are in the desired act
if act_idx == 3: act = Location.A3_TOWN_START
if act_idx == 2: act = Location.A2_TOWN_START
elif act_idx == 3: act = Location.A3_TOWN_START
elif act_idx == 4: act = Location.A4_TOWN_START
elif act_idx == 5: act = Location.A5_TOWN_START
else:
@@ -179,5 +184,6 @@ if __name__ == "__main__":
a5 = A5(screen, template_finder, pather, char, npc_manager)
a4 = A4(screen, template_finder, pather, char, npc_manager)
a3 = A3(screen, template_finder, pather, char, npc_manager)
town_manager = TownManager(template_finder, ui_manager, item_finder, a3, a4, a5)
print(town_manager.stash(Location.A5_STASH))
a2 = A2(screen, template_finder, pather, char, npc_manager)
town_manager = TownManager(template_finder, ui_manager, item_finder, a2, a3, a4, a5)
print(town_manager.open_wp(Location.A2_TOWN_START))
+1 -1
View File
@@ -189,4 +189,4 @@ if __name__ == "__main__":
ui_manager = UiManager(screen, template_finder)
belt_manager = BeltManager(screen, template_finder)
belt_manager.update_pot_needs()
print(belt_manager._pot_needs)
print(belt_manager._pot_needs)
+1 -1
View File
@@ -34,7 +34,7 @@ class UiManager():
:param act: Index of the desired act starting at 1 [A1 = 1, A2 = 2, A3 = 3, ...]
:param idx: Index of the waypoint from top. Note that it start at 0!
"""
str_to_idx_map = {"WP_A3_ACTIVE": 3, "WP_A4_ACTIVE": 4, "WP_A5_ACTIVE": 5}
str_to_idx_map = {"WP_A2_ACTIVE": 2, "WP_A3_ACTIVE": 3, "WP_A4_ACTIVE": 4, "WP_A5_ACTIVE": 5}
template_match = self._template_finder.search([*str_to_idx_map], self._screen.grab(), threshold=0.7, best_match=True, roi=self._config.ui_roi["wp_act_roi"])
curr_active_act = str_to_idx_map[template_match.name] if template_match.valid else -1
if curr_active_act != act:
+3 -2
View File
@@ -47,7 +47,7 @@ class NodeRecorder:
def find_templates(self, img):
ref_points = {}
for key in self._template_finder._templates:
found = self._template_finder.search(key, img, use_grayscale=True)
found = self._template_finder.search(key, img, use_grayscale=False, threshold=0.77)
if found.valid:
ref_points[key] = found.position
return ref_points
@@ -127,7 +127,7 @@ class NodeRecorder:
results = sorted(new_path, key=lambda r: r["dist"])
code = f"{k}: " + "{"
for i, res in enumerate(results):
if res["dist"] < 900 and i < 5:
if res["dist"] < 1100 and i < 8:
code += f'"{res["key"].upper()}": {res["pos"]}, '
f.write(code + "}\n")
f.close()
@@ -156,5 +156,6 @@ if __name__ == "__main__":
cv2.putText(img, key, recorder.ref_points[key], cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, cv2.LINE_AA)
except Exception:
pass
img = cv2.resize(img, None, fx=0.5, fy=0.5)
cv2.imshow("vis", img)
cv2.waitKey(1)