From 4e9ac375457284d724fb0fd80728839ba19614aa Mon Sep 17 00:00:00 2001 From: mgleed Date: Sun, 12 Jun 2022 22:18:41 -0400 Subject: [PATCH 01/44] init, wip --- src/char/i_char.py | 222 +++++++++++++++++++++++++--------- src/char/paladin/fohdin.py | 29 ++--- src/char/paladin/hammerdin.py | 47 ------- src/char/paladin/paladin.py | 76 +++--------- src/ui/skills.py | 3 +- 5 files changed, 197 insertions(+), 180 deletions(-) diff --git a/src/char/i_char.py b/src/char/i_char.py index 3735317fd..7aa10fa1f 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -1,43 +1,140 @@ +from turtle import Screen +from inventory import consumables from typing import Callable -import random -import time import cv2 -import math -from inventory import consumables import keyboard +import math import numpy as np +import random +import time + from char.capabilities import CharacterCapabilities -from ui_manager import is_visible, wait_until_visible +from config import Config +from logger import Logger +from ocr import Ocr +from screen import grab, convert_monitor_to_screen, convert_screen_to_abs, convert_abs_to_monitor, convert_screen_to_monitor, convert_abs_to_screen from ui import skills +from ui_manager import detect_screen_object, ScreenObjects +from ui_manager import is_visible, wait_until_visible from utils.custom_mouse import mouse from utils.misc import wait, cut_roi, is_in_roi, color_filter, arc_spread -from logger import Logger -from config import Config -from screen import grab, convert_monitor_to_screen, convert_screen_to_abs, convert_abs_to_monitor, convert_screen_to_monitor import template_finder -from ocr import Ocr -from ui_manager import detect_screen_object, ScreenObjects + + class IChar: _CrossGameCapabilities: None | CharacterCapabilities = None def __init__(self, skill_hotkeys: dict): - self._skill_hotkeys = skill_hotkeys - self._last_tp = time.time() - self._ocr = Ocr() - # Add a bit to be on the save side - self._cast_duration = Config().char["casting_frames"] * 0.04 + 0.01 - self.damage_scaling = float(Config().char.get("damage_scaling", 1.0)) - self.capabilities = None self._active_skill = { "left": "", "right": "" } + # Add a bit to be on the save side + self._cast_duration = Config().char["casting_frames"] * 0.04 + 0.01 + self._last_tp = time.time() + self._ocr = Ocr() + self._skill_hotkeys = skill_hotkeys + self._standing_still = False + self.capabilities = None + self.damage_scaling = float(Config().char.get("damage_scaling", 1.0)) + + def _log_cast(self, skill_name: str, cast_pos_abs: tuple[float, float], spray: int, min_duration: float, aura: str): + msg = f"Casting skill {skill_name}" + if cast_pos_abs: + msg += f" at screen coordinate {convert_abs_to_screen(cast_pos_abs)}" + if spray: + msg += f" with spray of {spray}" + if min_duration: + msg += f" for {round(min_duration, 1)}s" + if aura: + msg += f" with {aura} active" + Logger.debug(msg) + + def _click(self, mouse_click_type: str = "left", wait_before_release: float = 0.0): + """ + Sends a click to the mouse. + """ + if not wait_before_release: + mouse.click(button = mouse_click_type) + else: + mouse.press(button = mouse_click_type) + wait(wait_before_release) + mouse.release(button = mouse_click_type) + + def _click_left(self, wait_before_release: float = 0.0): + self._click("left", wait_before_release = wait_before_release) + + def _click_right(self, wait_before_release: float = 0.0): + self._click("right", wait_before_release = wait_before_release) + + def _cast_simple(self, skill_name: str, mouse_click_type: str = "left"): + """ + Selects and casts a skill. + """ + if self._active_skill[mouse_click_type] != skill_name: + self._select_skill(skill_name, mouse_click_type = mouse_click_type) + wait(0.04) + self._click(mouse_click_type) + + def _cast_at_position(self, cast_pos_abs: tuple[float, float], spray: int, mouse_click_type: str = "left"): + """ + Casts a skill at a given position. + """ + if cast_pos_abs: + x = cast_pos_abs[0] + y = cast_pos_abs[1] + if spray: + x += (random.random() * 2 * spray - spray) + y += (random.random() * 2 * spray - spray) + pos_m = convert_abs_to_monitor((x, y)) + mouse.move(*pos_m, delay_factor=[0.1, 0.2]) + wait(0.06, 0.08) + self._click(mouse_click_type) + + def _cast_left_with_aura(self, skill_name: str, cast_pos_abs: tuple[float, float] = None, spray: int = 0, min_duration: float = 0, aura: str = ""): + """ + Casts a skill with an aura active + :param skill_name: name of skill in params file; i.e., "holy_bolt" + :param cast_pos_abs: absolute position to cast toward + :param spray: amount of spray to apply + :param min_duration: minimum duration to cast the skill + :param aura: name of aura to ensure is active during skill cast + """ + + #self._log_cast(skill_name, cast_pos_abs, spray, min_duration, aura) + + # set aura if needed + if aura: + self._select_skill(aura, mouse_click_type = "right") + + keyboard.send(Config().char["stand_still"], do_release=False) + + # set left hand skill + self._select_skill(skill_name, mouse_click_type = "left") + wait(0.05, 0.1) + + # cast left hand skill + start = time.time() + if min_duration: + while (time.time() - start) <= min_duration: + self._cast_at_position(cast_pos_abs, spray) + else: + self._cast_at_position(cast_pos_abs, spray) + + keyboard.send(Config().char["stand_still"], do_press=False) def _set_active_skill(self, mouse_click_type: str = "left", skill: str =""): + """ + Sets the active skill internally, used to keep track of which skill is currently active + """ self._active_skill[mouse_click_type] = skill - def _select_skill(self, skill: str, mouse_click_type: str = "left", delay: float | list | tuple = None): + def _select_skill(self, skill: str, mouse_click_type: str = "left", delay: float | list | tuple = None) -> bool: + """ + Sets the active skill on left or right. + Will only set skill if not already selected + """ if not ( skill in self._skill_hotkeys and (hotkey := self._skill_hotkeys[skill]) or (skill in Config().char and (hotkey := Config().char[skill])) @@ -48,7 +145,7 @@ def _select_skill(self, skill: str, mouse_click_type: str = "left", delay: float if self._active_skill[mouse_click_type] != skill: keyboard.send(hotkey) - self._set_active_skill(mouse_click_type, skill) + self._set_active_skill(mouse_click_type, skill) if delay: try: wait(*delay) @@ -59,6 +156,32 @@ def _select_skill(self, skill: str, mouse_click_type: str = "left", delay: float Logger.warning(f"_select_skill: Failed to delay with delay: {delay}. Exception: {e}") return True + def _cast_teleport(self): + self._cast_simple(skill_name="teleport", mouse_click_type="right") + + def _cast_battle_orders(self): + self._cast_simple(skill_name="battle_orders", mouse_click_type="right") + + def _cast_battle_command(self): + self._cast_simple(skill_name="battle_command", mouse_click_type="right") + + def _cast_town_portal(self): + consumables.increment_need("tp", 1) + self._cast_simple(skill_name="tp", mouse_click_type="right") + + @staticmethod + def _weapon_switch(): + keyboard.send(Config().char["weapon_switch"]) + + def _stand_still(self, enable: bool): + if enable: + self._stand_still_enabled = True + keyboard.send(Config().char["stand_still"], do_release=False) + else: + self._stand_still_enabled = False + keyboard.send(Config().char["stand_still"], do_press=False) + + def _discover_capabilities(self) -> CharacterCapabilities: override = Config().advanced_options["override_capabilities"] if override is None: @@ -91,7 +214,7 @@ def on_capabilities_discovered(self, capabilities: CharacterCapabilities): def pick_up_item(self, pos: tuple[float, float], item_name: str = None, prev_cast_start: float = 0): mouse.move(pos[0], pos[1]) time.sleep(0.1) - mouse.click(button="left") + self._click_left() wait(0.45, 0.5) return prev_cast_start @@ -122,7 +245,7 @@ def select_by_template( Logger.debug(f"Select {template_match.name} ({template_match.score*100:.1f}% confidence)") mouse.move(*template_match.center_monitor) wait(0.2, 0.3) - mouse.click(button="left") + self._click_left() # check the successfunction for 2 sec, if not found, try again check_success_start = time.time() while time.time() - check_success_start < 2: @@ -156,7 +279,7 @@ def _remap_skill_hotkey(self, skill_asset, hotkey, skill_roi, expanded_skill_roi x, y, w, h = skill_roi x, y = convert_screen_to_monitor((x, y)) mouse.move(x + w/2, y + h / 2) - mouse.click("left") + self._click_left() wait(0.3) match = template_finder.search(skill_asset, grab(), threshold=0.84, roi=expanded_skill_roi) if match.valid: @@ -164,7 +287,7 @@ def _remap_skill_hotkey(self, skill_asset, hotkey, skill_roi, expanded_skill_roi wait(0.3) keyboard.send(hotkey) wait(0.3) - mouse.click("left") + self._click_left() wait(0.3) def remap_right_skill_hotkey(self, skill_asset, hotkey): @@ -188,10 +311,8 @@ def move(self, pos_monitor: tuple[float, float], force_tp: bool = False, force_m and skills.is_right_skill_active() ) ): - self._set_active_skill("right", "teleport") mouse.move(pos_monitor[0], pos_monitor[1], randomize=3, delay_factor=[factor*0.1, factor*0.14]) - wait(0.012, 0.02) - mouse.click(button="right") + self._cast_simple(skill_name="teleport", mouse_click_type="right") wait(self._cast_duration, self._cast_duration + 0.02) else: # in case we want to walk we actually want to move a bit before the point cause d2r will always "overwalk" @@ -208,7 +329,7 @@ def move(self, pos_monitor: tuple[float, float], force_tp: bool = False, force_m if force_move: keyboard.send(Config().char["force_move"]) else: - mouse.click(button="left") + self._click_left() def walk(self, pos_monitor: tuple[float, float], force_tp: bool = False, force_move: bool = False): factor = Config().advanced_options["pathing_delay_factor"] @@ -226,14 +347,14 @@ def walk(self, pos_monitor: tuple[float, float], force_tp: bool = False, force_m if force_move: keyboard.send(Config().char["force_move"]) else: - mouse.click(button="left") + self._click_left() def tp_town(self): # will check if tp is available and select the skill if not skills.has_tps(): return False - mouse.click(button="right") - consumables.increment_need("tp", 1) + self._cast_town_portal() + roi_mouse_move = [ int(Config().ui_pos["screen_width"] * 0.3), 0, @@ -241,7 +362,6 @@ def tp_town(self): int(Config().ui_pos["screen_height"] * 0.7) ] pos_away = convert_abs_to_monitor((-167, -30)) - wait(0.8, 1.3) # takes quite a while for tp to be visible start = time.time() retry_count = 0 while (time.time() - start) < 8: @@ -252,16 +372,15 @@ def tp_town(self): self.pre_move() self.move(pos_m) if skills.has_tps(): - mouse.click(button="right") - consumables.increment_need("tp", 1) - wait(0.8, 1.3) # takes quite a while for tp to be visible - if (template_match := detect_screen_object(ScreenObjects.TownPortal)).valid: + self._cast_town_portal() + else: + return False + if (template_match := wait_until_visible(ScreenObjects.TownPortal, timeout=3)).valid: pos = template_match.center_monitor 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") + mouse.move(*pos, randomize=6, delay_factor=[0.4, 0.6]) + self._click_left() 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 @@ -275,30 +394,29 @@ def _pre_buff_cta(self): skill_before = cut_roi(grab(), Config().ui_roi["skill_right"]) # Try to switch weapons and select bo until we find the skill on the right skill slot start = time.time() - switch_sucess = False + switch_success = False while time.time() - start < 4: - keyboard.send(Config().char["weapon_switch"]) + self._weapon_switch() wait(0.3, 0.35) self._select_skill(skill = "battle_command", mouse_click_type="right", delay=(0.1, 0.2)) if skills.is_right_skill_selected(["BC", "BO"]): - switch_sucess = True + switch_success = True break - if not switch_sucess: + if not switch_success: Logger.warning("You dont have Battle Command bound, or you do not have CTA. ending CTA buff") Config().char["cta_available"] = 0 else: # We switched succesfully, let's pre buff - mouse.click(button="right") + self._cast_battle_command() wait(self._cast_duration + 0.16, self._cast_duration + 0.18) - self._select_skill(skill = "battle_orders", mouse_click_type="right", delay=(0.1, 0.2)) - mouse.click(button="right") + self._cast_battle_orders() wait(self._cast_duration + 0.16, self._cast_duration + 0.18) # Make sure the switch back to the original weapon is good start = time.time() while time.time() - start < 4: - keyboard.send(Config().char["weapon_switch"]) + self._weapon_switch() wait(0.3, 0.35) skill_after = cut_roi(grab(), Config().ui_roi["skill_right"]) _, max_val, _, _ = cv2.minMaxLoc(cv2.matchTemplate(skill_after, skill_before, cv2.TM_CCOEFF_NORMED)) @@ -328,25 +446,21 @@ def cast_in_arc(self, ability: str, cast_pos_abs: tuple[float, float] = [0,-100] target = self.vec_to_monitor(arc_spread(cast_pos_abs, spread_deg=spread_deg)) mouse.move(*target,delay_factor=[0.95, 1.05]) if hold: - mouse.press(button="right") + self._click_right() start = time.time() while (time.time() - start) < time_in_s: target = self.vec_to_monitor(arc_spread(cast_pos_abs, spread_deg=spread_deg)) if hold: - mouse.move(*target,delay_factor=[3, 8]) + mouse.move(*target, delay_factor=[3, 8]) if not hold: - mouse.move(*target,delay_factor=[.2, .4]) - wait(0.02, 0.04) - mouse.press(button="right") - wait(0.02, 0.06) - mouse.release(button="right") + mouse.move(*target, delay_factor=[.2, .4]) + self._click_right(0.04) wait(self._cast_duration, self._cast_duration) if hold: mouse.release(button="right") keyboard.send(Config().char["stand_still"], do_press=False) - def pre_buff(self): pass diff --git a/src/char/paladin/fohdin.py b/src/char/paladin/fohdin.py index 99fe0227e..47bc52019 100644 --- a/src/char/paladin/fohdin.py +++ b/src/char/paladin/fohdin.py @@ -1,18 +1,18 @@ -import random import keyboard -import time import numpy as np +import random +import time -from health_manager import get_panel_check_paused, set_panel_check_paused +from char.paladin import Paladin +from config import Config +from health_manager import set_panel_check_paused from inventory.personal import inspect_items +from logger import Logger +from pather import Location from screen import convert_abs_to_monitor, convert_screen_to_abs, grab, convert_abs_to_screen +from target_detect import get_visible_targets, log_targets from utils.custom_mouse import mouse -from char.paladin import Paladin -from logger import Logger -from config import Config from utils.misc import wait -from pather import Location -from target_detect import get_visible_targets, TargetInfo, log_targets class FoHdin(Paladin): def __init__(self, *args, **kwargs): @@ -22,20 +22,11 @@ def __init__(self, *args, **kwargs): def _cast_foh(self, cast_pos_abs: tuple[float, float], spray: int = 10, min_duration: float = 0, aura: str = "conviction"): - return self._cast_skill_with_aura(skill_name = "foh", cast_pos_abs = cast_pos_abs, spray = spray, min_duration = min_duration, aura = aura) + return self._cast_left_with_aura(skill_name = "foh", cast_pos_abs = cast_pos_abs, spray = spray, min_duration = min_duration, aura = aura) def _cast_holy_bolt(self, cast_pos_abs: tuple[float, float], spray: int = 10, min_duration: float = 0, aura: str = "concentration"): #if skill is bound : concentration, use concentration, otherwise move on with conviction. alternatively use redemption whilst holybolting. conviction does not help holy bolt (its magic damage) - return self._cast_skill_with_aura(skill_name = "holy_bolt", cast_pos_abs = cast_pos_abs, spray = spray, min_duration = min_duration, aura = aura) - - def _cast_hammers(self, min_duration: float = 0, aura: str = "concentration"): #for nihlathak - return self._cast_skill_with_aura(skill_name = "blessed_hammer", spray = 0, min_duration = min_duration, aura = aura) - - def _move_and_attack(self, abs_move: tuple[int, int], atk_len: float, aura: str = "concentration"): #for nihalthak - pos_m = convert_abs_to_monitor(abs_move) - self.pre_move() - self.move(pos_m, force_move=True) - self._cast_hammers(atk_len, aura=aura) + return self._cast_left_with_aura(skill_name = "holy_bolt", cast_pos_abs = cast_pos_abs, spray = spray, min_duration = min_duration, aura = aura) def _generic_foh_attack_sequence( self, diff --git a/src/char/paladin/hammerdin.py b/src/char/paladin/hammerdin.py index ce19cca8f..682b0addd 100644 --- a/src/char/paladin/hammerdin.py +++ b/src/char/paladin/hammerdin.py @@ -24,53 +24,6 @@ def __init__(self, *args, **kwargs): #hammerdin needs to be closer to shenk to reach it with hammers self._pather.offset_node(149, (70, 10)) - def _cast_hammers(self, time_in_s: float, aura: str = "concentration"): - if aura in self._skill_hotkeys and self._skill_hotkeys[aura]: - keyboard.send(self._skill_hotkeys[aura]) - wait(0.05, 0.1) - keyboard.send(Config().char["stand_still"], do_release=False) - wait(0.05, 0.1) - if self._skill_hotkeys["blessed_hammer"]: - keyboard.send(self._skill_hotkeys["blessed_hammer"]) - wait(0.05, 0.1) - start = time.time() - while (time.time() - start) < time_in_s: - wait(0.06, 0.08) - mouse.press(button="left") - wait(0.1, 0.2) - mouse.release(button="left") - wait(0.01, 0.05) - keyboard.send(Config().char["stand_still"], do_press=False) - - def pre_buff(self): - if Config().char["cta_available"]: - self._pre_buff_cta() - keyboard.send(self._skill_hotkeys["holy_shield"]) - wait(0.04, 0.1) - mouse.click(button="right") - wait(self._cast_duration, self._cast_duration + 0.06) - - def on_capabilities_discovered(self, capabilities: CharacterCapabilities): - # In case we have a running pala, we want to switch to concentration when moving to the boss - # ass most likely we will click on some mobs and already cast hammers - if capabilities.can_teleport_natively: - self._do_pre_move = False - - def pre_move(self): - # select teleport if available - super().pre_move() - # in case teleport hotkey is not set or teleport can not be used, use vigor if set - should_cast_vigor = self._skill_hotkeys["vigor"] and not skills.is_right_skill_selected(["VIGOR"]) - can_teleport = self.capabilities.can_teleport_natively and skills.is_right_skill_active() - if should_cast_vigor and not can_teleport: - keyboard.send(self._skill_hotkeys["vigor"]) - wait(0.15, 0.25) - - def _move_and_attack(self, abs_move: tuple[int, int], atk_len: float): - pos_m = convert_abs_to_monitor(abs_move) - self.pre_move() - self.move(pos_m, force_move=True) - self._cast_hammers(atk_len) def kill_pindle(self) -> bool: wait(0.1, 0.15) diff --git a/src/char/paladin/paladin.py b/src/char/paladin/paladin.py index 85a86450e..f453027a9 100644 --- a/src/char/paladin/paladin.py +++ b/src/char/paladin/paladin.py @@ -15,7 +15,7 @@ class Paladin(IChar): def __init__(self, skill_hotkeys: dict, pather: Pather, pickit: PickIt): - Logger.info("Setting up Paladin") + # Logger.info("Setting up Paladin") super().__init__(skill_hotkeys) self._pather = pather self._do_pre_move = True @@ -23,11 +23,8 @@ def __init__(self, skill_hotkeys: dict, pather: Pather, pickit: PickIt): self._picked_up_items = False #for Diablo def pre_buff(self): - if Config().char["cta_available"]: - self._pre_buff_cta() - keyboard.send(self._skill_hotkeys["holy_shield"]) - wait(0.04, 0.1) - mouse.click(button="right") + self._pre_buff_cta() + self._cast_holy_shield() wait(self._cast_duration, self._cast_duration + 0.06) def on_capabilities_discovered(self, capabilities: CharacterCapabilities): @@ -43,59 +40,16 @@ def pre_move(self): should_cast_vigor = self._skill_hotkeys["vigor"] and not skills.is_right_skill_selected(["VIGOR"]) can_teleport = self.capabilities.can_teleport_natively and skills.is_right_skill_active() if should_cast_vigor and not can_teleport: - keyboard.send(self._skill_hotkeys["vigor"]) - wait(0.15, 0.25) + self._select_skill("vigor", delay=None) - def _log_cast(self, skill_name: str, cast_pos_abs: tuple[float, float], spray: int, min_duration: float, aura: str): - msg = f"Casting skill {skill_name}" - if cast_pos_abs: - msg += f" at screen coordinate {convert_abs_to_screen(cast_pos_abs)}" - if spray: - msg += f" with spray of {spray}" - if min_duration: - msg += f" for {round(min_duration, 1)}s" - if aura: - msg += f" with {aura} active" - Logger.debug(msg) + def _move_and_attack(self, abs_move: tuple[int, int], atk_len: float): + pos_m = convert_abs_to_monitor(abs_move) + self.pre_move() + self.move(pos_m, force_move=True) + self._cast_hammers(atk_len) - def _click_cast(self, cast_pos_abs: tuple[float, float], spray: int, mouse_click_type: str = "left"): - if cast_pos_abs: - x = cast_pos_abs[0] - y = cast_pos_abs[1] - if spray: - x += (random.random() * 2 * spray - spray) - y += (random.random() * 2 * spray - spray) - pos_m = convert_abs_to_monitor((x, y)) - mouse.move(*pos_m, delay_factor=[0.1, 0.2]) - wait(0.06, 0.08) - mouse.press(button = mouse_click_type) - wait(0.06, 0.08) - mouse.release(button = mouse_click_type) - - def _cast_skill_with_aura(self, skill_name: str, cast_pos_abs: tuple[float, float] = None, spray: int = 0, min_duration: float = 0, aura: str = ""): - #self._log_cast(skill_name, cast_pos_abs, spray, min_duration, aura) - - # set aura if needed - if aura: - self._select_skill(aura, mouse_click_type = "right") - - # ensure character stands still - keyboard.send(Config().char["stand_still"], do_release=False) - - # set left hand skill - self._select_skill(skill_name, mouse_click_type = "left") - wait(0.05, 0.1) - - # cast left hand skill - start = time.time() - if min_duration: - while (time.time() - start) <= min_duration: - self._click_cast(cast_pos_abs, spray) - else: - self._click_cast(cast_pos_abs, spray) - - # release stand still key - keyboard.send(Config().char["stand_still"], do_press=False) + def _activate_concentration_aura(self, delay=None): + self._select_skill("concentration", delay=delay) def _activate_redemption_aura(self, delay = [0.6, 0.8]): self._select_skill("redemption", delay=delay) @@ -105,4 +59,10 @@ def _activate_cleanse_aura(self, delay = [0.3, 0.4]): def _activate_cleanse_redemption(self): self._activate_cleanse_aura() - self._activate_redemption_aura() \ No newline at end of file + self._activate_redemption_aura() + + def _cast_holy_shield(self): + self._cast_simple(skill_name="holy_shield", mouse_click_type="right") + + def _cast_hammers(self, min_duration: float = 0, aura: str = "concentration"): #for nihlathak + return self._cast_left_with_aura(skill_name = "blessed_hammer", spray = 0, min_duration = min_duration, aura = aura) \ No newline at end of file diff --git a/src/ui/skills.py b/src/ui/skills.py index c307d7535..7e148246c 100644 --- a/src/ui/skills.py +++ b/src/ui/skills.py @@ -30,8 +30,7 @@ def has_tps() -> bool: if Config().general["info_screenshots"]: cv2.imwrite("./info_screenshots/debug_out_of_tps_" + time.strftime("%Y%m%d_%H%M%S") + ".png", grab()) return tps_remain - else: - return False + return False def select_tp(tp_hotkey): if tp_hotkey and not is_right_skill_selected( From 5656c2a31e717cb922e57d9b28125847126fce35 Mon Sep 17 00:00:00 2001 From: mgleed Date: Sun, 12 Jun 2022 22:45:32 -0400 Subject: [PATCH 02/44] through paladins --- src/char/paladin/fohdin.py | 23 ++++++++++++----------- src/char/paladin/hammerdin.py | 27 ++++++++++++++------------- src/char/paladin/paladin.py | 27 +++++++-------------------- 3 files changed, 33 insertions(+), 44 deletions(-) diff --git a/src/char/paladin/fohdin.py b/src/char/paladin/fohdin.py index 47bc52019..8e4294be8 100644 --- a/src/char/paladin/fohdin.py +++ b/src/char/paladin/fohdin.py @@ -1,6 +1,3 @@ -import keyboard -import numpy as np -import random import time from char.paladin import Paladin @@ -9,9 +6,8 @@ from inventory.personal import inspect_items from logger import Logger from pather import Location -from screen import convert_abs_to_monitor, convert_screen_to_abs, grab, convert_abs_to_screen +from screen import convert_abs_to_monitor, convert_screen_to_abs, grab from target_detect import get_visible_targets, log_targets -from utils.custom_mouse import mouse from utils.misc import wait class FoHdin(Paladin): @@ -20,6 +16,14 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._pather.adapt_path((Location.A3_TRAV_START, Location.A3_TRAV_CENTER_STAIRS), [220, 221, 222, 903, 904, 905, 906]) + def _move_and_attack(self, abs_move: tuple[int, int], atk_len: float): + pos_m = convert_abs_to_monitor(abs_move) + self.pre_move() + self.move(pos_m, force_move=True) + self._cast_hammers(atk_len) + + def _cast_hammers(self, min_duration: float = 0, aura: str = "concentration"): #for nihlathak + return self._cast_left_with_aura(skill_name = "blessed_hammer", spray = 0, min_duration = min_duration, aura = aura) def _cast_foh(self, cast_pos_abs: tuple[float, float], spray: int = 10, min_duration: float = 0, aura: str = "conviction"): return self._cast_left_with_aura(skill_name = "foh", cast_pos_abs = cast_pos_abs, spray = spray, min_duration = min_duration, aura = aura) @@ -99,8 +103,7 @@ def kill_pindle(self) -> bool: return False else: if not self._do_pre_move: - keyboard.send(self._skill_hotkeys["conviction"]) - wait(0.05, 0.15) + self._activate_conviction_aura() self._pather.traverse_nodes([103], self, timeout=1.0, do_pre_move=self._do_pre_move) cast_pos_abs = [pindle_pos_abs[0] * 0.9, pindle_pos_abs[1] * 0.9] @@ -110,8 +113,7 @@ def kill_pindle(self) -> bool: self._pather.traverse_nodes_fixed("pindle_end", self) else: if not self._do_pre_move: - keyboard.send(self._skill_hotkeys["redemption"]) - wait(0.05, 0.15) + self._activate_redemption_aura() self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, timeout=1.0, do_pre_move=self._do_pre_move) # Use target-based attack sequence one more time before pickit @@ -164,8 +166,7 @@ def kill_shenk(self): # traverse to shenk if not self._do_pre_move: - keyboard.send(self._skill_hotkeys["conviction"]) - wait(0.05, 0.15) + self._activate_conviction_aura() 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) diff --git a/src/char/paladin/hammerdin.py b/src/char/paladin/hammerdin.py index 682b0addd..2d077911a 100644 --- a/src/char/paladin/hammerdin.py +++ b/src/char/paladin/hammerdin.py @@ -1,21 +1,14 @@ import keyboard -from screen import convert_abs_to_monitor, convert_screen_to_abs, grab -from utils.custom_mouse import mouse + from char.paladin import Paladin -from pather import Pather -from logger import Logger from config import Config -from utils.misc import wait -import time -from pather import Location -import random - -from ui import skills -from char import IChar, CharacterCapabilities +from logger import Logger +from pather import Pather from pather import Pather, Location -from item.pickit import PickIt #for Diablo -import numpy as np +from screen import convert_abs_to_monitor, convert_screen_to_abs, grab from target_detect import get_visible_targets +from utils.custom_mouse import mouse +from utils.misc import wait class Hammerdin(Paladin): def __init__(self, *args, **kwargs): @@ -24,6 +17,14 @@ def __init__(self, *args, **kwargs): #hammerdin needs to be closer to shenk to reach it with hammers self._pather.offset_node(149, (70, 10)) + def _cast_hammers(self, min_duration: float = 0, aura: str = "concentration"): #for nihlathak + return self._cast_left_with_aura(skill_name = "blessed_hammer", spray = 0, min_duration = min_duration, aura = aura) + + def _move_and_attack(self, abs_move: tuple[int, int], atk_len: float): + pos_m = convert_abs_to_monitor(abs_move) + self.pre_move() + self.move(pos_m, force_move=True) + self._cast_hammers(atk_len) def kill_pindle(self) -> bool: wait(0.1, 0.15) diff --git a/src/char/paladin/paladin.py b/src/char/paladin/paladin.py index f453027a9..953f9a096 100644 --- a/src/char/paladin/paladin.py +++ b/src/char/paladin/paladin.py @@ -1,17 +1,10 @@ -import keyboard -from ui import skills -import time -import random -from utils.custom_mouse import mouse from char import IChar, CharacterCapabilities +from item.pickit import PickIt #for Diablo from pather import Pather -from logger import Logger -from config import Config -from utils.misc import wait -from screen import convert_abs_to_screen, convert_abs_to_monitor from pather import Pather -#import cv2 #for Diablo -from item.pickit import PickIt #for Diablo +from screen import convert_abs_to_monitor +from ui import skills +from utils.misc import wait class Paladin(IChar): def __init__(self, skill_hotkeys: dict, pather: Pather, pickit: PickIt): @@ -42,12 +35,6 @@ def pre_move(self): if should_cast_vigor and not can_teleport: self._select_skill("vigor", delay=None) - def _move_and_attack(self, abs_move: tuple[int, int], atk_len: float): - pos_m = convert_abs_to_monitor(abs_move) - self.pre_move() - self.move(pos_m, force_move=True) - self._cast_hammers(atk_len) - def _activate_concentration_aura(self, delay=None): self._select_skill("concentration", delay=delay) @@ -57,12 +44,12 @@ def _activate_redemption_aura(self, delay = [0.6, 0.8]): def _activate_cleanse_aura(self, delay = [0.3, 0.4]): self._select_skill("cleansing", delay=delay) + def _activate_conviction_aura(self, delay = None): + self._select_skill("conviction", delay=delay) + def _activate_cleanse_redemption(self): self._activate_cleanse_aura() self._activate_redemption_aura() def _cast_holy_shield(self): self._cast_simple(skill_name="holy_shield", mouse_click_type="right") - - def _cast_hammers(self, min_duration: float = 0, aura: str = "concentration"): #for nihlathak - return self._cast_left_with_aura(skill_name = "blessed_hammer", spray = 0, min_duration = min_duration, aura = aura) \ No newline at end of file From 0d0d9b7d8a95d1afc93232b75c4c0a97412fde83 Mon Sep 17 00:00:00 2001 From: mgleed Date: Sun, 12 Jun 2022 23:01:26 -0400 Subject: [PATCH 03/44] fix hold click --- src/char/i_char.py | 74 ++++++++++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 29 deletions(-) diff --git a/src/char/i_char.py b/src/char/i_char.py index 7aa10fa1f..088b298b7 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -1,25 +1,23 @@ -from turtle import Screen -from inventory import consumables -from typing import Callable import cv2 import keyboard import math import numpy as np import random import time +from typing import Callable from char.capabilities import CharacterCapabilities from config import Config from logger import Logger +from inventory import consumables from ocr import Ocr from screen import grab, convert_monitor_to_screen, convert_screen_to_abs, convert_abs_to_monitor, convert_screen_to_monitor, convert_abs_to_screen +import template_finder from ui import skills from ui_manager import detect_screen_object, ScreenObjects from ui_manager import is_visible, wait_until_visible from utils.custom_mouse import mouse from utils.misc import wait, cut_roi, is_in_roi, color_filter, arc_spread -import template_finder - class IChar: @@ -33,25 +31,19 @@ def __init__(self, skill_hotkeys: dict): # Add a bit to be on the save side self._cast_duration = Config().char["casting_frames"] * 0.04 + 0.01 self._last_tp = time.time() + self._mouse_click_held = { + "left": False, + "right": False + } self._ocr = Ocr() self._skill_hotkeys = skill_hotkeys self._standing_still = False self.capabilities = None self.damage_scaling = float(Config().char.get("damage_scaling", 1.0)) - def _log_cast(self, skill_name: str, cast_pos_abs: tuple[float, float], spray: int, min_duration: float, aura: str): - msg = f"Casting skill {skill_name}" - if cast_pos_abs: - msg += f" at screen coordinate {convert_abs_to_screen(cast_pos_abs)}" - if spray: - msg += f" with spray of {spray}" - if min_duration: - msg += f" for {round(min_duration, 1)}s" - if aura: - msg += f" with {aura} active" - Logger.debug(msg) - def _click(self, mouse_click_type: str = "left", wait_before_release: float = 0.0): + @staticmethod + def _click(mouse_click_type: str = "left", wait_before_release: float = 0.0): """ Sends a click to the mouse. """ @@ -62,6 +54,16 @@ def _click(self, mouse_click_type: str = "left", wait_before_release: float = 0. wait(wait_before_release) mouse.release(button = mouse_click_type) + def _hold_click(self, mouse_click_type: str = "left", enable: bool = True): + if enable: + if not self._mouse_click_held[mouse_click_type]: + self._mouse_click_held[mouse_click_type] = True + mouse.press(button = mouse_click_type) + else: + if self._mouse_click_held[mouse_click_type]: + self._mouse_click_held[mouse_click_type] = False + mouse.release(button = mouse_click_type) + def _click_left(self, wait_before_release: float = 0.0): self._click("left", wait_before_release = wait_before_release) @@ -108,7 +110,7 @@ def _cast_left_with_aura(self, skill_name: str, cast_pos_abs: tuple[float, float if aura: self._select_skill(aura, mouse_click_type = "right") - keyboard.send(Config().char["stand_still"], do_release=False) + self._stand_still(True) # set left hand skill self._select_skill(skill_name, mouse_click_type = "left") @@ -122,7 +124,20 @@ def _cast_left_with_aura(self, skill_name: str, cast_pos_abs: tuple[float, float else: self._cast_at_position(cast_pos_abs, spray) - keyboard.send(Config().char["stand_still"], do_press=False) + self._stand_still(False) + + @staticmethod + def _log_cast(skill_name: str, cast_pos_abs: tuple[float, float], spray: int, min_duration: float, aura: str): + msg = f"Casting skill {skill_name}" + if cast_pos_abs: + msg += f" at screen coordinate {convert_abs_to_screen(cast_pos_abs)}" + if spray: + msg += f" with spray of {spray}" + if min_duration: + msg += f" for {round(min_duration, 1)}s" + if aura: + msg += f" with {aura} active" + Logger.debug(msg) def _set_active_skill(self, mouse_click_type: str = "left", skill: str =""): """ @@ -175,12 +190,13 @@ def _weapon_switch(): def _stand_still(self, enable: bool): if enable: - self._stand_still_enabled = True - keyboard.send(Config().char["stand_still"], do_release=False) + if not self._stand_still_enabled: + keyboard.send(Config().char["stand_still"], do_release=False) + self._stand_still_enabled = True else: - self._stand_still_enabled = False - keyboard.send(Config().char["stand_still"], do_press=False) - + if self._stand_still_enabled: + keyboard.send(Config().char["stand_still"], do_press=False) + self._stand_still_enabled = False def _discover_capabilities(self) -> CharacterCapabilities: override = Config().advanced_options["override_capabilities"] @@ -215,7 +231,7 @@ def pick_up_item(self, pos: tuple[float, float], item_name: str = None, prev_cas mouse.move(pos[0], pos[1]) time.sleep(0.1) self._click_left() - wait(0.45, 0.5) + wait(0.2, 0.3) return prev_cast_start def select_by_template( @@ -440,13 +456,13 @@ def cast_in_arc(self, ability: str, cast_pos_abs: tuple[float, float] = [0,-100] Logger.debug(f'Casting {ability} for {time_in_s:.02f}s at {cast_pos_abs} with {spread_deg}°') if not self._skill_hotkeys[ability]: raise ValueError(f"You did not set {ability} hotkey!") - keyboard.send(Config().char["stand_still"], do_release=False) + self._stand_still(True) self._select_skill(skill = ability, mouse_click_type="right", delay=(0.02, 0.08)) target = self.vec_to_monitor(arc_spread(cast_pos_abs, spread_deg=spread_deg)) mouse.move(*target,delay_factor=[0.95, 1.05]) if hold: - self._click_right() + self._hold_click("right", True) start = time.time() while (time.time() - start) < time_in_s: target = self.vec_to_monitor(arc_spread(cast_pos_abs, spread_deg=spread_deg)) @@ -458,8 +474,8 @@ def cast_in_arc(self, ability: str, cast_pos_abs: tuple[float, float] = [0,-100] wait(self._cast_duration, self._cast_duration) if hold: - mouse.release(button="right") - keyboard.send(Config().char["stand_still"], do_press=False) + self._hold_click("right", False) + self._stand_still(False) def pre_buff(self): pass From d0bef3d8c56460f8b94568e596d9b534349af757 Mon Sep 17 00:00:00 2001 From: mgleed Date: Mon, 13 Jun 2022 10:49:41 -0400 Subject: [PATCH 04/44] move select_teleport() to char from ui.skills and rename --- src/bot.py | 4 ++-- src/char/i_char.py | 28 +++++++++++++++++++--------- src/pather.py | 2 +- src/ui/skills.py | 7 ------- 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/bot.py b/src/bot.py index 4cc5dadd9..deee19fa3 100644 --- a/src/bot.py +++ b/src/bot.py @@ -268,7 +268,7 @@ def on_start_from_town(self): wait_until_hidden(ScreenObjects.Corpse) belt.fill_up_belt_from_inventory(Config().char["num_loot_columns"]) self._char.discover_capabilities() - if corpse_present 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_teleport(): 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"]) @@ -383,7 +383,7 @@ def on_maintenance(self): # Check if we are out of tps or need repairing 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()) + need_refill_teleport = self._char.capabilities.can_teleport_with_charges and (not self._char.select_teleport() or self._char.is_low_on_teleport_charges()) if need_repair or need_routine_repair or need_refill_teleport or sell_items: if need_repair: Logger.info("Repair needed. Gear is about to break") diff --git a/src/char/i_char.py b/src/char/i_char.py index 088b298b7..e4c452ec8 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -145,16 +145,21 @@ def _set_active_skill(self, mouse_click_type: str = "left", skill: str =""): """ self._active_skill[mouse_click_type] = skill - def _select_skill(self, skill: str, mouse_click_type: str = "left", delay: float | list | tuple = None) -> bool: - """ - Sets the active skill on left or right. - Will only set skill if not already selected - """ + def _check_hotkey(self, skill: str): if not ( skill in self._skill_hotkeys and (hotkey := self._skill_hotkeys[skill]) or (skill in Config().char and (hotkey := Config().char[skill])) ): Logger.warning(f"No hotkey for skill: {skill}") + return False + return hotkey + + def _select_skill(self, skill: str, mouse_click_type: str = "left", delay: float | list | tuple = None) -> bool: + """ + Sets the active skill on left or right. + Will only set skill if not already selected + """ + if not (hotkey := self._check_hotkey(skill)): self._set_active_skill(mouse_click_type, "") return False @@ -202,7 +207,7 @@ def _discover_capabilities(self) -> CharacterCapabilities: override = Config().advanced_options["override_capabilities"] if override is None: if self._skill_hotkeys["teleport"]: - if self.select_tp(): + if self.select_teleport(): if self.skill_is_charged(): return CharacterCapabilities(can_teleport_natively=False, can_teleport_with_charges=True) else: @@ -309,13 +314,18 @@ def _remap_skill_hotkey(self, skill_asset, hotkey, skill_roi, expanded_skill_roi def remap_right_skill_hotkey(self, skill_asset, hotkey): return self._remap_skill_hotkey(skill_asset, hotkey, Config().ui_roi["skill_right"], Config().ui_roi["skill_right_expanded"]) - def select_tp(self): - return skills.select_tp(self._skill_hotkeys["teleport"]) + def select_teleport(self): + if not self._select_skill("teleport", "right", delay = [0.1, 0.2]): + return False + return skills.is_right_skill_selected(["TELE_ACTIVE", "TELE_INACTIVE"]) + + def can_teleport(self): + return self.select_teleport() def pre_move(self): # if teleport hotkey is set and if teleport is not already selected if self.capabilities.can_teleport_natively: - self.select_tp() + self.select_teleport() self._set_active_skill("right", "teleport") def move(self, pos_monitor: tuple[float, float], force_tp: bool = False, force_move: bool = False): diff --git a/src/pather.py b/src/pather.py index 96c6cc1c5..55ca95d14 100644 --- a/src/pather.py +++ b/src/pather.py @@ -625,7 +625,7 @@ def traverse_nodes( else: Logger.debug(f"Traverse: {path}") - if use_tp_charge and char.select_tp(): + if use_tp_charge and char.select_teleport(): # this means we want to use tele charge and we were able to select it pass elif do_pre_move: diff --git a/src/ui/skills.py b/src/ui/skills.py index 7e148246c..ebc36128a 100644 --- a/src/ui/skills.py +++ b/src/ui/skills.py @@ -32,13 +32,6 @@ def has_tps() -> bool: return tps_remain return False -def select_tp(tp_hotkey): - if tp_hotkey and not is_right_skill_selected( - ["TELE_ACTIVE", "TELE_INACTIVE"]): - keyboard.send(tp_hotkey) - wait(0.1, 0.2) - return is_right_skill_selected(["TELE_ACTIVE", "TELE_INACTIVE"]) - def is_right_skill_active() -> bool: """ :return: Bool if skill is red/available or not. Skill must be selected on right skill slot when calling the function. From aeb437b1b7f0e28d9353d6277f044b61bf56e5e6 Mon Sep 17 00:00:00 2001 From: mgleed Date: Mon, 13 Jun 2022 11:08:25 -0400 Subject: [PATCH 05/44] cleanup --- src/char/barbarian.py | 3 +-- src/char/i_char.py | 26 ++++++++++++++------------ src/char/paladin/paladin.py | 3 +-- src/utils/misc.py | 2 +- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/char/barbarian.py b/src/char/barbarian.py index 50f3f0332..4520ad808 100644 --- a/src/char/barbarian.py +++ b/src/char/barbarian.py @@ -74,8 +74,7 @@ def pre_move(self): super().pre_move() # in case teleport hotkey is not set or teleport can not be used, use leap if set should_cast_leap = self._skill_hotkeys["leap"] and not skills.is_left_skill_selected(["LEAP"]) - can_teleport = self.capabilities.can_teleport_natively and skills.is_right_skill_active() - if should_cast_leap and not can_teleport: + if should_cast_leap and not self.can_teleport(): keyboard.send(self._skill_hotkeys["leap"]) wait(0.15, 0.25) diff --git a/src/char/i_char.py b/src/char/i_char.py index e4c452ec8..3c07aa21b 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -145,13 +145,13 @@ def _set_active_skill(self, mouse_click_type: str = "left", skill: str =""): """ self._active_skill[mouse_click_type] = skill - def _check_hotkey(self, skill: str): + def _check_hotkey(self, skill: str) -> str | None: if not ( skill in self._skill_hotkeys and (hotkey := self._skill_hotkeys[skill]) or (skill in Config().char and (hotkey := Config().char[skill])) ): Logger.warning(f"No hotkey for skill: {skill}") - return False + return None return hotkey def _select_skill(self, skill: str, mouse_click_type: str = "left", delay: float | list | tuple = None) -> bool: @@ -232,7 +232,7 @@ def discover_capabilities(self, force = False): def on_capabilities_discovered(self, capabilities: CharacterCapabilities): pass - def pick_up_item(self, pos: tuple[float, float], item_name: str = None, prev_cast_start: float = 0): + def pick_up_item(self, pos: tuple[float, float], item_name: str = None, prev_cast_start: float = 0) -> float: mouse.move(pos[0], pos[1]) time.sleep(0.1) self._click_left() @@ -284,7 +284,7 @@ def skill_is_charged(self, img: np.ndarray = None) -> bool: return True return False - def is_low_on_teleport_charges(self): + def is_low_on_teleport_charges(self) -> bool: img = grab() charges_remaining = skills.get_skill_charges(self._ocr, img) if charges_remaining: @@ -296,7 +296,7 @@ def is_low_on_teleport_charges(self): Logger.error("is_low_on_teleport_charges: unable to determine skill charges, assume zero") return True - def _remap_skill_hotkey(self, skill_asset, hotkey, skill_roi, expanded_skill_roi): + def _remap_skill_hotkey(self, skill_asset, hotkey, skill_roi, expanded_skill_roi) -> bool: x, y, w, h = skill_roi x, y = convert_screen_to_monitor((x, y)) mouse.move(x + w/2, y + h / 2) @@ -310,17 +310,19 @@ def _remap_skill_hotkey(self, skill_asset, hotkey, skill_roi, expanded_skill_roi wait(0.3) self._click_left() wait(0.3) + return True + return False - def remap_right_skill_hotkey(self, skill_asset, hotkey): + def remap_right_skill_hotkey(self, skill_asset, hotkey) -> bool: return self._remap_skill_hotkey(skill_asset, hotkey, Config().ui_roi["skill_right"], Config().ui_roi["skill_right_expanded"]) - def select_teleport(self): + def select_teleport(self) -> bool: if not self._select_skill("teleport", "right", delay = [0.1, 0.2]): return False return skills.is_right_skill_selected(["TELE_ACTIVE", "TELE_INACTIVE"]) - def can_teleport(self): - return self.select_teleport() + def can_teleport(self) -> bool: + return (self.capabilities.can_teleport_natively or self.capabilities.can_teleport_with_charges) and self.select_teleport() def pre_move(self): # if teleport hotkey is set and if teleport is not already selected @@ -375,7 +377,7 @@ def walk(self, pos_monitor: tuple[float, float], force_tp: bool = False, force_m else: self._click_left() - def tp_town(self): + def tp_town(self) -> bool: # will check if tp is available and select the skill if not skills.has_tps(): return False @@ -453,11 +455,11 @@ def _pre_buff_cta(self): wait(0.5) - def vec_to_monitor(self, target): + def vec_to_monitor(self, target: tuple[float, float]) -> tuple[float, float]: circle_pos_screen = self._pather._adjust_abs_range_to_screen(target) return convert_abs_to_monitor(circle_pos_screen) - def _lerp(self,a: float,b: float, f:float): + def _lerp(self, a: float, b: float, f:float) -> float: return a + f * (b - a) def cast_in_arc(self, ability: str, cast_pos_abs: tuple[float, float] = [0,-100], time_in_s: float = 3, spread_deg: float = 10, hold=True): diff --git a/src/char/paladin/paladin.py b/src/char/paladin/paladin.py index 953f9a096..ce9a6cdf8 100644 --- a/src/char/paladin/paladin.py +++ b/src/char/paladin/paladin.py @@ -31,8 +31,7 @@ def pre_move(self): super().pre_move() # in case teleport hotkey is not set or teleport can not be used, use vigor if set should_cast_vigor = self._skill_hotkeys["vigor"] and not skills.is_right_skill_selected(["VIGOR"]) - can_teleport = self.capabilities.can_teleport_natively and skills.is_right_skill_active() - if should_cast_vigor and not can_teleport: + if should_cast_vigor and not self.can_teleport(): self._select_skill("vigor", delay=None) def _activate_concentration_aura(self, delay=None): diff --git a/src/utils/misc.py b/src/utils/misc.py index 5e627cfab..7dd8d0a28 100644 --- a/src/utils/misc.py +++ b/src/utils/misc.py @@ -221,7 +221,7 @@ def image_is_equal(img1: np.ndarray, img2: np.ndarray) -> bool: return False return not(np.bitwise_xor(img1, img2).any()) -def arc_spread(cast_dir: tuple[float,float], spread_deg: float=10, radius_spread: tuple[float, float] = [.95, 1.05]): +def arc_spread(cast_dir: tuple[float,float], spread_deg: float=10, radius_spread: tuple[float, float] = [.95, 1.05]) -> np.ndarray: """ Given an x,y vec (target), generate a new target that is the same vector but rotated by +/- spread_deg/2 """ From 69a25eb1ddc05815d7b6da3386ba04801586b85d Mon Sep 17 00:00:00 2001 From: mgleed Date: Mon, 13 Jun 2022 12:20:59 -0400 Subject: [PATCH 06/44] rework teleport / move / pather --- src/char/barbarian.py | 1 - src/char/i_char.py | 31 +++++++++++-------------------- src/char/paladin/fohdin.py | 8 ++++---- src/char/paladin/hammerdin.py | 10 +++++----- src/char/paladin/paladin.py | 6 ------ src/char/poison_necro.py | 6 +++--- src/char/sorceress/hydra_sorc.py | 6 +++--- src/char/sorceress/light_sorc.py | 2 +- src/pather.py | 14 ++++++-------- src/run/trav.py | 4 ++-- 10 files changed, 35 insertions(+), 53 deletions(-) diff --git a/src/char/barbarian.py b/src/char/barbarian.py index 4520ad808..0cd2a46a5 100644 --- a/src/char/barbarian.py +++ b/src/char/barbarian.py @@ -70,7 +70,6 @@ def pre_buff(self): wait(self._cast_duration + 0.08, self._cast_duration + 0.1) def pre_move(self): - # select teleport if available super().pre_move() # in case teleport hotkey is not set or teleport can not be used, use leap if set should_cast_leap = self._skill_hotkeys["leap"] and not skills.is_left_skill_selected(["LEAP"]) diff --git a/src/char/i_char.py b/src/char/i_char.py index 3c07aa21b..4590de6d4 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -166,14 +166,14 @@ def _select_skill(self, skill: str, mouse_click_type: str = "left", delay: float if self._active_skill[mouse_click_type] != skill: keyboard.send(hotkey) self._set_active_skill(mouse_click_type, skill) - if delay: - try: - wait(*delay) - except: + if delay: try: - wait(delay) - except Exception as e: - Logger.warning(f"_select_skill: Failed to delay with delay: {delay}. Exception: {e}") + wait(*delay) + except: + try: + wait(delay) + except Exception as e: + Logger.warning(f"_select_skill: Failed to delay with delay: {delay}. Exception: {e}") return True def _cast_teleport(self): @@ -325,20 +325,11 @@ def can_teleport(self) -> bool: return (self.capabilities.can_teleport_natively or self.capabilities.can_teleport_with_charges) and self.select_teleport() def pre_move(self): - # if teleport hotkey is set and if teleport is not already selected - if self.capabilities.can_teleport_natively: - self.select_teleport() - self._set_active_skill("right", "teleport") + pass - def move(self, pos_monitor: tuple[float, float], force_tp: bool = False, force_move: bool = False): + def move(self, pos_monitor: tuple[float, float], use_tp: bool = False, force_move: bool = False): factor = Config().advanced_options["pathing_delay_factor"] - if "teleport" in self._skill_hotkeys and self._skill_hotkeys["teleport"] and ( - force_tp - or ( - skills.is_right_skill_selected(["TELE_ACTIVE"]) - and skills.is_right_skill_active() - ) - ): + if use_tp and self.can_teleport(): # can_teleport() activates teleport hotkey if True mouse.move(pos_monitor[0], pos_monitor[1], randomize=3, delay_factor=[factor*0.1, factor*0.14]) self._cast_simple(skill_name="teleport", mouse_click_type="right") wait(self._cast_duration, self._cast_duration + 0.02) @@ -359,7 +350,7 @@ def move(self, pos_monitor: tuple[float, float], force_tp: bool = False, force_m else: self._click_left() - def walk(self, pos_monitor: tuple[float, float], force_tp: bool = False, force_move: bool = False): + def walk(self, pos_monitor: tuple[float, float], force_move: bool = False): factor = Config().advanced_options["pathing_delay_factor"] # in case we want to walk we actually want to move a bit before the point cause d2r will always "overwalk" pos_screen = convert_monitor_to_screen(pos_monitor) diff --git a/src/char/paladin/fohdin.py b/src/char/paladin/fohdin.py index 8e4294be8..16a28e6d4 100644 --- a/src/char/paladin/fohdin.py +++ b/src/char/paladin/fohdin.py @@ -92,14 +92,14 @@ def kill_pindle(self) -> bool: if self.capabilities.can_teleport_natively or self.capabilities.can_teleport_with_charges: # Slightly retreating, so the Merc gets charged - if not self._pather.traverse_nodes([102], self, timeout=1.0, do_pre_move=self._do_pre_move, force_move=True,force_tp=False, use_tp_charge=False): + if not self._pather.traverse_nodes([102], self, timeout=1.0, do_pre_move=self._do_pre_move, force_move=True, force_tp=False): return False # Doing one Teleport to safe_dist to grab our Merc Logger.debug("Teleporting backwards to let Pindle charge the MERC. Looks strange, but is intended!") #I would leave this message in, so users dont complain that there is a strange movement pattern. - if not self._pather.traverse_nodes([103], self, timeout=1.0, do_pre_move=self._do_pre_move, force_tp=True, use_tp_charge=True): + if not self._pather.traverse_nodes([103], self, timeout=1.0, do_pre_move=self._do_pre_move, force_tp=True): return False # Slightly retreating, so the Merc gets charged - if not self._pather.traverse_nodes([103], self, timeout=1.0, do_pre_move=self._do_pre_move, force_move=True, force_tp=False, use_tp_charge=False): + if not self._pather.traverse_nodes([103], self, timeout=1.0, do_pre_move=self._do_pre_move, force_move=True, force_tp=False): return False else: if not self._do_pre_move: @@ -167,7 +167,7 @@ def kill_shenk(self): # traverse to shenk if not self._do_pre_move: self._activate_conviction_aura() - 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) + 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) wait(0.05, 0.1) # bypass mob detect first diff --git a/src/char/paladin/hammerdin.py b/src/char/paladin/hammerdin.py index 2d077911a..04c172397 100644 --- a/src/char/paladin/hammerdin.py +++ b/src/char/paladin/hammerdin.py @@ -29,7 +29,7 @@ def _move_and_attack(self, abs_move: tuple[int, int], atk_len: float): def kill_pindle(self) -> bool: wait(0.1, 0.15) if self.capabilities.can_teleport_with_charges: - if not self._pather.traverse_nodes([104], self, timeout=1.0, force_tp=True, use_tp_charge=True): + if not self._pather.traverse_nodes([104], self, timeout=1.0, force_tp=True): return False elif self.capabilities.can_teleport_natively: if not self._pather.traverse_nodes_fixed("pindle_end", self): @@ -52,7 +52,7 @@ def kill_eldritch(self) -> bool: 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, timeout=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) wait(0.05, 0.1) self._cast_hammers(Config().char["atk_len_eldritch"]) wait(0.1, 0.15) @@ -63,7 +63,7 @@ def kill_shenk(self): 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, timeout=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) wait(0.05, 0.1) self._cast_hammers(Config().char["atk_len_shenk"]) wait(0.1, 0.15) @@ -77,14 +77,14 @@ def kill_council(self) -> bool: # 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, timeout=2.5, force_tp=True, use_tp_charge=True) + self._pather.traverse_nodes([228, 229], self, timeout=2.5, force_tp=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, timeout=2.5, force_tp=True, use_tp_charge=True) + self._pather.traverse_nodes([226], self, timeout=2.5, force_tp=True) self._cast_hammers(atk_len) # move a bit to the top self._move_and_attack((65, -30), atk_len) diff --git a/src/char/paladin/paladin.py b/src/char/paladin/paladin.py index ce9a6cdf8..63b7b3010 100644 --- a/src/char/paladin/paladin.py +++ b/src/char/paladin/paladin.py @@ -20,12 +20,6 @@ def pre_buff(self): self._cast_holy_shield() wait(self._cast_duration, self._cast_duration + 0.06) - def on_capabilities_discovered(self, capabilities: CharacterCapabilities): - # In case we have a running pala, we want to switch to concentration when moving to the boss - # ass most likely we will click on some mobs and already cast hammers - if capabilities.can_teleport_natively: - self._do_pre_move = False - def pre_move(self): # select teleport if available super().pre_move() diff --git a/src/char/poison_necro.py b/src/char/poison_necro.py index f33df185b..1afee612a 100644 --- a/src/char/poison_necro.py +++ b/src/char/poison_necro.py @@ -457,7 +457,7 @@ def kill_council(self) -> bool: pos_m = screen.convert_abs_to_monitor((0, -200)) self.pre_move() self.move(pos_m, force_move=True) - self._pather.traverse_nodes([229], self, timeout=2.5, force_tp=True, use_tp_charge=True) + self._pather.traverse_nodes([229], self, timeout=2.5, force_tp=True) pos_m = screen.convert_abs_to_monitor((50, 0)) self.walk(pos_m, force_move=True) #self._lower_res((-50, 0), spray=10) @@ -478,7 +478,7 @@ def kill_council(self) -> bool: pos_m = screen.convert_abs_to_monitor((-100, 200)) self.pre_move() self.move(pos_m, force_move=True) - self._pather.traverse_nodes([226], self, timeout=2.5, force_tp=True, use_tp_charge=True) + self._pather.traverse_nodes([226], self, timeout=2.5, force_tp=True) pos_m = screen.convert_abs_to_monitor((0, 30)) self.walk(pos_m, force_move=True) self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=4,cast_v_div=3,cast_spell='lower_res',delay=1.0) @@ -496,7 +496,7 @@ def kill_council(self) -> bool: pos_m = screen.convert_abs_to_monitor((-200, -200)) self.pre_move() self.move(pos_m, force_move=True) - self._pather.traverse_nodes([229], self, timeout=2.5, force_tp=True, use_tp_charge=True) + self._pather.traverse_nodes([229], self, timeout=2.5, force_tp=True) pos_m = screen.convert_abs_to_monitor((20, -50)) self.walk(pos_m, force_move=True) self.poison_nova(2.0) diff --git a/src/char/sorceress/hydra_sorc.py b/src/char/sorceress/hydra_sorc.py index dd555ea17..921f18298 100644 --- a/src/char/sorceress/hydra_sorc.py +++ b/src/char/sorceress/hydra_sorc.py @@ -13,7 +13,7 @@ class HydraSorc(Sorceress): def __init__(self, *args, **kwargs): Logger.info("Setting up HydraSorc Sorc") super().__init__(*args, **kwargs) - self._hydra_time = None + self._hydra_time = None def _alt_attack(self, cast_pos_abs: tuple[float, float], delay: tuple[float, float] = (0.16, 0.23), spray: float = 10): keyboard.send(Config().char["stand_still"], do_release=False) @@ -34,14 +34,14 @@ def _hydra(self, cast_pos_abs: tuple[float, float], spray: float = 10): if not self._skill_hotkeys["hydra"]: raise ValueError("You did not set a hotkey for hydra!") keyboard.send(self._skill_hotkeys["hydra"]) - self._hydra_time = time.time() + self._hydra_time = time.time() x = cast_pos_abs[0] + (random.random() * 2 * spray - spray) y = cast_pos_abs[1] + (random.random() * 2 * spray - spray) cast_pos_monitor = convert_abs_to_monitor((x, y)) mouse.move(*cast_pos_monitor) mouse.press(button="right") wait(2,3) - mouse.release(button="right") + mouse.release(button="right") def kill_pindle(self) -> bool: pindle_pos_abs = convert_screen_to_abs(Config().path["pindle_end"][0]) diff --git a/src/char/sorceress/light_sorc.py b/src/char/sorceress/light_sorc.py index 10abcf1ae..0d547bc54 100644 --- a/src/char/sorceress/light_sorc.py +++ b/src/char/sorceress/light_sorc.py @@ -75,7 +75,7 @@ def kill_eldritch(self) -> bool: wait(self._cast_duration, self._cast_duration + 0.2) pos_m = convert_abs_to_monitor((70, -200)) self.pre_move() - self.move(pos_m, force_move=True) + self.move(pos_m, force_move=True) self._pather.traverse_nodes(Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END) return True diff --git a/src/pather.py b/src/pather.py index 55ca95d14..09cf9d879 100644 --- a/src/pather.py +++ b/src/pather.py @@ -497,7 +497,8 @@ def _convert_rel_to_abs(rel_loc: tuple[float, float], pos_abs: tuple[float, floa return (rel_loc[0] + pos_abs[0], rel_loc[1] + pos_abs[1]) def traverse_nodes_fixed(self, key: str | list[tuple[float, float]], char: IChar) -> bool: - if not char.capabilities.can_teleport_natively: + # this will check if character can teleport. for charged or native teleporters, it'll select teleport + if not (char.capabilities.can_teleport_natively and char.can_teleport()): error_msg = "Teleport is required for static pathing" Logger.error(error_msg) raise ValueError(error_msg) @@ -599,7 +600,6 @@ def traverse_nodes( do_pre_move: bool = True, force_move: bool = False, threshold: float = 0.68, - use_tp_charge: bool = False ) -> bool: """Traverse from one location to another :param path: Either a list of node indices or a tuple with (start_location, end_location) @@ -625,10 +625,8 @@ def traverse_nodes( else: Logger.debug(f"Traverse: {path}") - if use_tp_charge and char.select_teleport(): - # this means we want to use tele charge and we were able to select it - pass - elif do_pre_move: + use_tp = char.capabilities.can_teleport_natively or (char.capabilities.can_teleport_with_charges and force_tp) + if do_pre_move: # we either want to tele charge but have no charges or don't wanna use the charge falling back to default pre_move handling char.pre_move() @@ -667,7 +665,7 @@ def traverse_nodes( pos_abs = self._adjust_abs_range_to_screen(pos_abs) Logger.debug(f"Pather: taking a random guess towards " + str(pos_abs)) x_m, y_m = convert_abs_to_monitor(pos_abs) - char.move((x_m, y_m), force_move=True) + char.move((x_m, y_m), use_tp=use_tp, force_move=True) did_force_move = True last_move = time.time() @@ -691,7 +689,7 @@ def traverse_nodes( else: # Move the char x_m, y_m = convert_abs_to_monitor(node_pos_abs) - char.move((x_m, y_m), force_tp=force_tp, force_move=force_move) + char.move((x_m, y_m), use_tp=use_tp, force_move=force_move) last_direction = node_pos_abs last_move = time.time() diff --git a/src/run/trav.py b/src/run/trav.py index 3726e2bc3..c3798d936 100644 --- a/src/run/trav.py +++ b/src/run/trav.py @@ -52,8 +52,8 @@ def battle(self, do_pre_buff: bool) -> bool | tuple[Location, bool]: 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, timeout=2.5, use_tp_charge=self._char.capabilities.can_teleport_natively): - self._pather.traverse_nodes([228, 229], self._char, timeout=2.5, use_tp_charge=True) + if not self._pather.traverse_nodes([229], self._char, timeout=2.5, force_tp=self._char.capabilities.can_teleport_natively): + self._pather.traverse_nodes([228, 229], self._char, timeout=2.5, force_tp=True) picked_up_items |= self._pickit.pick_up_items(self._char) # If travincal run is not the last run if self.name != self._runs[-1]: From f1b7b24c74c10ac56324c3773266cda946b06d5b Mon Sep 17 00:00:00 2001 From: mgleed Date: Mon, 13 Jun 2022 13:16:55 -0400 Subject: [PATCH 07/44] get rid of self._should_pre_move logic --- src/char/barbarian.py | 11 +++-------- src/char/basic.py | 17 +++-------------- src/char/basic_ranged.py | 1 - src/char/paladin/fohdin.py | 19 +++++++++---------- src/char/paladin/hammerdin.py | 31 +++++++++++++------------------ src/char/paladin/paladin.py | 1 - src/pather.py | 1 - 7 files changed, 28 insertions(+), 53 deletions(-) diff --git a/src/char/barbarian.py b/src/char/barbarian.py index 0cd2a46a5..aa572d56a 100644 --- a/src/char/barbarian.py +++ b/src/char/barbarian.py @@ -17,7 +17,6 @@ def __init__(self, skill_hotkeys: dict, pather: Pather): Logger.info("Setting up Barbarian") super().__init__(skill_hotkeys) self._pather = pather - self._do_pre_move = True # offset shenk final position further to the right and bottom def _cast_war_cry(self, time_in_s: float): @@ -88,11 +87,7 @@ def kill_pindle(self) -> bool: if self.capabilities.can_teleport_natively: self._pather.traverse_nodes_fixed("pindle_end", self) else: - 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, 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._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, timeout=1.0) self._cast_war_cry(Config().char["atk_len_pindle"]) wait(0.1, 0.15) self._do_hork(4) @@ -102,7 +97,7 @@ def kill_eldritch(self) -> bool: 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, timeout=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) wait(0.05, 0.1) self._cast_war_cry(Config().char["atk_len_eldritch"]) wait(0.1, 0.15) @@ -110,7 +105,7 @@ def kill_eldritch(self) -> bool: return True def kill_shenk(self): - self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=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) wait(0.05, 0.1) self._cast_war_cry(Config().char["atk_len_shenk"]) wait(0.1, 0.15) diff --git a/src/char/basic.py b/src/char/basic.py index 6703094f9..ca6512b9d 100644 --- a/src/char/basic.py +++ b/src/char/basic.py @@ -17,7 +17,6 @@ def __init__(self, skill_hotkeys: dict, pather: Pather): Logger.info("Setting up Basic Character") super().__init__(skill_hotkeys) self._pather = pather - self._do_pre_move = True def on_capabilities_discovered(self, capabilities: CharacterCapabilities): # offset shenk final position further to the right and bottom @@ -74,11 +73,7 @@ def kill_pindle(self) -> bool: if self.capabilities.can_teleport_natively: self._pather.traverse_nodes_fixed("pindle_end", self) else: - 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, 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._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, timeout=1.0) self._cast_attack_pattern(Config().char["atk_len_pindle"]) wait(0.1, 0.15) return True @@ -87,19 +82,13 @@ def kill_eldritch(self) -> bool: if self.capabilities.can_teleport_natively: self._pather.traverse_nodes_fixed("eldritch_end", self) else: - 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, timeout=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) wait(0.05, 0.1) self._cast_attack_pattern(Config().char["atk_len_eldritch"]) return True def kill_shenk(self): - # 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, timeout=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) wait(0.05, 0.1) self._cast_attack_pattern(Config().char["atk_len_shenk"]) wait(0.1, 0.15) diff --git a/src/char/basic_ranged.py b/src/char/basic_ranged.py index 50215ad5f..c77946178 100644 --- a/src/char/basic_ranged.py +++ b/src/char/basic_ranged.py @@ -19,7 +19,6 @@ def __init__(self, skill_hotkeys: dict, pather: Pather): Logger.info("Setting up Basic Ranged Character") super().__init__(skill_hotkeys) self._pather = pather - self._do_pre_move = True def _left_attack(self, cast_pos_abs: tuple[float, float], delay: tuple[float, float] = (0.2, 0.3), spray: int = 10): if self._skill_hotkeys["left_attack"]: diff --git a/src/char/paladin/fohdin.py b/src/char/paladin/fohdin.py index 16a28e6d4..8725cb3f1 100644 --- a/src/char/paladin/fohdin.py +++ b/src/char/paladin/fohdin.py @@ -92,19 +92,19 @@ def kill_pindle(self) -> bool: if self.capabilities.can_teleport_natively or self.capabilities.can_teleport_with_charges: # Slightly retreating, so the Merc gets charged - if not self._pather.traverse_nodes([102], self, timeout=1.0, do_pre_move=self._do_pre_move, force_move=True, force_tp=False): + if not self._pather.traverse_nodes([102], self, timeout=1.0, force_move=True, force_tp=False): return False # Doing one Teleport to safe_dist to grab our Merc Logger.debug("Teleporting backwards to let Pindle charge the MERC. Looks strange, but is intended!") #I would leave this message in, so users dont complain that there is a strange movement pattern. - if not self._pather.traverse_nodes([103], self, timeout=1.0, do_pre_move=self._do_pre_move, force_tp=True): + if not self._pather.traverse_nodes([103], self, timeout=1.0, force_tp=True): return False # Slightly retreating, so the Merc gets charged - if not self._pather.traverse_nodes([103], self, timeout=1.0, do_pre_move=self._do_pre_move, force_move=True, force_tp=False): + if not self._pather.traverse_nodes([103], self, timeout=1.0, force_move=True, force_tp=False): return False else: - if not self._do_pre_move: + if not self.can_teleport(): self._activate_conviction_aura() - self._pather.traverse_nodes([103], self, timeout=1.0, do_pre_move=self._do_pre_move) + self._pather.traverse_nodes([103], self, timeout=1.0, do_pre_move=False) cast_pos_abs = [pindle_pos_abs[0] * 0.9, pindle_pos_abs[1] * 0.9] self._generic_foh_attack_sequence(default_target_abs=cast_pos_abs, min_duration=atk_len_dur, max_duration=atk_len_dur*3, default_spray=11) @@ -112,9 +112,8 @@ def kill_pindle(self) -> bool: if self.capabilities.can_teleport_natively: self._pather.traverse_nodes_fixed("pindle_end", self) else: - if not self._do_pre_move: - self._activate_redemption_aura() - 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._activate_redemption_aura() + self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, timeout=1.0, do_pre_move=False) # Use target-based attack sequence one more time before pickit self._generic_foh_attack_sequence(default_target_abs=cast_pos_abs, max_duration=atk_len_dur, default_spray=11) @@ -165,9 +164,9 @@ def kill_shenk(self): atk_len_dur = float(Config().char["atk_len_shenk"]) # traverse to shenk - if not self._do_pre_move: + if not self.can_teleport(): self._activate_conviction_aura() - 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) + self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=1.0, do_pre_move=False, force_tp=True) wait(0.05, 0.1) # bypass mob detect first diff --git a/src/char/paladin/hammerdin.py b/src/char/paladin/hammerdin.py index 04c172397..9264973d2 100644 --- a/src/char/paladin/hammerdin.py +++ b/src/char/paladin/hammerdin.py @@ -35,10 +35,9 @@ def kill_pindle(self) -> bool: if not self._pather.traverse_nodes_fixed("pindle_end", self): return False else: - 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, timeout=1.0, do_pre_move=self._do_pre_move) + if not self.can_teleport(): + self._activate_conviction_aura() + self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, timeout=1.0, do_pre_move=False) self._cast_hammers(Config().char["atk_len_pindle"]) wait(0.1, 0.15) self._cast_hammers(1.6, "redemption") @@ -49,10 +48,9 @@ def kill_eldritch(self) -> bool: # Custom eld position for teleport that brings us closer to eld self._pather.traverse_nodes_fixed([(675, 30)], self) else: - 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, timeout=1.0, do_pre_move=self._do_pre_move, force_tp=True) + if not self.can_teleport(): + self._activate_conviction_aura() + self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, timeout=1.0, do_pre_move=False, force_tp=True) wait(0.05, 0.1) self._cast_hammers(Config().char["atk_len_eldritch"]) wait(0.1, 0.15) @@ -60,10 +58,9 @@ def kill_eldritch(self) -> bool: return True def kill_shenk(self): - 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, timeout=1.0, do_pre_move=self._do_pre_move, force_tp=True) + if not self.can_teleport(): + self._activate_conviction_aura() + self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=1.0, do_pre_move=False, force_tp=True) wait(0.05, 0.1) self._cast_hammers(Config().char["atk_len_shenk"]) wait(0.1, 0.15) @@ -71,18 +68,16 @@ def kill_shenk(self): return True def kill_council(self) -> bool: - if not self._do_pre_move: - keyboard.send(self._skill_hotkeys["concentration"]) - wait(0.05, 0.15) - # Check out the node screenshot in assets/templates/trav/nodes to see where each node is at atk_len = Config().char["atk_len_trav"] + if not self.can_teleport(): + self._activate_conviction_aura() # Go inside and hammer a bit - self._pather.traverse_nodes([228, 229], self, timeout=2.5, force_tp=True) + self._pather.traverse_nodes([228, 229], self, timeout=2.5, do_pre_move=False, force_tp=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: + if self.can_teleport(): # Back to center stairs and more hammers self._pather.traverse_nodes([226], self, timeout=2.5, force_tp=True) self._cast_hammers(atk_len) diff --git a/src/char/paladin/paladin.py b/src/char/paladin/paladin.py index 63b7b3010..f0e79dfba 100644 --- a/src/char/paladin/paladin.py +++ b/src/char/paladin/paladin.py @@ -11,7 +11,6 @@ def __init__(self, skill_hotkeys: dict, pather: Pather, pickit: PickIt): # Logger.info("Setting up Paladin") super().__init__(skill_hotkeys) self._pather = pather - self._do_pre_move = True self._pickit = pickit #for Diablo self._picked_up_items = False #for Diablo diff --git a/src/pather.py b/src/pather.py index 09cf9d879..91037e3a0 100644 --- a/src/pather.py +++ b/src/pather.py @@ -627,7 +627,6 @@ def traverse_nodes( use_tp = char.capabilities.can_teleport_natively or (char.capabilities.can_teleport_with_charges and force_tp) if do_pre_move: - # we either want to tele charge but have no charges or don't wanna use the charge falling back to default pre_move handling char.pre_move() last_direction = None From 967b53b15f859abca3a07feda1fdf43e45f5a132 Mon Sep 17 00:00:00 2001 From: mgleed Date: Mon, 13 Jun 2022 13:49:41 -0400 Subject: [PATCH 08/44] fix town portal --- src/char/i_char.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/char/i_char.py b/src/char/i_char.py index c005e8224..72ba8d595 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -186,8 +186,8 @@ def _cast_battle_command(self): self._cast_simple(skill_name="battle_command", mouse_click_type="right") def _cast_town_portal(self): - consumables.increment_need("tp", 1) - self._cast_simple(skill_name="tp", mouse_click_type="right") + consumables.increment_need("town_portal", 1) + self._cast_simple(skill_name="town_portal", mouse_click_type="right") @staticmethod def _weapon_switch(): From f24383d960138d3a1607f1d21721d603e47404be Mon Sep 17 00:00:00 2001 From: mgleed Date: Mon, 13 Jun 2022 14:50:39 -0400 Subject: [PATCH 09/44] fix teleport, pathing, auras --- config/params.ini | 2 +- src/char/i_char.py | 18 +++++++++--------- src/char/paladin/paladin.py | 22 ++++++++++++---------- src/pather.py | 4 ++-- 4 files changed, 24 insertions(+), 22 deletions(-) diff --git a/config/params.ini b/config/params.ini index 6d008f49a..be7c0dfaa 100644 --- a/config/params.ini +++ b/config/params.ini @@ -39,7 +39,6 @@ type=light_sorc casting_frames=10 ;Note: loot columns are counted from left to right. Items on the left side of inventory will be considered lootable. num_loot_columns=5 -;Note: this is different from the default hotkey as "~" is for many keyboards not reachable without also pressing altgr belt_rows=4 cta_available=0 @@ -52,6 +51,7 @@ teleport= force_move=e ; stand_still can not be the default "shift" as it would interfere with merc healing stand_still=capslock +; show_belt is different from the default hotkey as "~" is for many keyboards not reachable without also pressing altgr show_belt=k potion1=1 potion2=2 diff --git a/src/char/i_char.py b/src/char/i_char.py index 72ba8d595..512f5d77a 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -147,7 +147,7 @@ def _set_active_skill(self, mouse_click_type: str = "left", skill: str =""): def _check_hotkey(self, skill: str) -> str | None: if not ( - skill in self._skill_hotkeys and (hotkey := self._skill_hotkeys[skill]) + (skill in self._skill_hotkeys and (hotkey := self._skill_hotkeys[skill])) or (skill in Config().char and (hotkey := Config().char[skill])) ): Logger.warning(f"No hotkey for skill: {skill}") @@ -167,9 +167,9 @@ def _select_skill(self, skill: str, mouse_click_type: str = "left", delay: float keyboard.send(hotkey) self._set_active_skill(mouse_click_type, skill) if delay: - try: + if isinstance(delay, (list, tuple)): wait(*delay) - except: + else: try: wait(delay) except Exception as e: @@ -186,7 +186,7 @@ def _cast_battle_command(self): self._cast_simple(skill_name="battle_command", mouse_click_type="right") def _cast_town_portal(self): - consumables.increment_need("town_portal", 1) + consumables.increment_need("tp", 1) self._cast_simple(skill_name="town_portal", mouse_click_type="right") @staticmethod @@ -195,13 +195,13 @@ def _weapon_switch(): def _stand_still(self, enable: bool): if enable: - if not self._stand_still_enabled: + if not self._standing_still: keyboard.send(Config().char["stand_still"], do_release=False) - self._stand_still_enabled = True + self._standing_still = True else: - if self._stand_still_enabled: + if self._standing_still: keyboard.send(Config().char["stand_still"], do_press=False) - self._stand_still_enabled = False + self._standing_still = False def _discover_capabilities(self) -> CharacterCapabilities: override = Config().advanced_options["override_capabilities"] @@ -322,7 +322,7 @@ def select_teleport(self) -> bool: return skills.is_right_skill_selected(["TELE_ACTIVE", "TELE_INACTIVE"]) def can_teleport(self) -> bool: - return (self.capabilities.can_teleport_natively or self.capabilities.can_teleport_with_charges) and self.select_teleport() + return (self.capabilities.can_teleport_natively or self.capabilities.can_teleport_with_charges) and self.select_teleport() and skills.is_right_skill_active() def pre_move(self): pass diff --git a/src/char/paladin/paladin.py b/src/char/paladin/paladin.py index f0e79dfba..7db46f97e 100644 --- a/src/char/paladin/paladin.py +++ b/src/char/paladin/paladin.py @@ -23,25 +23,27 @@ def pre_move(self): # select teleport if available super().pre_move() # in case teleport hotkey is not set or teleport can not be used, use vigor if set - should_cast_vigor = self._skill_hotkeys["vigor"] and not skills.is_right_skill_selected(["VIGOR"]) - if should_cast_vigor and not self.can_teleport(): - self._select_skill("vigor", delay=None) + if not self.can_teleport(): + self._activate_vigor_aura() def _activate_concentration_aura(self, delay=None): - self._select_skill("concentration", delay=delay) + self._select_skill("concentration", delay=delay, mouse_click_type="right") def _activate_redemption_aura(self, delay = [0.6, 0.8]): - self._select_skill("redemption", delay=delay) + self._select_skill("redemption", delay=delay, mouse_click_type="right") def _activate_cleanse_aura(self, delay = [0.3, 0.4]): - self._select_skill("cleansing", delay=delay) + self._select_skill("cleansing", delay=delay, mouse_click_type="right") def _activate_conviction_aura(self, delay = None): - self._select_skill("conviction", delay=delay) + self._select_skill("conviction", delay=delay, mouse_click_type="right") - def _activate_cleanse_redemption(self): - self._activate_cleanse_aura() - self._activate_redemption_aura() + def _activate_vigor_aura(self, delay = None): + self._select_skill("vigor", delay=delay, mouse_click_type="right") def _cast_holy_shield(self): self._cast_simple(skill_name="holy_shield", mouse_click_type="right") + + def _activate_cleanse_redemption(self): + self._activate_cleanse_aura() + self._activate_redemption_aura() diff --git a/src/pather.py b/src/pather.py index 91037e3a0..df8240e62 100644 --- a/src/pather.py +++ b/src/pather.py @@ -514,7 +514,7 @@ def traverse_nodes_fixed(self, key: str | list[tuple[float, float]], char: IChar x_m += int(random.random() * 6 - 3) y_m += int(random.random() * 6 - 3) t0 = grab(force_new=True) - char.move((x_m, y_m)) + char.move((x_m, y_m), use_tp=True) t1 = grab(force_new=True) # check difference between the two frames to determine if tele was good or not diff = cv2.absdiff(t0, t1) @@ -625,7 +625,7 @@ def traverse_nodes( else: Logger.debug(f"Traverse: {path}") - use_tp = char.capabilities.can_teleport_natively or (char.capabilities.can_teleport_with_charges and force_tp) + use_tp = (char.capabilities.can_teleport_natively or (char.capabilities.can_teleport_with_charges and force_tp)) and char.can_teleport() if do_pre_move: char.pre_move() From 405b1e6ca358239e0de51c6172efc26740946fda Mon Sep 17 00:00:00 2001 From: mgleed Date: Mon, 13 Jun 2022 15:21:51 -0400 Subject: [PATCH 10/44] finetuning --- src/char/paladin/hammerdin.py | 3 +-- src/item/pickit.py | 13 +++++++++---- src/pather.py | 5 +++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/char/paladin/hammerdin.py b/src/char/paladin/hammerdin.py index 9264973d2..0a895017f 100644 --- a/src/char/paladin/hammerdin.py +++ b/src/char/paladin/hammerdin.py @@ -35,8 +35,7 @@ def kill_pindle(self) -> bool: if not self._pather.traverse_nodes_fixed("pindle_end", self): return False else: - if not self.can_teleport(): - self._activate_conviction_aura() + self._activate_conviction_aura() self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, timeout=1.0, do_pre_move=False) self._cast_hammers(Config().char["atk_len_pindle"]) wait(0.1, 0.15) diff --git a/src/item/pickit.py b/src/item/pickit.py index bc948ada0..5b76deebb 100644 --- a/src/item/pickit.py +++ b/src/item/pickit.py @@ -1,4 +1,5 @@ import time +from tracemalloc import start import keyboard import cv2 from operator import itemgetter @@ -177,15 +178,19 @@ def pick_up_items(self, char: IChar) -> bool: if __name__ == "__main__": import os from config import Config + from screen import start_detecting_window, stop_detecting_window from char.sorceress import LightSorc from char.paladin import Hammerdin from pather import Pather import keyboard + from item import ItemFinder - keyboard.add_hotkey('f12', lambda: Logger.info('Force Exit (f12)') or os._exit(1)) + start_detecting_window() + keyboard.add_hotkey('f12', lambda: Logger.info('Force Exit (f12)') or stop_detecting_window() or os._exit(1)) + keyboard.wait("f11") keyboard.wait("f11") pather = Pather() - item_finder = ItemFinder() - char = Hammerdin(Config().hammerdin, Config().char, pather) - pickit = PickIt(item_finder) + pickit = PickIt(ItemFinder()) + char = Hammerdin(Config().hammerdin, pather, pickit) + char.discover_capabilities() print(pickit.pick_up_items(char)) diff --git a/src/pather.py b/src/pather.py index df8240e62..6957ea21c 100644 --- a/src/pather.py +++ b/src/pather.py @@ -637,8 +637,9 @@ def traverse_nodes( teleport_count = 0 while not continue_to_next_node: img = grab(force_new=True) + elapsed = (time.time() - last_move) # Handle timeout - if (time.time() - last_move) > timeout: + if elapsed > timeout: if is_visible(ScreenObjects.WaypointLabel, img): # sometimes bot opens waypoint menu, close it to find templates again Logger.debug("Opened wp, closing it again") @@ -655,7 +656,7 @@ def traverse_nodes( return False # Sometimes we get stuck at rocks and stuff, after a few seconds force a move into the last known direction - if not did_force_move and time.time() - last_move > 3.1: + if not did_force_move and elapsed > 3.1: if last_direction is not None: pos_abs = last_direction else: From c4dc2f3ff815b021d1148c3d72fb21ccd8edf083 Mon Sep 17 00:00:00 2001 From: mgleed Date: Mon, 13 Jun 2022 15:33:09 -0400 Subject: [PATCH 11/44] pickit stuff --- src/char/i_char.py | 3 +-- src/item/pickit.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/char/i_char.py b/src/char/i_char.py index 512f5d77a..24c4a5497 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -234,9 +234,8 @@ def on_capabilities_discovered(self, capabilities: CharacterCapabilities): def pick_up_item(self, pos: tuple[float, float], item_name: str = None, prev_cast_start: float = 0) -> float: mouse.move(pos[0], pos[1]) - time.sleep(0.1) self._click_left() - wait(0.2, 0.3) + wait(0.3, 0.4) return prev_cast_start def select_by_template( diff --git a/src/item/pickit.py b/src/item/pickit.py index 5b76deebb..5333cdf61 100644 --- a/src/item/pickit.py +++ b/src/item/pickit.py @@ -164,7 +164,7 @@ def pick_up_items(self, char: IChar) -> bool: picked_up_items.append(closest_item.name) else: char.pre_move() - char.move((x_m, y_m), force_move=True) + char.move((x_m, y_m), use_tp=char.capabilities.can_teleport_natively, force_move=True) if not char.capabilities.can_teleport_natively: time.sleep(0.3) time.sleep(0.1) From 96801c41a4539ed2e5b37e9f4e87154047dd5881 Mon Sep 17 00:00:00 2001 From: mgleed Date: Mon, 13 Jun 2022 15:34:56 -0400 Subject: [PATCH 12/44] remove vscode insertions --- src/item/pickit.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/item/pickit.py b/src/item/pickit.py index 5333cdf61..8f167cac3 100644 --- a/src/item/pickit.py +++ b/src/item/pickit.py @@ -1,17 +1,17 @@ -import time -from tracemalloc import start -import keyboard -import cv2 from operator import itemgetter -from ui_manager import ScreenObjects, is_visible -from utils.custom_mouse import mouse +import cv2 +import keyboard +import parse +import time + +from char import IChar from config import Config +from inventory import consumables +from item import ItemFinder, Item from logger import Logger 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 +from ui_manager import ScreenObjects, is_visible +from utils.custom_mouse import mouse class PickIt: From e29beea72a1781964e4f39daae43c8f7208f60a8 Mon Sep 17 00:00:00 2001 From: mgleed Date: Mon, 13 Jun 2022 15:56:29 -0400 Subject: [PATCH 13/44] revert delay --- src/char/i_char.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/char/i_char.py b/src/char/i_char.py index 24c4a5497..dcf934a0e 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -316,7 +316,7 @@ def remap_right_skill_hotkey(self, skill_asset, hotkey) -> bool: return self._remap_skill_hotkey(skill_asset, hotkey, Config().ui_roi["skill_right"], Config().ui_roi["skill_right_expanded"]) def select_teleport(self) -> bool: - if not self._select_skill("teleport", "right", delay = [0.1, 0.2]): + if not self._select_skill("teleport", "right", delay = [0.15, 0.2]): return False return skills.is_right_skill_selected(["TELE_ACTIVE", "TELE_INACTIVE"]) From eef0ffbc201db7a60d4da397e25dfae94c7dbb3a Mon Sep 17 00:00:00 2001 From: mgleed Date: Mon, 13 Jun 2022 17:50:12 -0400 Subject: [PATCH 14/44] hammerdin enigma, charged, and walk classes working --- config/params.ini | 2 +- src/char/i_char.py | 16 +++++++++++++--- src/char/paladin/fohdin.py | 12 ++++-------- src/char/paladin/hammerdin.py | 17 +++++------------ src/char/paladin/paladin.py | 8 +------- src/char/poison_necro.py | 2 +- src/char/sorceress/nova_sorc.py | 2 +- src/pather.py | 10 ++++++++++ src/run/nihlathak.py | 4 ++-- src/ui/skills.py | 15 ++++++++------- 10 files changed, 46 insertions(+), 42 deletions(-) diff --git a/config/params.ini b/config/params.ini index be7c0dfaa..52e62a2a1 100644 --- a/config/params.ini +++ b/config/params.ini @@ -292,7 +292,7 @@ region_ips= dclone_hotip= [advanced_options] -;use "can_teleport_natively" or "can_teleport_with_charges" if you want to force certain behavior in case autodetection isn't working properly +;use "can_teleport_natively", "can_teleport_with_charges", or "walk" if you want to force certain behavior in case autodetection isn't working properly override_capabilities= pathing_delay_factor=4 message_headers= diff --git a/src/char/i_char.py b/src/char/i_char.py index dcf934a0e..d021a7be8 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -14,7 +14,7 @@ from screen import grab, convert_monitor_to_screen, convert_screen_to_abs, convert_abs_to_monitor, convert_screen_to_monitor, convert_abs_to_screen import template_finder from ui import skills -from ui_manager import detect_screen_object, ScreenObjects +from ui_manager import detect_screen_object, ScreenObjects, wait_for_update from ui_manager import is_visible, wait_until_visible from utils.custom_mouse import mouse from utils.misc import wait, cut_roi, is_in_roi, color_filter, arc_spread @@ -38,6 +38,7 @@ def __init__(self, skill_hotkeys: dict): self._ocr = Ocr() self._skill_hotkeys = skill_hotkeys self._standing_still = False + self.default_move_skill = "" self.capabilities = None self.damage_scaling = float(Config().char.get("damage_scaling", 1.0)) @@ -84,8 +85,7 @@ def _cast_at_position(self, cast_pos_abs: tuple[float, float], spray: int, mouse Casts a skill at a given position. """ if cast_pos_abs: - x = cast_pos_abs[0] - y = cast_pos_abs[1] + x, y = cast_pos_abs if spray: x += (random.random() * 2 * spray - spray) y += (random.random() * 2 * spray - spray) @@ -164,8 +164,12 @@ def _select_skill(self, skill: str, mouse_click_type: str = "left", delay: float return False if self._active_skill[mouse_click_type] != skill: + # img = grab() keyboard.send(hotkey) self._set_active_skill(mouse_click_type, skill) + # if not wait_for_update(img=img, roi= skills.RIGHT_SKILL_ROI, timeout=3): + # Logger.warning(f"_select_skill: Failed to select skill {skill}, no update after hotkey") + # return False if delay: if isinstance(delay, (list, tuple)): wait(*delay) @@ -176,6 +180,9 @@ def _select_skill(self, skill: str, mouse_click_type: str = "left", delay: float Logger.warning(f"_select_skill: Failed to delay with delay: {delay}. Exception: {e}") return True + def select_skill(self, skill: str, mouse_click_type: str = "left", delay: float | list | tuple = None) -> bool: + return self._select_skill(skill, mouse_click_type, delay) + def _cast_teleport(self): self._cast_simple(skill_name="teleport", mouse_click_type="right") @@ -215,6 +222,9 @@ def _discover_capabilities(self) -> CharacterCapabilities: return CharacterCapabilities(can_teleport_natively=False, can_teleport_with_charges=True) else: return CharacterCapabilities(can_teleport_natively=False, can_teleport_with_charges=False) + elif override == "walk": + Logger.debug(f"override_capabilities is set to {override}") + return CharacterCapabilities(can_teleport_natively=False, can_teleport_with_charges=False) else: Logger.debug(f"override_capabilities is set to {override}") return CharacterCapabilities( diff --git a/src/char/paladin/fohdin.py b/src/char/paladin/fohdin.py index 8725cb3f1..6d3534e20 100644 --- a/src/char/paladin/fohdin.py +++ b/src/char/paladin/fohdin.py @@ -102,9 +102,7 @@ def kill_pindle(self) -> bool: if not self._pather.traverse_nodes([103], self, timeout=1.0, force_move=True, force_tp=False): return False else: - if not self.can_teleport(): - self._activate_conviction_aura() - self._pather.traverse_nodes([103], self, timeout=1.0, do_pre_move=False) + self._pather.traverse_nodes([103], self, timeout=1.0, active_skill="conviction") cast_pos_abs = [pindle_pos_abs[0] * 0.9, pindle_pos_abs[1] * 0.9] self._generic_foh_attack_sequence(default_target_abs=cast_pos_abs, min_duration=atk_len_dur, max_duration=atk_len_dur*3, default_spray=11) @@ -113,7 +111,7 @@ def kill_pindle(self) -> bool: self._pather.traverse_nodes_fixed("pindle_end", self) else: self._activate_redemption_aura() - self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, timeout=1.0, do_pre_move=False) + self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, timeout=1.0) # Use target-based attack sequence one more time before pickit self._generic_foh_attack_sequence(default_target_abs=cast_pos_abs, max_duration=atk_len_dur, default_spray=11) @@ -164,9 +162,7 @@ def kill_shenk(self): atk_len_dur = float(Config().char["atk_len_shenk"]) # traverse to shenk - if not self.can_teleport(): - self._activate_conviction_aura() - self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=1.0, do_pre_move=False, force_tp=True) + self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=1.0, force_tp=True, active_skill="conviction") wait(0.05, 0.1) # bypass mob detect first @@ -182,7 +178,7 @@ def kill_shenk(self): def kill_nihlathak(self, end_nodes: list[int]) -> bool: atk_len_dur = Config().char["atk_len_nihlathak"] # Move close to nihlathak - self._pather.traverse_nodes(end_nodes, self, timeout=0.8, do_pre_move=False) + self._pather.traverse_nodes(end_nodes, self, timeout=0.8) if self._select_skill("blessed_hammer"): self._cast_hammers(atk_len_dur/4) self._cast_hammers(2*atk_len_dur/4, "redemption") diff --git a/src/char/paladin/hammerdin.py b/src/char/paladin/hammerdin.py index 0a895017f..888501daf 100644 --- a/src/char/paladin/hammerdin.py +++ b/src/char/paladin/hammerdin.py @@ -35,8 +35,7 @@ def kill_pindle(self) -> bool: if not self._pather.traverse_nodes_fixed("pindle_end", self): return False else: - self._activate_conviction_aura() - self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, timeout=1.0, do_pre_move=False) + self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, timeout=1.0, active_skill="concentration") self._cast_hammers(Config().char["atk_len_pindle"]) wait(0.1, 0.15) self._cast_hammers(1.6, "redemption") @@ -47,9 +46,7 @@ def kill_eldritch(self) -> bool: # Custom eld position for teleport that brings us closer to eld self._pather.traverse_nodes_fixed([(675, 30)], self) else: - if not self.can_teleport(): - self._activate_conviction_aura() - self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, timeout=1.0, do_pre_move=False, force_tp=True) + self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, timeout=1.0, force_tp=True, active_skill="concentration") wait(0.05, 0.1) self._cast_hammers(Config().char["atk_len_eldritch"]) wait(0.1, 0.15) @@ -57,9 +54,7 @@ def kill_eldritch(self) -> bool: return True def kill_shenk(self): - if not self.can_teleport(): - self._activate_conviction_aura() - self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=1.0, do_pre_move=False, force_tp=True) + self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=1.0, force_tp=True, active_skill="concentration") wait(0.05, 0.1) self._cast_hammers(Config().char["atk_len_shenk"]) wait(0.1, 0.15) @@ -68,10 +63,8 @@ def kill_shenk(self): def kill_council(self) -> bool: atk_len = Config().char["atk_len_trav"] - if not self.can_teleport(): - self._activate_conviction_aura() # Go inside and hammer a bit - self._pather.traverse_nodes([228, 229], self, timeout=2.5, do_pre_move=False, force_tp=True) + self._pather.traverse_nodes([228, 229], self, timeout=2.5, force_tp=True, active_skill="concentration") self._cast_hammers(atk_len) # Move a bit back and another round self._move_and_attack((40, 20), atk_len) @@ -91,7 +84,7 @@ def kill_council(self) -> bool: def kill_nihlathak(self, end_nodes: list[int]) -> bool: # Move close to nihlathak - self._pather.traverse_nodes(end_nodes, self, timeout=0.8, do_pre_move=False) + self._pather.traverse_nodes(end_nodes, self, timeout=0.8) # 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]) diff --git a/src/char/paladin/paladin.py b/src/char/paladin/paladin.py index 7db46f97e..401f5b798 100644 --- a/src/char/paladin/paladin.py +++ b/src/char/paladin/paladin.py @@ -13,19 +13,13 @@ def __init__(self, skill_hotkeys: dict, pather: Pather, pickit: PickIt): self._pather = pather self._pickit = pickit #for Diablo self._picked_up_items = False #for Diablo + self.default_move_skill = "vigor" def pre_buff(self): self._pre_buff_cta() self._cast_holy_shield() wait(self._cast_duration, self._cast_duration + 0.06) - def pre_move(self): - # select teleport if available - super().pre_move() - # in case teleport hotkey is not set or teleport can not be used, use vigor if set - if not self.can_teleport(): - self._activate_vigor_aura() - def _activate_concentration_aura(self, delay=None): self._select_skill("concentration", delay=delay, mouse_click_type="right") diff --git a/src/char/poison_necro.py b/src/char/poison_necro.py index 1afee612a..363aab697 100644 --- a/src/char/poison_necro.py +++ b/src/char/poison_necro.py @@ -513,7 +513,7 @@ def kill_council(self) -> bool: def kill_nihlathak(self, end_nodes: list[int]) -> bool: # Move close to nihlathak - self._pather.traverse_nodes(end_nodes, self, timeout=0.8, do_pre_move=True) + self._pather.traverse_nodes(end_nodes, self, timeout=0.8) pos_m = screen.convert_abs_to_monitor((20, 20)) self.walk(pos_m, force_move=True) self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=4,cast_v_div=3,cast_spell='lower_res',delay=1.0) diff --git a/src/char/sorceress/nova_sorc.py b/src/char/sorceress/nova_sorc.py index 88a06749a..978c80230 100644 --- a/src/char/sorceress/nova_sorc.py +++ b/src/char/sorceress/nova_sorc.py @@ -82,7 +82,7 @@ def clear_outside(): 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, timeout=0.8, do_pre_move=False) + self._pather.traverse_nodes(end_nodes, self, timeout=0.8) # move mouse to center pos_m = convert_abs_to_monitor((0, 0)) mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7]) diff --git a/src/pather.py b/src/pather.py index 6957ea21c..34135346a 100644 --- a/src/pather.py +++ b/src/pather.py @@ -600,12 +600,17 @@ def traverse_nodes( do_pre_move: bool = True, force_move: bool = False, threshold: float = 0.68, + active_skill: str = "", ) -> bool: """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 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 + :param force_tp: Bool value if teleport should be used for pathing for charged characters + :param do_pre_move: Bool value if pre-move function should be used prior to moving + :param threshold: Threshold for template matching + :param active_skill: Name of the skill/aura that should be active during the traverse for walking chars or charged TP chars not using TP :return: Bool if traversed successful or False if it got stuck """ if len(path) == 0: @@ -628,6 +633,11 @@ def traverse_nodes( use_tp = (char.capabilities.can_teleport_natively or (char.capabilities.can_teleport_with_charges and force_tp)) and char.can_teleport() if do_pre_move: char.pre_move() + if not use_tp: + if active_skill == "": + active_skill = char.default_move_skill + if active_skill is not None: + char.select_skill(active_skill, mouse_click_type = "right") last_direction = None for i, node_idx in enumerate(path): diff --git a/src/run/nihlathak.py b/src/run/nihlathak.py index fc2f1d3ff..aaa2629e6 100644 --- a/src/run/nihlathak.py +++ b/src/run/nihlathak.py @@ -89,7 +89,7 @@ class EyeCheckData: # 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, timeout=2, do_pre_move=False) + self._pather.traverse_nodes(data.save_dist_nodes, self._char, timeout=2) end_nodes = data.end_nodes break @@ -97,7 +97,7 @@ class EyeCheckData: 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, timeout=2, do_pre_move=False) + self._pather.traverse_nodes(check_arr[0].save_dist_nodes, self._char, timeout=2) end_nodes = check_arr[0].end_nodes # Attack & Pick items diff --git a/src/ui/skills.py b/src/ui/skills.py index fa00329ae..2b68ff140 100644 --- a/src/ui/skills.py +++ b/src/ui/skills.py @@ -9,6 +9,13 @@ import template_finder from ui_manager import wait_until_visible, ScreenObjects +RIGHT_SKILL_ROI = [ + Config().ui_pos["skill_right_x"] - (Config().ui_pos["skill_width"] // 2), + Config().ui_pos["skill_y"] - (Config().ui_pos["skill_height"] // 2), + Config().ui_pos["skill_width"], + Config().ui_pos["skill_height"] +] + def is_left_skill_selected(template_list: list[str]) -> bool: """ :return: Bool if skill is currently the selected skill on the left skill slot. @@ -36,13 +43,7 @@ def is_right_skill_active() -> bool: """ :return: Bool if skill is red/available or not. Skill must be selected on right skill slot when calling the function. """ - roi = [ - Config().ui_pos["skill_right_x"] - (Config().ui_pos["skill_width"] // 2), - Config().ui_pos["skill_y"] - (Config().ui_pos["skill_height"] // 2), - Config().ui_pos["skill_width"], - Config().ui_pos["skill_height"] - ] - img = cut_roi(grab(), roi) + img = cut_roi(grab(), RIGHT_SKILL_ROI) avg = np.average(img) return avg > 75.0 From fa2436f54678b9745aa9d6e71fe69b77659e4df8 Mon Sep 17 00:00:00 2001 From: mgleed Date: Mon, 13 Jun 2022 21:57:55 -0400 Subject: [PATCH 15/44] start sorc --- src/char/basic.py | 3 +- src/char/basic_ranged.py | 3 +- src/char/bone_necro.py | 3 +- src/char/i_char.py | 44 +++++++++++++----------- src/char/necro.py | 3 +- src/char/poison_necro.py | 3 +- src/char/sorceress/light_sorc.py | 11 ++++++ src/char/sorceress/nova_sorc.py | 31 ++++++----------- src/char/sorceress/sorceress.py | 59 +++++++++++++------------------- src/char/trapsin.py | 3 +- 10 files changed, 75 insertions(+), 88 deletions(-) diff --git a/src/char/basic.py b/src/char/basic.py index ca6512b9d..9bab92d03 100644 --- a/src/char/basic.py +++ b/src/char/basic.py @@ -42,8 +42,7 @@ def _cast_attack_pattern(self, time_in_s: float): keyboard.send(Config().char["stand_still"], do_press=False) def pre_buff(self): - if Config().char["cta_available"]: - self._pre_buff_cta() + self._pre_buff_cta() if self._skill_hotkeys["buff_1"]: keyboard.send(self._skill_hotkeys["buff_1"]) wait(0.5, 0.15) diff --git a/src/char/basic_ranged.py b/src/char/basic_ranged.py index c77946178..a4dbdda16 100644 --- a/src/char/basic_ranged.py +++ b/src/char/basic_ranged.py @@ -42,8 +42,7 @@ def _right_attack(self, cast_pos_abs: tuple[float, float], delay: tuple[float, f mouse.click(button="right") def pre_buff(self): - if Config().char["cta_available"]: - self._pre_buff_cta() + self._pre_buff_cta() if self._skill_hotkeys["buff_1"]: keyboard.send(self._skill_hotkeys["buff_1"]) wait(0.5, 0.15) diff --git a/src/char/bone_necro.py b/src/char/bone_necro.py index ff3ef122f..a3b4b3901 100644 --- a/src/char/bone_necro.py +++ b/src/char/bone_necro.py @@ -46,8 +46,7 @@ def bone_wall(self, cast_pos_abs: tuple[float, float], spray: int): def pre_buff(self): self.bone_armor() #only CTA if pre trav - if Config().char["cta_available"]: - self._pre_buff_cta() + self._pre_buff_cta() Logger.info("prebuff/cta") def _clay_golem(self): diff --git a/src/char/i_char.py b/src/char/i_char.py index d021a7be8..527aef07c 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -71,16 +71,25 @@ def _click_left(self, wait_before_release: float = 0.0): def _click_right(self, wait_before_release: float = 0.0): self._click("right", wait_before_release = wait_before_release) - def _cast_simple(self, skill_name: str, mouse_click_type: str = "left"): + def _cast_simple(self, skill_name: str, mouse_click_type: str = "left", duration: float = 0): """ Selects and casts a skill. """ if self._active_skill[mouse_click_type] != skill_name: self._select_skill(skill_name, mouse_click_type = mouse_click_type) wait(0.04) - self._click(mouse_click_type) + start = time.time() + if duration: + self._stand_still(True) + while (time.time() - start) <= duration: + self._click(mouse_click_type) + wait(self._cast_duration) + self._stand_still(False) + else: + self._click(mouse_click_type) - def _cast_at_position(self, cast_pos_abs: tuple[float, float], spray: int, mouse_click_type: str = "left"): + + def _cast_at_position(self, cast_pos_abs: tuple[float, float], spray: int, mouse_click_type: str = "left", duration: float = 0): """ Casts a skill at a given position. """ @@ -92,7 +101,15 @@ def _cast_at_position(self, cast_pos_abs: tuple[float, float], spray: int, mouse pos_m = convert_abs_to_monitor((x, y)) mouse.move(*pos_m, delay_factor=[0.1, 0.2]) wait(0.06, 0.08) - self._click(mouse_click_type) + start = time.time() + if duration: + self._stand_still(True) + while (time.time() - start) <= duration: + self._click(mouse_click_type) + wait(self._cast_duration) + self._stand_still(False) + else: + self._click(mouse_click_type) def _cast_left_with_aura(self, skill_name: str, cast_pos_abs: tuple[float, float] = None, spray: int = 0, min_duration: float = 0, aura: str = ""): """ @@ -105,26 +122,11 @@ def _cast_left_with_aura(self, skill_name: str, cast_pos_abs: tuple[float, float """ #self._log_cast(skill_name, cast_pos_abs, spray, min_duration, aura) - - # set aura if needed if aura: self._select_skill(aura, mouse_click_type = "right") - - self._stand_still(True) - - # set left hand skill self._select_skill(skill_name, mouse_click_type = "left") wait(0.05, 0.1) - - # cast left hand skill - start = time.time() - if min_duration: - while (time.time() - start) <= min_duration: - self._cast_at_position(cast_pos_abs, spray) - else: - self._cast_at_position(cast_pos_abs, spray) - - self._stand_still(False) + self._cast_at_position(cast_pos_abs, spray, duration = min_duration) @staticmethod def _log_cast(skill_name: str, cast_pos_abs: tuple[float, float], spray: int, min_duration: float, aura: str): @@ -418,6 +420,8 @@ def tp_town(self) -> bool: return False def _pre_buff_cta(self): + if not Config().char["cta_available"]: + return # Save current skill img skill_before = cut_roi(grab(), Config().ui_roi["skill_right"]) # Try to switch weapons and select bo until we find the skill on the right skill slot diff --git a/src/char/necro.py b/src/char/necro.py index 6a632ffc2..07bd5c59f 100644 --- a/src/char/necro.py +++ b/src/char/necro.py @@ -240,8 +240,7 @@ def _raise_mage(self, cast_pos_abs: tuple[float, float], spray: int = 10, cast_c def pre_buff(self): #only CTA if pre trav - if Config().char["cta_available"]: - self._pre_buff_cta() + self._pre_buff_cta() if self._shenk_dead==1: Logger.info("trav buff?") #self._heart_of_wolverine() diff --git a/src/char/poison_necro.py b/src/char/poison_necro.py index 363aab697..2f700199b 100644 --- a/src/char/poison_necro.py +++ b/src/char/poison_necro.py @@ -247,8 +247,7 @@ def _raise_mage(self, cast_pos_abs: tuple[float, float], spray: int = 10, cast_c def pre_buff(self): #only CTA if pre trav - if Config().char["cta_available"]: - self._pre_buff_cta() + self._pre_buff_cta() if self._shenk_dead==1: Logger.info("trav buff?") #self._heart_of_wolverine() diff --git a/src/char/sorceress/light_sorc.py b/src/char/sorceress/light_sorc.py index 0d547bc54..e1198728c 100644 --- a/src/char/sorceress/light_sorc.py +++ b/src/char/sorceress/light_sorc.py @@ -15,6 +15,17 @@ def __init__(self, *args, **kwargs): Logger.info("Setting up Light Sorc") super().__init__(*args, **kwargs) + # def _cast_chain_lightning(self, cast_pos_abs: tuple[float, float], duration: float = 0): + # return self._cast_left_with_aura(skill_name="chain_lightning", spray = 10, cast_pos_abs = cast_pos_abs, min_duration = duration) + # + # def _cast_lightning(self, cast_pos_abs: tuple[float, float], duration: float = 0): + # return self._cast_left_with_aura(skill_name="lightning", spray = 10, cast_pos_abs = cast_pos_abs, min_duration = duration) + # + # def _cast_frozen_orb(self, cast_pos_abs: tuple[float, float], duration: float = 0): + # self._select_skill("frozen_orb", mouse_click_type = "right") + # wait(0.05, 0.1) + # self._cast_at_position(cast_pos_abs, spray=10, duration = duration) + def _chain_lightning(self, cast_pos_abs: tuple[float, float], delay: tuple[float, float] = (0.2, 0.3), spray: int = 10): keyboard.send(Config().char["stand_still"], do_release=False) if self._skill_hotkeys["chain_lightning"]: diff --git a/src/char/sorceress/nova_sorc.py b/src/char/sorceress/nova_sorc.py index 978c80230..765ed4918 100644 --- a/src/char/sorceress/nova_sorc.py +++ b/src/char/sorceress/nova_sorc.py @@ -17,40 +17,31 @@ def __init__(self, *args, **kwargs): # we want to change positions a bit of end points self._pather.offset_node(149, (70, 10)) - def _nova(self, time_in_s: float): - if not self._skill_hotkeys["nova"]: - raise ValueError("You did not set nova hotkey!") - keyboard.send(self._skill_hotkeys["nova"]) - wait(0.05, 0.1) - start = time.time() - while (time.time() - start) < time_in_s: - wait(0.03, 0.04) - mouse.press(button="right") - wait(0.12, 0.2) - mouse.release(button="right") + def _cast_nova(self, min_duration: float = 0): + return self._cast_simple(skill_name = "nova", mouse_click_type = "right", duration = min_duration) def _move_and_attack(self, abs_move: tuple[int, int], atk_len: float): pos_m = convert_abs_to_monitor(abs_move) self.pre_move() self.move(pos_m, force_move=True) - self._nova(atk_len) + self._cast_nova(atk_len) def kill_pindle(self) -> bool: self._pather.traverse_nodes_fixed("pindle_end", self) self._cast_static(0.6) - self._nova(Config().char["atk_len_pindle"]) + self._cast_nova(Config().char["atk_len_pindle"]) return True def kill_eldritch(self) -> bool: self._pather.traverse_nodes_fixed([(675, 30)], self) self._cast_static(0.6) - self._nova(Config().char["atk_len_eldritch"]) + self._cast_nova(Config().char["atk_len_eldritch"]) return True def kill_shenk(self) -> bool: 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"]) + self._cast_nova(Config().char["atk_len_shenk"]) return True def kill_council(self) -> bool: @@ -62,13 +53,13 @@ def kill_council(self) -> bool: def clear_inside(): self._pather.traverse_nodes_fixed([(1110, 120)], self) self._pather.traverse_nodes([229], self, timeout=0.8, force_tp=True) - self._nova(atk_len) + self._cast_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, timeout=0.8, force_tp=True) - self._nova(atk_len) + self._cast_nova(atk_len) self._move_and_attack((45, -20), atk_len) self._move_and_attack((-45, 20), atk_len) clear_inside() @@ -87,7 +78,7 @@ def kill_nihlathak(self, end_nodes: list[int]) -> bool: pos_m = convert_abs_to_monitor((0, 0)) mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7]) self._cast_static(0.6) - self._nova(atk_len) + self._cast_nova(atk_len) self._move_and_attack((50, 25), atk_len) self._move_and_attack((-70, -35), atk_len) return True @@ -97,11 +88,11 @@ def kill_summoner(self) -> bool: pos_m = convert_abs_to_monitor((0, 20)) mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7]) # Attack - self._nova(Config().char["atk_len_arc"]) + self._cast_nova(Config().char["atk_len_arc"]) # Move a bit back and another round self._move_and_attack((0, 80), Config().char["atk_len_arc"] * 0.5) wait(0.1, 0.15) - self._nova(Config().char["atk_len_arc"] * 0.5) + self._cast_nova(Config().char["atk_len_arc"] * 0.5) return True diff --git a/src/char/sorceress/sorceress.py b/src/char/sorceress/sorceress.py index 0737d7fe6..615a4deca 100644 --- a/src/char/sorceress/sorceress.py +++ b/src/char/sorceress/sorceress.py @@ -10,6 +10,7 @@ from pather import Pather from config import Config from ui_manager import ScreenObjects, is_visible +from screen import convert_screen_to_abs class Sorceress(IChar): def __init__(self, skill_hotkeys: dict, pather: Pather): @@ -17,12 +18,8 @@ def __init__(self, skill_hotkeys: dict, pather: Pather): self._pather = pather def pick_up_item(self, pos: tuple[float, float], item_name: str = None, prev_cast_start: float = 0): - if self._skill_hotkeys["telekinesis"] and any(x in item_name for x in ['potion', 'misc_gold', 'tp_scroll']): - keyboard.send(self._skill_hotkeys["telekinesis"]) - wait(0.1, 0.2) - mouse.move(pos[0], pos[1]) - wait(0.1, 0.2) - mouse.click(button="right") + if any(x in item_name for x in ['potion', 'misc_gold', 'tp_scroll']) and self._set_active_skill(mouse_click_type="right", skill="telekinesis"): + self._cast_telekinesis(*pos) # need about 0.4s delay before next capture for the item not to persist on screen cast_start = time.time() interval = (cast_start - prev_cast_start) @@ -42,7 +39,7 @@ def select_by_template( 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: + if not (telekinesis and self._check_hotkey("telekinesis")): 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 @@ -52,11 +49,8 @@ def select_by_template( while timeout is None or (time.time() - start) < timeout: template_match = template_finder.search(template_type, grab(), threshold=threshold) if template_match.valid: - keyboard.send(self._skill_hotkeys["telekinesis"]) - wait(0.1, 0.2) - mouse.move(*template_match.center_monitor) - wait(0.2, 0.3) - mouse.click(button="right") + pos_abs = convert_screen_to_abs(template_match.center) + self._cast_telekinesis(*pos_abs) # check the successfunction for 2 sec, if not found, try again check_success_start = time.time() while time.time() - check_success_start < 2: @@ -66,29 +60,22 @@ def select_by_template( return super().select_by_template(template_type, success_func, timeout, threshold) def pre_buff(self): - if Config().char["cta_available"]: - self._pre_buff_cta() - if self._skill_hotkeys["energy_shield"]: - keyboard.send(self._skill_hotkeys["energy_shield"]) - wait(0.1, 0.13) - mouse.click(button="right") - wait(self._cast_duration) - if self._skill_hotkeys["thunder_storm"]: - keyboard.send(self._skill_hotkeys["thunder_storm"]) - wait(0.1, 0.13) - mouse.click(button="right") - wait(self._cast_duration) - if self._skill_hotkeys["frozen_armor"]: - keyboard.send(self._skill_hotkeys["frozen_armor"]) - wait(0.1, 0.13) - mouse.click(button="right") - wait(self._cast_duration) + self._pre_buff_cta() + self._cast_energy_shield() + self._cast_thunder_storm() + self._cast_frozen_armor() def _cast_static(self, duration: float = 1.4): - if self._skill_hotkeys["static_field"]: - keyboard.send(self._skill_hotkeys["static_field"]) - wait(0.1, 0.13) - start = time.time() - while time.time() - start < duration: - mouse.click(button="right") - wait(self._cast_duration) + self._cast_simple(skill_name="static_field", mouse_click_type = "right", duration=duration) + + def _cast_telekinesis(self, cast_pos_abs: tuple[float, float]): + self._cast_at_position(cast_pos_abs, spray = 0, mouse_click_type = "right") + + def _cast_thunder_storm(self): + self._cast_simple(skill_name="thunder_storm", mouse_click_type="right") + + def _cast_energy_shield(self): + self._cast_simple(skill_name="energy_shield", mouse_click_type="right") + + def _cast_frozen_armor(self): + self._cast_simple(skill_name="frozen_armor", mouse_click_type="right") \ No newline at end of file diff --git a/src/char/trapsin.py b/src/char/trapsin.py index f7117058f..8416d5ead 100644 --- a/src/char/trapsin.py +++ b/src/char/trapsin.py @@ -18,8 +18,7 @@ def __init__(self, skill_hotkeys: dict, pather: Pather): self._pather = pather def pre_buff(self): - if Config().char["cta_available"]: - self._pre_buff_cta() + self._pre_buff_cta() if self._skill_hotkeys["fade"]: keyboard.send(self._skill_hotkeys["fade"]) wait(0.1, 0.13) From 222567aeed137500c6c53b47db5a015aa6529ab1 Mon Sep 17 00:00:00 2001 From: mgleed Date: Mon, 13 Jun 2022 22:26:34 -0400 Subject: [PATCH 16/44] lightsorc wip --- src/char/i_char.py | 42 +++++++++++++++++++-------------- src/char/paladin/paladin.py | 29 +++++++++++------------ src/char/sorceress/nova_sorc.py | 2 +- src/char/sorceress/sorceress.py | 20 ++++++++-------- 4 files changed, 49 insertions(+), 44 deletions(-) diff --git a/src/char/i_char.py b/src/char/i_char.py index 527aef07c..1c22a7447 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -71,12 +71,13 @@ def _click_left(self, wait_before_release: float = 0.0): def _click_right(self, wait_before_release: float = 0.0): self._click("right", wait_before_release = wait_before_release) - def _cast_simple(self, skill_name: str, mouse_click_type: str = "left", duration: float = 0): + def _cast_simple(self, skill_name: str, mouse_click_type: str = "left", duration: float = 0) -> bool: """ Selects and casts a skill. """ if self._active_skill[mouse_click_type] != skill_name: - self._select_skill(skill_name, mouse_click_type = mouse_click_type) + if not self._select_skill(skill_name, mouse_click_type = mouse_click_type): + return False wait(0.04) start = time.time() if duration: @@ -87,12 +88,17 @@ def _cast_simple(self, skill_name: str, mouse_click_type: str = "left", duration self._stand_still(False) else: self._click(mouse_click_type) + return True - - def _cast_at_position(self, cast_pos_abs: tuple[float, float], spray: int, mouse_click_type: str = "left", duration: float = 0): + def _cast_at_position(self, skill_name: str, cast_pos_abs: tuple[float, float], spray: int, mouse_click_type: str = "left", duration: float = 0) -> bool: """ Casts a skill at a given position. """ + if self._active_skill[mouse_click_type] != skill_name: + if not self._select_skill(skill_name, mouse_click_type = mouse_click_type): + return False + wait(0.04) + if cast_pos_abs: x, y = cast_pos_abs if spray: @@ -110,8 +116,9 @@ def _cast_at_position(self, cast_pos_abs: tuple[float, float], spray: int, mouse self._stand_still(False) else: self._click(mouse_click_type) + return True - def _cast_left_with_aura(self, skill_name: str, cast_pos_abs: tuple[float, float] = None, spray: int = 0, min_duration: float = 0, aura: str = ""): + def _cast_left_with_aura(self, skill_name: str, cast_pos_abs: tuple[float, float] = None, spray: int = 0, min_duration: float = 0, aura: str = "") -> bool: """ Casts a skill with an aura active :param skill_name: name of skill in params file; i.e., "holy_bolt" @@ -124,9 +131,7 @@ def _cast_left_with_aura(self, skill_name: str, cast_pos_abs: tuple[float, float #self._log_cast(skill_name, cast_pos_abs, spray, min_duration, aura) if aura: self._select_skill(aura, mouse_click_type = "right") - self._select_skill(skill_name, mouse_click_type = "left") - wait(0.05, 0.1) - self._cast_at_position(cast_pos_abs, spray, duration = min_duration) + return self._cast_at_position(skill_name=skill_name, cast_pos_abs=cast_pos_abs, spray=spray, mouse_click_type="left", duration=min_duration) @staticmethod def _log_cast(skill_name: str, cast_pos_abs: tuple[float, float], spray: int, min_duration: float, aura: str): @@ -152,7 +157,7 @@ def _check_hotkey(self, skill: str) -> str | None: (skill in self._skill_hotkeys and (hotkey := self._skill_hotkeys[skill])) or (skill in Config().char and (hotkey := Config().char[skill])) ): - Logger.warning(f"No hotkey for skill: {skill}") + # Logger.warning(f"No hotkey for skill: {skill}") return None return hotkey @@ -185,18 +190,19 @@ def _select_skill(self, skill: str, mouse_click_type: str = "left", delay: float def select_skill(self, skill: str, mouse_click_type: str = "left", delay: float | list | tuple = None) -> bool: return self._select_skill(skill, mouse_click_type, delay) - def _cast_teleport(self): - self._cast_simple(skill_name="teleport", mouse_click_type="right") + def _cast_teleport(self) -> bool: + return self._cast_simple(skill_name="teleport", mouse_click_type="right") - def _cast_battle_orders(self): - self._cast_simple(skill_name="battle_orders", mouse_click_type="right") + def _cast_battle_orders(self) -> bool: + return self._cast_simple(skill_name="battle_orders", mouse_click_type="right") - def _cast_battle_command(self): - self._cast_simple(skill_name="battle_command", mouse_click_type="right") + def _cast_battle_command(self) -> bool: + return self._cast_simple(skill_name="battle_command", mouse_click_type="right") - def _cast_town_portal(self): - consumables.increment_need("tp", 1) - self._cast_simple(skill_name="town_portal", mouse_click_type="right") + def _cast_town_portal(self) -> bool: + if res := self._cast_simple(skill_name="town_portal", mouse_click_type="right"): + consumables.increment_need("tp", 1) + return res @staticmethod def _weapon_switch(): diff --git a/src/char/paladin/paladin.py b/src/char/paladin/paladin.py index 401f5b798..1048195a8 100644 --- a/src/char/paladin/paladin.py +++ b/src/char/paladin/paladin.py @@ -20,24 +20,23 @@ def pre_buff(self): self._cast_holy_shield() wait(self._cast_duration, self._cast_duration + 0.06) - def _activate_concentration_aura(self, delay=None): - self._select_skill("concentration", delay=delay, mouse_click_type="right") + def _activate_concentration_aura(self, delay=None) -> bool: + return self._select_skill("concentration", delay=delay, mouse_click_type="right") - def _activate_redemption_aura(self, delay = [0.6, 0.8]): - self._select_skill("redemption", delay=delay, mouse_click_type="right") + def _activate_redemption_aura(self, delay = [0.6, 0.8]) -> bool: + return self._select_skill("redemption", delay=delay, mouse_click_type="right") - def _activate_cleanse_aura(self, delay = [0.3, 0.4]): - self._select_skill("cleansing", delay=delay, mouse_click_type="right") + def _activate_cleanse_aura(self, delay = [0.3, 0.4]) -> bool: + return self._select_skill("cleansing", delay=delay, mouse_click_type="right") - def _activate_conviction_aura(self, delay = None): - self._select_skill("conviction", delay=delay, mouse_click_type="right") + def _activate_conviction_aura(self, delay = None) -> bool: + return self._select_skill("conviction", delay=delay, mouse_click_type="right") - def _activate_vigor_aura(self, delay = None): - self._select_skill("vigor", delay=delay, mouse_click_type="right") + def _activate_vigor_aura(self, delay = None) -> bool: + return self._select_skill("vigor", delay=delay, mouse_click_type="right") - def _cast_holy_shield(self): - self._cast_simple(skill_name="holy_shield", mouse_click_type="right") + def _cast_holy_shield(self) -> bool: + return self._cast_simple(skill_name="holy_shield", mouse_click_type="right") - def _activate_cleanse_redemption(self): - self._activate_cleanse_aura() - self._activate_redemption_aura() + def _activate_cleanse_redemption(self) -> bool: + return self._activate_cleanse_aura() or self._activate_redemption_aura() diff --git a/src/char/sorceress/nova_sorc.py b/src/char/sorceress/nova_sorc.py index 765ed4918..fe72adb37 100644 --- a/src/char/sorceress/nova_sorc.py +++ b/src/char/sorceress/nova_sorc.py @@ -17,7 +17,7 @@ def __init__(self, *args, **kwargs): # we want to change positions a bit of end points self._pather.offset_node(149, (70, 10)) - def _cast_nova(self, min_duration: float = 0): + def _cast_nova(self, min_duration: float = 0) -> bool: return self._cast_simple(skill_name = "nova", mouse_click_type = "right", duration = min_duration) def _move_and_attack(self, abs_move: tuple[int, int], atk_len: float): diff --git a/src/char/sorceress/sorceress.py b/src/char/sorceress/sorceress.py index 615a4deca..bc9af7e4b 100644 --- a/src/char/sorceress/sorceress.py +++ b/src/char/sorceress/sorceress.py @@ -65,17 +65,17 @@ def pre_buff(self): self._cast_thunder_storm() self._cast_frozen_armor() - def _cast_static(self, duration: float = 1.4): - self._cast_simple(skill_name="static_field", mouse_click_type = "right", duration=duration) + def _cast_static(self, duration: float = 1.4) -> bool: + return self._cast_simple(skill_name="static_field", mouse_click_type = "right", duration=duration) - def _cast_telekinesis(self, cast_pos_abs: tuple[float, float]): - self._cast_at_position(cast_pos_abs, spray = 0, mouse_click_type = "right") + def _cast_telekinesis(self, cast_pos_abs: tuple[float, float]) -> bool: + return self._cast_at_position(skill_name="telekinesis", cast_pos_abs = cast_pos_abs, spray = 0, mouse_click_type = "right") - def _cast_thunder_storm(self): - self._cast_simple(skill_name="thunder_storm", mouse_click_type="right") + def _cast_thunder_storm(self) -> bool: + return self._cast_simple(skill_name="thunder_storm", mouse_click_type="right") - def _cast_energy_shield(self): - self._cast_simple(skill_name="energy_shield", mouse_click_type="right") + def _cast_energy_shield(self) -> bool: + return self._cast_simple(skill_name="energy_shield", mouse_click_type="right") - def _cast_frozen_armor(self): - self._cast_simple(skill_name="frozen_armor", mouse_click_type="right") \ No newline at end of file + def _cast_frozen_armor(self) -> bool: + return self._cast_simple(skill_name="frozen_armor", mouse_click_type="right") \ No newline at end of file From 2d91d82d388f0219e24429af6c5b600e76f7377c Mon Sep 17 00:00:00 2001 From: mgleed Date: Tue, 14 Jun 2022 09:09:42 -0400 Subject: [PATCH 17/44] continue sorc, bug right now with holding still --- src/char/i_char.py | 23 +++++- src/char/sorceress/light_sorc.py | 138 +++++++++++-------------------- src/char/sorceress/sorceress.py | 12 ++- src/utils/static_run_recorder.py | 12 ++- 4 files changed, 84 insertions(+), 101 deletions(-) diff --git a/src/char/i_char.py b/src/char/i_char.py index 1c22a7447..34a72aa38 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -339,7 +339,21 @@ def select_teleport(self) -> bool: return skills.is_right_skill_selected(["TELE_ACTIVE", "TELE_INACTIVE"]) def can_teleport(self) -> bool: - return (self.capabilities.can_teleport_natively or self.capabilities.can_teleport_with_charges) and self.select_teleport() and skills.is_right_skill_active() + can_tp = False + if res := (self.capabilities.can_teleport_natively or self.capabilities.can_teleport_with_charges): + can_tp |= res + else: + print("can't tp because no capability") + if res := self.select_teleport(): + can_tp &= res + else: + print("can't tp because can't select skill") + if res := skills.is_right_skill_active(): + can_tp &= res + else: + print("can't tp because skill is not active") + return res + #return (self.capabilities.can_teleport_natively or self.capabilities.can_teleport_with_charges) and self.select_teleport() and skills.is_right_skill_active() def pre_move(self): pass @@ -425,9 +439,9 @@ def tp_town(self) -> bool: mouse.move(*pos_away, randomize=40, delay_factor=[0.8, 1.4]) return False - def _pre_buff_cta(self): + def _pre_buff_cta(self) -> bool: if not Config().char["cta_available"]: - return + return False # Save current skill img skill_before = cut_roi(grab(), Config().ui_roi["skill_right"]) # Try to switch weapons and select bo until we find the skill on the right skill slot @@ -453,7 +467,7 @@ def _pre_buff_cta(self): # Make sure the switch back to the original weapon is good start = time.time() - while time.time() - start < 4: + while (elapsed := time.time() - start < 4): self._weapon_switch() wait(0.3, 0.35) skill_after = cut_roi(grab(), Config().ui_roi["skill_right"]) @@ -463,6 +477,7 @@ def _pre_buff_cta(self): else: Logger.warning("Failed to switch weapon, try again") wait(0.5) + return elapsed def vec_to_monitor(self, target: tuple[float, float]) -> tuple[float, float]: diff --git a/src/char/sorceress/light_sorc.py b/src/char/sorceress/light_sorc.py index e1198728c..8050fbb21 100644 --- a/src/char/sorceress/light_sorc.py +++ b/src/char/sorceress/light_sorc.py @@ -15,63 +15,26 @@ def __init__(self, *args, **kwargs): Logger.info("Setting up Light Sorc") super().__init__(*args, **kwargs) - # def _cast_chain_lightning(self, cast_pos_abs: tuple[float, float], duration: float = 0): - # return self._cast_left_with_aura(skill_name="chain_lightning", spray = 10, cast_pos_abs = cast_pos_abs, min_duration = duration) - # - # def _cast_lightning(self, cast_pos_abs: tuple[float, float], duration: float = 0): - # return self._cast_left_with_aura(skill_name="lightning", spray = 10, cast_pos_abs = cast_pos_abs, min_duration = duration) - # - # def _cast_frozen_orb(self, cast_pos_abs: tuple[float, float], duration: float = 0): - # self._select_skill("frozen_orb", mouse_click_type = "right") - # wait(0.05, 0.1) - # self._cast_at_position(cast_pos_abs, spray=10, duration = duration) + def _cast_chain_lightning(self, cast_pos_abs: tuple[float, float], spray: float = 20, duration: float = 0) -> bool: + return self._cast_left_with_aura(skill_name="chain_lightning", spray = spray, cast_pos_abs = cast_pos_abs, min_duration = duration) - def _chain_lightning(self, cast_pos_abs: tuple[float, float], delay: tuple[float, float] = (0.2, 0.3), spray: int = 10): - keyboard.send(Config().char["stand_still"], do_release=False) - if self._skill_hotkeys["chain_lightning"]: - keyboard.send(self._skill_hotkeys["chain_lightning"]) - for _ in range(4): - x = cast_pos_abs[0] + (random.random() * 2 * spray - spray) - y = cast_pos_abs[1] + (random.random() * 2 * spray - spray) - pos_m = convert_abs_to_monitor((x, y)) - mouse.move(*pos_m, delay_factor=[0.3, 0.6]) - mouse.press(button="left") - wait(delay[0], delay[1]) - mouse.release(button="left") - keyboard.send(Config().char["stand_still"], do_press=False) + def _cast_lightning(self, cast_pos_abs: tuple[float, float], spray: float = 20, duration: float = 0) -> bool: + return self._cast_left_with_aura(skill_name="lightning", spray = spray, cast_pos_abs = cast_pos_abs, min_duration = duration) - def _lightning(self, cast_pos_abs: tuple[float, float], delay: tuple[float, float] = (0.2, 0.3), spray: float = 10): - if not self._skill_hotkeys["lightning"]: - raise ValueError("You did not set lightning hotkey!") - keyboard.send(self._skill_hotkeys["lightning"]) - for _ in range(3): - x = cast_pos_abs[0] + (random.random() * 2 * spray - spray) - y = cast_pos_abs[1] + (random.random() * 2 * spray - spray) - cast_pos_monitor = convert_abs_to_monitor((x, y)) - mouse.move(*cast_pos_monitor, delay_factor=[0.3, 0.6]) - mouse.press(button="right") - wait(delay[0], delay[1]) - mouse.release(button="right") + def _cast_frozen_orb(self, cast_pos_abs: tuple[float, float], spray: float = 10, duration: float = 0) -> bool: + return self._cast_at_position("frozen_orb", cast_pos_abs, spray = spray, duration = duration) - def _frozen_orb(self, cast_pos_abs: tuple[float, float], delay: tuple[float, float] = (0.2, 0.3), spray: float = 10): - if self._skill_hotkeys["frozen_orb"]: - keyboard.send(self._skill_hotkeys["frozen_orb"]) - for _ in range(3): - x = cast_pos_abs[0] + (random.random() * 2 * spray - spray) - y = cast_pos_abs[1] + (random.random() * 2 * spray - spray) - cast_pos_monitor = convert_abs_to_monitor((x, y)) - mouse.move(*cast_pos_monitor) - mouse.press(button="right") - wait(delay[0], delay[1]) - mouse.release(button="right") + def _generic_light_sorc_attack_sequence(self, cast_pos_abs: tuple[float, float], chain_spray: float = 20, duration: float = 0): + self._cast_lightning(cast_pos_abs, spray=5) + wait(self._cast_duration, self._cast_duration + 0.2) + self._cast_chain_lightning(cast_pos_abs, spray=chain_spray, duration=duration) + wait(self._cast_duration, self._cast_duration + 0.2) 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) - wait(self._cast_duration, self._cast_duration + 0.2) + atk_len_dur = Config().char["atk_len_pindle"] + self._generic_light_sorc_attack_sequence(cast_pos_abs, chain_spray=11, duration=atk_len_dur) # Move to items self._pather.traverse_nodes_fixed("pindle_end", self) return True @@ -79,11 +42,9 @@ def kill_pindle(self) -> bool: 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) + atk_len_dur = Config().char["atk_len_eldritch"] + self._generic_light_sorc_attack_sequence(cast_pos_abs, chain_spray=90, duration=atk_len_dur) # Move to items - wait(self._cast_duration, self._cast_duration + 0.2) pos_m = convert_abs_to_monitor((70, -200)) self.pre_move() self.move(pos_m, force_move=True) @@ -95,25 +56,20 @@ def kill_shenk(self) -> bool: 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] - 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) + atk_len_dur = Config().char["atk_len_shenk"] + self._generic_light_sorc_attack_sequence(cast_pos_abs, chain_spray=90, duration=atk_len_dur/2) 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) + self._generic_light_sorc_attack_sequence(cast_pos_abs, chain_spray=90, duration=atk_len_dur/2) 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._generic_light_sorc_attack_sequence(cast_pos_abs, chain_spray=90, duration=atk_len_dur) self.pre_move() self.move(pos_m, force_move=True) # Move to items @@ -128,9 +84,9 @@ def kill_council(self) -> bool: 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) - self._lightning((-150, 0), spray=10) - self._chain_lightning((-150, 15), spray=10) + self._cast_frozen_orb((-150, -10), spray=10) + self._cast_lightning((-150, 0), spray=10) + self._cast_chain_lightning((-150, 15), spray=10) wait(0.5) pos_m = convert_abs_to_monitor((-50, 200)) self.pre_move() @@ -144,9 +100,9 @@ def kill_council(self) -> bool: 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) - self._chain_lightning((200, -185), spray=20) - self._chain_lightning((-170, -150), spray=20) + self._cast_frozen_orb((-150, -130), spray=10) + self._cast_chain_lightning((200, -185), spray=20) + self._cast_chain_lightning((-170, -150), spray=20) wait(0.5) self._pather.traverse_nodes_fixed([(1110, 15)], self) self._pather.traverse_nodes([300], self, timeout=1.0, force_tp=True) @@ -154,10 +110,10 @@ def kill_council(self) -> bool: self.pre_move() self.move(pos_m, force_move=True) wait(0.5) - self._frozen_orb((-170, -100), spray=40) - self._chain_lightning((-300, -100), spray=10) - self._chain_lightning((-300, -90), spray=10) - self._lightning((-300, -110), spray=10) + self._cast_frozen_orb((-170, -100), spray=40) + self._cast_chain_lightning((-300, -100), spray=10) + self._cast_chain_lightning((-300, -90), spray=10) + self._cast_lightning((-300, -110), spray=10) wait(0.5) # Move back outside and attack pos_m = convert_abs_to_monitor((-430, 230)) @@ -167,10 +123,10 @@ def kill_council(self) -> bool: 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) - self._chain_lightning((-170, -150), spray=20) - self._chain_lightning((300, -200), spray=20) - self._chain_lightning((-170, -150), spray=20) + self._cast_frozen_orb((175, -170), spray=40) + self._cast_chain_lightning((-170, -150), spray=20) + self._cast_chain_lightning((300, -200), spray=20) + self._cast_chain_lightning((-170, -150), spray=20) wait(0.5) # Move back inside and attack pos_m = convert_abs_to_monitor((350, -350)) @@ -181,9 +137,9 @@ def kill_council(self) -> bool: self.move(pos_m, force_move=True) wait(0.5) # Attack sequence center - self._frozen_orb((0, 20), spray=40) - self._lightning((-50, 50), spray=30) - self._lightning((50, 50), spray=30) + self._cast_frozen_orb((0, 20), spray=40) + self._cast_lightning((-50, 50), spray=30) + self._cast_lightning((50, 50), spray=30) wait(0.5) # Move inside pos_m = convert_abs_to_monitor((40, -30)) @@ -191,9 +147,9 @@ def kill_council(self) -> bool: self.move(pos_m, force_move=True) # Attack sequence to center wait(0.5) - self._chain_lightning((-150, 100), spray=20) - self._chain_lightning((150, 200), spray=40) - self._chain_lightning((-150, 0), spray=20) + self._cast_chain_lightning((-150, 100), spray=20) + self._cast_chain_lightning((150, 200), spray=40) + self._cast_chain_lightning((-150, 0), spray=20) wait(0.5) pos_m = convert_abs_to_monitor((-200, 240)) self.pre_move() @@ -219,8 +175,8 @@ def kill_nihlathak(self, end_nodes: list[int]) -> bool: 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) + self._cast_lightning(cast_pos_abs, spray=60) + self._cast_chain_lightning(cast_pos_abs, delay, 90) # Do some tele "dancing" after each sequence if i < atk_len - 1: rot_deg = random.randint(-10, 10) if i % 2 == 0 else random.randint(170, 190) @@ -229,7 +185,7 @@ def kill_nihlathak(self, end_nodes: list[int]) -> bool: self.pre_move() self.move(pos_m) else: - self._lightning(cast_pos_abs, spray=60) + self._cast_lightning(cast_pos_abs, spray=60) else: Logger.warning(f"Casting static as the last position isn't known. Skipping attack sequence") self._cast_static(duration=2) @@ -242,12 +198,10 @@ def kill_nihlathak(self, end_nodes: list[int]) -> bool: def kill_summoner(self) -> bool: # Attack cast_pos_abs = np.array([0, 0]) - 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._lightning(cast_pos_abs, spray=11) - self._chain_lightning(cast_pos_abs, spray=11) - wait(self._cast_duration, self._cast_duration + 0.2) + atk_len_dur = Config().char["atk_len_arc"] + # pos_m = convert_abs_to_monitor((-20, 20)) + # mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7]) + self._generic_light_sorc_attack_sequence(cast_pos_abs, chain_spray=11, duration=atk_len_dur) return True if __name__ == "__main__": diff --git a/src/char/sorceress/sorceress.py b/src/char/sorceress/sorceress.py index bc9af7e4b..4974c9928 100644 --- a/src/char/sorceress/sorceress.py +++ b/src/char/sorceress/sorceress.py @@ -60,10 +60,14 @@ def select_by_template( return super().select_by_template(template_type, success_func, timeout, threshold) def pre_buff(self): - self._pre_buff_cta() - self._cast_energy_shield() - self._cast_thunder_storm() - self._cast_frozen_armor() + if self._pre_buff_cta(): + wait(self._cast_duration + 0.1) + if self._cast_energy_shield(): + wait(self._cast_duration + 0.1) + if self._cast_thunder_storm(): + wait(self._cast_duration + 0.1) + if self._cast_frozen_armor(): + wait(self._cast_duration + 0.1) def _cast_static(self, duration: float = 1.4) -> bool: return self._cast_simple(skill_name="static_field", mouse_click_type = "right", duration=duration) diff --git a/src/utils/static_run_recorder.py b/src/utils/static_run_recorder.py index 9ddef9843..ff189fce0 100644 --- a/src/utils/static_run_recorder.py +++ b/src/utils/static_run_recorder.py @@ -1,7 +1,17 @@ -from screen import convert_monitor_to_screen +from tracemalloc import start +from screen import convert_monitor_to_screen, start_detecting_window, stop_detecting_window import mouse +import keyboard +from logger import Logger +import os if __name__ == "__main__": + + keyboard.add_hotkey('f12', lambda: Logger.info('Force Exit (f12)') or stop_detecting_window() or os._exit(1)) + Logger.debug("Start with F11") + start_detecting_window() + keyboard.wait("f11") + pos_list = [] while 1: mouse.wait(button=mouse.RIGHT, target_types=mouse.DOWN) From ad7a4c7e10b56d7855fa517bca65a5f0c74f0f06 Mon Sep 17 00:00:00 2001 From: mgleed Date: Fri, 17 Jun 2022 12:29:39 -0400 Subject: [PATCH 18/44] breakpoints wip --- src/char/__init__.py | 3 +- src/char/barbarian.py | 3 +- src/char/basic.py | 3 +- src/char/i_char.py | 2 +- src/char/paladin/paladin.py | 2 +- src/char/utils/__init__.py | 1 + src/char/{ => utils}/capabilities.py | 0 src/char/utils/cast_frames.py | 47 ++++++++++++++++++++++++++++ 8 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 src/char/utils/__init__.py rename src/char/{ => utils}/capabilities.py (100%) create mode 100644 src/char/utils/cast_frames.py diff --git a/src/char/__init__.py b/src/char/__init__.py index b60068a00..02390943f 100644 --- a/src/char/__init__.py +++ b/src/char/__init__.py @@ -1,2 +1 @@ -from .i_char import IChar -from .capabilities import CharacterCapabilities \ No newline at end of file +from .i_char import IChar \ No newline at end of file diff --git a/src/char/barbarian.py b/src/char/barbarian.py index aa572d56a..6b68855eb 100644 --- a/src/char/barbarian.py +++ b/src/char/barbarian.py @@ -1,7 +1,8 @@ import keyboard from ui import skills from utils.custom_mouse import mouse -from char import IChar, CharacterCapabilities +from char import IChar +from char.utils.capabilities import CharacterCapabilities import template_finder from pather import Pather from logger import Logger diff --git a/src/char/basic.py b/src/char/basic.py index 9bab92d03..4674c9771 100644 --- a/src/char/basic.py +++ b/src/char/basic.py @@ -1,7 +1,8 @@ import keyboard from ui import skills from utils.custom_mouse import mouse -from char import IChar,CharacterCapabilities +from char import IChar +from char.utils.capabilities import CharacterCapabilities import template_finder from pather import Pather from logger import Logger diff --git a/src/char/i_char.py b/src/char/i_char.py index dc609db42..de15680db 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -6,7 +6,7 @@ import random import time -from char.capabilities import CharacterCapabilities +from char.utils.capabilities import CharacterCapabilities from config import Config from item import consumables from logger import Logger diff --git a/src/char/paladin/paladin.py b/src/char/paladin/paladin.py index 1048195a8..5a37ada3d 100644 --- a/src/char/paladin/paladin.py +++ b/src/char/paladin/paladin.py @@ -1,4 +1,4 @@ -from char import IChar, CharacterCapabilities +from char import IChar from item.pickit import PickIt #for Diablo from pather import Pather from pather import Pather diff --git a/src/char/utils/__init__.py b/src/char/utils/__init__.py new file mode 100644 index 000000000..768facb81 --- /dev/null +++ b/src/char/utils/__init__.py @@ -0,0 +1 @@ +from .capabilities import CharacterCapabilities \ No newline at end of file diff --git a/src/char/capabilities.py b/src/char/utils/capabilities.py similarity index 100% rename from src/char/capabilities.py rename to src/char/utils/capabilities.py diff --git a/src/char/utils/cast_frames.py b/src/char/utils/cast_frames.py new file mode 100644 index 000000000..d18c9132d --- /dev/null +++ b/src/char/utils/cast_frames.py @@ -0,0 +1,47 @@ +import math + +BASE_FRAMES = { + "amazon": 20, + "assassin": 17, + "barbarian": 14, + "druid": 15, + "necromancer": 16, + "paladin": 16, + "sorceress": 14, + "lightning_skills": 19, +} + +ANIMATION_SPEED = { + "default": 256, + "werewolf": 228, + "werebear": 229, +} + +def _get_base_frames(class_base: str, skill_name: str): + if "lightning" in skill_name: + class_base = "lightning_skills" + if class_base not in BASE_FRAMES: + class_base = "default" + return BASE_FRAMES[class_base] + +def _get_animation_speed(class_base: str): + return ANIMATION_SPEED[class_base] if class_base in ANIMATION_SPEED else ANIMATION_SPEED["default"] + +def _efcr(fcr: int) -> float: + return math.floor(fcr * 120 / (fcr + 120)) + +# =(ROUNDUP((256*B12)/ROUNDDOWN((E12*(100+D12)/100), 0), 0))-1 +def _casting_frame_default(class_base: str, skill_name: str, fcr: int): + return math.ceil(256 * _get_base_frames(class_base, skill_name) / math.floor(_get_animation_speed(class_base) * (100 + _efcr(fcr)) / 100)) - 1 + +def _casting_frame_lightning(class_base: str, skill_name: str, fcr: int): + return math.ceil(256 * _get_base_frames(class_base, skill_name) / math.floor(256 * (100 + _efcr(fcr)) / 100)) + +casting_frame_formula = { + "default": _casting_frame_default, + "lightning": _casting_frame_lightning, + "chain_lightning": _casting_frame_lightning, +} + +for i in range(0, 201): + print(f"{i} {_casting_frame_lightning('sorceress', 'lightning', i)}") \ No newline at end of file From 3690cc9feda4be5d4738c37b14956e5c4cf277f6 Mon Sep 17 00:00:00 2001 From: mgleed Date: Fri, 17 Jun 2022 14:55:02 -0400 Subject: [PATCH 19/44] finish fn --- src/char/utils/cast_frames.py | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/char/utils/cast_frames.py b/src/char/utils/cast_frames.py index d18c9132d..ad1aed8a2 100644 --- a/src/char/utils/cast_frames.py +++ b/src/char/utils/cast_frames.py @@ -1,4 +1,5 @@ import math +from functools import cache BASE_FRAMES = { "amazon": 20, @@ -9,6 +10,7 @@ "paladin": 16, "sorceress": 14, "lightning_skills": 19, + "default": 20 } ANIMATION_SPEED = { @@ -17,8 +19,9 @@ "werebear": 229, } + def _get_base_frames(class_base: str, skill_name: str): - if "lightning" in skill_name: + if "lightning" in skill_name.lower() and class_base == "sorceress": class_base = "lightning_skills" if class_base not in BASE_FRAMES: class_base = "default" @@ -30,18 +33,12 @@ def _get_animation_speed(class_base: str): def _efcr(fcr: int) -> float: return math.floor(fcr * 120 / (fcr + 120)) -# =(ROUNDUP((256*B12)/ROUNDDOWN((E12*(100+D12)/100), 0), 0))-1 -def _casting_frame_default(class_base: str, skill_name: str, fcr: int): - return math.ceil(256 * _get_base_frames(class_base, skill_name) / math.floor(_get_animation_speed(class_base) * (100 + _efcr(fcr)) / 100)) - 1 - -def _casting_frame_lightning(class_base: str, skill_name: str, fcr: int): - return math.ceil(256 * _get_base_frames(class_base, skill_name) / math.floor(256 * (100 + _efcr(fcr)) / 100)) - -casting_frame_formula = { - "default": _casting_frame_default, - "lightning": _casting_frame_lightning, - "chain_lightning": _casting_frame_lightning, -} +@cache +def get_casting_frames(class_base: str, skill_name: str, fcr: int): + if "lightning" in skill_name.lower() and class_base == "sorceress": + return math.ceil(256 * _get_base_frames(class_base, skill_name) / math.floor(256 * (100 + _efcr(fcr)) / 100)) + else: + return math.ceil(256 * _get_base_frames(class_base, skill_name) / math.floor(_get_animation_speed(class_base) * (100 + _efcr(fcr)) / 100)) - 1 -for i in range(0, 201): - print(f"{i} {_casting_frame_lightning('sorceress', 'lightning', i)}") \ No newline at end of file +def get_cast_wait_time(class_base: str, skill_name: str, fcr: int): + return get_casting_frames(class_base, skill_name, fcr) * (1/25) \ No newline at end of file From 44897783e23316cbfb6504970dfb5ecac3074968 Mon Sep 17 00:00:00 2001 From: mgleed Date: Fri, 17 Jun 2022 21:54:44 -0400 Subject: [PATCH 20/44] commiting WIP on move() --- src/char/i_char.py | 43 ++++++------------------------------------- src/ui/skills.py | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 37 deletions(-) diff --git a/src/char/i_char.py b/src/char/i_char.py index de15680db..7ddcdf2e3 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -7,6 +7,7 @@ import time from char.utils.capabilities import CharacterCapabilities +from char.utils.cast_frames import get_cast_wait_time from config import Config from item import consumables from logger import Logger @@ -38,6 +39,7 @@ def __init__(self, skill_hotkeys: dict): self.capabilities = None self.damage_scaling = float(Config().char.get("damage_scaling", 1.0)) self._use_safer_routines = Config().char["safer_routines"] + self._base_class = "" @staticmethod def _click(mouse_click_type: str = "left", wait_before_release: float = 0.0): @@ -219,7 +221,7 @@ def _discover_capabilities(self) -> CharacterCapabilities: if override is None: if Config().char["teleport"]: if self.select_teleport(): - if self.skill_is_charged(): + if skills.skill_is_charged(): return CharacterCapabilities(can_teleport_natively=False, can_teleport_with_charges=True) else: return CharacterCapabilities(can_teleport_natively=True, can_teleport_with_charges=False) @@ -288,27 +290,6 @@ def select_by_template( Logger.error(f"Wanted to select {template_type}, but could not find it") return False - def skill_is_charged(self, img: np.ndarray = None) -> bool: - if img is None: - img = grab() - skill_img = cut_roi(img, Config().ui_roi["skill_right"]) - charge_mask, _ = color_filter(skill_img, Config().colors["blue"]) - if np.sum(charge_mask) > 0: - return True - return False - - def is_low_on_teleport_charges(self) -> bool: - img = grab() - charges_remaining = skills.get_skill_charges(img) - if charges_remaining: - Logger.debug(f"{charges_remaining} teleport charges remain") - return charges_remaining <= 3 - else: - charges_present = self.skill_is_charged(img) - if charges_present: - Logger.error("is_low_on_teleport_charges: unable to determine skill charges, assume zero") - return True - def _remap_skill_hotkey(self, skill_asset, hotkey, skill_roi, expanded_skill_roi) -> bool: x, y, w, h = skill_roi x, y = convert_screen_to_monitor((x, y)) @@ -335,30 +316,18 @@ def select_teleport(self) -> bool: return skills.is_right_skill_selected(["TELE_ACTIVE", "TELE_INACTIVE"]) def can_teleport(self) -> bool: - can_tp = False - if res := (self.capabilities.can_teleport_natively or self.capabilities.can_teleport_with_charges): - can_tp |= res - else: - print("can't tp because no capability") - if res := self.select_teleport(): - can_tp &= res - else: - print("can't tp because can't select skill") - if res := skills.is_right_skill_active(): - can_tp &= res - else: - print("can't tp because skill is not active") - return res - #return (self.capabilities.can_teleport_natively or self.capabilities.can_teleport_with_charges) and self.select_teleport() and skills.is_right_skill_active() + return (self.capabilities.can_teleport_natively or self.capabilities.can_teleport_with_charges) and self.select_teleport() and skills.is_right_skill_active() def pre_move(self): pass def move(self, pos_monitor: tuple[float, float], use_tp: bool = False, force_move: bool = False): factor = Config().advanced_options["pathing_delay_factor"] + start=time.perf_counter() if use_tp and self.can_teleport(): # can_teleport() activates teleport hotkey if True mouse.move(pos_monitor[0], pos_monitor[1], randomize=3, delay_factor=[factor*0.1, factor*0.14]) self._cast_simple(skill_name="teleport", mouse_click_type="right") + min_wait = get_cast_wait_time(self._base_class, "teleport", Config().char["fcr"]) wait(self._cast_duration, self._cast_duration + 0.02) else: # in case we want to walk we actually want to move a bit before the point cause d2r will always "overwalk" diff --git a/src/ui/skills.py b/src/ui/skills.py index 9dd631fcd..86e9dcce6 100644 --- a/src/ui/skills.py +++ b/src/ui/skills.py @@ -96,3 +96,23 @@ def get_skill_charges(img: np.ndarray = None): return int(ocr_result.text) except: return None + +def skill_is_charged(img: np.ndarray = None) -> bool: + img = grab() if img is None else img + skill_img = cut_roi(img, Config().ui_roi["skill_right"]) + charge_mask, _ = color_filter(skill_img, Config().colors["blue"]) + if np.sum(charge_mask) > 0: + return True + return False + +def is_low_on_teleport_charges(img: np.ndarray = None) -> bool: + img = grab() if img is None else img + charges_remaining = get_skill_charges(img) + if charges_remaining: + Logger.debug(f"{charges_remaining} teleport charges remain") + return charges_remaining <= 3 + else: + charges_present = skill_is_charged(img) + if charges_present: + Logger.error("is_low_on_teleport_charges: unable to determine skill charges, assume zero") + return True \ No newline at end of file From 0c55339ad258ef6b8c0930e6f20f0944fe875e69 Mon Sep 17 00:00:00 2001 From: mgleed Date: Sat, 18 Jun 2022 09:51:20 -0400 Subject: [PATCH 21/44] start implementing quickcast--WIP --- README.md | 2 +- config/params.ini | 3 +- src/char/i_char.py | 191 +++++++++--------- src/char/paladin/fohdin.py | 12 +- src/char/paladin/hammerdin.py | 4 +- src/char/sorceress/light_sorc.py | 4 +- src/char/sorceress/sorceress.py | 2 +- .../utils/{cast_frames.py => skill_data.py} | 25 ++- src/config.py | 3 +- 9 files changed, 140 insertions(+), 106 deletions(-) rename src/char/utils/{cast_frames.py => skill_data.py} (72%) diff --git a/README.md b/README.md index e588f674f..757d6bda9 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ order=run_pindle, run_eldritch | ------------------ | -------------------------------------------------------------------------------------------------| | type | Build type. Currently only "sorceress" or "hammerdin" is supported | | belt_rows | Integer value of how many rows the char's belt has | -| casting_frames | Depending on your char and fcr you will have a specific casting frame count. Check it here: https://diablo2.diablowiki.net/Breakpoints and fill in the right number. Determines how much delay there is after each teleport for example. If your system has some delay e.g. on vms, you might have to increase this value above the suggest value in the table! | +| extra_casting_frames | Depending on your char and fcr you will have a specific casting frame count. Check it here: https://diablo2.diablowiki.net/Breakpoints and fill in the right number. Determines how much delay there is after each teleport for example. If your system has some delay e.g. on vms, you might have to increase this value above the suggest value in the table! | | cta_available | 0: no cta available, 1: cta is available and should be used during prebuff | | safer_routines | Set to 1 to enable optional defensive maneuvers/etc during combat/runs at the cost of increased runtime (ex. hardcore players) | num_loot_columns | Number of columns in inventory used for loot (from left!). Remaining space can be used for charms | diff --git a/config/params.ini b/config/params.ini index 1da04c7fb..812324a48 100644 --- a/config/params.ini +++ b/config/params.ini @@ -46,7 +46,8 @@ order=run_pindle, run_eldritch_shenk ; These configs have to be alligned with your d2r settings and char build type=light_sorc belt_rows=4 -casting_frames=10 +faster_cast_rate=105 +extra_casting_frames=1 cta_available=0 ; safer_routines: enable for optional defensive maneuvers/etc during combat/runs at the cost of increased runtime (ex. hardcore players) safer_routines=0 diff --git a/src/char/i_char.py b/src/char/i_char.py index 7ddcdf2e3..91b2b2677 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -1,13 +1,14 @@ -from typing import Callable +from typing import Callable, TypeVar import cv2 import keyboard import math import numpy as np import random import time +from functools import cached_property from char.utils.capabilities import CharacterCapabilities -from char.utils.cast_frames import get_cast_wait_time +from char.utils.skill_data import get_cast_wait_time from config import Config from item import consumables from logger import Logger @@ -22,17 +23,14 @@ class IChar: _CrossGameCapabilities: None | CharacterCapabilities = None def __init__(self, skill_hotkeys: dict): - self._active_skill = { - "left": "", - "right": "" - } + self._active_aura = "" # Add a bit to be on the save side - self._cast_duration = Config().char["casting_frames"] * 0.04 + 0.01 self._last_tp = time.time() self._mouse_click_held = { "left": False, "right": False } + self._key_held = dict.fromkeys([v[0] for v in keyboard._winkeyboard.official_virtual_keys.values()], False) self._skill_hotkeys = skill_hotkeys self._standing_still = False self.default_move_skill = "" @@ -41,16 +39,38 @@ def __init__(self, skill_hotkeys: dict): self._use_safer_routines = Config().char["safer_routines"] self._base_class = "" + """ + MOUSE AND KEYBOARD METHODS + """ + @staticmethod - def _click(mouse_click_type: str = "left", wait_before_release: float = 0.0): - """ - Sends a click to the mouse. - """ - if not wait_before_release: + def _handle_delay(delay: float | list | tuple | None = None): + if delay is None: + return + if isinstance(delay, (list, tuple)): + wait(*delay) + else: + try: + wait(delay) + except Exception as e: + Logger.warning(f"Failed to delay with delay: {delay}. Exception: {e}") + + def _keypress(self, key: str, hold_time: float | list | tuple | None = None): + if not hold_time: + keyboard.send(key) + else: + self._key_held[key] = True + keyboard.send(key, do_release=False) + self._handle_delay(hold_time) + keyboard.send(key, do_press=False) + self._key_held[key] = False + + def _click(self, mouse_click_type: str = "left", hold_time: float | list | tuple | None = None): + if not hold_time: mouse.click(button = mouse_click_type) else: mouse.press(button = mouse_click_type) - wait(wait_before_release) + self._handle_delay(hold_time) mouse.release(button = mouse_click_type) def _hold_click(self, mouse_click_type: str = "left", enable: bool = True): @@ -63,40 +83,74 @@ def _hold_click(self, mouse_click_type: str = "left", enable: bool = True): self._mouse_click_held[mouse_click_type] = False mouse.release(button = mouse_click_type) - def _click_left(self, wait_before_release: float = 0.0): - self._click("left", wait_before_release = wait_before_release) + def _click_left(self, hold_time: float | list | tuple | None = None): + self._click("left", hold_time = hold_time) + + def _click_right(self, hold_time: float | list | tuple | None = None): + self._click("right", hold_time = hold_time) + + @cached_property + def _get_hotkey(self, skill: str) -> str | None: + if not ( + (skill in self._skill_hotkeys and (hotkey := self._skill_hotkeys[skill])) + or (skill in Config().char and (hotkey := Config().char[skill])) + ): + # Logger.warning(f"No hotkey for skill: {skill}") + return None + return hotkey + + """ + SKILL / CASTING METHODS + """ + + @staticmethod + def _log_cast(skill_name: str, cast_pos_abs: tuple[float, float], spray: int, min_duration: float, aura: str): + msg = f"Casting skill {skill_name}" + if cast_pos_abs: + msg += f" at screen coordinate {convert_abs_to_screen(cast_pos_abs)}" + if spray: + msg += f" with spray of {spray}" + if min_duration: + msg += f" for {round(min_duration, 1)}s" + if aura: + msg += f" with {aura} active" + Logger.debug(msg) + + def _send_skill_and_cooldown(self, skill_name: str): + self._keypress(self._get_hotkey(skill_name)) + wait(get_cast_wait_time(skill_name)) - def _click_right(self, wait_before_release: float = 0.0): - self._click("right", wait_before_release = wait_before_release) + def _activate_aura(self, skill_name: str): + if not self._get_hotkey(skill_name): + return False + if self._activate_aura != skill_name: + self._keypress(self._get_hotkey(skill_name)) + self._active_aura = skill_name + wait(0.04, 0.08) + return True - def _cast_simple(self, skill_name: str, mouse_click_type: str = "left", duration: float = 0) -> bool: + def _cast_simple(self, skill_name: str, duration: float | list | tuple | None = None) -> bool: """ - Selects and casts a skill. + Casts a skill """ - if self._active_skill[mouse_click_type] != skill_name: - if not self._select_skill(skill_name, mouse_click_type = mouse_click_type): - return False - wait(0.04) - start = time.time() - if duration: + if not (hotkey := self._get_hotkey(skill_name)): + return False + if self._key_held[hotkey]: # skill is already active + return True + if not duration: + self._send_skill_and_cooldown(skill_name) + else: self._stand_still(True) - while (time.time() - start) <= duration: - self._click(mouse_click_type) - wait(self._cast_duration) + self._keypress(self._get_hotkey(skill_name), hold_time=duration) self._stand_still(False) - else: - self._click(mouse_click_type) return True - def _cast_at_position(self, skill_name: str, cast_pos_abs: tuple[float, float], spray: int, mouse_click_type: str = "left", duration: float = 0) -> bool: + def _cast_at_position(self, skill_name: str, cast_pos_abs: tuple[float, float], spray: int, duration: float | list | tuple | None = None) -> bool: """ Casts a skill at a given position. """ - if self._active_skill[mouse_click_type] != skill_name: - if not self._select_skill(skill_name, mouse_click_type = mouse_click_type): - return False - wait(0.04) - + if not self._get_hotkey(skill_name): + return False if cast_pos_abs: x, y = cast_pos_abs if spray: @@ -104,67 +158,28 @@ def _cast_at_position(self, skill_name: str, cast_pos_abs: tuple[float, float], y += (random.random() * 2 * spray - spray) pos_m = convert_abs_to_monitor((x, y)) mouse.move(*pos_m, delay_factor=[0.1, 0.2]) - wait(0.06, 0.08) - start = time.time() - if duration: - self._stand_still(True) - while (time.time() - start) <= duration: - self._click(mouse_click_type) - wait(self._cast_duration) - self._stand_still(False) - else: - self._click(mouse_click_type) + self._cast_simple(skill_name, duration) return True - def _cast_left_with_aura(self, skill_name: str, cast_pos_abs: tuple[float, float] = None, spray: int = 0, min_duration: float = 0, aura: str = "") -> bool: + def _cast_left_with_aura(self, skill_name: str, cast_pos_abs: tuple[float, float] = None, spray: int = 0, duration: float | list | tuple | None = None, aura: str = "") -> bool: """ Casts a skill with an aura active - :param skill_name: name of skill in params file; i.e., "holy_bolt" - :param cast_pos_abs: absolute position to cast toward - :param spray: amount of spray to apply - :param min_duration: minimum duration to cast the skill - :param aura: name of aura to ensure is active during skill cast """ - - #self._log_cast(skill_name, cast_pos_abs, spray, min_duration, aura) - if aura: - self._select_skill(aura, mouse_click_type = "right") - return self._cast_at_position(skill_name=skill_name, cast_pos_abs=cast_pos_abs, spray=spray, mouse_click_type="left", duration=min_duration) - - @staticmethod - def _log_cast(skill_name: str, cast_pos_abs: tuple[float, float], spray: int, min_duration: float, aura: str): - msg = f"Casting skill {skill_name}" - if cast_pos_abs: - msg += f" at screen coordinate {convert_abs_to_screen(cast_pos_abs)}" - if spray: - msg += f" with spray of {spray}" - if min_duration: - msg += f" for {round(min_duration, 1)}s" + #self._log_cast(skill_name, cast_pos_abs, spray, duration, aura) if aura: - msg += f" with {aura} active" - Logger.debug(msg) + self._activate_aura(aura) + return self._cast_at_position(skill_name=skill_name, cast_pos_abs=cast_pos_abs, spray=spray, mouse_click_type="left", duration=duration) - def _set_active_skill(self, mouse_click_type: str = "left", skill: str =""): - """ - Sets the active skill internally, used to keep track of which skill is currently active - """ - self._active_skill[mouse_click_type] = skill - - def _check_hotkey(self, skill: str) -> str | None: - if not ( - (skill in self._skill_hotkeys and (hotkey := self._skill_hotkeys[skill])) - or (skill in Config().char and (hotkey := Config().char[skill])) - ): - # Logger.warning(f"No hotkey for skill: {skill}") - return None - return hotkey + def _set_active_aura(self, skill: str =""): + if self._active_aura != skill: + self._active_aura = skill def _select_skill(self, skill: str, mouse_click_type: str = "left", delay: float | list | tuple = None) -> bool: """ Sets the active skill on left or right. Will only set skill if not already selected """ - if not (hotkey := self._check_hotkey(skill)): + if not (hotkey := self._get_hotkey(skill)): self._set_active_skill(mouse_click_type, "") return False @@ -176,13 +191,7 @@ def _select_skill(self, skill: str, mouse_click_type: str = "left", delay: float # Logger.warning(f"_select_skill: Failed to select skill {skill}, no update after hotkey") # return False if delay: - if isinstance(delay, (list, tuple)): - wait(*delay) - else: - try: - wait(delay) - except Exception as e: - Logger.warning(f"_select_skill: Failed to delay with delay: {delay}. Exception: {e}") + self._handle_delay(delay) return True def select_skill(self, skill: str, mouse_click_type: str = "left", delay: float | list | tuple = None) -> bool: diff --git a/src/char/paladin/fohdin.py b/src/char/paladin/fohdin.py index 60deacf4f..dd6ee3905 100644 --- a/src/char/paladin/fohdin.py +++ b/src/char/paladin/fohdin.py @@ -22,15 +22,15 @@ def _move_and_attack(self, abs_move: tuple[int, int], atk_len: float): self.move(pos_m, force_move=True) self._cast_hammers(atk_len) - def _cast_hammers(self, min_duration: float = 0, aura: str = "concentration"): #for nihlathak - return self._cast_left_with_aura(skill_name = "blessed_hammer", spray = 0, min_duration = min_duration, aura = aura) + def _cast_hammers(self, duration: float = 0, aura: str = "concentration"): #for nihlathak + return self._cast_left_with_aura(skill_name = "blessed_hammer", spray = 0, duration = duration, aura = aura) - def _cast_foh(self, cast_pos_abs: tuple[float, float], spray: int = 10, min_duration: float = 0, aura: str = "conviction"): - return self._cast_left_with_aura(skill_name = "foh", cast_pos_abs = cast_pos_abs, spray = spray, min_duration = min_duration, aura = aura) + def _cast_foh(self, cast_pos_abs: tuple[float, float], spray: int = 10, duration: float = 0, aura: str = "conviction"): + return self._cast_left_with_aura(skill_name = "foh", cast_pos_abs = cast_pos_abs, spray = spray, duration = duration, aura = aura) - def _cast_holy_bolt(self, cast_pos_abs: tuple[float, float], spray: int = 10, min_duration: float = 0, aura: str = "concentration"): + def _cast_holy_bolt(self, cast_pos_abs: tuple[float, float], spray: int = 10, duration: float = 0, aura: str = "concentration"): #if skill is bound : concentration, use concentration, otherwise move on with conviction. alternatively use redemption whilst holybolting. conviction does not help holy bolt (its magic damage) - return self._cast_left_with_aura(skill_name = "holy_bolt", cast_pos_abs = cast_pos_abs, spray = spray, min_duration = min_duration, aura = aura) + return self._cast_left_with_aura(skill_name = "holy_bolt", cast_pos_abs = cast_pos_abs, spray = spray, duration = duration, aura = aura) def _generic_foh_attack_sequence( self, diff --git a/src/char/paladin/hammerdin.py b/src/char/paladin/hammerdin.py index c2ea8489c..cc95df682 100644 --- a/src/char/paladin/hammerdin.py +++ b/src/char/paladin/hammerdin.py @@ -17,8 +17,8 @@ def __init__(self, *args, **kwargs): #hammerdin needs to be closer to shenk to reach it with hammers self._pather.offset_node(149, (70, 10)) - def _cast_hammers(self, min_duration: float = 0, aura: str = "concentration"): #for nihlathak - return self._cast_left_with_aura(skill_name = "blessed_hammer", spray = 0, min_duration = min_duration, aura = aura) + def _cast_hammers(self, duration: float = 0, aura: str = "concentration"): #for nihlathak + return self._cast_left_with_aura(skill_name = "blessed_hammer", spray = 0, duration = duration, aura = aura) def _move_and_attack(self, abs_move: tuple[int, int], atk_len: float): pos_m = convert_abs_to_monitor(abs_move) diff --git a/src/char/sorceress/light_sorc.py b/src/char/sorceress/light_sorc.py index 8050fbb21..213e50145 100644 --- a/src/char/sorceress/light_sorc.py +++ b/src/char/sorceress/light_sorc.py @@ -16,10 +16,10 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def _cast_chain_lightning(self, cast_pos_abs: tuple[float, float], spray: float = 20, duration: float = 0) -> bool: - return self._cast_left_with_aura(skill_name="chain_lightning", spray = spray, cast_pos_abs = cast_pos_abs, min_duration = duration) + return self._cast_left_with_aura(skill_name="chain_lightning", spray = spray, cast_pos_abs = cast_pos_abs, duration = duration) def _cast_lightning(self, cast_pos_abs: tuple[float, float], spray: float = 20, duration: float = 0) -> bool: - return self._cast_left_with_aura(skill_name="lightning", spray = spray, cast_pos_abs = cast_pos_abs, min_duration = duration) + return self._cast_left_with_aura(skill_name="lightning", spray = spray, cast_pos_abs = cast_pos_abs, duration = duration) def _cast_frozen_orb(self, cast_pos_abs: tuple[float, float], spray: float = 10, duration: float = 0) -> bool: return self._cast_at_position("frozen_orb", cast_pos_abs, spray = spray, duration = duration) diff --git a/src/char/sorceress/sorceress.py b/src/char/sorceress/sorceress.py index 4974c9928..8a60f636f 100644 --- a/src/char/sorceress/sorceress.py +++ b/src/char/sorceress/sorceress.py @@ -39,7 +39,7 @@ def select_by_template( telekinesis: bool = False ) -> bool: # In case telekinesis is False or hotkey is not set, just call the base implementation - if not (telekinesis and self._check_hotkey("telekinesis")): + if not (telekinesis and self._get_hotkey("telekinesis")): 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 diff --git a/src/char/utils/cast_frames.py b/src/char/utils/skill_data.py similarity index 72% rename from src/char/utils/cast_frames.py rename to src/char/utils/skill_data.py index ad1aed8a2..493ed98a0 100644 --- a/src/char/utils/cast_frames.py +++ b/src/char/utils/skill_data.py @@ -1,5 +1,6 @@ import math from functools import cache +from config import Config BASE_FRAMES = { "amazon": 20, @@ -19,6 +20,28 @@ "werebear": 229, } +AURAS = { + "blessed_aim", + "cleansing", + "concentration", + "conviction" + "defiance", + "fanaticism", + "holy_fire", + "holy_freeze", + "holy_shock", + "meditation", + "might", + "prayer", + "redemption", + "resist_cold", + "resist_fire", + "resist_lightning", + "salvation", + "sanctuary", + "thorns", + "vigor" +} def _get_base_frames(class_base: str, skill_name: str): if "lightning" in skill_name.lower() and class_base == "sorceress": @@ -41,4 +64,4 @@ def get_casting_frames(class_base: str, skill_name: str, fcr: int): return math.ceil(256 * _get_base_frames(class_base, skill_name) / math.floor(_get_animation_speed(class_base) * (100 + _efcr(fcr)) / 100)) - 1 def get_cast_wait_time(class_base: str, skill_name: str, fcr: int): - return get_casting_frames(class_base, skill_name, fcr) * (1/25) \ No newline at end of file + return (get_casting_frames(class_base, skill_name, fcr) + Config().char["extra_casting_frames"]) * (1/25) \ No newline at end of file diff --git a/src/config.py b/src/config.py index 4ec6060d2..dcf4f2af1 100644 --- a/src/config.py +++ b/src/config.py @@ -190,7 +190,8 @@ def load_data(self): "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")), + "extra_casting_frames": int(self._select_val("char", "extra_casting_frames")), + "faster_cast_rate": int(self._select_val("char", "faster_cast_rate")), "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")), From 66dfe0d8aef4aa7b32744d582fd699d9c2160329 Mon Sep 17 00:00:00 2001 From: mgleed Date: Sat, 18 Jun 2022 09:57:39 -0400 Subject: [PATCH 22/44] save --- README.md | 3 ++- src/char/i_char.py | 44 ++++++++++---------------------------------- 2 files changed, 12 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 757d6bda9..7e9495bcf 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,8 @@ order=run_pindle, run_eldritch | ------------------ | -------------------------------------------------------------------------------------------------| | type | Build type. Currently only "sorceress" or "hammerdin" is supported | | belt_rows | Integer value of how many rows the char's belt has | -| extra_casting_frames | Depending on your char and fcr you will have a specific casting frame count. Check it here: https://diablo2.diablowiki.net/Breakpoints and fill in the right number. Determines how much delay there is after each teleport for example. If your system has some delay e.g. on vms, you might have to increase this value above the suggest value in the table! | +| faster_cast_rate | Set to your character's faster cast rate, will calculate skill cooldowns | +| extra_casting_frames | This will cause skills to wait an additional `extra_casting_frames` after calculated cooldown period. Helpful for low-performance. | | cta_available | 0: no cta available, 1: cta is available and should be used during prebuff | | safer_routines | Set to 1 to enable optional defensive maneuvers/etc during combat/runs at the cost of increased runtime (ex. hardcore players) | num_loot_columns | Number of columns in inventory used for loot (from left!). Remaining space can be used for charms | diff --git a/src/char/i_char.py b/src/char/i_char.py index 91b2b2677..73c29441a 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -120,13 +120,13 @@ def _send_skill_and_cooldown(self, skill_name: str): self._keypress(self._get_hotkey(skill_name)) wait(get_cast_wait_time(skill_name)) - def _activate_aura(self, skill_name: str): + def _activate_aura(self, skill_name: str, delay: float | list | tuple | None = (0.04, 0.08)): if not self._get_hotkey(skill_name): return False - if self._activate_aura != skill_name: + if self._activate_aura != skill_name: # if aura is already active, don't activate it again self._keypress(self._get_hotkey(skill_name)) self._active_aura = skill_name - wait(0.04, 0.08) + self._handle_delay(delay) return True def _cast_simple(self, skill_name: str, duration: float | list | tuple | None = None) -> bool: @@ -135,14 +135,13 @@ def _cast_simple(self, skill_name: str, duration: float | list | tuple | None = """ if not (hotkey := self._get_hotkey(skill_name)): return False - if self._key_held[hotkey]: # skill is already active - return True - if not duration: - self._send_skill_and_cooldown(skill_name) - else: - self._stand_still(True) - self._keypress(self._get_hotkey(skill_name), hold_time=duration) - self._stand_still(False) + if not self._key_held[hotkey]: # if skill is already active, don't activate it again + if not duration: + self._send_skill_and_cooldown(skill_name) + else: + self._stand_still(True) + self._keypress(self._get_hotkey(skill_name), hold_time=duration) + self._stand_still(False) return True def _cast_at_position(self, skill_name: str, cast_pos_abs: tuple[float, float], spray: int, duration: float | list | tuple | None = None) -> bool: @@ -170,29 +169,6 @@ def _cast_left_with_aura(self, skill_name: str, cast_pos_abs: tuple[float, float self._activate_aura(aura) return self._cast_at_position(skill_name=skill_name, cast_pos_abs=cast_pos_abs, spray=spray, mouse_click_type="left", duration=duration) - def _set_active_aura(self, skill: str =""): - if self._active_aura != skill: - self._active_aura = skill - - def _select_skill(self, skill: str, mouse_click_type: str = "left", delay: float | list | tuple = None) -> bool: - """ - Sets the active skill on left or right. - Will only set skill if not already selected - """ - if not (hotkey := self._get_hotkey(skill)): - self._set_active_skill(mouse_click_type, "") - return False - - if self._active_skill[mouse_click_type] != skill: - # img = grab() - keyboard.send(hotkey) - self._set_active_skill(mouse_click_type, skill) - # if not wait_for_update(img=img, roi= skills.RIGHT_SKILL_ROI, timeout=3): - # Logger.warning(f"_select_skill: Failed to select skill {skill}, no update after hotkey") - # return False - if delay: - self._handle_delay(delay) - return True def select_skill(self, skill: str, mouse_click_type: str = "left", delay: float | list | tuple = None) -> bool: return self._select_skill(skill, mouse_click_type, delay) From 590a614d77caf42caaf99caf6cc10076d37c3c66 Mon Sep 17 00:00:00 2001 From: mgleed Date: Sat, 18 Jun 2022 19:52:13 -0400 Subject: [PATCH 23/44] save --- README.md | 3 +- .../ui/skills/bar_blessed_hammer_active.png | Bin 0 -> 3220 bytes .../ui/skills/bar_blessed_hammer_inactive.png | Bin 0 -> 3085 bytes .../templates/ui/skills/bar_charge_active.png | Bin 0 -> 3422 bytes .../ui/skills/bar_charge_inactive.png | Bin 0 -> 3308 bytes .../templates/ui/skills/bar_holy_shield.png | Bin 0 -> 3183 bytes .../templates/ui/skills/bar_tele_active.png | Bin 0 -> 3289 bytes .../templates/ui/skills/bar_tele_inactive.png | Bin 0 -> 3139 bytes assets/templates/ui/skills/bar_tp_active.png | Bin 0 -> 3328 bytes .../templates/ui/skills/bar_tp_inactive.png | Bin 0 -> 3206 bytes assets/templates/ui/skills/bar_vigor.png | Bin 0 -> 3277 bytes config/game.ini | 1 + config/params.ini | 4 +- src/bot.py | 3 +- src/char/i_char.py | 124 ++++++++---------- src/config.py | 2 +- src/ui/skills.py | 5 +- src/ui_manager.py | 6 + 18 files changed, 70 insertions(+), 78 deletions(-) create mode 100644 assets/templates/ui/skills/bar_blessed_hammer_active.png create mode 100644 assets/templates/ui/skills/bar_blessed_hammer_inactive.png create mode 100644 assets/templates/ui/skills/bar_charge_active.png create mode 100644 assets/templates/ui/skills/bar_charge_inactive.png create mode 100644 assets/templates/ui/skills/bar_holy_shield.png create mode 100644 assets/templates/ui/skills/bar_tele_active.png create mode 100644 assets/templates/ui/skills/bar_tele_inactive.png create mode 100644 assets/templates/ui/skills/bar_tp_active.png create mode 100644 assets/templates/ui/skills/bar_tp_inactive.png create mode 100644 assets/templates/ui/skills/bar_vigor.png diff --git a/README.md b/README.md index 7e9495bcf..501e96963 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,8 @@ order=run_pindle, run_eldritch | faster_cast_rate | Set to your character's faster cast rate, will calculate skill cooldowns | | extra_casting_frames | This will cause skills to wait an additional `extra_casting_frames` after calculated cooldown period. Helpful for low-performance. | | cta_available | 0: no cta available, 1: cta is available and should be used during prebuff | -| safer_routines | Set to 1 to enable optional defensive maneuvers/etc during combat/runs at the cost of increased runtime (ex. hardcore players) +| safer_routines | Set to 1 to enable optional defensive maneuvers/etc during combat/runs at the cost of increased runtime (ex. hardcore players) | +| use_charged_teleport | 0: Character doesn't teleport or is able to teleport without charges. 1: Character depends on teleport charges to teleport. | | num_loot_columns | Number of columns in inventory used for loot (from left!). Remaining space can be used for charms | | force_move | Hotkey for "force move" | | inventory_screen | Hotkey to open inventory | diff --git a/assets/templates/ui/skills/bar_blessed_hammer_active.png b/assets/templates/ui/skills/bar_blessed_hammer_active.png new file mode 100644 index 0000000000000000000000000000000000000000..7c53cf450daa9b5c0cf560160798fd70fe8bb677 GIT binary patch literal 3220 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 B{`&v` literal 0 HcmV?d00001 diff --git a/assets/templates/ui/skills/bar_blessed_hammer_inactive.png b/assets/templates/ui/skills/bar_blessed_hammer_inactive.png new file mode 100644 index 0000000000000000000000000000000000000000..007af820d44c6ab2ff683dcee38511f40d32367f GIT binary patch literal 3085 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 literal 0 HcmV?d00001 diff --git a/assets/templates/ui/skills/bar_charge_inactive.png b/assets/templates/ui/skills/bar_charge_inactive.png new file mode 100644 index 0000000000000000000000000000000000000000..30dc42907ebe88afe5d0d8b711cc0bd16a445a9e GIT binary patch literal 3308 zcmcInYg7~08Vwee+GRzcy$T9LEDGw7$t0OPBpO0Qh{h;T9xbSonMnvHnV1P7fePyN zMO(F2Tb4k@7ob=WTPb#8pGl!2ID1n$Kk}t&Yi+w zj2qBK#hPOkOGFr{4@7ZNMFd*)MjFjv1TC-{QEW9~22_MbYY;PgK0nI@w78fV!&5*C zV+fI`4NElIibU72Q z7DdQHhl|m7VrHV*Y!tCrDJdy|DV#vkq+zjzLLm!+SuhOJ2#~TF%%~MKP%}mtWCVqo zv_`X*Gyo1pR7ED6#Y|f2&=~Z_QCb5v>=W%6%ZeIV>_EsdrV*e*@ocDGKZ>Tz3oUe& zVZDDzOhws@1S^7|$Yc{nEVK{?^Nf*V#zfLgQiXdC_czZBxwXhCDLNjGQ@j6LsF@Hm9bE$< z1Y`@M*g_FUAc6%T#1%o%5L7`20Y}Z~^MOfRtF}E4RG|=s8z?hsz=&{}m`RHY)M|0M zyb9s-*$61)bJ-wWd75fDLC zIPDb|2T>lE0}=uifeHDrK+Wbdfj{epkUG+&AaUAV4imsJk(xB=Q93_bJ*pvCMuWzY zW}2NIMVOYNTW1@dACbhG;VYdM7z~{V#T;1|GciY#37k3nSo;_5J)h1Y+LT0sCVfWp zhhP+`Hm9H_B3MH^{g2^d{hfFUwLDw;^ELkeD1F#gED<$m2zupYF&%?pIhL5Arm+6E zUw`&{Bs0NunjPJwhHa3dno&dM|8+z;DjXLeYC7;Rht3zwB|th&bm~<-PH&K-4GMKXZ1Zk z@->4oc2~G8I7+Z>Y*m~tK4vQ8)<7=R{C)QxYp%f3#kFK_j{UyqwZq8zAX)X~eL!`9 z)Wdi0(V|J4#=mEM;fRYrOVa*=3lGfyK=RGUCmM^h*HWV6IW^u5?~2>Lwg%Omq57%% zhW?#Cdd1Y#c=!G8G6aoWTf4&JOpIa4j%^m+c5nx=IS8#yqI| zciMs*8HaT(Wmjfv`nnW}r`;FscFpRQ+;gpsu>Bg1&G0DoesPmKbH;!y{T7*@y=O`N z67Oz4b~&wOPD1C|{QRP}KK3X07G85*M|O=zR`1c1N$`oJ?K#m+)g9Mt=cX+=#FbkL z>|+A#+-~WS#EtXE9)Joce9?EAf*Apdvn^fRw7v==W?JENXSTwA^L|!C_mnfyZ`4oH zt(}?G8Cs!h@!fIbRL0G$*Yf0%@(kYEM`g_=+6ngNg9{Ioempf~U0pT7cVPxBKQKwQ z=B<^Gg*dY_(~G@v`904|O{_0h<+tTH^9pk3TYm~a)-rL7w7pW-cU{`rgnhMQnonM; zeOid_!ISpd+WmM^P?p{PI8Ax?v!CDpprtsWpn!cbAQnCUasejME?GW_h=}7v_ZLsf zyZY7UlqXNTW<3d7url#V=ia*;TV)w%gQvRQ|8cKoMWr>pbU}*Meqr_H$W5V-m1X3% z-+n*4rl&riqe%#4`ljWa+#2x_13T{?w&H@H`|Nq_QlfsRX4Xt@O~+mhb>o;<3wAvC z@*n5UZF%Wr(`Eb1%cQ5#!*e>CPEH86rK*%o$*vcg$MrTDG^C;KY;%zatLu(Fu{^!2 zx8!ai_cQM>3#484h0^mQCq?R+Ul!%(XUt9CgIwBJWOZA-{Zq~^VZW{QkDsS3YV@zm zsrk05>gM$d+o~2&2kV$*Q^?Z^W||26E@TqTJr&tt?EY^_Tr(n|$%zqVBN#UD=@wdvfHM2&r39 z-;GBFvY_l>pmo+8)F{Dz4sg+GE=aB_nfn_Dt&{!0YlWzD|D5lllHFMPICZY14rpTHM|l zczEsiI*mHA_C$4gRpZ36ersE8;Z1w`r8PcWeq|*#_n?ET}a z`pE|lCxso7eHrSZ_HSIoeO zV~sf#*d<Q$R8}Hgwc$TfEEf#` literal 0 HcmV?d00001 diff --git a/assets/templates/ui/skills/bar_holy_shield.png b/assets/templates/ui/skills/bar_holy_shield.png new file mode 100644 index 0000000000000000000000000000000000000000..9020f5cc128497fe75df8c35cb54aef644d23de4 GIT binary patch literal 3183 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 LvAE2A^~V1IHMii9 literal 0 HcmV?d00001 diff --git a/assets/templates/ui/skills/bar_tele_active.png b/assets/templates/ui/skills/bar_tele_active.png new file mode 100644 index 0000000000000000000000000000000000000000..5dc1b5abe7559d037e92e44d1619b2ec6b129e8f GIT binary patch literal 3289 zcmcInc~leU9vv5?D%!Z5ii#ShqF9;CWM2^3!X`v0qTq&1W+ow$WI_@Mf)507p$b|B zi!5SMREpcNuP7jD(L&ufH2M%x5K)2RhNx(G6SmrSto5Ilb0+i6JzYx36>2B2t?VR7SKv-yFjs;|H10&W!aG(= z%!{2HgvCZ+LL79SO`jDbq6k!k9;L^qlxm$Q#tH1^6;WgJZ5X6?L-Y|&psTqcJwy^f zcPF(3ozGxF7=j>lzL3G?@dzo#b)d5l77IqWFd~EyzKF#Vv4r&A4@i~M;xbX7*t547 zb>#$x>-8ED3>ypvhJno>wQ`s#6bfO41+!QXg@AM`)Os`qQtQ6%V-OQMOsml76{MPO zW<;fAl->!Xr1q>qrRk?t>w0~n9D`#}4a{U9<~8*JC6Z4=RjPipPVW&-Rq56HQDR-t z3Jn1V5;`(UixD2tgj)Y~UolNMsV8;ed`QSdF^G*~2$#>75mEx});tQC3T)<;O zh>VFsQiLOggnTIu@wf;kz<7iJAq0JF{#pg~=%C6ESMT9b($*0`oX~?9?i} z6*{VQD|(-gc|>IINU5NAhfaiI=Bzt`n7PRW4))$we8j!a)7e972q!4gPc**=rXyu~ z1F9ul<&@L^7%uql#Ou)LPfP#2#{VCs_u7huqiQ)py*XjfycpR0is@+z{J;JBv)_H0 zai!91?xwES1~*jRZ^-PAN0cqasf5aSkdVdZK|B_RfCMt85E4jvOeqKF5eSdlqe7|p zCoVpV!(!oFF2olSG6>^xSdf&3@gNS$l;WtADWo2v&*c(OvBpqJ7fw(?7x1Xa3s5!} zBA6^8in90!Q$T#c)$LQya^X+=(vAG!CEjN;wKJH9{d*F1(Z8z^YN{bxYR@vrhRg&2 zi&Z{i*C0Wv#joc{8~Fg>X&bjKw>`~}`^%)Z*ygF6LifPz1&KdFn*swC2aH-1I;?)# zqtro`BbNxkNEaKxHW?V|vD{Y^Sf$S4seEeeB@Jf;k;>!6#O9ji&x z)8-$OWhMEIg$@rqVGi+fL!HSkpL2O(mTdlI%wj{st-ap|kKg3BjGw!uVPn$H8_Lpy zj!xW+X_1GVU)R5=G1isE>Qs%&7a`xI?`aOyehZAAdB%2gWHP5Bss_CJO3j(@?cj{T zyY0_J$1S^_niZ@tyj5sj)|(z(jUDTuuvzv;hh?W@XIyLHku3fB7ydgUQVzGbyy?vS zTGA*!=Qq&RD*u_)A|l)6Tnzch`Cs+7E{qvI-Z$iuvIr0H{AIxl_qw8uxeCA9W2Rqg z;|!LqmM*I*{TlD2e6{bw$mywRd2G5m{>0fMIpLXu!ZV{QEP!E}Aqj7`j+;Dk^j7Qb zL94=>POjY3UVYTD{BFua&NYW*TJpVnhb;%J&ktL9e26VaC6{Tx^Spn3!v>G6!HEgY zKQE3b3O2Ug85VtXa>Ba@$e4f`i>v0?GVE@TKO;UmwZcv;kb~zl#txU~9W!*yyWF=?Y_UW9H~HEu~eoK9yLVQ*e#-2~)k%&({bDc3TY+K8cQh z-?Smo`K{5p>(ZK)t*Ln<*1Fde4`-}3?Q7XKkzM8g%R`46#x+4&enfw_6V$g%QgtHwOAj^4t~rBxJ6@R$LHxMdK(q3M>VUC%}tjcvKhLSDWS`^w)vDax*CO-WgvGtKlgyz~C*(4!Nryy6_c6rQg< zhfQsl{}d|A^8PM9({Z=GcE_4si+&b9iL$W@x3%(}Zta@qIcJ8IU8Qvx_&lj;|Mk}D zC-W>C)&vHwUv%K^^4ysHxA*&3+ZUv-Z98;eY02@Im)hskT@TQf=k+7>pfGryk%dWR=A!$V$2L6agD^q78$i9@;ASw ziqeBKr`gU~Xj)hI{;p1cy80UDl6B{e%XRNAPU(s@ydPduY+tagc~<+KpAWrz?RBvs z)pOVV`LlLb$M1b{b~+y}e}D7z#fkpKd!qbyhYj56=9-i~+U2W(7$e5^L(mBvxJG+MaU7z&4I??>+ O!^dN;_@G-@(tiQ6q!Z!* literal 0 HcmV?d00001 diff --git a/assets/templates/ui/skills/bar_tele_inactive.png b/assets/templates/ui/skills/bar_tele_inactive.png new file mode 100644 index 0000000000000000000000000000000000000000..5c763d5f87d47aa923997592e1cbce575532936d GIT binary patch literal 3139 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 K%$~Y%#eV^bGQiaU literal 0 HcmV?d00001 diff --git a/assets/templates/ui/skills/bar_tp_active.png b/assets/templates/ui/skills/bar_tp_active.png new file mode 100644 index 0000000000000000000000000000000000000000..a8c4cc14f9b982275725ff4c6b6686919280e660 GIT binary patch literal 3328 zcmcInX;>528V)U3svtouMZMZd5VtzaWU`TH*o+d9AOcr%uj^!H0+DPa!2kj(l@>(Q zx*=k;h|mUVlv@#zB3g`8>y8^DxS^teC=?Y1?S!qp_i3$vZk}f{XC~iyzxR9J@0{}_ zJtR25ap>rw6bi+0wn!LC-f84Twf}(p{`8yGFnRk}Bl?$)LK)^_y=*8ucDhk0L*B~6 zC3=ZCh>xk2o+z%C5}t8N4H-?LO!tY?pjb4a2c$%#TqU5ll~>XMIWC|_u*I-gGlPhd zixRX%SVC|(mJp5caJr8-Fg=b>0w@VR3dAWDDjh#gK<~xnlVj^^hz|6I=%WR6KWjoj zA`Ss&sI>&Z^+Z4nhGBrq^JH^4gcM^>1rQiPAearoJP_vc5rmKMfc_7iOsB@yOi#5o5@PUpJP1Z01OdqiP#3S#qj8{0=RSZSBy^ZouF=cY zD!__}O4YG?0iC4USA$YB2&>Zd>qIIB#i1IA;R#!78VD4N-yNz{4o2(rGnbQD`uYAT zur54aLqMT~P93Yoh?&a?mEL_InI=lDSL>qGf5C0==l^1eV}l7bv06p1JU9js3PMR@ z>&RF}FFy@`hL%9}YHhe$tr&1BWFRAeFg;noqzJhRR~vMbt&I975JFT>2?v?PKv_xGz*pHUf_7(f12h<8oR2`-FBeT%Cx5@S$Jz6|H$ z3*(@##XUVt>Ce9c@o2!+RO9xf_5!K2HClO^#2F7K;7|4P- z7$}pmWT2Eo5FkoO83-3+p)gZM2Y$~xL#!0M_L!(zdpib)P&K3GE(L4v-+ z{Jt=qTBbLkTEZ`qRQhk53;HYaI&}HFslT7`|3~WmvSLxFDv}^~PKa(T2C{B3eWpPF z>(}po5A=*5+09lrb^S8j5eGNm}~#gTD99>U~+9E3%HUNQy`^pbKIQWnl3 zU=F*Fg+l&!SX_jKAUK;1a(RRd#K`3WN)e0$vQUN;N2LrNiox%TJFb?Pe@dbcjUk$>342Sg@M2J3L}Od=l!Pc=eC8lol7 ztSnwi9))7}?QEf6xL2m#@o2@OSR2aIx6RYr%3j>8GnZBV};G+F-pG3nZrK{XZ&4;3MD@vn3YHqD~ zcy!6boo~A;yL)cm>A7_~WmG}QzdLrnsr|S*hP7@=$qh?og%3R=)X*O7L1om%hEw5n z>@^^eJuZuyegKv31B`8nZB4J$#_Nce=9X@MoaL^0f7|q~?icB~KTH}e9`RFbhN%6i zF;C#s1ML1qE`+W&M>ZsO)vY+RXo+c9?N8L1qo2)K3xA1H9P|F6`7q#!-w6AdAcvHd zv3Fk{U-9C!toZIfS8S^2x_>|a5a-Nx`ge2ecHKmzG|TqNPV*P$oG4CVSuJ@zA*faeM|Gf9htNC zy?QdOt-EV?%{CXGp6VaIYP=^}qa@68J4hc@Bxbcf6Wp{kK zsv<8X_0-;R9vhwf?Y)m!syb(vv;gCGPh!sPO%a%0X6qiB&v%zD?0l1aXYOl5G8m!s zP#9zEKDjMvvvuY;f^l^+#HMM)~o;N<;FPl@tD&?1_JUg7za(+iaIxAyo{S-Uns#){g03njCH%X3^ys`@e z8l$AuKi>#Fy6`*uMNIZ(N7Jzr4n5|Sg(6Ft>PC{_oXM2c@%btyTSyi7eGp9WRDfD<8GDe_GlqQ7nxb?`n9i z7_-G!N=qz!dR^nQDU`cT;8J_yw#>9QT&qZwK0n#rblo-Rz{Zr)zVV}E;(8z zUkbM;%{-W?QdHO9;y>>`?<9CGsdIAGl{Gky*}LVCa+g52!%*byVrz116xk2|X2ceA z{Qb@QD|4H7Y|^$hl-GRN;}6?j&Yd{Uu(`r)a*F&R!hE(v^||p;z|`#v7DYqiw@(`D zii(OtPQ5t;wf2gCNxEdOABE;WbW%Q$vZ|(O*Qw)WW#>lv&I+PvZX;v+42+jGtZt>3V3qNITpncn|6%+l}&NI7PJlr0OTM`;YH;WzXslL4R3yR`4cqcTLJ2kk3 zBy@ke|4Wo=dnf6&ljvl@xBF@KhPJK=A8DSSoSOG5^zdBge?oIFZF6&WG^N#2Z)awE zPu_bM84*}@;NtV+Iu>Rfo0r+b=JuOzJF|>|uFOJa5f~?(T*8i-*NG zjt}dUVb40^0n2B>#s*dFwxhp}Uo8@iW=OM*t9m}$LluCMX(>aj|50Yo3>KRGm!|v% DU+mp|9NxH%-lQq?)Urd_xs&@ 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 str | None: return None return hotkey + """ + CAPABILITIES METHODS + """ + + + + def can_teleport(self) -> bool: + if Config().char["use_charged_teleport"]: + if not self._get_hotkey("teleport"): + raise Exception("No hotkey for teleport even though param.ini 'use_charged_teleport' is set to True") + else: + + + + + + return (self.capabilities.can_teleport_natively or self.capabilities.can_teleport_with_charges) and self.select_teleport() and skills.is_right_skill_active() + """ SKILL / CASTING METHODS """ @@ -162,34 +177,57 @@ def _cast_at_position(self, skill_name: str, cast_pos_abs: tuple[float, float], def _cast_left_with_aura(self, skill_name: str, cast_pos_abs: tuple[float, float] = None, spray: int = 0, duration: float | list | tuple | None = None, aura: str = "") -> bool: """ - Casts a skill with an aura active + Casts a skill at given position with an aura active """ #self._log_cast(skill_name, cast_pos_abs, spray, duration, aura) if aura: self._activate_aura(aura) return self._cast_at_position(skill_name=skill_name, cast_pos_abs=cast_pos_abs, spray=spray, mouse_click_type="left", duration=duration) + def _remap_skill_hotkey(self, skill_asset, hotkey, skill_roi, expanded_skill_roi) -> bool: + x, y, w, h = skill_roi + x, y = convert_screen_to_monitor((x, y)) + mouse.move(x + w/2, y + h / 2) + self._click_left() + wait(0.3) + match = template_finder.search(skill_asset, grab(), threshold=0.84, roi=expanded_skill_roi) + if match.valid: + mouse.move(*match.center_monitor) + wait(0.3) + keyboard.send(hotkey) + wait(0.3) + self._click_left() + wait(0.3) + return True + return False + + def remap_right_skill_hotkey(self, skill_asset, hotkey) -> bool: + return self._remap_skill_hotkey(skill_asset, hotkey, Config().ui_roi["skill_right"], Config().ui_roi["skill_right_expanded"]) - def select_skill(self, skill: str, mouse_click_type: str = "left", delay: float | list | tuple = None) -> bool: - return self._select_skill(skill, mouse_click_type, delay) + """ + GLOBAL SKILLS + """ def _cast_teleport(self) -> bool: - return self._cast_simple(skill_name="teleport", mouse_click_type="right") + return self._cast_simple(skill_name="teleport") def _cast_battle_orders(self) -> bool: - return self._cast_simple(skill_name="battle_orders", mouse_click_type="right") + return self._cast_simple(skill_name="battle_orders") def _cast_battle_command(self) -> bool: - return self._cast_simple(skill_name="battle_command", mouse_click_type="right") + return self._cast_simple(skill_name="battle_command") def _cast_town_portal(self) -> bool: - if res := self._cast_simple(skill_name="town_portal", mouse_click_type="right"): + if res := self._cast_simple(skill_name="town_portal"): consumables.increment_need("tp", 1) return res - @staticmethod - def _weapon_switch(): - keyboard.send(Config().char["weapon_switch"]) + def _weapon_switch(self): + return self._keypress(self._get_hotkey("weapon_switch")) + + """ + CHARACTER ACTIONS AND MOVEMENT METHODS + """ def _stand_still(self, enable: bool): if enable: @@ -201,38 +239,6 @@ def _stand_still(self, enable: bool): keyboard.send(Config().char["stand_still"], do_press=False) self._standing_still = False - def _discover_capabilities(self) -> CharacterCapabilities: - override = Config().advanced_options["override_capabilities"] - if override is None: - if Config().char["teleport"]: - if self.select_teleport(): - if skills.skill_is_charged(): - return CharacterCapabilities(can_teleport_natively=False, can_teleport_with_charges=True) - else: - return CharacterCapabilities(can_teleport_natively=True, can_teleport_with_charges=False) - return CharacterCapabilities(can_teleport_natively=False, can_teleport_with_charges=True) - else: - return CharacterCapabilities(can_teleport_natively=False, can_teleport_with_charges=False) - elif override == "walk": - Logger.debug(f"override_capabilities is set to {override}") - return CharacterCapabilities(can_teleport_natively=False, can_teleport_with_charges=False) - else: - Logger.debug(f"override_capabilities is set to {override}") - return CharacterCapabilities( - can_teleport_natively="can_teleport_natively" in override, - can_teleport_with_charges="can_teleport_with_charges" in override - ) - - def discover_capabilities(self, force = False): - if IChar._CrossGameCapabilities is None or force: - capabilities = self._discover_capabilities() - self.capabilities = capabilities - Logger.info(f"Capabilities: {self.capabilities}") - self.on_capabilities_discovered(self.capabilities) - - def on_capabilities_discovered(self, capabilities: CharacterCapabilities): - pass - def pick_up_item(self, pos: tuple[float, float], item_name: str = None, prev_cast_start: float = 0) -> float: mouse.move(pos[0], pos[1]) self._click_left() @@ -275,33 +281,11 @@ def select_by_template( Logger.error(f"Wanted to select {template_type}, but could not find it") return False - def _remap_skill_hotkey(self, skill_asset, hotkey, skill_roi, expanded_skill_roi) -> bool: - x, y, w, h = skill_roi - x, y = convert_screen_to_monitor((x, y)) - mouse.move(x + w/2, y + h / 2) - self._click_left() - wait(0.3) - match = template_finder.search(skill_asset, grab(), threshold=0.84, roi=expanded_skill_roi) - if match.valid: - mouse.move(*match.center_monitor) - wait(0.3) - keyboard.send(hotkey) - wait(0.3) - self._click_left() - wait(0.3) - return True - return False - - def remap_right_skill_hotkey(self, skill_asset, hotkey) -> bool: - return self._remap_skill_hotkey(skill_asset, hotkey, Config().ui_roi["skill_right"], Config().ui_roi["skill_right_expanded"]) - def select_teleport(self) -> bool: if not self._select_skill("teleport", "right", delay = [0.15, 0.2]): return False return skills.is_right_skill_selected(["TELE_ACTIVE", "TELE_INACTIVE"]) - def can_teleport(self) -> bool: - return (self.capabilities.can_teleport_natively or self.capabilities.can_teleport_with_charges) and self.select_teleport() and skills.is_right_skill_active() def pre_move(self): pass @@ -311,7 +295,7 @@ def move(self, pos_monitor: tuple[float, float], use_tp: bool = False, force_mov start=time.perf_counter() if use_tp and self.can_teleport(): # can_teleport() activates teleport hotkey if True mouse.move(pos_monitor[0], pos_monitor[1], randomize=3, delay_factor=[factor*0.1, factor*0.14]) - self._cast_simple(skill_name="teleport", mouse_click_type="right") + self._cast_simple(skill_name="teleport") min_wait = get_cast_wait_time(self._base_class, "teleport", Config().char["fcr"]) wait(self._cast_duration, self._cast_duration + 0.02) else: @@ -400,7 +384,7 @@ def _pre_buff_cta(self) -> bool: while time.time() - start < 4: self._weapon_switch() wait(0.3, 0.35) - self._select_skill(skill = "battle_command", mouse_click_type="right", delay=(0.1, 0.2)) + self._select_skill(skill = "battle_command", delay=(0.1, 0.2)) if skills.is_right_skill_selected(["BC", "BO"]): switch_success = True break @@ -444,7 +428,7 @@ def cast_in_arc(self, ability: str, cast_pos_abs: tuple[float, float] = [0,-100] if not self._skill_hotkeys[ability]: raise ValueError(f"You did not set {ability} hotkey!") self._stand_still(True) - self._select_skill(skill = ability, mouse_click_type="right", delay=(0.02, 0.08)) + self._select_skill(skill = ability, delay=(0.02, 0.08)) target = self.vec_to_monitor(arc_spread(cast_pos_abs, spread_deg=spread_deg)) mouse.move(*target,delay_factor=[0.95, 1.05]) diff --git a/src/config.py b/src/config.py index dcf4f2af1..21c4d6a8e 100644 --- a/src/config.py +++ b/src/config.py @@ -160,6 +160,7 @@ def load_data(self): "inventory_screen": self._select_val("char", "inventory_screen"), "teleport": self._select_val("char", "teleport"), "stand_still": self._select_val("char", "stand_still"), + "use_charged_teleport": bool(int(self._select_val("char", "use_charged_teleport"))), "force_move": self._select_val("char", "force_move"), "num_loot_columns": int(self._select_val("char", "num_loot_columns")), "take_health_potion": float(self._select_val("char", "take_health_potion")), @@ -305,7 +306,6 @@ def load_data(self): "window_client_area_offset": tuple(map(int, Config()._select_val("advanced_options", "window_client_area_offset").split(","))), "ocr_during_pickit": bool(int(self._select_val("advanced_options", "ocr_during_pickit"))), "launch_options": self._select_val("advanced_options", "launch_options").replace("", only_lowercase_letters(self.general["name"].lower())), - "override_capabilities": _default_iff(Config()._select_optional("advanced_options", "override_capabilities"), ""), } self.colors = {} diff --git a/src/ui/skills.py b/src/ui/skills.py index 2ef618e25..02aad7e99 100644 --- a/src/ui/skills.py +++ b/src/ui/skills.py @@ -7,7 +7,7 @@ from screen import grab from config import Config import template_finder -from ui_manager import wait_until_visible, ScreenObjects +from ui_manager import is_visible, wait_until_visible, ScreenObjects from d2r_image import ocr RIGHT_SKILL_ROI = [ @@ -32,8 +32,7 @@ def has_tps() -> bool: :return: Returns True if botty has town portals available. False otherwise """ if Config().char["town_portal"]: - keyboard.send(Config().char["town_portal"]) - if not (tps_remain := wait_until_visible(ScreenObjects.TownPortalSkill, timeout=4).valid): + if not (tps_remain := is_visible(ScreenObjects.BarTownPortalSkill)): Logger.warning("You are out of tps") if Config().general["info_screenshots"]: cv2.imwrite("./log/screenshots/info/debug_out_of_tps_" + time.strftime("%Y%m%d_%H%M%S") + ".png", grab()) diff --git a/src/ui_manager.py b/src/ui_manager.py index 09bff9406..686f5a62c 100644 --- a/src/ui_manager.py +++ b/src/ui_manager.py @@ -159,6 +159,12 @@ class ScreenObjects: best_match=True, threshold=0.79 ) + BarTownPortalSkill=ScreenObject( + ref=["BAR_TP_ACTIVE", "BAR_TP_INACTIVE"], + roi="active_skills_bar", + best_match=True, + threshold=0.79 + ) RepairBtn=ScreenObject( ref="REPAIR_BTN", roi="repair_btn", From 9f24b9bd03203f28318e345383604b608ab14723 Mon Sep 17 00:00:00 2001 From: mgleed Date: Sat, 18 Jun 2022 22:17:41 -0400 Subject: [PATCH 24/44] wip --- src/char/i_char.py | 39 ++++++++++++++++------ src/ui/skills.py | 82 ++++++++++++++++++++++++++++------------------ 2 files changed, 79 insertions(+), 42 deletions(-) diff --git a/src/char/i_char.py b/src/char/i_char.py index 09a5ce0b8..d57c3f9ed 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -35,6 +35,7 @@ def __init__(self, skill_hotkeys: dict): self.damage_scaling = float(Config().char.get("damage_scaling", 1.0)) self._use_safer_routines = Config().char["safer_routines"] self._base_class = "" + self.can_teleport = "" """ MOUSE AND KEYBOARD METHODS @@ -100,17 +101,35 @@ def _get_hotkey(self, skill: str) -> str | None: CAPABILITIES METHODS """ - - def can_teleport(self) -> bool: - if Config().char["use_charged_teleport"]: - if not self._get_hotkey("teleport"): - raise Exception("No hotkey for teleport even though param.ini 'use_charged_teleport' is set to True") - else: - - - + """ + 1. player can teleport natively + a. and has teleport bound and is visible + b. and does not have teleport hotkey bound + 2. player can teleport with charges + a. and has teleport bound and is visible + b. and does not have teleport hotkey bound + c. and has run out of teleport charges + 3. player can't teleport + """ + # 3. player can't teleport + if Config().char["use_charged_teleport"] and not self._get_hotkey("teleport"): + Logger.error("No hotkey for teleport even though param.ini 'use_charged_teleport' is set to True") + return False + if not self._get_hotkey("teleport"): + return False + # 2. player can teleport with charges + if Config().char["use_charged_teleport"]: + if not skills.is_skill_bound(["BAR_TP_ACTIVE", "BAR_TP_INACTIVE"]): + # 2c. + Logger.debug("can_teleport: player can teleport with charges, but has no teleport bound. Likely needs repair.") + return False + # 2a. + return True + # 1. player can teleport natively + if not Config().char["use_charged_teleport"] and skills.is_skill_bound(["BAR_TP_ACTIVE", "BAR_TP_INACTIVE"]): + return True return (self.capabilities.can_teleport_natively or self.capabilities.can_teleport_with_charges) and self.select_teleport() and skills.is_right_skill_active() @@ -202,7 +221,7 @@ def _remap_skill_hotkey(self, skill_asset, hotkey, skill_roi, expanded_skill_roi return False def remap_right_skill_hotkey(self, skill_asset, hotkey) -> bool: - return self._remap_skill_hotkey(skill_asset, hotkey, Config().ui_roi["skill_right"], Config().ui_roi["skill_right_expanded"]) + return self._remap_skill_hotkey(skill_asset, hotkey, Config().ui_roi["skill_right"], Config().ui_roi["skill_speed_bar"]) """ GLOBAL SKILLS diff --git a/src/ui/skills.py b/src/ui/skills.py index 02aad7e99..26fb759f5 100644 --- a/src/ui/skills.py +++ b/src/ui/skills.py @@ -10,6 +10,14 @@ from ui_manager import is_visible, wait_until_visible, ScreenObjects from d2r_image import ocr +LEFT_SKILL_ROI = [ + Config().ui_pos["skill_left_x"] - (Config().ui_pos["skill_width"] // 2), + Config().ui_pos["skill_y"] - (Config().ui_pos["skill_height"] // 2), + Config().ui_pos["skill_width"], + Config().ui_pos["skill_height"] +] + + RIGHT_SKILL_ROI = [ Config().ui_pos["skill_right_x"] - (Config().ui_pos["skill_width"] // 2), Config().ui_pos["skill_y"] - (Config().ui_pos["skill_height"] // 2), @@ -17,52 +25,62 @@ Config().ui_pos["skill_height"] ] -def is_left_skill_selected(template_list: list[str]) -> bool: + +def _is_skill_active(roi: list[int]) -> bool: """ - :return: Bool if skill is currently the selected skill on the left skill slot. + :return: Bool if skill is currently active in the desired ROI + """ + img = cut_roi(grab(), roi) + avg = np.average(img) + return avg > 75.0 + +def is_skill_active(roi: list[int]) -> bool: + return _is_skill_active(roi) + +def is_right_skill_active() -> bool: + return _is_skill_active(roi = RIGHT_SKILL_ROI) + +def is_left_skill_active() -> bool: + return _is_skill_active(roi = LEFT_SKILL_ROI) + + +def _is_skill_bound(template_list: list[str] | str, roi: list[int]) -> bool: """ - skill_left_ui_roi = Config().ui_roi["skill_left"] + :return: Bool if skill is currently the selected skill on the right skill slot. + """ + if isinstance(template_list, str): + template_list = [template_list] for template in template_list: - if template_finder.search(template, grab(), threshold=0.84, roi=skill_left_ui_roi).valid: + if template_finder.search(template, grab(), threshold=0.84, roi=roi).valid: return True return False -def has_tps() -> bool: +def is_skill_bound(template_list: list[str] | str, roi: list[int] = Config().ui_roi["active_skills_bar"]) -> bool: """ - :return: Returns True if botty has town portals available. False otherwise + :return: Bool if skill is visible in ROI, defaults to active skills bar. """ - if Config().char["town_portal"]: - if not (tps_remain := is_visible(ScreenObjects.BarTownPortalSkill)): - Logger.warning("You are out of tps") - if Config().general["info_screenshots"]: - cv2.imwrite("./log/screenshots/info/debug_out_of_tps_" + time.strftime("%Y%m%d_%H%M%S") + ".png", grab()) - return tps_remain - return False + return _is_skill_bound(template_list, roi) -def select_tp(tp_hotkey): - if tp_hotkey and not is_right_skill_selected( - ["TELE_ACTIVE", "TELE_INACTIVE"]): - keyboard.send(tp_hotkey) - wait(0.1, 0.2) - return is_right_skill_selected(["TELE_ACTIVE", "TELE_INACTIVE"]) - -def is_right_skill_active() -> bool: +def is_right_skill_bound(template_list: list[str] | str) -> bool: """ - :return: Bool if skill is red/available or not. Skill must be selected on right skill slot when calling the function. + :return: Bool if skill is currently the selected skill on the right skill slot. """ - img = cut_roi(grab(), RIGHT_SKILL_ROI) - avg = np.average(img) - return avg > 75.0 + return _is_skill_bound(template_list, RIGHT_SKILL_ROI) -def is_right_skill_selected(template_list: list[str]) -> bool: +def is_left_skill_bound(template_list: list[str] | str) -> bool: """ - :return: Bool if skill is currently the selected skill on the right skill slot. + :return: Bool if skill is currently the selected skill on the left skill slot. """ - skill_right_ui_roi = Config().ui_roi["skill_right"] - for template in template_list: - if template_finder.search(template, grab(), threshold=0.84, roi=skill_right_ui_roi).valid: - return True - return False + return _is_skill_bound(template_list, LEFT_SKILL_ROI) + + +def has_tps() -> bool: + if not (tps_remain := is_visible(ScreenObjects.BarTownPortalSkill)): + Logger.warning("You are out of tps") + if Config().general["info_screenshots"]: + cv2.imwrite("./log/screenshots/info/debug_out_of_tps_" + time.strftime("%Y%m%d_%H%M%S") + ".png", grab()) + return tps_remain + def get_skill_charges(img: np.ndarray = None): if img is None: From ebf9ca56c6a8e3a8a7ce6a8dcc68e1eacfef53dd Mon Sep 17 00:00:00 2001 From: mgleed Date: Sun, 19 Jun 2022 11:07:47 -0400 Subject: [PATCH 25/44] save --- config/game.ini | 2 +- config/params.ini | 2 +- src/bot.py | 4 +- src/char/bone_necro.py | 38 +++--- src/char/i_char.py | 263 ++++++++++++++++++++------------------- src/char/poison_necro.py | 18 +-- src/pather.py | 3 +- src/screen.py | 32 +++++ src/ui/skills.py | 41 ++++-- src/utils/misc.py | 49 +++++--- 10 files changed, 263 insertions(+), 189 deletions(-) diff --git a/config/game.ini b/config/game.ini index f0909d957..5770c4fe8 100644 --- a/config/game.ini +++ b/config/game.ini @@ -107,7 +107,7 @@ repair_btn=318,473,90,80 left_inventory=35,92,378,378 right_inventory=866,348,379,152 skill_right=664,673,41,41 -skill_right_expanded=655,375,385,255 +skill_speed_bar=655,375,385,255 skill_left=574,673,41,41 main_menu_top_left=0,0,100,100 gamebar_anchor=600,650,90,90 diff --git a/config/params.ini b/config/params.ini index 72bf10108..48f2bdce2 100644 --- a/config/params.ini +++ b/config/params.ini @@ -317,6 +317,6 @@ logg_lvl=debug message_body_template={{"content": "{msg}"}} message_headers= ocr_during_pickit=0 -pathing_delay_factor=4 +pathing_delay_factor=0 ;If you want to control Hyper-V window from host use 0,51 here window_client_area_offset=0,0 diff --git a/src/bot.py b/src/bot.py index c90076a97..bc45dd23b 100644 --- a/src/bot.py +++ b/src/bot.py @@ -266,10 +266,10 @@ def on_start_from_town(self): belt.fill_up_belt_from_inventory(Config().char["num_loot_columns"]) if self._char.capabilities is None or not self._char.capabilities.can_teleport_natively: self._char.discover_capabilities() - if corpse_present and self._char.capabilities.can_teleport_with_charges and not self._char.select_teleport(): + if corpse_present and self._char.capabilities.can_teleport_with_charges: keybind = Config().char["teleport"] Logger.info(f"Teleport keybind is lost upon death. Rebinding teleport to '{keybind}'") - self._char.remap_right_skill_hotkey("TELE_ACTIVE", keybind) + skills.remap_right_skill_hotkey("TELE_ACTIVE", keybind) # Run /nopickup command to avoid picking up stuff on accident if Config().char["enable_no_pickup"] and (not self._ran_no_pickup and not self._game_stats._nopickup_active): diff --git a/src/char/bone_necro.py b/src/char/bone_necro.py index a3b4b3901..50feb79eb 100644 --- a/src/char/bone_necro.py +++ b/src/char/bone_necro.py @@ -128,9 +128,9 @@ def _cast_circle(self, cast_dir: tuple[float,float],cast_start_angle: float=0.0, def kill_pindle(self) -> bool: for pos in [[200,-100], [-150,100] ]: self.bone_wall(pos, spray=10) - self.cast_in_arc(ability='bone_spear', cast_pos_abs=[110,-50], spread_deg=15, time_in_s=5) + self._cast_in_arc(skill_name='bone_spear', cast_pos_abs=[110,-50], spread_deg=15, time_in_s=5) self._corpse_explosion([165,-75], spray=100, cast_count=5) - self.cast_in_arc(ability='bone_spirit', cast_pos_abs=[110,-50], spread_deg=15, time_in_s=2.5) + self._cast_in_arc(skill_name='bone_spirit', cast_pos_abs=[110,-50], spread_deg=15, time_in_s=2.5) self._pather.traverse_nodes_fixed("pindle_end", self) return True @@ -138,10 +138,10 @@ def kill_eldritch(self) -> bool: #build an arc of bone walls for pos in [[50,-200], [-200,-175], [-350,50]]: self.bone_wall(pos, spray=10) - self.cast_in_arc(ability='teeth', cast_pos_abs=[-20,-150], spread_deg=15, time_in_s=3) - self.cast_in_arc(ability='bone_spear', cast_pos_abs=[-20,-150], spread_deg=15, time_in_s=2) + self._cast_in_arc(skill_name='teeth', cast_pos_abs=[-20,-150], spread_deg=15, time_in_s=3) + self._cast_in_arc(skill_name='bone_spear', cast_pos_abs=[-20,-150], spread_deg=15, time_in_s=2) self._corpse_explosion([-20,-240], spray=100, cast_count=5) - self.cast_in_arc(ability='bone_spirit', cast_pos_abs=[0,-80], spread_deg=60, time_in_s=2.5) + self._cast_in_arc(skill_name='bone_spirit', cast_pos_abs=[0,-80], spread_deg=60, time_in_s=2.5) self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, timeout=0.6, force_tp=True) self.bone_armor() return True @@ -149,12 +149,12 @@ def kill_eldritch(self) -> bool: def kill_shenk(self) -> bool: self._cast_circle(cast_dir=[1,1],cast_start_angle=0,cast_end_angle=360,cast_div=5,cast_spell='bone_wall',delay=.8,radius=100, hold=False) - self.cast_in_arc(ability='teeth', cast_pos_abs=[160,75], spread_deg=360, time_in_s=6) - self.cast_in_arc(ability='teeth', cast_pos_abs=[160,75], spread_deg=30, time_in_s=2) + self._cast_in_arc(skill_name='teeth', cast_pos_abs=[160,75], spread_deg=360, time_in_s=6) + self._cast_in_arc(skill_name='teeth', cast_pos_abs=[160,75], spread_deg=30, time_in_s=2) self._corpse_explosion([0,0], spray=200, cast_count=4) - self.cast_in_arc(ability='bone_spear', cast_pos_abs=[160,75], spread_deg=30, time_in_s=3) + self._cast_in_arc(skill_name='bone_spear', cast_pos_abs=[160,75], spread_deg=30, time_in_s=3) self._corpse_explosion([240,112], spray=200, cast_count=8) - self.cast_in_arc(ability='bone_spirit', cast_pos_abs=[80,37], spread_deg=60, time_in_s=3) + self._cast_in_arc(skill_name='bone_spirit', cast_pos_abs=[80,37], spread_deg=60, time_in_s=3) self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=1.0) return True @@ -167,13 +167,13 @@ def kill_council(self) -> bool: #moat on right side, encircle with bone walls on the other 3 sides for pos in [[100,-100], [-125,-25], [-50,100]]: self.bone_wall(pos, spray=10) - self.cast_in_arc(ability='teeth', cast_pos_abs=[40,-100], spread_deg=180, time_in_s=5) - self.cast_in_arc(ability='bone_spear', cast_pos_abs=[40,-100], spread_deg=120, time_in_s=8) + self._cast_in_arc(skill_name='teeth', cast_pos_abs=[40,-100], spread_deg=180, time_in_s=5) + self._cast_in_arc(skill_name='bone_spear', cast_pos_abs=[40,-100], spread_deg=120, time_in_s=8) self._corpse_explosion([40,-100], spray=200, cast_count=8) - self.cast_in_arc(ability='bone_spirit', cast_pos_abs=[20,-50], spread_deg=180, time_in_s=5) + self._cast_in_arc(skill_name='bone_spirit', cast_pos_abs=[20,-50], spread_deg=180, time_in_s=5) self._corpse_explosion([40,-100], spray=200, cast_count=8) - self.cast_in_arc(ability='bone_spirit', cast_pos_abs=[20,-50], spread_deg=360, time_in_s=4) + self._cast_in_arc(skill_name='bone_spirit', cast_pos_abs=[20,-50], spread_deg=360, time_in_s=4) return True @@ -187,14 +187,14 @@ def kill_nihlathak(self, end_nodes: list[int]) -> bool: cast_pos_abs = np.array(nihlathak_pos_abs)*.2 self._cast_circle(cast_dir=[1,1],cast_start_angle=0,cast_end_angle=360,cast_div=5,cast_spell='bone_wall',delay=.8,radius=100, hold=False) self._bone_armor() - self.cast_in_arc(ability='teeth', cast_pos_abs=cast_pos_abs, spread_deg=150, time_in_s=5) + self._cast_in_arc(skill_name='teeth', cast_pos_abs=cast_pos_abs, spread_deg=150, time_in_s=5) self._bone_armor() self._corpse_explosion(cast_pos_abs, spray=200, cast_count=8) - self.cast_in_arc(ability='bone_spear', cast_pos_abs=cast_pos_abs, spread_deg=10, time_in_s=5) + self._cast_in_arc(skill_name='bone_spear', cast_pos_abs=cast_pos_abs, spread_deg=10, time_in_s=5) self._bone_armor() self._corpse_explosion(np.array(nihlathak_pos_abs)*.75, spray=200, cast_count=10) - self.cast_in_arc(ability='bone_spirit', cast_pos_abs=cast_pos_abs, spread_deg=30, time_in_s=2.5) + self._cast_in_arc(skill_name='bone_spirit', cast_pos_abs=cast_pos_abs, spread_deg=30, time_in_s=2.5) # Move to items wait(self._cast_duration, self._cast_duration + 0.2) @@ -203,10 +203,10 @@ def kill_nihlathak(self, end_nodes: list[int]) -> bool: def kill_summoner(self) -> bool: # Attack - self.cast_in_arc(ability='teeth', cast_pos_abs=[30,30], spread_deg=360, time_in_s=3) - self.cast_in_arc(ability='bone_spirit', cast_pos_abs=[30,30], spread_deg=360, time_in_s=2) + self._cast_in_arc(skill_name='teeth', cast_pos_abs=[30,30], spread_deg=360, time_in_s=3) + self._cast_in_arc(skill_name='bone_spirit', cast_pos_abs=[30,30], spread_deg=360, time_in_s=2) self._corpse_explosion([0,0], spray=200, cast_count=8) - self.cast_in_arc(ability='bone_spirit', cast_pos_abs=[30,30], spread_deg=360, time_in_s=2) + self._cast_in_arc(skill_name='bone_spirit', cast_pos_abs=[30,30], spread_deg=360, time_in_s=2) return True diff --git a/src/char/i_char.py b/src/char/i_char.py index d57c3f9ed..74881ee0b 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -12,11 +12,11 @@ from config import Config from item import consumables from logger import Logger -from screen import grab, convert_monitor_to_screen, convert_screen_to_abs, convert_abs_to_monitor, convert_screen_to_monitor, convert_abs_to_screen +from screen import grab, convert_monitor_to_screen, convert_screen_to_abs, convert_abs_to_monitor, convert_screen_to_monitor, convert_abs_to_screen, ensure_coordinates_in_screen from ui import skills from ui_manager import detect_screen_object, ScreenObjects, wait_for_update, is_visible, wait_until_visible from utils.custom_mouse import mouse -from utils.misc import wait, cut_roi, is_in_roi, color_filter, arc_spread +from utils.misc import wait, cut_roi, is_in_roi, color_filter, arc_spread, random_point_in_circle import template_finder class IChar: @@ -36,6 +36,7 @@ def __init__(self, skill_hotkeys: dict): self._use_safer_routines = Config().char["safer_routines"] self._base_class = "" self.can_teleport = "" + self.capabilities = None """ MOUSE AND KEYBOARD METHODS @@ -101,7 +102,7 @@ def _get_hotkey(self, skill: str) -> str | None: CAPABILITIES METHODS """ - def can_teleport(self) -> bool: + def _get_teleport_type(self) -> str: """ 1. player can teleport natively a. and has teleport bound and is visible @@ -116,22 +117,41 @@ def can_teleport(self) -> bool: # 3. player can't teleport if Config().char["use_charged_teleport"] and not self._get_hotkey("teleport"): Logger.error("No hotkey for teleport even though param.ini 'use_charged_teleport' is set to True") - return False + return "walk" if not self._get_hotkey("teleport"): - return False + return "walk" # 2. player can teleport with charges if Config().char["use_charged_teleport"]: if not skills.is_skill_bound(["BAR_TP_ACTIVE", "BAR_TP_INACTIVE"]): # 2c. Logger.debug("can_teleport: player can teleport with charges, but has no teleport bound. Likely needs repair.") - return False # 2a. - return True + return "charges" # 1. player can teleport natively if not Config().char["use_charged_teleport"] and skills.is_skill_bound(["BAR_TP_ACTIVE", "BAR_TP_INACTIVE"]): - return True + return "native" + return "walk" + + @staticmethod + def _teleport_active(): + return skills.is_teleport_active() + + def can_teleport(self): + return (self.capabilities.can_teleport_natively or self.capabilities.can_teleport_with_charges) and self._teleport_active() - return (self.capabilities.can_teleport_natively or self.capabilities.can_teleport_with_charges) and self.select_teleport() and skills.is_right_skill_active() + def _discover_capabilities(self) -> CharacterCapabilities: + type = self._get_teleport_type() + return CharacterCapabilities(can_teleport_natively=(type == "native"), can_teleport_with_charges=(type == "charges")) + + def discover_capabilities(self): + if IChar._CrossGameCapabilities is None: + capabilities = self._discover_capabilities() + self.capabilities = capabilities + Logger.info(f"Capabilities: {self.capabilities}") + self.on_capabilities_discovered(self.capabilities) + + def on_capabilities_discovered(self, capabilities: CharacterCapabilities): + pass """ SKILL / CASTING METHODS @@ -178,12 +198,21 @@ def _cast_simple(self, skill_name: str, duration: float | list | tuple | None = self._stand_still(False) return True - def _cast_at_position(self, skill_name: str, cast_pos_abs: tuple[float, float], spray: int, duration: float | list | tuple | None = None) -> bool: + def _cast_at_position(self, skill_name: str, cast_pos_abs: tuple[float, float], spray: float = 0, spread_deg: float = 0, duration: float | list | tuple | None = None) -> bool: """ Casts a skill at a given position. """ + def _adjust_position(cast_pos_abs, spray, spread_deg): + if spread_deg: + pos = vec_to_monitor(arc_spread(cast_pos_abs, spread_deg=spread_deg)) + if spray: + pos = random_point_in_circle(pos = cast_pos_abs, r = spray) + return ensure_coordinates_in_screen(pos, "abs") + + if not self._get_hotkey(skill_name): return False + if cast_pos_abs: x, y = cast_pos_abs if spray: @@ -203,25 +232,31 @@ def _cast_left_with_aura(self, skill_name: str, cast_pos_abs: tuple[float, float self._activate_aura(aura) return self._cast_at_position(skill_name=skill_name, cast_pos_abs=cast_pos_abs, spray=spray, mouse_click_type="left", duration=duration) - def _remap_skill_hotkey(self, skill_asset, hotkey, skill_roi, expanded_skill_roi) -> bool: - x, y, w, h = skill_roi - x, y = convert_screen_to_monitor((x, y)) - mouse.move(x + w/2, y + h / 2) - self._click_left() - wait(0.3) - match = template_finder.search(skill_asset, grab(), threshold=0.84, roi=expanded_skill_roi) - if match.valid: - mouse.move(*match.center_monitor) - wait(0.3) - keyboard.send(hotkey) - wait(0.3) - self._click_left() - wait(0.3) - return True - return False + def _cast_in_arc(self, skill_name: str, cast_pos_abs: tuple[float, float] = [0,-100], time_in_s: float = 3, spread_deg: float = 10, hold=True): + #scale cast time by damage_scaling + time_in_s *= self.damage_scaling + Logger.debug(f'Casting {skill_name} for {time_in_s:.02f}s at {cast_pos_abs} with {spread_deg}°') + if not self._skill_hotkeys[skill_name]: + raise ValueError(f"You did not set {skill_name} hotkey!") + self._stand_still(True) + + target = self.vec_to_monitor(arc_spread(cast_pos_abs, spread_deg=spread_deg)) + mouse.move(*target,delay_factor=[0.95, 1.05]) + if hold: + self._hold_click("right", True) + start = time.time() + while (time.time() - start) < time_in_s: + target = self.vec_to_monitor(arc_spread(cast_pos_abs, spread_deg=spread_deg)) + if hold: + mouse.move(*target, delay_factor=[3, 8]) + if not hold: + mouse.move(*target, delay_factor=[.2, .4]) + self._click_right(0.04) + wait(self._cast_duration, self._cast_duration) - def remap_right_skill_hotkey(self, skill_asset, hotkey) -> bool: - return self._remap_skill_hotkey(skill_asset, hotkey, Config().ui_roi["skill_right"], Config().ui_roi["skill_speed_bar"]) + if hold: + self._hold_click("right", False) + self._stand_still(False) """ GLOBAL SKILLS @@ -247,15 +282,17 @@ def _weapon_switch(self): """ CHARACTER ACTIONS AND MOVEMENT METHODS """ + def _force_move(self): + self._keypress(self._get_hotkey("force_move")) def _stand_still(self, enable: bool): if enable: if not self._standing_still: - keyboard.send(Config().char["stand_still"], do_release=False) + keyboard.send(self._get_hotkey("stand_still"), do_release=False) self._standing_still = True else: if self._standing_still: - keyboard.send(Config().char["stand_still"], do_press=False) + keyboard.send(self._get_hotkey("stand_still"), do_press=False) self._standing_still = False def pick_up_item(self, pos: tuple[float, float], item_name: str = None, prev_cast_start: float = 0) -> float: @@ -264,79 +301,18 @@ def pick_up_item(self, pos: tuple[float, float], item_name: str = None, prev_cas wait(0.25, 0.35) return prev_cast_start - def select_by_template( - self, - template_type: str | list[str], - success_func: Callable = None, - timeout: float = 8, - threshold: float = 0.68, - telekinesis: bool = False - ) -> bool: - """ - 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 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 - if is_visible(ScreenObjects.WaypointLabel): - keyboard.send("esc") - start = time.time() - while timeout is None or (time.time() - start) < timeout: - template_match = template_finder.search(template_type, grab(), threshold=threshold) - if template_match.valid: - Logger.debug(f"Select {template_match.name} ({template_match.score*100:.1f}% confidence)") - mouse.move(*template_match.center_monitor) - wait(0.2, 0.3) - self._click_left() - # 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 - Logger.error(f"Wanted to select {template_type}, but could not find it") - return False - - def select_teleport(self) -> bool: - if not self._select_skill("teleport", "right", delay = [0.15, 0.2]): - return False - return skills.is_right_skill_selected(["TELE_ACTIVE", "TELE_INACTIVE"]) - - def pre_move(self): pass - def move(self, pos_monitor: tuple[float, float], use_tp: bool = False, force_move: bool = False): + def _teleport_to_position(self, pos_monitor: tuple[float, float]): factor = Config().advanced_options["pathing_delay_factor"] - start=time.perf_counter() - if use_tp and self.can_teleport(): # can_teleport() activates teleport hotkey if True - mouse.move(pos_monitor[0], pos_monitor[1], randomize=3, delay_factor=[factor*0.1, factor*0.14]) - self._cast_simple(skill_name="teleport") - min_wait = get_cast_wait_time(self._base_class, "teleport", Config().char["fcr"]) - wait(self._cast_duration, self._cast_duration + 0.02) - else: - # in case we want to walk we actually want to move a bit before the point cause d2r will always "overwalk" - pos_screen = convert_monitor_to_screen(pos_monitor) - pos_abs = convert_screen_to_abs(pos_screen) - dist = math.dist(pos_abs, (0, 0)) - min_wd = max(10, Config().ui_pos["min_walk_dist"]) - max_wd = random.randint(int(Config().ui_pos["max_walk_dist"] * 0.65), Config().ui_pos["max_walk_dist"]) - adjust_factor = max(max_wd, min(min_wd, dist - 50)) / max(min_wd, dist) - pos_abs = [int(pos_abs[0] * adjust_factor), int(pos_abs[1] * adjust_factor)] - x, y = convert_abs_to_monitor(pos_abs) - mouse.move(x, y, randomize=5, delay_factor=[factor*0.1, factor*0.14]) - wait(0.012, 0.02) - if force_move: - keyboard.send(Config().char["force_move"]) - else: - self._click_left() + mouse.move(pos_monitor[0], pos_monitor[1], randomize=3, delay_factor=[(2+factor)/25, (4+factor)/25]) + wait(0.012, 0.02) + self._keypress(self._get_hotkey("teleport")) - def walk(self, pos_monitor: tuple[float, float], force_move: bool = False): + def _walk_to_position(self, pos_monitor: tuple[float, float], force_move: bool = False): factor = Config().advanced_options["pathing_delay_factor"] - # in case we want to walk we actually want to move a bit before the point cause d2r will always "overwalk" + # in case we want to walk we actually want to move a bit before the point cause d2r will always "overwalk" pos_screen = convert_monitor_to_screen(pos_monitor) pos_abs = convert_screen_to_abs(pos_screen) dist = math.dist(pos_abs, (0, 0)) @@ -345,13 +321,26 @@ def walk(self, pos_monitor: tuple[float, float], force_move: bool = False): adjust_factor = max(max_wd, min(min_wd, dist - 50)) / max(min_wd, dist) pos_abs = [int(pos_abs[0] * adjust_factor), int(pos_abs[1] * adjust_factor)] x, y = convert_abs_to_monitor(pos_abs) - mouse.move(x, y, randomize=5, delay_factor=[factor*0.1, factor*0.14]) + mouse.move(x, y, randomize=3, delay_factor=[(2+factor)/25, (4+factor)/25]) wait(0.012, 0.02) if force_move: - keyboard.send(Config().char["force_move"]) + self._force_move() else: self._click_left() + def move(self, pos_monitor: tuple[float, float], use_tp: bool = False, force_move: bool = False, last_move_time: float = time.time()) -> float: + factor = Config().advanced_options["pathing_delay_factor"] + if use_tp and self.can_teleport(): # can_teleport() activates teleport hotkey if True + # 7 frames is the fastest that teleport can be casted with 200 fcr on sorc + self._teleport_to_position(pos_monitor) + min_wait = get_cast_wait_time(self._base_class, "teleport", Config().char["fcr"]) + factor/25 + # if there's still time remaining in cooldown, wait + while time.time() - last_move_time < min_wait: + wait(0.02) + else: + self._walk_to_position(pos_monitor = pos_monitor, force_move=force_move) + return time.time() + def tp_town(self) -> bool: # will check if tp is available and select the skill if not skills.has_tps(): @@ -432,6 +421,50 @@ def _pre_buff_cta(self) -> bool: wait(0.5) return elapsed + def pre_buff(self): + pass + + + """ + OTHER METHODS + """ + + def select_by_template( + self, + template_type: str | list[str], + success_func: Callable = None, + timeout: float = 8, + threshold: float = 0.68, + telekinesis: bool = False + ) -> bool: + """ + 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 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 + if is_visible(ScreenObjects.WaypointLabel): + keyboard.send("esc") + start = time.time() + while timeout is None or (time.time() - start) < timeout: + template_match = template_finder.search(template_type, grab(), threshold=threshold) + if template_match.valid: + Logger.debug(f"Select {template_match.name} ({template_match.score*100:.1f}% confidence)") + mouse.move(*template_match.center_monitor) + wait(0.2, 0.3) + self._click_left() + # 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 + Logger.error(f"Wanted to select {template_type}, but could not find it") + return False + def vec_to_monitor(self, target: tuple[float, float]) -> tuple[float, float]: circle_pos_screen = self._pather._adjust_abs_range_to_screen(target) @@ -440,35 +473,9 @@ def vec_to_monitor(self, target: tuple[float, float]) -> tuple[float, float]: def _lerp(self, a: float, b: float, f:float) -> float: return a + f * (b - a) - def cast_in_arc(self, ability: str, cast_pos_abs: tuple[float, float] = [0,-100], time_in_s: float = 3, spread_deg: float = 10, hold=True): - #scale cast time by damage_scaling - time_in_s *= self.damage_scaling - Logger.debug(f'Casting {ability} for {time_in_s:.02f}s at {cast_pos_abs} with {spread_deg}°') - if not self._skill_hotkeys[ability]: - raise ValueError(f"You did not set {ability} hotkey!") - self._stand_still(True) - self._select_skill(skill = ability, delay=(0.02, 0.08)) - - target = self.vec_to_monitor(arc_spread(cast_pos_abs, spread_deg=spread_deg)) - mouse.move(*target,delay_factor=[0.95, 1.05]) - if hold: - self._hold_click("right", True) - start = time.time() - while (time.time() - start) < time_in_s: - target = self.vec_to_monitor(arc_spread(cast_pos_abs, spread_deg=spread_deg)) - if hold: - mouse.move(*target, delay_factor=[3, 8]) - if not hold: - mouse.move(*target, delay_factor=[.2, .4]) - self._click_right(0.04) - wait(self._cast_duration, self._cast_duration) - - if hold: - self._hold_click("right", False) - self._stand_still(False) - - def pre_buff(self): - pass + """ + KILL ROUTINES + """ def kill_pindle(self) -> bool: raise ValueError("Pindle is not implemented!") diff --git a/src/char/poison_necro.py b/src/char/poison_necro.py index 2f700199b..5ebd4684d 100644 --- a/src/char/poison_necro.py +++ b/src/char/poison_necro.py @@ -383,14 +383,14 @@ def _cast_circle(self, cast_dir: tuple[float,float],cast_start_angle: float=0.0, def kill_pindle(self) -> bool: pos_m = screen.convert_abs_to_monitor((0, 30)) - self.walk(pos_m, force_move=True) + self._walk_to_position(pos_m, force_move=True) self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=4,cast_v_div=3,cast_spell='lower_res',delay=1.0) self.poison_nova(3.0) pos_m = screen.convert_abs_to_monitor((0, -50)) self.pre_move() self.move(pos_m, force_move=True) pos_m = screen.convert_abs_to_monitor((50, 0)) - self.walk(pos_m, force_move=True) + self._walk_to_position(pos_m, force_move=True) self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=120,cast_div=5,cast_v_div=2,cast_spell='corpse_explosion',delay=1.1,offset=1.8) self.poison_nova(3.0) return True @@ -432,7 +432,7 @@ def kill_eldritch(self) -> bool: def kill_shenk(self) -> bool: self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=1.0) #pos_m = self._screen.convert_abs_to_monitor((50, 0)) - #self.walk(pos_m, force_move=True) + #self._walk_to_position(pos_m, force_move=True) self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=4,cast_v_div=3,cast_spell='lower_res',delay=1.0) self.poison_nova(3.0) pos_m = screen.convert_abs_to_monitor((0, -50)) @@ -458,7 +458,7 @@ def kill_council(self) -> bool: self.move(pos_m, force_move=True) self._pather.traverse_nodes([229], self, timeout=2.5, force_tp=True) pos_m = screen.convert_abs_to_monitor((50, 0)) - self.walk(pos_m, force_move=True) + self._walk_to_position(pos_m, force_move=True) #self._lower_res((-50, 0), spray=10) self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=4,cast_v_div=3,cast_spell='lower_res',delay=1.0) self.poison_nova(2.0) @@ -467,7 +467,7 @@ def kill_council(self) -> bool: self.pre_move() self.move(pos_m, force_move=True) pos_m = screen.convert_abs_to_monitor((30, -50)) - self.walk(pos_m, force_move=True) + self._walk_to_position(pos_m, force_move=True) self.poison_nova(2.0) #self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=120,cast_div=2,cast_v_div=1,cast_spell='corpse_explosion',delay=3.0,offset=1.8) #wait(self._cast_duration, self._cast_duration +.2) @@ -479,7 +479,7 @@ def kill_council(self) -> bool: self.move(pos_m, force_move=True) self._pather.traverse_nodes([226], self, timeout=2.5, force_tp=True) pos_m = screen.convert_abs_to_monitor((0, 30)) - self.walk(pos_m, force_move=True) + self._walk_to_position(pos_m, force_move=True) self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=4,cast_v_div=3,cast_spell='lower_res',delay=1.0) wait(0.5) self.poison_nova(4.0) @@ -497,7 +497,7 @@ def kill_council(self) -> bool: self.move(pos_m, force_move=True) self._pather.traverse_nodes([229], self, timeout=2.5, force_tp=True) pos_m = screen.convert_abs_to_monitor((20, -50)) - self.walk(pos_m, force_move=True) + self._walk_to_position(pos_m, force_move=True) self.poison_nova(2.0) pos_m = screen.convert_abs_to_monitor((50, 0)) self.pre_move() @@ -514,7 +514,7 @@ def kill_nihlathak(self, end_nodes: list[int]) -> bool: # Move close to nihlathak self._pather.traverse_nodes(end_nodes, self, timeout=0.8) pos_m = screen.convert_abs_to_monitor((20, 20)) - self.walk(pos_m, force_move=True) + self._walk_to_position(pos_m, force_move=True) self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=4,cast_v_div=3,cast_spell='lower_res',delay=1.0) self.poison_nova(3.0) pos_m = screen.convert_abs_to_monitor((50, 0)) @@ -528,7 +528,7 @@ def kill_nihlathak(self, end_nodes: list[int]) -> bool: def kill_summoner(self) -> bool: # Attack pos_m = screen.convert_abs_to_monitor((0, 30)) - self.walk(pos_m, force_move=True) + self._walk_to_position(pos_m, force_move=True) self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=4,cast_v_div=3,cast_spell='lower_res',delay=1.0) wait(0.5) self.poison_nova(3.0) diff --git a/src/pather.py b/src/pather.py index 2a6a985ad..533d3090a 100644 --- a/src/pather.py +++ b/src/pather.py @@ -513,12 +513,13 @@ def traverse_nodes_fixed(self, key: str | list[tuple[float, float]], char: IChar path = key i = 0 stuck_count = 0 + last_move_time = time.time() while i < len(path): x_m, y_m = convert_screen_to_monitor(path[i]) x_m += int(random.random() * 6 - 3) y_m += int(random.random() * 6 - 3) t0 = grab(force_new=True) - char.move((x_m, y_m), use_tp=True) + last_move_time = char.move((x_m, y_m), use_tp=True, last_move_time=last_move_time) t1 = grab(force_new=True) # check difference between the two frames to determine if tele was good or not diff = cv2.absdiff(t0, t1) diff --git a/src/screen.py b/src/screen.py index 78b788f6c..df635ea9b 100644 --- a/src/screen.py +++ b/src/screen.py @@ -123,3 +123,35 @@ def convert_abs_to_monitor(abs_coord: tuple[float, float]) -> tuple[float, float screen_coord = convert_abs_to_screen(abs_coord) monitor_coord = convert_screen_to_monitor(screen_coord) return monitor_coord + +def ensure_coordinates_in_screen(pos: tuple[float, float], type: str = "abs") -> tuple[float, float]: + match type: + case "monitor": + pos = convert_monitor_to_screen(pos) + pos_abs = convert_screen_to_abs(pos) + case "screen": + pos_abs = convert_screen_to_abs(pos) + case "abs": + pos_abs = pos + case _: + Logger.error(f"ensure_coordinates_in_screen: unknown type {type}") + return pos + + new = pos_abs + max_x = Config().ui_pos["screen_width"] / 2 + max_y = Config().ui_pos["screen_height"] / 2 + if pos_abs[0] >= max_x: + new[0] = max_x - 1 + elif pos_abs[0] <= -1 * max_x: + new[0] = -1 * max_x + 1 + if pos_abs[1] >= max_y: + new[1] = max_y - 1 + elif pos_abs[1] <= -1 * max_y: + new[1] = -1 * max_y + 1 + + match type: + case "monitor": + return convert_abs_to_monitor(new) + case "screen": + return convert_abs_to_screen(new) + return new \ No newline at end of file diff --git a/src/ui/skills.py b/src/ui/skills.py index 26fb759f5..0496f205b 100644 --- a/src/ui/skills.py +++ b/src/ui/skills.py @@ -2,9 +2,10 @@ from logger import Logger import cv2 import time +from utils.custom_mouse import mouse import numpy as np from utils.misc import cut_roi, color_filter, wait -from screen import grab +from screen import grab, convert_screen_to_monitor from config import Config import template_finder from ui_manager import is_visible, wait_until_visible, ScreenObjects @@ -62,17 +63,16 @@ def is_skill_bound(template_list: list[str] | str, roi: list[int] = Config().ui_ return _is_skill_bound(template_list, roi) def is_right_skill_bound(template_list: list[str] | str) -> bool: - """ - :return: Bool if skill is currently the selected skill on the right skill slot. - """ return _is_skill_bound(template_list, RIGHT_SKILL_ROI) def is_left_skill_bound(template_list: list[str] | str) -> bool: - """ - :return: Bool if skill is currently the selected skill on the left skill slot. - """ return _is_skill_bound(template_list, LEFT_SKILL_ROI) +def is_teleport_active(img: np.ndarray = None) -> bool: + img = grab() if img is None else img + if (match := template_finder.search(["BAR_TP_ACTIVE", "BAR_TP_INACTIVE"], img, roi=Config().ui_roi["active_skills_bar"], best_match=True)).valid: + return not "inactive" in match.name.lower() + return False def has_tps() -> bool: if not (tps_remain := is_visible(ScreenObjects.BarTownPortalSkill)): @@ -81,7 +81,6 @@ def has_tps() -> bool: cv2.imwrite("./log/screenshots/info/debug_out_of_tps_" + time.strftime("%Y%m%d_%H%M%S") + ".png", grab()) return tps_remain - def get_skill_charges(img: np.ndarray = None): if img is None: img = grab() @@ -129,4 +128,28 @@ def is_low_on_teleport_charges(img: np.ndarray = None) -> bool: charges_present = skill_is_charged(img) if charges_present: Logger.error("is_low_on_teleport_charges: unable to determine skill charges, assume zero") - return True \ No newline at end of file + return True + +def _remap_skill_hotkey(skill_assets: list[str] | str, hotkey: str, skill_roi: list[int], expanded_skill_roi: list[int]) -> bool: + x, y, w, h = skill_roi + x, y = convert_screen_to_monitor((x, y)) + mouse.move(x + w/2, y + h / 2) + mouse.click("left") + wait(0.3) + + if isinstance(skill_assets, str): + skill_assets = [skill_assets] + for skill_asset in skill_assets: + match = template_finder.search(skill_asset, grab(), threshold=0.84, roi=expanded_skill_roi) + if match.valid: + mouse.move(*match.center_monitor) + wait(0.3) + keyboard.send(hotkey) + wait(0.3) + mouse.click("left") + wait(0.3) + return True + return False + +def remap_right_skill_hotkey(skill_assets: list[str] | str, hotkey: str) -> bool: + return _remap_skill_hotkey(skill_assets, hotkey, Config().ui_roi["skill_right"], Config().ui_roi["skill_speed_bar"]) \ No newline at end of file diff --git a/src/utils/misc.py b/src/utils/misc.py index 6e6f98bee..030d52dbf 100644 --- a/src/utils/misc.py +++ b/src/utils/misc.py @@ -13,7 +13,7 @@ from logger import Logger import cv2 import os -from math import cos, sin, dist +from math import cos, sin, dist, pi import subprocess from win32con import HWND_TOPMOST, SWP_NOMOVE, SWP_NOSIZE, HWND_NOTOPMOST from win32gui import GetWindowText, SetWindowPos, EnumWindows, GetClientRect, ClientToScreen @@ -209,13 +209,7 @@ def list_files_in_folder(path: str): r.append(os.path.join(root, name)) return r -def rotate_vec(vec: np.ndarray, deg: float) -> np.ndarray: - theta = np.deg2rad(deg) - rot_matrix = np.array([[cos(theta), -sin(theta)], [sin(theta), cos(theta)]]) - return np.dot(rot_matrix, vec) -def unit_vector(vec: np.ndarray) -> np.ndarray: - return vec / dist(vec, (0, 0)) def image_is_equal(img1: np.ndarray, img2: np.ndarray) -> bool: shape_equal = img1.shape == img2.shape @@ -224,17 +218,6 @@ def image_is_equal(img1: np.ndarray, img2: np.ndarray) -> bool: return False return not(np.bitwise_xor(img1, img2).any()) -def arc_spread(cast_dir: tuple[float,float], spread_deg: float=10, radius_spread: tuple[float, float] = [.95, 1.05]) -> np.ndarray: - """ - Given an x,y vec (target), generate a new target that is the same vector but rotated by +/- spread_deg/2 - """ - cast_dir = np.array(cast_dir) - length = dist(cast_dir, (0, 0)) - adj = (radius_spread[1] - radius_spread[0])*random.random() + radius_spread[0] - rot = spread_deg*(random.random() - .5) - return rotate_vec(unit_vector(cast_dir)*(length+adj), rot) - - @dataclass class BestMatchResult: match: str @@ -265,4 +248,32 @@ def slugify(value, allow_unicode=False): def only_lowercase_letters(value): if not (x := ''.join(filter( lambda x: x in 'abcdefghijklmnopqrstuvwxyz', value ))): x = "botty" - return x \ No newline at end of file + return x + +""" +GEOMETRY +""" + +def rotate_vec(vec: np.ndarray, deg: float) -> np.ndarray: + theta = np.deg2rad(deg) + rot_matrix = np.array([[cos(theta), -sin(theta)], [sin(theta), cos(theta)]]) + return np.dot(rot_matrix, vec) + +def unit_vector(vec: np.ndarray) -> np.ndarray: + return vec / dist(vec, (0, 0)) + +def arc_spread(cast_dir: tuple[float,float], spread_deg: float=10, radius_spread: tuple[float, float] = [.95, 1.05]) -> np.ndarray: + """ + Given an x,y vec (target), generate a new target that is the same vector but rotated by +/- spread_deg/2 + """ + cast_dir = np.array(cast_dir) + length = dist(cast_dir, (0, 0)) + adj = (radius_spread[1] - radius_spread[0])*random.random() + radius_spread[0] + rot = spread_deg*(random.random() - .5) + return rotate_vec(unit_vector(cast_dir)*(length+adj), rot) + +# return random point in circle centered at x,y with radius r +def random_point_in_circle(pos: tuple[float, float], r: float) -> tuple[int, int]: + x, y = pos + theta = random.random()*2*pi + return round(x + r*cos(theta)), round(y + r*sin(theta)) \ No newline at end of file From 6719b303485c898b2f333f0c7dd324cedccce79c Mon Sep 17 00:00:00 2001 From: mgleed Date: Sun, 19 Jun 2022 19:36:27 -0400 Subject: [PATCH 26/44] wip --- src/char/i_char.py | 9 +++-- .../utils/calculations.py} | 34 +++++++++---------- src/utils/misc.py | 30 ---------------- 3 files changed, 21 insertions(+), 52 deletions(-) rename src/{utils/geometry.py => char/utils/calculations.py} (50%) diff --git a/src/char/i_char.py b/src/char/i_char.py index 7aaf90470..2cb531637 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -172,7 +172,7 @@ def _log_cast(skill_name: str, cast_pos_abs: tuple[float, float], spray: int, mi def _adjust_position(cast_pos_abs, spray, spread_deg): if spread_deg: - cast_pos_abs = vec_to_monitor(arc_spread(cast_pos_abs, spread_deg=spread_deg)) + cast_pos_abs = arc_spread(cast_pos_abs, spread_deg=spread_deg) if spray: cast_pos_abs = random_point_in_circle(pos = cast_pos_abs, r = spray) return get_closest_non_hud_pixel(cast_pos_abs, "abs") @@ -469,12 +469,11 @@ def select_by_template( Logger.error(f"Wanted to select {template_type}, but could not find it") return False - - def vec_to_monitor(self, target: tuple[float, float]) -> tuple[float, float]: + @staticmethod + def vec_to_monitor(target: tuple[float, float]) -> tuple[float, float]: return convert_abs_to_monitor(target) - def _lerp(self, a: float, b: float, f:float) -> float: - return a + f * (b - a) + """ KILL ROUTINES diff --git a/src/utils/geometry.py b/src/char/utils/calculations.py similarity index 50% rename from src/utils/geometry.py rename to src/char/utils/calculations.py index 55c84bd2d..ea29715af 100644 --- a/src/utils/geometry.py +++ b/src/char/utils/calculations.py @@ -1,8 +1,21 @@ import random import numpy as np -from math import cos, sin, dist, pi +from math import cos, sin, dist, pi, radians, degrees +# return location of a point on a circle +def point_on_circle(radius: float, theta_deg: float, center: np.ndarray = (0, 0)) -> np.ndarray: + theta = radians(theta_deg) + return center + radius * np.array([cos(theta), sin(theta)]) + +# return location of a point equidistant from origin randomly distributed between theta of spread_deg +def spread(point: tuple[float, float], spread_deg: float): + # random theta between -spread_deg and +spread_deg + x1, y1 = point + start_deg = degrees(np.arctan2(y1, x1)) + random_theta_deg = random.uniform(start_deg-spread_deg/2, start_deg+spread_deg/2) + return point_on_circle(dist(point, (0, 0)), random_theta_deg) +# rotate a vector by angle degrees def rotate_vec(vec: np.ndarray, deg: float) -> np.ndarray: theta = np.deg2rad(deg) rot_matrix = np.array([[cos(theta), -sin(theta)], [sin(theta), cos(theta)]]) @@ -11,17 +24,7 @@ def rotate_vec(vec: np.ndarray, deg: float) -> np.ndarray: def unit_vector(vec: np.ndarray) -> np.ndarray: return vec / dist(vec, (0, 0)) -# random point along arc length of a circle -def point_on_circle(radius: float, center: np.ndarray = (0, 0), ) -> np.ndarray: - theta = random.uniform(0, 2 * pi) - return center + radius * np.array([cos(theta), sin(theta)]) - -# convert angle in degrees to radians -def deg_to_rad(deg: float) -> float: - return deg * pi / 180 - - -def arc_spread(cast_dir: tuple[float,float], spread_deg: float=10, radius_spread: tuple[float, float] = [.95, 1.05]) -> np.ndarray: +def arc_spread(cast_dir: tuple[float,float], spread_deg: float=10, radius_spread: tuple[float, float] = [.95, 1.05]): """ Given an x,y vec (target), generate a new target that is the same vector but rotated by +/- spread_deg/2 """ @@ -31,8 +34,5 @@ def arc_spread(cast_dir: tuple[float,float], spread_deg: float=10, radius_spread rot = spread_deg*(random.random() - .5) return rotate_vec(unit_vector(cast_dir)*(length+adj), rot) -# return random point in circle centered at x,y with radius r -def random_point_in_circle(pos: tuple[float, float], r: float) -> tuple[int, int]: - x, y = pos - theta = random.random()*2*pi - return round(x + r*cos(theta)), round(y + r*sin(theta)) \ No newline at end of file +def lerp(a: float, b: float, f:float) -> float: + return a + f * (b - a) \ No newline at end of file diff --git a/src/utils/misc.py b/src/utils/misc.py index 030d52dbf..e9b47bfa0 100644 --- a/src/utils/misc.py +++ b/src/utils/misc.py @@ -209,8 +209,6 @@ def list_files_in_folder(path: str): r.append(os.path.join(root, name)) return r - - def image_is_equal(img1: np.ndarray, img2: np.ndarray) -> bool: shape_equal = img1.shape == img2.shape if not shape_equal: @@ -249,31 +247,3 @@ def only_lowercase_letters(value): if not (x := ''.join(filter( lambda x: x in 'abcdefghijklmnopqrstuvwxyz', value ))): x = "botty" return x - -""" -GEOMETRY -""" - -def rotate_vec(vec: np.ndarray, deg: float) -> np.ndarray: - theta = np.deg2rad(deg) - rot_matrix = np.array([[cos(theta), -sin(theta)], [sin(theta), cos(theta)]]) - return np.dot(rot_matrix, vec) - -def unit_vector(vec: np.ndarray) -> np.ndarray: - return vec / dist(vec, (0, 0)) - -def arc_spread(cast_dir: tuple[float,float], spread_deg: float=10, radius_spread: tuple[float, float] = [.95, 1.05]) -> np.ndarray: - """ - Given an x,y vec (target), generate a new target that is the same vector but rotated by +/- spread_deg/2 - """ - cast_dir = np.array(cast_dir) - length = dist(cast_dir, (0, 0)) - adj = (radius_spread[1] - radius_spread[0])*random.random() + radius_spread[0] - rot = spread_deg*(random.random() - .5) - return rotate_vec(unit_vector(cast_dir)*(length+adj), rot) - -# return random point in circle centered at x,y with radius r -def random_point_in_circle(pos: tuple[float, float], r: float) -> tuple[int, int]: - x, y = pos - theta = random.random()*2*pi - return round(x + r*cos(theta)), round(y + r*sin(theta)) \ No newline at end of file From b36a903225daea384e5f3bc7f91f09ae43b8a11e Mon Sep 17 00:00:00 2001 From: mgleed Date: Sun, 19 Jun 2022 21:25:38 -0400 Subject: [PATCH 27/44] cleanup i_char.py --- src/char/i_char.py | 121 +++++++++++++++++++++---------- src/char/sorceress/light_sorc.py | 2 +- src/char/sorceress/sorceress.py | 2 +- src/char/utils/calculations.py | 18 +++-- src/char/utils/test.py | 0 5 files changed, 96 insertions(+), 47 deletions(-) create mode 100644 src/char/utils/test.py diff --git a/src/char/i_char.py b/src/char/i_char.py index 2cb531637..c929ee52f 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -9,6 +9,7 @@ from char.utils.capabilities import CharacterCapabilities from char.utils.skill_data import get_cast_wait_time +from char.utils import calculations from config import Config from item import consumables from logger import Logger @@ -16,14 +17,12 @@ from ui import skills from ui_manager import detect_screen_object, ScreenObjects, get_closest_non_hud_pixel, is_visible, wait_until_visible from utils.custom_mouse import mouse -from utils.misc import wait, cut_roi, is_in_roi, color_filter, arc_spread, random_point_in_circle +from utils.misc import wait, cut_roi, is_in_roi, color_filter import template_finder class IChar: def __init__(self, skill_hotkeys: dict): self._active_aura = "" - # Add a bit to be on the save side - self._last_tp = time.time() self._mouse_click_held = { "left": False, "right": False @@ -35,8 +34,8 @@ def __init__(self, skill_hotkeys: dict): self.damage_scaling = float(Config().char.get("damage_scaling", 1.0)) self._use_safer_routines = Config().char["safer_routines"] self._base_class = "" - self.can_teleport = "" self.capabilities = None + self._current_weapon = 0 # 0 for main, 1 for offhand """ MOUSE AND KEYBOARD METHODS @@ -54,7 +53,7 @@ def _handle_delay(delay: float | list | tuple | None = None): except Exception as e: Logger.warning(f"Failed to delay with delay: {delay}. Exception: {e}") - def _keypress(self, key: str, hold_time: float | list | tuple | None = None): + def _key_press(self, key: str, hold_time: float | list | tuple | None = None): if not hold_time: keyboard.send(key) else: @@ -64,6 +63,14 @@ def _keypress(self, key: str, hold_time: float | list | tuple | None = None): keyboard.send(key, do_press=False) self._key_held[key] = False + def _key_hold(self, key: str, enable: bool = True): + if enable and not self._key_held[key]: + self._key_held[key] = True + keyboard.send(key, do_release=False) + elif not enable: + self._key_held[key] = False + keyboard.send(key, do_press=False) + def _click(self, mouse_click_type: str = "left", hold_time: float | list | tuple | None = None): if not hold_time: mouse.click(button = mouse_click_type) @@ -72,7 +79,7 @@ def _click(self, mouse_click_type: str = "left", hold_time: float | list | tuple self._handle_delay(hold_time) mouse.release(button = mouse_click_type) - def _hold_click(self, mouse_click_type: str = "left", enable: bool = True): + def _click_hold(self, mouse_click_type: str = "left", enable: bool = True): if enable: if not self._mouse_click_held[mouse_click_type]: self._mouse_click_held[mouse_click_type] = True @@ -170,27 +177,27 @@ def _log_cast(skill_name: str, cast_pos_abs: tuple[float, float], spray: int, mi msg += f" with {aura} active" Logger.debug(msg) - def _adjust_position(cast_pos_abs, spray, spread_deg): + def _randomize_position(pos_abs: tuple[float, float], spray: float = 0, spread_deg: float = 0): if spread_deg: - cast_pos_abs = arc_spread(cast_pos_abs, spread_deg=spread_deg) + pos_abs = calculations.spread(pos_abs = pos_abs, spread_deg = spread_deg) if spray: - cast_pos_abs = random_point_in_circle(pos = cast_pos_abs, r = spray) - return get_closest_non_hud_pixel(cast_pos_abs, "abs") + pos_abs = calculations.spray(pos_abs = pos_abs, r = spray) + return get_closest_non_hud_pixel(pos_abs, "abs") def _send_skill_and_cooldown(self, skill_name: str): - self._keypress(self._get_hotkey(skill_name)) + self._key_press(self._get_hotkey(skill_name)) wait(get_cast_wait_time(skill_name)) def _activate_aura(self, skill_name: str, delay: float | list | tuple | None = (0.04, 0.08)): if not self._get_hotkey(skill_name): return False if self._activate_aura != skill_name: # if aura is already active, don't activate it again - self._keypress(self._get_hotkey(skill_name)) + self._key_press(self._get_hotkey(skill_name)) self._active_aura = skill_name self._handle_delay(delay) return True - def _cast_simple(self, skill_name: str, duration: float | list | tuple | None = None) -> bool: + def _cast_simple(self, skill_name: str, duration: float | list | tuple | None = None, tp_frequency: float = 0) -> bool: """ Casts a skill """ @@ -201,40 +208,77 @@ def _cast_simple(self, skill_name: str, duration: float | list | tuple | None = self._send_skill_and_cooldown(skill_name) else: self._stand_still(True) - self._keypress(self._get_hotkey(skill_name), hold_time=duration) + self._key_press(self._get_hotkey(skill_name), hold_time=duration) self._stand_still(False) return True - - - def _cast_at_position(self, skill_name: str, cast_pos_abs: tuple[float, float], spray: float = 0, spread_deg: float = 0, duration: float | list | tuple | None = None) -> bool: + def _teleport_to_origin(self): """ - Casts a skill at a given position. + Teleports to the origin """ + random_abs = self._randomize_position(pos_abs = (0,0), spray = 5) + pos_m = convert_abs_to_monitor(random_abs) + mouse.move(*pos_m, [0.12, 0.2]) + self._cast_teleport() - - + def _cast_at_target( + self, + skill_name: str, + cast_pos_abs: tuple[float, float], + spray: float = 0, + spread_deg: float = 0, + duration: float = 0, + teleport_frequency: float = 0, + ) -> bool: + """ + Casts a skill toward a given target. + :param skill_name: name of skill to cast + :param cast_pos_abs: absolute position of target + :param spray: apply randomization within circle of radius 'spray' centered at target + :param spread_deg: apply randomization of target distributed along arc between theta of spread_deg + :param duration: hold down skill key for 'duration' seconds + :param teleport_frequency: teleport to origin every 'teleport_frequency' seconds + :return: True if function finished, False otherwise + """ if not self._get_hotkey(skill_name): return False - if cast_pos_abs: - x, y = cast_pos_abs - if spray: - x += (random.random() * 2 * spray - spray) - y += (random.random() * 2 * spray - spray) - pos_m = convert_abs_to_monitor((x, y)) - mouse.move(*pos_m, delay_factor=[0.1, 0.2]) - self._cast_simple(skill_name, duration) + mouse_move_delay = [0.3, 0.5] + + if duration: + self._stand_still(True) + start = time_of_last_tp = time.perf_counter() + while (elapsed_time := time.perf_counter() - start) < duration: + random_abs = self._randomize_position(pos_abs = cast_pos_abs, spray = spray, spread_deg = spread_deg) + pos_m = convert_abs_to_monitor(random_abs) + mouse.move(*pos_m, delay_factor=mouse_move_delay) + self._key_hold(self._get_hotkey(skill_name), True) + if teleport_frequency and (elapsed_time - time_of_last_tp) >= teleport_frequency: + self._key_hold(self._get_hotkey(skill_name), False) + wait(0.04, 0.08) + self._teleport_to_origin() + time_of_last_tp = elapsed_time + self._key_hold(self._get_hotkey(skill_name), False) + self._stand_still(False) + else: + random_abs = self._randomize_position(pos_abs = cast_pos_abs, spray = spray, spread_deg = spread_deg) + pos_m = convert_abs_to_monitor(random_abs) + mouse.move(*pos_m, delay_factor = [x/2 for x in mouse_move_delay]) + self._cast_simple(skill_name) + return True - def _cast_left_with_aura(self, skill_name: str, cast_pos_abs: tuple[float, float] = None, spray: int = 0, duration: float | list | tuple | None = None, aura: str = "") -> bool: + def _cast_left_with_aura(self, skill_name: str, cast_pos_abs: tuple[float, float] = None, spray: float = 0, spread_deg: float = 0, duration: float | list | tuple | None = None, aura: str = "") -> bool: """ Casts a skill at given position with an aura active """ #self._log_cast(skill_name, cast_pos_abs, spray, duration, aura) if aura: self._activate_aura(aura) - return self._cast_at_position(skill_name=skill_name, cast_pos_abs=cast_pos_abs, spray=spray, mouse_click_type="left", duration=duration) + return self._cast_at_target(skill_name=skill_name, cast_pos_abs=cast_pos_abs, spray=spray, spread_deg = spread_deg, mouse_click_type="left", duration=duration) + + """ + TODO: Update this fn def _cast_in_arc(self, skill_name: str, cast_pos_abs: tuple[float, float] = [0,-100], time_in_s: float = 3, spread_deg: float = 10, hold=True): #scale cast time by damage_scaling @@ -244,13 +288,13 @@ def _cast_in_arc(self, skill_name: str, cast_pos_abs: tuple[float, float] = [0,- raise ValueError(f"You did not set {skill_name} hotkey!") self._stand_still(True) - target = self.vec_to_monitor(arc_spread(cast_pos_abs, spread_deg=spread_deg)) + target = convert_abs_to_monitor(calculations.arc_spread(cast_pos_abs, spread_deg=spread_deg)) mouse.move(*target,delay_factor=[0.95, 1.05]) if hold: self._hold_click("right", True) start = time.time() while (time.time() - start) < time_in_s: - target = self.vec_to_monitor(arc_spread(cast_pos_abs, spread_deg=spread_deg)) + target = convert_abs_to_monitor(calculations.arc_spread(cast_pos_abs, spread_deg=spread_deg)) if hold: mouse.move(*target, delay_factor=[3, 8]) if not hold: @@ -261,6 +305,7 @@ def _cast_in_arc(self, skill_name: str, cast_pos_abs: tuple[float, float] = [0,- if hold: self._hold_click("right", False) self._stand_still(False) + """ """ GLOBAL SKILLS @@ -281,13 +326,14 @@ def _cast_town_portal(self) -> bool: return res def _weapon_switch(self): - return self._keypress(self._get_hotkey("weapon_switch")) + self._current_weapon = not self._current_weapon + return self._key_press(self._get_hotkey("weapon_switch")) """ CHARACTER ACTIONS AND MOVEMENT METHODS """ def _force_move(self): - self._keypress(self._get_hotkey("force_move")) + self._key_press(self._get_hotkey("force_move")) def _stand_still(self, enable: bool): if enable: @@ -312,7 +358,7 @@ def _teleport_to_position(self, pos_monitor: tuple[float, float]): factor = Config().advanced_options["pathing_delay_factor"] mouse.move(pos_monitor[0], pos_monitor[1], randomize=3, delay_factor=[(2+factor)/25, (4+factor)/25]) wait(0.012, 0.02) - self._keypress(self._get_hotkey("teleport")) + self._key_press(self._get_hotkey("teleport")) def _walk_to_position(self, pos_monitor: tuple[float, float], force_move: bool = False): factor = Config().advanced_options["pathing_delay_factor"] @@ -469,11 +515,6 @@ def select_by_template( Logger.error(f"Wanted to select {template_type}, but could not find it") return False - @staticmethod - def vec_to_monitor(target: tuple[float, float]) -> tuple[float, float]: - return convert_abs_to_monitor(target) - - """ KILL ROUTINES diff --git a/src/char/sorceress/light_sorc.py b/src/char/sorceress/light_sorc.py index 213e50145..3afa02c29 100644 --- a/src/char/sorceress/light_sorc.py +++ b/src/char/sorceress/light_sorc.py @@ -22,7 +22,7 @@ def _cast_lightning(self, cast_pos_abs: tuple[float, float], spray: float = 20, return self._cast_left_with_aura(skill_name="lightning", spray = spray, cast_pos_abs = cast_pos_abs, duration = duration) def _cast_frozen_orb(self, cast_pos_abs: tuple[float, float], spray: float = 10, duration: float = 0) -> bool: - return self._cast_at_position("frozen_orb", cast_pos_abs, spray = spray, duration = duration) + return self._cast_at_target("frozen_orb", cast_pos_abs, spray = spray, duration = duration) def _generic_light_sorc_attack_sequence(self, cast_pos_abs: tuple[float, float], chain_spray: float = 20, duration: float = 0): self._cast_lightning(cast_pos_abs, spray=5) diff --git a/src/char/sorceress/sorceress.py b/src/char/sorceress/sorceress.py index 8a60f636f..f8d2ec128 100644 --- a/src/char/sorceress/sorceress.py +++ b/src/char/sorceress/sorceress.py @@ -73,7 +73,7 @@ def _cast_static(self, duration: float = 1.4) -> bool: return self._cast_simple(skill_name="static_field", mouse_click_type = "right", duration=duration) def _cast_telekinesis(self, cast_pos_abs: tuple[float, float]) -> bool: - return self._cast_at_position(skill_name="telekinesis", cast_pos_abs = cast_pos_abs, spray = 0, mouse_click_type = "right") + return self._cast_at_target(skill_name="telekinesis", cast_pos_abs = cast_pos_abs, spray = 0, mouse_click_type = "right") def _cast_thunder_storm(self) -> bool: return self._cast_simple(skill_name="thunder_storm", mouse_click_type="right") diff --git a/src/char/utils/calculations.py b/src/char/utils/calculations.py index ea29715af..24f2a0bdf 100644 --- a/src/char/utils/calculations.py +++ b/src/char/utils/calculations.py @@ -3,17 +3,25 @@ from math import cos, sin, dist, pi, radians, degrees # return location of a point on a circle -def point_on_circle(radius: float, theta_deg: float, center: np.ndarray = (0, 0)) -> np.ndarray: +def point_on_circle(radius: float, theta_deg: float, center: np.ndarray = (0, 0)) -> tuple[int, int]: theta = radians(theta_deg) - return center + radius * np.array([cos(theta), sin(theta)]) + res = center + radius * np.array([cos(theta), sin(theta)]) + return tuple([round(i) for i in res]) # return location of a point equidistant from origin randomly distributed between theta of spread_deg -def spread(point: tuple[float, float], spread_deg: float): +def spread(pos_abs: tuple[float, float], spread_deg: float) -> tuple[int, int]: # random theta between -spread_deg and +spread_deg - x1, y1 = point + x1, y1 = pos_abs start_deg = degrees(np.arctan2(y1, x1)) random_theta_deg = random.uniform(start_deg-spread_deg/2, start_deg+spread_deg/2) - return point_on_circle(dist(point, (0, 0)), random_theta_deg) + return point_on_circle(radius = dist(pos_abs, (0, 0)), theta_deg = random_theta_deg) + +# return random point within circle centered at x1, y1 with radius r +def spray(pos_abs: tuple[float, float], r: float) -> tuple[int, int]: + x1, y1 = pos_abs + x2 = random.uniform(x1-r, x1+r) + y2 = random.uniform(y1-r, y1+r) + return tuple([round(i) for i in (x2, y2)]) # rotate a vector by angle degrees def rotate_vec(vec: np.ndarray, deg: float) -> np.ndarray: diff --git a/src/char/utils/test.py b/src/char/utils/test.py new file mode 100644 index 000000000..e69de29bb From dec483aa099bbf7f9f28737ce61afc9ec63470dd Mon Sep 17 00:00:00 2001 From: mgleed Date: Sun, 19 Jun 2022 23:03:18 -0400 Subject: [PATCH 28/44] through i_char.py --- .../inventory/active_weapon_main.png | Bin 0 -> 4754 bytes .../inventory/active_weapon_offhand.png | Bin 0 -> 4758 bytes config/game.ini | 1 + src/bot.py | 3 + src/char/barbarian.py | 2 +- src/char/basic.py | 2 +- src/char/i_char.py | 133 ++++++++---------- src/char/sorceress/light_sorc.py | 2 +- src/char/sorceress/sorceress.py | 2 +- src/char/{utils => tools}/__init__.py | 0 src/char/{utils => tools}/calculations.py | 0 src/char/{utils => tools}/capabilities.py | 0 src/char/{utils => tools}/skill_data.py | 0 src/char/utils/test.py | 0 src/inventory/personal.py | 7 + src/ui_manager.py | 7 + 16 files changed, 84 insertions(+), 75 deletions(-) create mode 100644 assets/templates/inventory/active_weapon_main.png create mode 100644 assets/templates/inventory/active_weapon_offhand.png rename src/char/{utils => tools}/__init__.py (100%) rename src/char/{utils => tools}/calculations.py (100%) rename src/char/{utils => tools}/capabilities.py (100%) rename src/char/{utils => tools}/skill_data.py (100%) delete mode 100644 src/char/utils/test.py diff --git a/assets/templates/inventory/active_weapon_main.png b/assets/templates/inventory/active_weapon_main.png new file mode 100644 index 0000000000000000000000000000000000000000..38ed3844edb46ea09b6ebb989ab639684b81df6c GIT binary patch literal 4754 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 au>Qo9z7)enr{?}O^8I=ptH^WL-v0rkiK_De literal 0 HcmV?d00001 diff --git a/assets/templates/inventory/active_weapon_offhand.png b/assets/templates/inventory/active_weapon_offhand.png new file mode 100644 index 0000000000000000000000000000000000000000..bbe266a0387af124d70832489359ce6200247243 GIT binary patch literal 4758 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 literal 0 HcmV?d00001 diff --git a/config/game.ini b/config/game.ini index 5770c4fe8..1b5a9628a 100644 --- a/config/game.ini +++ b/config/game.ini @@ -137,6 +137,7 @@ deposit_btn=454,365,186,43 equipped_inventory_area=861,59,387,287 inventory_bg_pattern=21,622,1236,75 active_skills_bar=372,605,538,47 +active_weapon_tabs=864,65,93,30 [path] ; static pathes in format: x0,y0, x1,y1, x2,y2, ... diff --git a/src/bot.py b/src/bot.py index bc45dd23b..a95a790e7 100644 --- a/src/bot.py +++ b/src/bot.py @@ -302,6 +302,9 @@ def on_maintenance(self): need_inspect |= (self._game_stats._run_counter - 1) % Config().char["runs_per_stash"] == 0 if need_inspect: img = personal.open() + # Check which weapon is bound (main vs offhand) + if self._char.main_weapon_equipped is None: + self._char.main_weapon_equipped = personal.is_main_weapon_active(img) # 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 diff --git a/src/char/barbarian.py b/src/char/barbarian.py index 6b68855eb..d8b6e03a2 100644 --- a/src/char/barbarian.py +++ b/src/char/barbarian.py @@ -2,7 +2,7 @@ from ui import skills from utils.custom_mouse import mouse from char import IChar -from char.utils.capabilities import CharacterCapabilities +from char.tools.capabilities import CharacterCapabilities import template_finder from pather import Pather from logger import Logger diff --git a/src/char/basic.py b/src/char/basic.py index 4674c9771..dbd5f2a73 100644 --- a/src/char/basic.py +++ b/src/char/basic.py @@ -2,7 +2,7 @@ from ui import skills from utils.custom_mouse import mouse from char import IChar -from char.utils.capabilities import CharacterCapabilities +from char.tools.capabilities import CharacterCapabilities import template_finder from pather import Pather from logger import Logger diff --git a/src/char/i_char.py b/src/char/i_char.py index c929ee52f..a14ab67f8 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -7,9 +7,9 @@ import time from functools import cached_property -from char.utils.capabilities import CharacterCapabilities -from char.utils.skill_data import get_cast_wait_time -from char.utils import calculations +from char.tools.capabilities import CharacterCapabilities +from char.tools.skill_data import get_cast_wait_time +from char.tools import calculations from config import Config from item import consumables from logger import Logger @@ -35,7 +35,7 @@ def __init__(self, skill_hotkeys: dict): self._use_safer_routines = Config().char["safer_routines"] self._base_class = "" self.capabilities = None - self._current_weapon = 0 # 0 for main, 1 for offhand + self.main_weapon_equipped = None """ MOUSE AND KEYBOARD METHODS @@ -221,16 +221,16 @@ def _teleport_to_origin(self): mouse.move(*pos_m, [0.12, 0.2]) self._cast_teleport() - def _cast_at_target( + def _cast_at_position( self, - skill_name: str, - cast_pos_abs: tuple[float, float], - spray: float = 0, - spread_deg: float = 0, - duration: float = 0, - teleport_frequency: float = 0, - ) -> bool: - """ + skill_name: str, + cast_pos_abs: tuple[float, float], + spray: float = 0, + spread_deg: float = 0, + duration: float = 0, + teleport_frequency: float = 0, + ) -> bool: + """ Casts a skill toward a given target. :param skill_name: name of skill to cast :param cast_pos_abs: absolute position of target @@ -243,7 +243,7 @@ def _cast_at_target( if not self._get_hotkey(skill_name): return False - mouse_move_delay = [0.3, 0.5] + mouse_move_delay = [0.4, 0.6] if duration: self._stand_still(True) @@ -263,7 +263,7 @@ def _cast_at_target( else: random_abs = self._randomize_position(pos_abs = cast_pos_abs, spray = spray, spread_deg = spread_deg) pos_m = convert_abs_to_monitor(random_abs) - mouse.move(*pos_m, delay_factor = [x/2 for x in mouse_move_delay]) + mouse.move(*pos_m, delay_factor = [x/2 for x in mouse_move_delay]) self._cast_simple(skill_name) return True @@ -275,7 +275,7 @@ def _cast_left_with_aura(self, skill_name: str, cast_pos_abs: tuple[float, float #self._log_cast(skill_name, cast_pos_abs, spray, duration, aura) if aura: self._activate_aura(aura) - return self._cast_at_target(skill_name=skill_name, cast_pos_abs=cast_pos_abs, spray=spray, spread_deg = spread_deg, mouse_click_type="left", duration=duration) + return self._cast_at_position(skill_name=skill_name, cast_pos_abs=cast_pos_abs, spray=spray, spread_deg = spread_deg, mouse_click_type="left", duration=duration) """ TODO: Update this fn @@ -325,28 +325,36 @@ def _cast_town_portal(self) -> bool: consumables.increment_need("tp", 1) return res - def _weapon_switch(self): - self._current_weapon = not self._current_weapon - return self._key_press(self._get_hotkey("weapon_switch")) - """ CHARACTER ACTIONS AND MOVEMENT METHODS """ + + def _weapon_switch(self): + if self.main_weapon_equipped is not None: + self.main_weapon_equipped = not self.main_weapon_equipped + return self._key_press(self._get_hotkey("weapon_switch")) + + def _switch_to_main_weapon(self): + if self.main_weapon_equipped == False: + self._weapon_switch() + + def _switch_to_offhand_weapon(self): + if self.main_weapon_equipped: + self._weapon_switch() + def _force_move(self): self._key_press(self._get_hotkey("force_move")) def _stand_still(self, enable: bool): - if enable: - if not self._standing_still: - keyboard.send(self._get_hotkey("stand_still"), do_release=False) - self._standing_still = True - else: - if self._standing_still: - keyboard.send(self._get_hotkey("stand_still"), do_press=False) - self._standing_still = False + if enable and not self._standing_still: + keyboard.send(self._get_hotkey("stand_still"), do_release=False) + self._standing_still = True + elif not enable and self._standing_still: + keyboard.send(self._get_hotkey("stand_still"), do_press=False) + self._standing_still = False def pick_up_item(self, pos: tuple[float, float], item_name: str = None, prev_cast_start: float = 0) -> float: - mouse.move(pos[0], pos[1]) + mouse.move(*pos) self._click_left() wait(0.25, 0.35) return prev_cast_start @@ -378,7 +386,21 @@ def _walk_to_position(self, pos_monitor: tuple[float, float], force_move: bool = else: self._click_left() - def move(self, pos_monitor: tuple[float, float], use_tp: bool = False, force_move: bool = False, last_move_time: float = time.time()) -> float: + def move( + self, + pos_monitor: tuple[float, float], + use_tp: bool = False, + force_move: bool = False, + last_move_time: float = time.time(), + ) -> float: + """ + Moves character to position. + :param pos_monitor: Position to move to (screen coordinates) + :param use_tp: Use teleport if able to + :param force_move: Use force_move hotkey to move if not teleporting + :param last_move_time: Time of last move. + :return: Time of move completed. + """ factor = Config().advanced_options["pathing_delay_factor"] if use_tp and self.can_teleport(): # can_teleport() activates teleport hotkey if True # 7 frames is the fastest that teleport can be casted with 200 fcr on sorc @@ -398,12 +420,11 @@ def tp_town(self) -> bool: self._cast_town_portal() roi_mouse_move = [ - int(Config().ui_pos["screen_width"] * 0.3), + round(Config().ui_pos["screen_width"] * 0.3), 0, - int(Config().ui_pos["screen_width"] * 0.4), - int(Config().ui_pos["screen_height"] * 0.7) + round(Config().ui_pos["screen_width"] * 0.4), + round(Config().ui_pos["screen_height"] * 0.7) ] - pos_away = convert_abs_to_monitor((-167, -30)) start = time.time() retry_count = 0 while (time.time() - start) < 8: @@ -428,48 +449,18 @@ def tp_town(self) -> bool: # move mouse away to not overlay with the town portal if mouse is in center pos_screen = convert_monitor_to_screen(mouse.get_position()) if is_in_roi(roi_mouse_move, pos_screen): + pos_away = convert_abs_to_monitor((-167, -30)) mouse.move(*pos_away, randomize=40, delay_factor=[0.8, 1.4]) return False def _pre_buff_cta(self) -> bool: - if not Config().char["cta_available"]: + if not self._get_hotkey("cta_available"): return False - # Save current skill img - skill_before = cut_roi(grab(), Config().ui_roi["skill_right"]) - # Try to switch weapons and select bo until we find the skill on the right skill slot - start = time.time() - switch_success = False - while time.time() - start < 4: - self._weapon_switch() - wait(0.3, 0.35) - self._select_skill(skill = "battle_command", delay=(0.1, 0.2)) - if skills.is_right_skill_selected(["BC", "BO"]): - switch_success = True - break - - if not switch_success: - Logger.warning("You dont have Battle Command bound, or you do not have CTA. ending CTA buff") - Config().char["cta_available"] = 0 - else: - # We switched succesfully, let's pre buff - self._cast_battle_command() - wait(self._cast_duration + 0.16, self._cast_duration + 0.18) - self._cast_battle_orders() - wait(self._cast_duration + 0.16, self._cast_duration + 0.18) - - # Make sure the switch back to the original weapon is good - start = time.time() - while (elapsed := time.time() - start < 4): - self._weapon_switch() - wait(0.3, 0.35) - skill_after = cut_roi(grab(), Config().ui_roi["skill_right"]) - _, max_val, _, _ = cv2.minMaxLoc(cv2.matchTemplate(skill_after, skill_before, cv2.TM_CCOEFF_NORMED)) - if max_val > 0.9: - break - else: - Logger.warning("Failed to switch weapon, try again") - wait(0.5) - return elapsed + if self.main_weapon_equipped(): + self._switch_to_offhand_weapon() + self._cast_battle_command() + self._cast_battle_orders() + self._switch_to_main_weapon() def pre_buff(self): pass diff --git a/src/char/sorceress/light_sorc.py b/src/char/sorceress/light_sorc.py index 3afa02c29..213e50145 100644 --- a/src/char/sorceress/light_sorc.py +++ b/src/char/sorceress/light_sorc.py @@ -22,7 +22,7 @@ def _cast_lightning(self, cast_pos_abs: tuple[float, float], spray: float = 20, return self._cast_left_with_aura(skill_name="lightning", spray = spray, cast_pos_abs = cast_pos_abs, duration = duration) def _cast_frozen_orb(self, cast_pos_abs: tuple[float, float], spray: float = 10, duration: float = 0) -> bool: - return self._cast_at_target("frozen_orb", cast_pos_abs, spray = spray, duration = duration) + return self._cast_at_position("frozen_orb", cast_pos_abs, spray = spray, duration = duration) def _generic_light_sorc_attack_sequence(self, cast_pos_abs: tuple[float, float], chain_spray: float = 20, duration: float = 0): self._cast_lightning(cast_pos_abs, spray=5) diff --git a/src/char/sorceress/sorceress.py b/src/char/sorceress/sorceress.py index f8d2ec128..8a60f636f 100644 --- a/src/char/sorceress/sorceress.py +++ b/src/char/sorceress/sorceress.py @@ -73,7 +73,7 @@ def _cast_static(self, duration: float = 1.4) -> bool: return self._cast_simple(skill_name="static_field", mouse_click_type = "right", duration=duration) def _cast_telekinesis(self, cast_pos_abs: tuple[float, float]) -> bool: - return self._cast_at_target(skill_name="telekinesis", cast_pos_abs = cast_pos_abs, spray = 0, mouse_click_type = "right") + return self._cast_at_position(skill_name="telekinesis", cast_pos_abs = cast_pos_abs, spray = 0, mouse_click_type = "right") def _cast_thunder_storm(self) -> bool: return self._cast_simple(skill_name="thunder_storm", mouse_click_type="right") diff --git a/src/char/utils/__init__.py b/src/char/tools/__init__.py similarity index 100% rename from src/char/utils/__init__.py rename to src/char/tools/__init__.py diff --git a/src/char/utils/calculations.py b/src/char/tools/calculations.py similarity index 100% rename from src/char/utils/calculations.py rename to src/char/tools/calculations.py diff --git a/src/char/utils/capabilities.py b/src/char/tools/capabilities.py similarity index 100% rename from src/char/utils/capabilities.py rename to src/char/tools/capabilities.py diff --git a/src/char/utils/skill_data.py b/src/char/tools/skill_data.py similarity index 100% rename from src/char/utils/skill_data.py rename to src/char/tools/skill_data.py diff --git a/src/char/utils/test.py b/src/char/utils/test.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/inventory/personal.py b/src/inventory/personal.py index cb715448b..8ce7f597e 100644 --- a/src/inventory/personal.py +++ b/src/inventory/personal.py @@ -70,6 +70,13 @@ def inventory_has_items(img: np.ndarray = None, close_window = False) -> bool: return True return False +def is_main_weapon_active(img: np.ndarray = None) -> bool | None: + # inventory must be open + img = grab() if img is None else img + if (res := detect_screen_object(ScreenObjects.ActiveWeaponBound)).valid: + return "main" in res.name.lower() + Logger.warning("is_main_weapon_active(): Failed to detect active weapon, is inventory not open?") + return None def stash_all_items(items: list = None): """ diff --git a/src/ui_manager.py b/src/ui_manager.py index 745d927c8..96795ce40 100644 --- a/src/ui_manager.py +++ b/src/ui_manager.py @@ -270,6 +270,13 @@ class ScreenObjects: roi="inventory_bg_pattern", threshold=0.8, ) + ActiveWeaponBound=ScreenObject( + ref=["ACTIVE_WEAPON_MAIN", "ACTIVE_WEAPON_OFFHAND"], + roi="active_weapon_tabs", + threshold=0.8, + use_grayscale=True, + best_match=True, + ) 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 From 9254215c3a976452baacedccd872671ef0471404 Mon Sep 17 00:00:00 2001 From: mgleed Date: Sun, 19 Jun 2022 23:04:54 -0400 Subject: [PATCH 29/44] cleanup unused imports --- src/char/i_char.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/char/i_char.py b/src/char/i_char.py index a14ab67f8..d3ab069d6 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -1,8 +1,6 @@ -from typing import Callable, TypeVar -import cv2 +from typing import Callable import keyboard import math -import numpy as np import random import time from functools import cached_property @@ -13,11 +11,11 @@ from config import Config from item import consumables from logger import Logger -from screen import grab, convert_monitor_to_screen, convert_screen_to_abs, convert_abs_to_monitor, convert_screen_to_monitor, convert_abs_to_screen +from screen import grab, convert_monitor_to_screen, convert_screen_to_abs, convert_abs_to_monitor, convert_abs_to_screen from ui import skills -from ui_manager import detect_screen_object, ScreenObjects, get_closest_non_hud_pixel, is_visible, wait_until_visible +from ui_manager import ScreenObjects, get_closest_non_hud_pixel, is_visible, wait_until_visible from utils.custom_mouse import mouse -from utils.misc import wait, cut_roi, is_in_roi, color_filter +from utils.misc import wait, is_in_roi import template_finder class IChar: From 250ba0bc3c68d80fec165fd1908f3ca212fbf9cb Mon Sep 17 00:00:00 2001 From: mgleed Date: Mon, 20 Jun 2022 16:12:54 -0400 Subject: [PATCH 30/44] bring target detect into _cast_at_position function --- src/char/basic_ranged.py | 2 +- src/char/bone_necro.py | 4 +-- src/char/i_char.py | 58 ++++++++++++++++++++++------------ src/char/necro.py | 12 +++---- src/char/paladin/fohdin.py | 32 ++++++++++++++----- src/char/paladin/paladin.py | 22 ++++++------- src/char/poison_necro.py | 12 +++---- src/char/tools/calculations.py | 3 +- src/char/trapsin.py | 2 +- 9 files changed, 88 insertions(+), 59 deletions(-) diff --git a/src/char/basic_ranged.py b/src/char/basic_ranged.py index a4dbdda16..4e0b4bf48 100644 --- a/src/char/basic_ranged.py +++ b/src/char/basic_ranged.py @@ -20,7 +20,7 @@ def __init__(self, skill_hotkeys: dict, pather: Pather): super().__init__(skill_hotkeys) self._pather = pather - def _left_attack(self, cast_pos_abs: tuple[float, float], delay: tuple[float, float] = (0.2, 0.3), spray: int = 10): + def _left_attack(self, cast_pos_abs: tuple[float, float], delay: tuple[float, float] = (0.2, 0.3), spray: float = 10): if self._skill_hotkeys["left_attack"]: keyboard.send(self._skill_hotkeys["left_attack"]) for _ in range(4): diff --git a/src/char/bone_necro.py b/src/char/bone_necro.py index 8fe52ad2b..095bc236e 100644 --- a/src/char/bone_necro.py +++ b/src/char/bone_necro.py @@ -29,7 +29,7 @@ def move_to(self, x, y): self.pre_move() self.move(pos_m, force_move=True) - def bone_wall(self, cast_pos_abs: tuple[float, float], spray: int): + def bone_wall(self, cast_pos_abs: tuple[float, float], spray: float): if not self._skill_hotkeys["bone_wall"]: raise ValueError("You did not set bone_wall hotkey!") keyboard.send(Config().char["stand_still"], do_release=False) @@ -76,7 +76,7 @@ def _bone_armor(self): mouse.click(button="right") wait(self._cast_duration) - def _corpse_explosion(self, cast_pos_abs: tuple[float, float], spray: int = 10,cast_count: int = 8): + def _corpse_explosion(self, cast_pos_abs: tuple[float, float], spray: float = 10,cast_count: int = 8): keyboard.send(Config().char["stand_still"], do_release=False) Logger.debug(f'casting corpse explosion {cast_count} times with spray = {spray}') for _ in range(cast_count): diff --git a/src/char/i_char.py b/src/char/i_char.py index d3ab069d6..81825bf9e 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -17,6 +17,7 @@ from utils.custom_mouse import mouse from utils.misc import wait, is_in_roi import template_finder +from target_detect import get_visible_targets class IChar: def __init__(self, skill_hotkeys: dict): @@ -163,7 +164,7 @@ def on_capabilities_discovered(self, capabilities: CharacterCapabilities): """ @staticmethod - def _log_cast(skill_name: str, cast_pos_abs: tuple[float, float], spray: int, min_duration: float, aura: str): + def _log_cast(skill_name: str, cast_pos_abs: tuple[float, float], spray: float, min_duration: float, aura: str): msg = f"Casting skill {skill_name}" if cast_pos_abs: msg += f" at screen coordinate {convert_abs_to_screen(cast_pos_abs)}" @@ -190,12 +191,11 @@ def _activate_aura(self, skill_name: str, delay: float | list | tuple | None = ( if not self._get_hotkey(skill_name): return False if self._activate_aura != skill_name: # if aura is already active, don't activate it again - self._key_press(self._get_hotkey(skill_name)) self._active_aura = skill_name - self._handle_delay(delay) + self._key_press(self._get_hotkey(skill_name), hold_time= delay) return True - def _cast_simple(self, skill_name: str, duration: float | list | tuple | None = None, tp_frequency: float = 0) -> bool: + def _cast_simple(self, skill_name: str, duration: float | list | tuple | None = None) -> bool: """ Casts a skill """ @@ -206,27 +206,20 @@ def _cast_simple(self, skill_name: str, duration: float | list | tuple | None = self._send_skill_and_cooldown(skill_name) else: self._stand_still(True) - self._key_press(self._get_hotkey(skill_name), hold_time=duration) + self._key_press(self._get_hotkey(skill_name), hold_time = duration) self._stand_still(False) return True - def _teleport_to_origin(self): - """ - Teleports to the origin - """ - random_abs = self._randomize_position(pos_abs = (0,0), spray = 5) - pos_m = convert_abs_to_monitor(random_abs) - mouse.move(*pos_m, [0.12, 0.2]) - self._cast_teleport() - def _cast_at_position( self, skill_name: str, cast_pos_abs: tuple[float, float], spray: float = 0, spread_deg: float = 0, - duration: float = 0, + min_duration: float = 0, + max_duration: float = 0, teleport_frequency: float = 0, + use_target_detect = False, ) -> bool: """ Casts a skill toward a given target. @@ -234,8 +227,10 @@ def _cast_at_position( :param cast_pos_abs: absolute position of target :param spray: apply randomization within circle of radius 'spray' centered at target :param spread_deg: apply randomization of target distributed along arc between theta of spread_deg - :param duration: hold down skill key for 'duration' seconds + :param min_duration: hold down skill key for minimum 'duration' seconds + :param max_duration: hold down skill key for maximum 'duration' seconds (used only if use_target_detect is True) :param teleport_frequency: teleport to origin every 'teleport_frequency' seconds + :param use_target_detect: override cast_pos_abs with target detection :return: True if function finished, False otherwise """ if not self._get_hotkey(skill_name): @@ -243,19 +238,33 @@ def _cast_at_position( mouse_move_delay = [0.4, 0.6] - if duration: + if max_duration: self._stand_still(True) start = time_of_last_tp = time.perf_counter() - while (elapsed_time := time.perf_counter() - start) < duration: - random_abs = self._randomize_position(pos_abs = cast_pos_abs, spray = spray, spread_deg = spread_deg) - pos_m = convert_abs_to_monitor(random_abs) + # cast while time is less than max duration + while (elapsed_time := time.perf_counter() - start) < max_duration: + targets = None + # if target detection is enabled, use it to get target position + if use_target_detect: + targets = get_visible_targets() + pos_abs = targets[0].center_abs + pos_abs = self._randomize_position(pos_abs = pos_abs, spray = 5, spread_deg = 0) + # otherwise, use the given position with randomization parameters + else: + pos_abs = self._randomize_position(pos_abs = cast_pos_abs, spray = spray, spread_deg = spread_deg) + pos_m = convert_abs_to_monitor(pos_abs) mouse.move(*pos_m, delay_factor=mouse_move_delay) + # start keyhold, if not already started self._key_hold(self._get_hotkey(skill_name), True) + # if teleport frequency is set, teleport every teleport_frequency seconds if teleport_frequency and (elapsed_time - time_of_last_tp) >= teleport_frequency: self._key_hold(self._get_hotkey(skill_name), False) wait(0.04, 0.08) self._teleport_to_origin() time_of_last_tp = elapsed_time + # if target detection is enabled and minimum time has elapsed and no targets remain, end casting + if use_target_detect and (elapsed_time > min_duration) and not targets: + break self._key_hold(self._get_hotkey(skill_name), False) self._stand_still(False) else: @@ -327,6 +336,15 @@ def _cast_town_portal(self) -> bool: CHARACTER ACTIONS AND MOVEMENT METHODS """ + def _teleport_to_origin(self): + """ + Teleports to the origin + """ + random_abs = self._randomize_position(pos_abs = (0,0), spray = 5) + pos_m = convert_abs_to_monitor(random_abs) + mouse.move(*pos_m, [0.12, 0.2]) + self._cast_teleport() + def _weapon_switch(self): if self.main_weapon_equipped is not None: self.main_weapon_equipped = not self.main_weapon_equipped diff --git a/src/char/necro.py b/src/char/necro.py index c5effdd99..3859bcbea 100644 --- a/src/char/necro.py +++ b/src/char/necro.py @@ -150,7 +150,7 @@ def _summon_stat(self): ''' print counts for summons ''' Logger.info('\33[31m'+"Summon status | "+str(self._skeletons_count)+"skele | "+str(self._revive_count)+" rev | "+self._golem_count+" |"+'\033[0m') - def _revive(self, cast_pos_abs: tuple[float, float], spray: int = 10, cast_count: int=12): + def _revive(self, cast_pos_abs: tuple[float, float], spray: float = 10, cast_count: int=12): Logger.info('\033[94m'+"raise revive"+'\033[0m') keyboard.send(Config().char["stand_still"], do_release=False) for _ in range(cast_count): @@ -179,7 +179,7 @@ def _revive(self, cast_pos_abs: tuple[float, float], spray: int = 10, cast_count mouse.release(button="right") keyboard.send(Config().char["stand_still"], do_press=False) - def _raise_skeleton(self, cast_pos_abs: tuple[float, float], spray: int = 10, cast_count: int=16): + def _raise_skeleton(self, cast_pos_abs: tuple[float, float], spray: float = 10, cast_count: int=16): Logger.info('\033[94m'+"raise skeleton"+'\033[0m') keyboard.send(Config().char["stand_still"], do_release=False) for _ in range(cast_count): @@ -208,7 +208,7 @@ def _raise_skeleton(self, cast_pos_abs: tuple[float, float], spray: int = 10, ca mouse.release(button="right") keyboard.send(Config().char["stand_still"], do_press=False) - def _raise_mage(self, cast_pos_abs: tuple[float, float], spray: int = 10, cast_count: int=16): + def _raise_mage(self, cast_pos_abs: tuple[float, float], spray: float = 10, cast_count: int=16): Logger.info('\033[94m'+"raise mage"+'\033[0m') keyboard.send(Config().char["stand_still"], do_release=False) for _ in range(cast_count): @@ -283,7 +283,7 @@ def _bone_armor(self): - def _left_attack(self, cast_pos_abs: tuple[float, float], spray: int = 10): + def _left_attack(self, cast_pos_abs: tuple[float, float], spray: float = 10): keyboard.send(Config().char["stand_still"], do_release=False) if self._skill_hotkeys["skill_left"]: keyboard.send(self._skill_hotkeys["skill_left"]) @@ -298,7 +298,7 @@ def _left_attack(self, cast_pos_abs: tuple[float, float], spray: int = 10): keyboard.send(Config().char["stand_still"], do_press=False) - def _left_attack_single(self, cast_pos_abs: tuple[float, float], spray: int = 10, cast_count: int=6): + def _left_attack_single(self, cast_pos_abs: tuple[float, float], spray: float = 10, cast_count: int=6): keyboard.send(Config().char["stand_still"], do_release=False) if self._skill_hotkeys["skill_left"]: keyboard.send(self._skill_hotkeys["skill_left"]) @@ -325,7 +325,7 @@ def _amp_dmg(self, cast_pos_abs: tuple[float, float], spray: float = 10): wait(0.25, 0.35) mouse.release(button="right") - def _corpse_explosion(self, cast_pos_abs: tuple[float, float], spray: int = 10,cast_count: int = 8): + def _corpse_explosion(self, cast_pos_abs: tuple[float, float], spray: float = 10,cast_count: int = 8): keyboard.send(Config().char["stand_still"], do_release=False) Logger.info('\033[93m'+"corpse explosion~> random cast"+'\033[0m') for _ in range(cast_count): diff --git a/src/char/paladin/fohdin.py b/src/char/paladin/fohdin.py index dd6ee3905..b046a1e9c 100644 --- a/src/char/paladin/fohdin.py +++ b/src/char/paladin/fohdin.py @@ -7,7 +7,7 @@ from logger import Logger from pather import Location from screen import convert_abs_to_monitor, convert_screen_to_abs, grab -from target_detect import get_visible_targets, log_targets +from target_detect import get_visible_targets from utils.misc import wait class FoHdin(Paladin): @@ -22,15 +22,31 @@ def _move_and_attack(self, abs_move: tuple[int, int], atk_len: float): self.move(pos_m, force_move=True) self._cast_hammers(atk_len) - def _cast_hammers(self, duration: float = 0, aura: str = "concentration"): #for nihlathak - return self._cast_left_with_aura(skill_name = "blessed_hammer", spray = 0, duration = duration, aura = aura) + def _cast_hammers( + self, + duration: float = 0, + aura: str = "concentration" + ): #for nihlathak + return self._cast_left_with_aura(skill_name = "blessed_hammer", spray = 0, spread_deg=0, duration = duration, aura = aura) - def _cast_foh(self, cast_pos_abs: tuple[float, float], spray: int = 10, duration: float = 0, aura: str = "conviction"): + def _cast_foh( + self, + cast_pos_abs: tuple[float, float], + spray: float = 10, + duration: float = 0, + aura: str = "conviction", + ): return self._cast_left_with_aura(skill_name = "foh", cast_pos_abs = cast_pos_abs, spray = spray, duration = duration, aura = aura) - def _cast_holy_bolt(self, cast_pos_abs: tuple[float, float], spray: int = 10, duration: float = 0, aura: str = "concentration"): - #if skill is bound : concentration, use concentration, otherwise move on with conviction. alternatively use redemption whilst holybolting. conviction does not help holy bolt (its magic damage) - return self._cast_left_with_aura(skill_name = "holy_bolt", cast_pos_abs = cast_pos_abs, spray = spray, duration = duration, aura = aura) + def _cast_holy_bolt( + self, + cast_pos_abs: tuple[float, float], + spray: float = 10, + spread_deg: float = 10, + duration: float = 0, + aura: str = "concentration", + ): + return self._cast_left_with_aura(skill_name = "holy_bolt", cast_pos_abs = cast_pos_abs, spray = spray, spread_deg = spread_deg, duration = duration, aura = aura) def _generic_foh_attack_sequence( self, @@ -39,7 +55,7 @@ def _generic_foh_attack_sequence( max_duration: float = 15, foh_to_holy_bolt_ratio: int = 3, target_detect: bool = True, - default_spray: int = 50, + default_spray: float = 50, aura: str = "" ) -> bool: start = time.time() diff --git a/src/char/paladin/paladin.py b/src/char/paladin/paladin.py index 5a37ada3d..6344a5e99 100644 --- a/src/char/paladin/paladin.py +++ b/src/char/paladin/paladin.py @@ -2,9 +2,6 @@ from item.pickit import PickIt #for Diablo from pather import Pather from pather import Pather -from screen import convert_abs_to_monitor -from ui import skills -from utils.misc import wait class Paladin(IChar): def __init__(self, skill_hotkeys: dict, pather: Pather, pickit: PickIt): @@ -18,25 +15,24 @@ def __init__(self, skill_hotkeys: dict, pather: Pather, pickit: PickIt): def pre_buff(self): self._pre_buff_cta() self._cast_holy_shield() - wait(self._cast_duration, self._cast_duration + 0.06) def _activate_concentration_aura(self, delay=None) -> bool: - return self._select_skill("concentration", delay=delay, mouse_click_type="right") + return self._activate_aura("concentration", delay=delay) - def _activate_redemption_aura(self, delay = [0.6, 0.8]) -> bool: - return self._select_skill("redemption", delay=delay, mouse_click_type="right") + def _activate_redemption_aura(self, delay = None) -> bool: + return self._activate_aura("redemption", delay=delay) - def _activate_cleanse_aura(self, delay = [0.3, 0.4]) -> bool: - return self._select_skill("cleansing", delay=delay, mouse_click_type="right") + def _activate_cleansing_aura(self, delay = None) -> bool: + return self._activate_aura("cleansing", delay=delay) def _activate_conviction_aura(self, delay = None) -> bool: - return self._select_skill("conviction", delay=delay, mouse_click_type="right") + return self._activate_aura("conviction", delay=delay) def _activate_vigor_aura(self, delay = None) -> bool: - return self._select_skill("vigor", delay=delay, mouse_click_type="right") + return self._activate_aura("vigor", delay=delay) def _cast_holy_shield(self) -> bool: - return self._cast_simple(skill_name="holy_shield", mouse_click_type="right") + return self._cast_simple(skill_name="holy_shield") def _activate_cleanse_redemption(self) -> bool: - return self._activate_cleanse_aura() or self._activate_redemption_aura() + return self._activate_cleansing_aura([0.3, 0.5]) or self._activate_redemption_aura([0.3, 0.5]) diff --git a/src/char/poison_necro.py b/src/char/poison_necro.py index 2584af444..2975ee0f2 100644 --- a/src/char/poison_necro.py +++ b/src/char/poison_necro.py @@ -158,7 +158,7 @@ def _summon_stat(self): ''' print counts for summons ''' Logger.info('\33[31m'+"Summon status | "+str(self._skeletons_count)+"skele | "+str(self._revive_count)+" rev | "+self._golem_count+" |"+'\033[0m') - def _revive(self, cast_pos_abs: tuple[float, float], spray: int = 10, cast_count: int=12): + def _revive(self, cast_pos_abs: tuple[float, float], spray: float = 10, cast_count: int=12): Logger.info('\033[94m'+"raise revive"+'\033[0m') keyboard.send(Config().char["stand_still"], do_release=False) for _ in range(cast_count): @@ -187,7 +187,7 @@ def _revive(self, cast_pos_abs: tuple[float, float], spray: int = 10, cast_count mouse.release(button="right") keyboard.send(Config().char["stand_still"], do_press=False) - def _raise_skeleton(self, cast_pos_abs: tuple[float, float], spray: int = 10, cast_count: int=16): + def _raise_skeleton(self, cast_pos_abs: tuple[float, float], spray: float = 10, cast_count: int=16): Logger.info('\033[94m'+"raise skeleton"+'\033[0m') keyboard.send(Config().char["stand_still"], do_release=False) for _ in range(cast_count): @@ -216,7 +216,7 @@ def _raise_skeleton(self, cast_pos_abs: tuple[float, float], spray: int = 10, ca mouse.release(button="right") keyboard.send(Config().char["stand_still"], do_press=False) - def _raise_mage(self, cast_pos_abs: tuple[float, float], spray: int = 10, cast_count: int=16): + def _raise_mage(self, cast_pos_abs: tuple[float, float], spray: float = 10, cast_count: int=16): Logger.info('\033[94m'+"raise mage"+'\033[0m') keyboard.send(Config().char["stand_still"], do_release=False) for _ in range(cast_count): @@ -291,7 +291,7 @@ def _bone_armor(self): - def _left_attack(self, cast_pos_abs: tuple[float, float], spray: int = 10): + def _left_attack(self, cast_pos_abs: tuple[float, float], spray: float = 10): keyboard.send(Config().char["stand_still"], do_release=False) if self._skill_hotkeys["skill_left"]: keyboard.send(self._skill_hotkeys["skill_left"]) @@ -306,7 +306,7 @@ def _left_attack(self, cast_pos_abs: tuple[float, float], spray: int = 10): keyboard.send(Config().char["stand_still"], do_press=False) - def _left_attack_single(self, cast_pos_abs: tuple[float, float], spray: int = 10, cast_count: int=6): + def _left_attack_single(self, cast_pos_abs: tuple[float, float], spray: float = 10, cast_count: int=6): keyboard.send(Config().char["stand_still"], do_release=False) if self._skill_hotkeys["skill_left"]: keyboard.send(self._skill_hotkeys["skill_left"]) @@ -345,7 +345,7 @@ def _lower_res(self, cast_pos_abs: tuple[float, float], spray: float = 10): wait(0.25, 0.35) mouse.release(button="right") - def _corpse_explosion(self, cast_pos_abs: tuple[float, float], spray: int = 10,cast_count: int = 8): + def _corpse_explosion(self, cast_pos_abs: tuple[float, float], spray: float = 10,cast_count: int = 8): keyboard.send(Config().char["stand_still"], do_release=False) Logger.info('\033[93m'+"corpse explosion~> random cast"+'\033[0m') for _ in range(cast_count): diff --git a/src/char/tools/calculations.py b/src/char/tools/calculations.py index 24f2a0bdf..32e47c078 100644 --- a/src/char/tools/calculations.py +++ b/src/char/tools/calculations.py @@ -6,11 +6,10 @@ def point_on_circle(radius: float, theta_deg: float, center: np.ndarray = (0, 0)) -> tuple[int, int]: theta = radians(theta_deg) res = center + radius * np.array([cos(theta), sin(theta)]) - return tuple([round(i) for i in res]) + return tuple([round(i) for i in res]) # return location of a point equidistant from origin randomly distributed between theta of spread_deg def spread(pos_abs: tuple[float, float], spread_deg: float) -> tuple[int, int]: - # random theta between -spread_deg and +spread_deg x1, y1 = pos_abs start_deg = degrees(np.arctan2(y1, x1)) random_theta_deg = random.uniform(start_deg-spread_deg/2, start_deg+spread_deg/2) diff --git a/src/char/trapsin.py b/src/char/trapsin.py index 8416d5ead..3e83556d8 100644 --- a/src/char/trapsin.py +++ b/src/char/trapsin.py @@ -35,7 +35,7 @@ def pre_buff(self): mouse.click(button="right") wait(self._cast_duration) - def _left_attack(self, cast_pos_abs: tuple[float, float], spray: int = 10): + def _left_attack(self, cast_pos_abs: tuple[float, float], spray: float = 10): keyboard.send(Config().char["stand_still"], do_release=False) if self._skill_hotkeys["skill_left"]: keyboard.send(self._skill_hotkeys["skill_left"]) From cdad1454516d568ca209f46c78c9edcce18a85ab Mon Sep 17 00:00:00 2001 From: mgleed Date: Mon, 20 Jun 2022 16:16:03 -0400 Subject: [PATCH 31/44] cleanup --- src/char/i_char.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/char/i_char.py b/src/char/i_char.py index 81825bf9e..4b265bb8d 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -224,19 +224,21 @@ def _cast_at_position( """ Casts a skill toward a given target. :param skill_name: name of skill to cast - :param cast_pos_abs: absolute position of target - :param spray: apply randomization within circle of radius 'spray' centered at target - :param spread_deg: apply randomization of target distributed along arc between theta of spread_deg + :param cast_pos_abs: default absolute position to cast at + :param spray: apply randomization within circle of radius 'spray' centered at cast_pos_abs + :param spread_deg: apply randomization of cast position distributed along arc between theta of spread_deg :param min_duration: hold down skill key for minimum 'duration' seconds - :param max_duration: hold down skill key for maximum 'duration' seconds (used only if use_target_detect is True) + :param max_duration: hold down skill key for maximum 'duration' seconds :param teleport_frequency: teleport to origin every 'teleport_frequency' seconds - :param use_target_detect: override cast_pos_abs with target detection + :param use_target_detect: override cast_pos_abs with closest target position :return: True if function finished, False otherwise """ if not self._get_hotkey(skill_name): return False mouse_move_delay = [0.4, 0.6] + if min_duration > max_duration: + max_duration = min_duration if max_duration: self._stand_still(True) From 7f4f2ec579f89cab21dc41f66e1926ccf1959aea Mon Sep 17 00:00:00 2001 From: mgleed Date: Mon, 20 Jun 2022 18:02:57 -0400 Subject: [PATCH 32/44] save as --- src/char/i_char.py | 65 +++++++++++++-------------- src/char/paladin/fohdin.py | 76 ++++++++++++++++++-------------- src/char/paladin/hammerdin.py | 2 +- src/char/sorceress/light_sorc.py | 4 +- src/char/tools/skill_data.py | 26 +++++++++++ 5 files changed, 102 insertions(+), 71 deletions(-) diff --git a/src/char/i_char.py b/src/char/i_char.py index 4b265bb8d..4affe610f 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -20,6 +20,8 @@ from target_detect import get_visible_targets class IChar: + _CrossGameCapabilities: None | CharacterCapabilities = None + def __init__(self, skill_hotkeys: dict): self._active_aura = "" self._mouse_click_held = { @@ -94,7 +96,6 @@ def _click_left(self, hold_time: float | list | tuple | None = None): def _click_right(self, hold_time: float | list | tuple | None = None): self._click("right", hold_time = hold_time) - @cached_property def _get_hotkey(self, skill: str) -> str | None: if not ( (skill in self._skill_hotkeys and (hotkey := self._skill_hotkeys[skill])) @@ -183,19 +184,20 @@ def _randomize_position(pos_abs: tuple[float, float], spray: float = 0, spread_d pos_abs = calculations.spray(pos_abs = pos_abs, r = spray) return get_closest_non_hud_pixel(pos_abs, "abs") - def _send_skill_and_cooldown(self, skill_name: str): + def _send_skill(self, skill_name: str, cooldown = True): self._key_press(self._get_hotkey(skill_name)) - wait(get_cast_wait_time(skill_name)) + if cooldown: + wait(get_cast_wait_time(skill_name)) def _activate_aura(self, skill_name: str, delay: float | list | tuple | None = (0.04, 0.08)): if not self._get_hotkey(skill_name): return False - if self._activate_aura != skill_name: # if aura is already active, don't activate it again + if self._active_aura != skill_name: # if aura is already active, don't activate it again self._active_aura = skill_name - self._key_press(self._get_hotkey(skill_name), hold_time= delay) + self._key_press(self._get_hotkey(skill_name), hold_time = delay) return True - def _cast_simple(self, skill_name: str, duration: float | list | tuple | None = None) -> bool: + def _cast_simple(self, skill_name: str, duration: float | list | tuple | None = None, cooldown = True) -> bool: """ Casts a skill """ @@ -203,7 +205,7 @@ def _cast_simple(self, skill_name: str, duration: float | list | tuple | None = return False if not self._key_held[hotkey]: # if skill is already active, don't activate it again if not duration: - self._send_skill_and_cooldown(skill_name) + self._send_skill(skill_name, cooldown = cooldown) else: self._stand_still(True) self._key_press(self._get_hotkey(skill_name), hold_time = duration) @@ -213,29 +215,34 @@ def _cast_simple(self, skill_name: str, duration: float | list | tuple | None = def _cast_at_position( self, skill_name: str, - cast_pos_abs: tuple[float, float], + cast_pos_abs: tuple[float, float] = (0, 0), spray: float = 0, spread_deg: float = 0, min_duration: float = 0, max_duration: float = 0, teleport_frequency: float = 0, use_target_detect = False, + aura: str = None, ) -> bool: """ Casts a skill toward a given target. :param skill_name: name of skill to cast - :param cast_pos_abs: default absolute position to cast at + :param cast_pos_abs: default absolute position to cast at. Defaults to origin (0, 0) :param spray: apply randomization within circle of radius 'spray' centered at cast_pos_abs :param spread_deg: apply randomization of cast position distributed along arc between theta of spread_deg :param min_duration: hold down skill key for minimum 'duration' seconds :param max_duration: hold down skill key for maximum 'duration' seconds :param teleport_frequency: teleport to origin every 'teleport_frequency' seconds :param use_target_detect: override cast_pos_abs with closest target position + :param aura: name of aura to attempt to keep active while casting :return: True if function finished, False otherwise """ if not self._get_hotkey(skill_name): return False + if aura: + self._activate_aura(aura) + mouse_move_delay = [0.4, 0.6] if min_duration > max_duration: max_duration = min_duration @@ -277,15 +284,6 @@ def _cast_at_position( return True - def _cast_left_with_aura(self, skill_name: str, cast_pos_abs: tuple[float, float] = None, spray: float = 0, spread_deg: float = 0, duration: float | list | tuple | None = None, aura: str = "") -> bool: - """ - Casts a skill at given position with an aura active - """ - #self._log_cast(skill_name, cast_pos_abs, spray, duration, aura) - if aura: - self._activate_aura(aura) - return self._cast_at_position(skill_name=skill_name, cast_pos_abs=cast_pos_abs, spray=spray, spread_deg = spread_deg, mouse_click_type="left", duration=duration) - """ TODO: Update this fn @@ -320,8 +318,8 @@ def _cast_in_arc(self, skill_name: str, cast_pos_abs: tuple[float, float] = [0,- GLOBAL SKILLS """ - def _cast_teleport(self) -> bool: - return self._cast_simple(skill_name="teleport") + def _cast_teleport(self, cooldown: bool = True) -> bool: + return self._cast_simple(skill_name="teleport", cooldown=cooldown) def _cast_battle_orders(self) -> bool: return self._cast_simple(skill_name="battle_orders") @@ -338,15 +336,6 @@ def _cast_town_portal(self) -> bool: CHARACTER ACTIONS AND MOVEMENT METHODS """ - def _teleport_to_origin(self): - """ - Teleports to the origin - """ - random_abs = self._randomize_position(pos_abs = (0,0), spray = 5) - pos_m = convert_abs_to_monitor(random_abs) - mouse.move(*pos_m, [0.12, 0.2]) - self._cast_teleport() - def _weapon_switch(self): if self.main_weapon_equipped is not None: self.main_weapon_equipped = not self.main_weapon_equipped @@ -380,11 +369,19 @@ def pick_up_item(self, pos: tuple[float, float], item_name: str = None, prev_cas def pre_move(self): pass - def _teleport_to_position(self, pos_monitor: tuple[float, float]): + def _teleport_to_position(self, pos_monitor: tuple[float, float], cooldown: bool = True): factor = Config().advanced_options["pathing_delay_factor"] - mouse.move(pos_monitor[0], pos_monitor[1], randomize=3, delay_factor=[(2+factor)/25, (4+factor)/25]) + mouse.move(*pos_monitor, randomize=3, delay_factor=[(2+factor)/25, (4+factor)/25]) wait(0.012, 0.02) - self._key_press(self._get_hotkey("teleport")) + self._cast_teleport(cooldown = cooldown) + + def _teleport_to_origin(self): + """ + Teleports to the origin + """ + random_abs = self._randomize_position(pos_abs = (0,0), spray = 5) + pos_m = convert_abs_to_monitor(random_abs) + self._teleport_to_position(pos_monitor = pos_m, cooldown = True) def _walk_to_position(self, pos_monitor: tuple[float, float], force_move: bool = False): factor = Config().advanced_options["pathing_delay_factor"] @@ -422,7 +419,7 @@ def move( factor = Config().advanced_options["pathing_delay_factor"] if use_tp and self.can_teleport(): # can_teleport() activates teleport hotkey if True # 7 frames is the fastest that teleport can be casted with 200 fcr on sorc - self._teleport_to_position(pos_monitor) + self._teleport_to_position(pos_monitor, cooldown = False) min_wait = get_cast_wait_time(self._base_class, "teleport", Config().char["fcr"]) + factor/25 # if there's still time remaining in cooldown, wait while time.time() - last_move_time < min_wait: @@ -483,7 +480,6 @@ def _pre_buff_cta(self) -> bool: def pre_buff(self): pass - """ OTHER METHODS """ @@ -524,7 +520,6 @@ def select_by_template( Logger.error(f"Wanted to select {template_type}, but could not find it") return False - """ KILL ROUTINES """ diff --git a/src/char/paladin/fohdin.py b/src/char/paladin/fohdin.py index b046a1e9c..d546f1785 100644 --- a/src/char/paladin/fohdin.py +++ b/src/char/paladin/fohdin.py @@ -24,29 +24,29 @@ def _move_and_attack(self, abs_move: tuple[int, int], atk_len: float): def _cast_hammers( self, - duration: float = 0, + max_duration: float = 0, aura: str = "concentration" ): #for nihlathak - return self._cast_left_with_aura(skill_name = "blessed_hammer", spray = 0, spread_deg=0, duration = duration, aura = aura) + return self._cast_at_position(skill_name = "blessed_hammer", spray = 0, spread_deg=0, max_duration = max_duration, aura = aura) def _cast_foh( self, cast_pos_abs: tuple[float, float], spray: float = 10, - duration: float = 0, + max_duration: float = 0, aura: str = "conviction", ): - return self._cast_left_with_aura(skill_name = "foh", cast_pos_abs = cast_pos_abs, spray = spray, duration = duration, aura = aura) + return self._cast_at_position(skill_name = "foh", cast_pos_abs = cast_pos_abs, spray = spray, max_duration = max_duration, aura = aura) def _cast_holy_bolt( self, cast_pos_abs: tuple[float, float], spray: float = 10, spread_deg: float = 10, - duration: float = 0, + max_duration: float = 0, aura: str = "concentration", ): - return self._cast_left_with_aura(skill_name = "holy_bolt", cast_pos_abs = cast_pos_abs, spray = spray, spread_deg = spread_deg, duration = duration, aura = aura) + return self._cast_at_position(skill_name = "holy_bolt", cast_pos_abs = cast_pos_abs, spray = spray, spread_deg = spread_deg, max_duration = max_duration, aura = aura) def _generic_foh_attack_sequence( self, @@ -58,32 +58,9 @@ def _generic_foh_attack_sequence( default_spray: float = 50, aura: str = "" ) -> bool: - start = time.time() - target_check_count = 1 - foh_aura = aura if aura else "conviction" - holy_bolt_aura = aura if aura else "concentration" - while (elapsed := (time.time() - start)) <= max_duration: - cast_pos_abs = default_target_abs - spray = default_spray - # if targets are detected, switch to targeting with reduced spread rather than present default cast position and default spread - if target_detect and (targets := get_visible_targets()): - # log_targets(targets) - spray = 5 - cast_pos_abs = targets[0].center_abs - - # if time > minimum and either targets aren't set or targets don't exist, exit loop - if elapsed > min_duration and (not target_detect or not targets): - break - else: - - # TODO: add delay between FOH casts--doesn't properly cast each FOH in sequence - # cast foh to holy bolt with preset ratio (e.g. 3 foh followed by 1 holy bolt if foh_to_holy_bolt_ratio = 3) - if foh_to_holy_bolt_ratio > 0 and not target_check_count % (foh_to_holy_bolt_ratio + 1): - self._cast_holy_bolt(cast_pos_abs, spray=spray, aura=holy_bolt_aura) - else: - self._cast_foh(cast_pos_abs, spray=spray, aura=foh_aura) - - target_check_count += 1 + + self._cast_at_position(skill_name = "foh", cast_pos_abs = default_target_abs, spray = default_spray, min_duration = min_duration, max_duration = max_duration, aura = aura) + return True #FOHdin Attack Sequence Optimized for trash @@ -763,4 +740,37 @@ def kill_diablo(self) -> bool: self._activate_cleanse_redemption() ### LOOT ### #self._cs_pickit() - return True \ No newline at end of file + return True + +if __name__ == "__main__": + import os + from config import Config + from char.paladin import FoHdin + from pather import Pather + from item.pickit import PickIt + import keyboard + from logger import Logger + from screen import start_detecting_window, stop_detecting_window + + keyboard.add_hotkey('f12', lambda: Logger.info('Force Exit (f12)') or stop_detecting_window() or os._exit(1)) + start_detecting_window() + print("Move to d2r window and press f11") + print("Press F9 to test attack sequence") + keyboard.wait("f11") + + pather = Pather() + pickit = PickIt() + char = FoHdin(Config().fohdin, pather, pickit) + + char.discover_capabilities() + + def routine(): + char._key_press(char._get_hotkey("foh"), hold_time=(0.04)) + time.sleep(0.15) + char._key_press(char._get_hotkey("holy_bolt"), hold_time=(0.4)) + time.sleep(0.04) + char._key_press(char._get_hotkey("foh"), hold_time=(3)) + + keyboard.add_hotkey('f9', lambda: routine()) + while True: + wait(0.01) diff --git a/src/char/paladin/hammerdin.py b/src/char/paladin/hammerdin.py index cc95df682..9819350f2 100644 --- a/src/char/paladin/hammerdin.py +++ b/src/char/paladin/hammerdin.py @@ -18,7 +18,7 @@ def __init__(self, *args, **kwargs): self._pather.offset_node(149, (70, 10)) def _cast_hammers(self, duration: float = 0, aura: str = "concentration"): #for nihlathak - return self._cast_left_with_aura(skill_name = "blessed_hammer", spray = 0, duration = duration, aura = aura) + return self._cast_at_position(skill_name = "blessed_hammer", spray = 0, duration = duration, aura = aura) def _move_and_attack(self, abs_move: tuple[int, int], atk_len: float): pos_m = convert_abs_to_monitor(abs_move) diff --git a/src/char/sorceress/light_sorc.py b/src/char/sorceress/light_sorc.py index 213e50145..65ec3790d 100644 --- a/src/char/sorceress/light_sorc.py +++ b/src/char/sorceress/light_sorc.py @@ -16,10 +16,10 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def _cast_chain_lightning(self, cast_pos_abs: tuple[float, float], spray: float = 20, duration: float = 0) -> bool: - return self._cast_left_with_aura(skill_name="chain_lightning", spray = spray, cast_pos_abs = cast_pos_abs, duration = duration) + return self._cast_at_position(skill_name="chain_lightning", spray = spray, cast_pos_abs = cast_pos_abs, duration = duration) def _cast_lightning(self, cast_pos_abs: tuple[float, float], spray: float = 20, duration: float = 0) -> bool: - return self._cast_left_with_aura(skill_name="lightning", spray = spray, cast_pos_abs = cast_pos_abs, duration = duration) + return self._cast_at_position(skill_name="lightning", spray = spray, cast_pos_abs = cast_pos_abs, duration = duration) def _cast_frozen_orb(self, cast_pos_abs: tuple[float, float], spray: float = 10, duration: float = 0) -> bool: return self._cast_at_position("frozen_orb", cast_pos_abs, spray = spray, duration = duration) diff --git a/src/char/tools/skill_data.py b/src/char/tools/skill_data.py index 493ed98a0..3ec3e2bf8 100644 --- a/src/char/tools/skill_data.py +++ b/src/char/tools/skill_data.py @@ -43,6 +43,32 @@ "vigor" } +CHANNELED_SKILLS = { + "armageddon": 0, # 2.4: removed + "blade_sentinel": 2, + "blizzard": 1.8, + "dragon_flight": 1, + "fire_wall": 1.4, + "firestorm": 0.6, + "fissure": 2, + "fist_of_the_heavens": 1, # 2.4: 1 -> 0.4 + "frozen_orb": 1, + "hurricane": 0, # 2.4: removed + "hydra": 0, # 2.4: removed + "immolation_arrow": 1, + "meteor": 1.2, + "molten_boulder": 1, # 2.4: 2 -> 1 + "plague_javelin": 1, # 2.4: 4 -> 1 + "poison_javelin": 0.6, + "shadow_master": 0.6, # 2.4: 6 -> 0.6 + "shadow_warrior": 0.6, # 2.4: 6 -> 0.6 + "shock_web": 0.6, + "valkyrie": 0.6, # 2.4: 6 -> 0.6 + "volcano": 4, + "werebear": 1, + "werewolf": 1, +} + def _get_base_frames(class_base: str, skill_name: str): if "lightning" in skill_name.lower() and class_base == "sorceress": class_base = "lightning_skills" From a326adbcdaa0630bf102cfdd2321a33c813a5a26 Mon Sep 17 00:00:00 2001 From: mgleed Date: Mon, 20 Jun 2022 22:18:57 -0400 Subject: [PATCH 33/44] wip, pindle working :p --- assets/NTItemAlias.dbl | 2 +- .../inventory/active_weapon_main.png | Bin 4754 -> 4698 bytes .../inventory/active_weapon_offhand.png | Bin 4758 -> 4758 bytes src/char/bone_necro.py | 7 +- src/char/i_char.py | 10 +- src/char/necro.py | 11 +- src/char/paladin/fohdin.py | 102 ++++++++++++------ src/char/poison_necro.py | 8 +- src/char/sorceress/blizz_sorc.py | 3 +- src/char/sorceress/light_sorc.py | 5 +- src/char/tools/skill_data.py | 2 +- src/char/trapsin.py | 5 +- src/d2r_image/d2data_data.py | 2 +- src/inventory/personal.py | 12 ++- src/pather.py | 2 +- src/ui_manager.py | 2 +- 16 files changed, 110 insertions(+), 63 deletions(-) diff --git a/assets/NTItemAlias.dbl b/assets/NTItemAlias.dbl index 282450f6c..14af6b9ee 100644 --- a/assets/NTItemAlias.dbl +++ b/assets/NTItemAlias.dbl @@ -939,7 +939,7 @@ NTIPAliasStat["skillpoisonoverridelength"] = 101; NTIPAliasStat["itemfasterblockrate"] = 102; NTIPAliasStat["fbr"] = 102; NTIPAliasStat["skillbypassundead"] = 103; NTIPAliasStat["skillbypassdemons"] = 104; -NTIPAliasStat["itemfastercastrate"] = 105; NTIPAliasStat["fcr"] = 105; +NTIPAliasStat["itemfastercastrate"] = 105; NTIPAliasStat["faster_cast_rate"] = 105; NTIPAliasStat["skillbypassbeasts"] = 106; NTIPAliasStat["itemsingleskill"] = 107; diff --git a/assets/templates/inventory/active_weapon_main.png b/assets/templates/inventory/active_weapon_main.png index 38ed3844edb46ea09b6ebb989ab639684b81df6c..dbd9729d574a117d3ff44f14e8a06743b905376f 100644 GIT binary patch delta 3160 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 V%nxY^#$>=Zd3yNb8{N-l{|^!S{WJgo delta 3216 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 T)PKkazK_FCM;Ya|=db-22SCBh diff --git a/assets/templates/inventory/active_weapon_offhand.png b/assets/templates/inventory/active_weapon_offhand.png index bbe266a0387af124d70832489359ce6200247243..617b3047b2d50eb797e46749477d9261bfcd6f60 100644 GIT binary patch delta 306 zcma)$JF3D!6hJYFSlDYTzN8T@+&kRNM_!|A&;{INGM$xRtAz^?27;D?uNPd2-CK-> zb*wB8oCBvV+Twmmh6-1T=B1h|6K~11!_Q>W;a4)vNl>)q8nkE!Y&n1ph9M9-^qR2r z6qo5NXA*LnJT%r50Ey=5u6=7fKU(}1)UXo5;qUyplfmI z_~1Qwr|gtHFVRGr4Pq%wNv63)^A5kFS%*K-JS}wPtqmSfk^u@z0F!3GDzGpn|ld`QUzGW0SawNdql69nq!=%9D)gs7;5noQ78vSXbYBw^Mp0$m2BSe fp`W-ST=srvy)!^B##@opkD=AWV diff --git a/src/char/bone_necro.py b/src/char/bone_necro.py index 095bc236e..a512fc586 100644 --- a/src/char/bone_necro.py +++ b/src/char/bone_necro.py @@ -6,7 +6,8 @@ from logger import Logger from screen import grab, convert_abs_to_monitor, convert_screen_to_abs from config import Config -from utils.misc import wait, rotate_vec, unit_vector +from utils.misc import wait +from char.tools import calculations import random from pather import Location, Pather import screen as screen @@ -103,8 +104,8 @@ def _cast_circle(self, cast_dir: tuple[float,float],cast_start_angle: float=0.0, mouse.press(button="right") for i in range(cast_div): - angle = self._lerp(cast_start_angle,cast_end_angle,float(i)/cast_div) - target = unit_vector(rotate_vec(cast_dir, angle)) + angle = calculations.lerp(cast_start_angle,cast_end_angle,float(i)/cast_div) + target = calculations.unit_vector(calculations.rotate_vec(cast_dir, angle)) Logger.debug(f"Circle cast - current angle: {angle}º") circle_pos_abs = get_closest_non_hud_pixel(pos = target*radius, pos_type="abs") circle_pos_monitor = convert_abs_to_monitor(circle_pos_abs) diff --git a/src/char/i_char.py b/src/char/i_char.py index 4affe610f..fe6ac866b 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -177,7 +177,7 @@ def _log_cast(skill_name: str, cast_pos_abs: tuple[float, float], spray: float, msg += f" with {aura} active" Logger.debug(msg) - def _randomize_position(pos_abs: tuple[float, float], spray: float = 0, spread_deg: float = 0): + def _randomize_position(self, pos_abs: tuple[float, float], spray: float = 0, spread_deg: float = 0) -> tuple[int, int]: if spread_deg: pos_abs = calculations.spread(pos_abs = pos_abs, spread_deg = spread_deg) if spray: @@ -187,7 +187,7 @@ def _randomize_position(pos_abs: tuple[float, float], spray: float = 0, spread_d def _send_skill(self, skill_name: str, cooldown = True): self._key_press(self._get_hotkey(skill_name)) if cooldown: - wait(get_cast_wait_time(skill_name)) + wait(get_cast_wait_time(class_base = self._base_class, skill_name = skill_name)) def _activate_aura(self, skill_name: str, delay: float | list | tuple | None = (0.04, 0.08)): if not self._get_hotkey(skill_name): @@ -205,7 +205,7 @@ def _cast_simple(self, skill_name: str, duration: float | list | tuple | None = return False if not self._key_held[hotkey]: # if skill is already active, don't activate it again if not duration: - self._send_skill(skill_name, cooldown = cooldown) + self._send_skill(skill_name = skill_name, cooldown = cooldown) else: self._stand_still(True) self._key_press(self._get_hotkey(skill_name), hold_time = duration) @@ -420,7 +420,7 @@ def move( if use_tp and self.can_teleport(): # can_teleport() activates teleport hotkey if True # 7 frames is the fastest that teleport can be casted with 200 fcr on sorc self._teleport_to_position(pos_monitor, cooldown = False) - min_wait = get_cast_wait_time(self._base_class, "teleport", Config().char["fcr"]) + factor/25 + min_wait = get_cast_wait_time(class_base = self._base_class, skill_name = "teleport") + factor/25 # if there's still time remaining in cooldown, wait while time.time() - last_move_time < min_wait: wait(0.02) @@ -471,7 +471,7 @@ def tp_town(self) -> bool: def _pre_buff_cta(self) -> bool: if not self._get_hotkey("cta_available"): return False - if self.main_weapon_equipped(): + if self.main_weapon_equipped: self._switch_to_offhand_weapon() self._cast_battle_command() self._cast_battle_orders() diff --git a/src/char/necro.py b/src/char/necro.py index 3859bcbea..1492d32f1 100644 --- a/src/char/necro.py +++ b/src/char/necro.py @@ -7,7 +7,8 @@ from logger import Logger from screen import grab, convert_abs_to_monitor, convert_screen_to_abs from config import Config -from utils.misc import wait, rotate_vec, unit_vector +from utils.misc import wait +from char.tools import calculations import random from pather import Location, Pather import numpy as np @@ -347,8 +348,8 @@ def _cast_circle(self, cast_dir: tuple[float,float],cast_start_angle: float=0.0, mouse.press(button="right") for i in range(cast_div): - angle = self._lerp(cast_start_angle,cast_end_angle,float(i)/cast_div) - target = unit_vector(rotate_vec(cast_dir, angle)) + angle = calculations.lerp(cast_start_angle,cast_end_angle,float(i)/cast_div) + target = calculations.unit_vector(calculations.rotate_vec(cast_dir, angle)) #Logger.info("current angle ~> "+str(angle)) for j in range(cast_v_div): circle_pos_abs = get_closest_non_hud_pixel(pos = (target*120.0*float(j+1.0))*offset, pos_type="abs") @@ -424,12 +425,12 @@ def kill_pindle(self) -> bool: for _ in range(2): - corpse_pos = unit_vector(rotate_vec(cast_pos_abs, rot_deg)) * 200 + corpse_pos = calculations.unit_vector(calculations.rotate_vec(cast_pos_abs, rot_deg)) * 200 self._corpse_explosion(pc,40,cast_count=2) rot_deg-=7 rot_deg=0 for _ in range(2): - corpse_pos = unit_vector(rotate_vec(cast_pos_abs, rot_deg)) * 200 + corpse_pos = calculations.unit_vector(calculations.rotate_vec(cast_pos_abs, rot_deg)) * 200 self._corpse_explosion(pc,40,cast_count=2) rot_deg+=7 diff --git a/src/char/paladin/fohdin.py b/src/char/paladin/fohdin.py index d546f1785..c55631ef4 100644 --- a/src/char/paladin/fohdin.py +++ b/src/char/paladin/fohdin.py @@ -1,5 +1,6 @@ import time +from utils.custom_mouse import mouse from char.paladin import Paladin from config import Config from health_manager import set_panel_check_paused @@ -33,10 +34,11 @@ def _cast_foh( self, cast_pos_abs: tuple[float, float], spray: float = 10, + spread_deg: float = 10, max_duration: float = 0, aura: str = "conviction", ): - return self._cast_at_position(skill_name = "foh", cast_pos_abs = cast_pos_abs, spray = spray, max_duration = max_duration, aura = aura) + return self._cast_at_position(skill_name = "foh", cast_pos_abs = cast_pos_abs, spray = spray, spread_deg = spread_deg, max_duration = max_duration, aura = aura) def _cast_holy_bolt( self, @@ -50,31 +52,63 @@ def _cast_holy_bolt( def _generic_foh_attack_sequence( self, - default_target_abs: tuple[int, int] = (0, 0), + cast_pos_abs: tuple[int, int] = (0, 0), min_duration: float = 0, max_duration: float = 15, - foh_to_holy_bolt_ratio: int = 3, - target_detect: bool = True, - default_spray: float = 50, - aura: str = "" + spray: float = 50, + spread_deg: float = 10, + aura: str = "conviction" ) -> bool: - - self._cast_at_position(skill_name = "foh", cast_pos_abs = default_target_abs, spray = default_spray, min_duration = min_duration, max_duration = max_duration, aura = aura) - + # custom FOH alternating with holy bolt routine + if Config().char["faster_cast_rate"] >= 75: + self._activate_conviction_aura() + self._stand_still(True) + start = time_of_last_tp = time.perf_counter() + # cast while time is less than max duration + while (elapsed_time := time.perf_counter() - start) < max_duration: + targets = get_visible_targets() + if targets: + pos_abs = targets[0].center_abs + pos_abs = self._randomize_position(pos_abs = pos_abs, spray = 5, spread_deg = 0) + # otherwise, use the given position with randomization parameters + else: + pos_abs = self._randomize_position(cast_pos_abs, spray = spray, spread_deg = spread_deg) + pos_m = convert_abs_to_monitor(pos_abs) + mouse.move(*pos_m, delay_factor= [0.4, 0.6]) + # at frame 0, press key + # frame 1-6 startup of FOH + # frame 6 can cast HB + # frame 15 FOH able to be cast again + # frame 16 FOH startup + self._key_press(self._get_hotkey("foh"), hold_time=3/25) + time.sleep(2/25) #total of 5 frame startup + self._key_press(self._get_hotkey("holy_bolt"), hold_time=(8/25)) # now at frame 6 + time.sleep(2/25) # now at frame 15 + # if teleport frequency is set, teleport every teleport_frequency seconds + if (elapsed_time - time_of_last_tp) >= 3.5: + self._teleport_to_origin() + time_of_last_tp = elapsed_time + # if target detection is enabled and minimum time has elapsed and no targets remain, end casting + if (elapsed_time > min_duration) and not targets: + break + self._stand_still(False) + else: + self._cast_at_position(skill_name = "foh", cast_pos_abs = cast_pos_abs, spray = spray, spread_deg = spread_deg, min_duration = min_duration, max_duration = max_duration, teleport_frequency = 3.5, use_target_detect = True, aura = aura) return True #FOHdin Attack Sequence Optimized for trash - def _cs_attack_sequence(self, min_duration: float = Config().char["atk_len_cs_trashmobs"], max_duration: float = Config().char["atk_len_cs_trashmobs"] * 3): - self._generic_foh_attack_sequence(default_target_abs=(20,20), min_duration = min_duration, max_duration = max_duration, default_spray=100, foh_to_holy_bolt_ratio=6) + def _cs_attack_sequence(self, min_duration: float, max_duration: float): + self._generic_foh_attack_sequence(cast_pos_abs=(0, 0), min_duration = min_duration, max_duration = max_duration, spread=100) self._activate_redemption_aura() - def _cs_trash_mobs_attack_sequence(self, min_duration: float = 1.2, max_duration: float = Config().char["atk_len_cs_trashmobs"]): - self._cs_attack_sequence(min_duration = min_duration, max_duration = max_duration) + def _cs_trash_mobs_attack_sequence(self): + self._cs_attack_sequence(min_duration = Config().char["atk_len_cs_trashmobs"], max_duration = Config().char["atk_len_cs_trashmobs"]*3) def _cs_pickit(self, skip_inspect: bool = False): new_items = self._pickit.pick_up_items(self) self._picked_up_items |= new_items if not skip_inspect and new_items: + wait(1) set_panel_check_paused(True) inspect_items(grab(), ignore_sell=True) set_panel_check_paused(False) @@ -98,8 +132,8 @@ def kill_pindle(self) -> bool: else: self._pather.traverse_nodes([103], self, timeout=1.0, active_skill="conviction") - cast_pos_abs = [pindle_pos_abs[0] * 0.9, pindle_pos_abs[1] * 0.9] - self._generic_foh_attack_sequence(default_target_abs=cast_pos_abs, min_duration=atk_len_dur, max_duration=atk_len_dur*3, default_spray=11) + cast_pos_abs = (pindle_pos_abs[0] * 0.9, pindle_pos_abs[1] * 0.9) + self._generic_foh_attack_sequence(cast_pos_abs=cast_pos_abs, min_duration=atk_len_dur, max_duration=atk_len_dur*3, spray=11) if self.capabilities.can_teleport_natively: self._pather.traverse_nodes_fixed("pindle_end", self) @@ -108,7 +142,7 @@ def kill_pindle(self) -> bool: self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, timeout=1.0) # Use target-based attack sequence one more time before pickit - self._generic_foh_attack_sequence(default_target_abs=cast_pos_abs, max_duration=atk_len_dur, default_spray=11) + self._generic_foh_attack_sequence(cast_pos_abs=cast_pos_abs, max_duration=atk_len_dur, spray=11) self._activate_cleanse_redemption() return True @@ -121,13 +155,13 @@ def kill_council(self) -> bool: nodes = [225, 226, 300] for i, node in enumerate(nodes): self._pather.traverse_nodes([node], self, timeout=2.2, do_pre_move = False, force_tp=(self.capabilities.can_teleport_natively or i > 0), use_tp_charge=(self.capabilities.can_teleport_natively or i > 0)) - default_target_abs = self._pather.find_abs_node_pos(node, img := grab()) or self._pather.find_abs_node_pos(906, img) or (-50, -50) - self._generic_foh_attack_sequence(default_target_abs=default_target_abs, min_duration=atk_len_dur, max_duration=atk_len_dur*3, default_spray=80) + cast_pos_abs = self._pather.find_abs_node_pos(node, img := grab()) or self._pather.find_abs_node_pos(906, img) or (-50, -50) + self._generic_foh_attack_sequence(cast_pos_abs=cast_pos_abs, min_duration=atk_len_dur, max_duration=atk_len_dur*3, spray=80) # return to 226 and prepare for pickit self._pather.traverse_nodes([226], self, timeout=2.2, do_pre_move = False, force_tp=True, use_tp_charge=True) - default_target_abs = self._pather.find_abs_node_pos(226, img := grab()) or self._pather.find_abs_node_pos(906, img) or (-50, -50) - self._generic_foh_attack_sequence(default_target_abs=default_target_abs, max_duration=atk_len_dur*3, default_spray=80) + cast_pos_abs = self._pather.find_abs_node_pos(226, img := grab()) or self._pather.find_abs_node_pos(906, img) or (-50, -50) + self._generic_foh_attack_sequence(cast_pos_abs=cast_pos_abs, max_duration=atk_len_dur*3, spray=80) self._activate_cleanse_redemption() @@ -137,7 +171,7 @@ def kill_eldritch(self) -> bool: eld_pos_abs = convert_screen_to_abs(Config().path["eldritch_end"][0]) atk_len_dur = float(Config().char["atk_len_eldritch"]) - self._generic_foh_attack_sequence(default_target_abs=eld_pos_abs, min_duration=atk_len_dur, max_duration=atk_len_dur*3, default_spray=70) + self._generic_foh_attack_sequence(cast_pos_abs=eld_pos_abs, min_duration=atk_len_dur, max_duration=atk_len_dur*3, spray=70) # move to end node pos_m = convert_abs_to_monitor((70, -200)) @@ -146,7 +180,7 @@ def kill_eldritch(self) -> bool: self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, timeout=0.1) # check mobs one more time before pickit - self._generic_foh_attack_sequence(default_target_abs=eld_pos_abs, max_duration=atk_len_dur, default_spray=70) + self._generic_foh_attack_sequence(cast_pos_abs=eld_pos_abs, max_duration=atk_len_dur, spray=70) self._activate_cleanse_redemption() return True @@ -163,7 +197,7 @@ def kill_shenk(self): self._cast_foh((0, 0), spray=11, min_duration = 2, aura = "conviction") # then do generic mob detect sequence diff = atk_len_dur if atk_len_dur <= 2 else (atk_len_dur - 2) - self._generic_foh_attack_sequence(min_duration=atk_len_dur - diff, max_duration=atk_len_dur*3 - diff, default_spray=10, target_detect=False) + self._generic_foh_attack_sequence(min_duration=atk_len_dur - diff, max_duration=atk_len_dur*3 - diff, spray=10, target_detect=False) self._activate_cleanse_redemption() return True @@ -173,22 +207,22 @@ def kill_nihlathak(self, end_nodes: list[int]) -> bool: atk_len_dur = Config().char["atk_len_nihlathak"] # Move close to nihlathak self._pather.traverse_nodes(end_nodes, self, timeout=0.8) - if self._select_skill("blessed_hammer"): + if self._get_hotkey("blessed_hammer"): self._cast_hammers(atk_len_dur/4) self._cast_hammers(2*atk_len_dur/4, "redemption") self._move_and_attack((30, 15), atk_len_dur/4, "redemption") else: Logger.warning("FOHDin without blessed hammer is not very strong vs. Nihlathak!") - self._generic_foh_attack_sequence(min_duration=atk_len_dur/2, max_duration=atk_len_dur, default_spray=70, aura="redemption") - self._generic_foh_attack_sequence(min_duration=atk_len_dur/2, max_duration=atk_len_dur, default_spray=70, aura="redemption") - self._generic_foh_attack_sequence(max_duration=atk_len_dur*2, default_spray=70) + self._generic_foh_attack_sequence(min_duration=atk_len_dur/2, max_duration=atk_len_dur, spray=70, aura="redemption") + self._generic_foh_attack_sequence(min_duration=atk_len_dur/2, max_duration=atk_len_dur, spray=70, aura="redemption") + self._generic_foh_attack_sequence(max_duration=atk_len_dur*2, spray=70) self._activate_cleanse_redemption() return True def kill_summoner(self) -> bool: # Attack atk_len_dur = Config().char["atk_len_arc"] - self._generic_foh_attack_sequence(min_duration=atk_len_dur, max_duration=atk_len_dur*2, default_spray=80) + self._generic_foh_attack_sequence(min_duration=atk_len_dur, max_duration=atk_len_dur*2, spray=80) self._activate_cleanse_redemption() return True @@ -736,7 +770,7 @@ def kill_diablo(self) -> bool: atk_len_dur = float(Config().char["atk_len_diablo"]) Logger.debug("Attacking Diablo at position 1/1") diablo_abs = [100,-100] #hardcoded dia pos. - self._generic_foh_attack_sequence(default_target_abs=diablo_abs, min_duration=atk_len_dur, max_duration=atk_len_dur*3, aura="concentration", foh_to_holy_bolt_ratio=2) + self._generic_foh_attack_sequence(cast_pos_abs=diablo_abs, min_duration=atk_len_dur, max_duration=atk_len_dur*3, aura="concentration", foh_to_holy_bolt_ratio=2) self._activate_cleanse_redemption() ### LOOT ### #self._cs_pickit() @@ -765,11 +799,13 @@ def kill_diablo(self) -> bool: char.discover_capabilities() def routine(): - char._key_press(char._get_hotkey("foh"), hold_time=(0.04)) - time.sleep(0.15) - char._key_press(char._get_hotkey("holy_bolt"), hold_time=(0.4)) - time.sleep(0.04) + char._key_press(char._get_hotkey("foh"), hold_time=4/25) + time.sleep(1/25) #total of 5 frame startup + char._key_press(char._get_hotkey("holy_bolt"), hold_time=(1/25)) char._key_press(char._get_hotkey("foh"), hold_time=(3)) + # for _ in range(0, 10): + # char._key_press(char._get_hotkey("foh"), hold_time=(0.1)) + # time.sleep(0.3) keyboard.add_hotkey('f9', lambda: routine()) while True: diff --git a/src/char/poison_necro.py b/src/char/poison_necro.py index 2975ee0f2..e90f8f5c5 100644 --- a/src/char/poison_necro.py +++ b/src/char/poison_necro.py @@ -6,11 +6,11 @@ from logger import Logger from screen import grab from config import Config -from utils.misc import wait, rotate_vec, unit_vector +from utils.misc import wait +from char.tools import calculations import random from pather import Location, Pather import screen as screen -import numpy as np import time import os from ui_manager import get_closest_non_hud_pixel @@ -368,8 +368,8 @@ def _cast_circle(self, cast_dir: tuple[float,float],cast_start_angle: float=0.0, mouse.press(button="right") for i in range(cast_div): - angle = self._lerp(cast_start_angle,cast_end_angle,float(i)/cast_div) - target = unit_vector(rotate_vec(cast_dir, angle)) + angle = calculations.lerp(cast_start_angle,cast_end_angle,float(i)/cast_div) + target = calculations.unit_vector(calculations.rotate_vec(cast_dir, angle)) #Logger.info("current angle ~> "+str(angle)) for j in range(cast_v_div): circle_pos_abs = get_closest_non_hud_pixel(pos = ((target*120.0*float(j+1.0))*offset), pos_type="abs") diff --git a/src/char/sorceress/blizz_sorc.py b/src/char/sorceress/blizz_sorc.py index 39cc7bd5d..de0f25af5 100644 --- a/src/char/sorceress/blizz_sorc.py +++ b/src/char/sorceress/blizz_sorc.py @@ -2,10 +2,11 @@ from char.sorceress import Sorceress from utils.custom_mouse import mouse from logger import Logger -from utils.misc import wait, rotate_vec, unit_vector +from utils.misc import wait import random from pather import Location import numpy as np +from char.tools import calculations from screen import convert_abs_to_monitor, grab, convert_screen_to_abs from config import Config import template_finder diff --git a/src/char/sorceress/light_sorc.py b/src/char/sorceress/light_sorc.py index 65ec3790d..530fb0e8a 100644 --- a/src/char/sorceress/light_sorc.py +++ b/src/char/sorceress/light_sorc.py @@ -2,7 +2,8 @@ from char.sorceress import Sorceress from utils.custom_mouse import mouse from logger import Logger -from utils.misc import wait, rotate_vec, unit_vector +from utils.misc import wait +from char.tools import calculations import random from pather import Location import numpy as np @@ -180,7 +181,7 @@ def kill_nihlathak(self, end_nodes: list[int]) -> bool: # Do some tele "dancing" after each sequence if i < atk_len - 1: rot_deg = random.randint(-10, 10) if i % 2 == 0 else random.randint(170, 190) - tele_pos_abs = unit_vector(rotate_vec(cast_pos_abs, rot_deg)) * 100 + tele_pos_abs = calculations.unit_vector(calculations.rotate_vec(cast_pos_abs, rot_deg)) * 100 pos_m = convert_abs_to_monitor(tele_pos_abs) self.pre_move() self.move(pos_m) diff --git a/src/char/tools/skill_data.py b/src/char/tools/skill_data.py index 3ec3e2bf8..b71e94930 100644 --- a/src/char/tools/skill_data.py +++ b/src/char/tools/skill_data.py @@ -89,5 +89,5 @@ def get_casting_frames(class_base: str, skill_name: str, fcr: int): else: return math.ceil(256 * _get_base_frames(class_base, skill_name) / math.floor(_get_animation_speed(class_base) * (100 + _efcr(fcr)) / 100)) - 1 -def get_cast_wait_time(class_base: str, skill_name: str, fcr: int): +def get_cast_wait_time(class_base: str, skill_name: str, fcr: int = Config().char["faster_cast_rate"]): return (get_casting_frames(class_base, skill_name, fcr) + Config().char["extra_casting_frames"]) * (1/25) \ No newline at end of file diff --git a/src/char/trapsin.py b/src/char/trapsin.py index 3e83556d8..c0987f016 100644 --- a/src/char/trapsin.py +++ b/src/char/trapsin.py @@ -5,7 +5,8 @@ from logger import Logger from screen import convert_abs_to_monitor, convert_screen_to_abs, grab from config import Config -from utils.misc import wait, rotate_vec, unit_vector +from utils.misc import wait +from char.tools import calculations import random from pather import Location, Pather import numpy as np @@ -123,7 +124,7 @@ def kill_nihlathak(self, end_nodes: list[int]) -> bool: # Do some tele "dancing" after each sequence if i < atk_len - 1: rot_deg = random.randint(-10, 10) if i % 2 == 0 else random.randint(170, 190) - tele_pos_abs = unit_vector(rotate_vec(cast_pos_abs, rot_deg)) * 100 + tele_pos_abs = calculations.unit_vector(calculations.rotate_vec(cast_pos_abs, rot_deg)) * 100 pos_m = convert_abs_to_monitor(tele_pos_abs) self.pre_move() self.move(pos_m) diff --git a/src/d2r_image/d2data_data.py b/src/d2r_image/d2data_data.py index b3fe05de5..8e990548e 100644 --- a/src/d2r_image/d2data_data.py +++ b/src/d2r_image/d2data_data.py @@ -35563,7 +35563,7 @@ "cast1", "item_fastercastrate", "itemfastercastrate", - "fcr", + "faster_cast_rate", "fastercastrate", "cast2", "cast3" diff --git a/src/inventory/personal.py b/src/inventory/personal.py index 8ce7f597e..249d4a9dc 100644 --- a/src/inventory/personal.py +++ b/src/inventory/personal.py @@ -73,9 +73,15 @@ def inventory_has_items(img: np.ndarray = None, close_window = False) -> bool: def is_main_weapon_active(img: np.ndarray = None) -> bool | None: # inventory must be open img = grab() if img is None else img - if (res := detect_screen_object(ScreenObjects.ActiveWeaponBound)).valid: - return "main" in res.name.lower() - Logger.warning("is_main_weapon_active(): Failed to detect active weapon, is inventory not open?") + if (res := detect_screen_object(ScreenObjects.ActiveWeaponBound, img)).valid: + state = "main" in res.name.lower() + equipped = "Main" if state else "Offhand" + Logger.debug(f"{equipped} weapon is active") + return state + if not is_visible(ScreenObjects.InventoryBackground): + Logger.warning("is_main_weapon_active(): Failed to detect active weapon, is inventory not open?") + else: + Logger.warning('fail') return None def stash_all_items(items: list = None): diff --git a/src/pather.py b/src/pather.py index 84c2c76a2..486e90aa0 100644 --- a/src/pather.py +++ b/src/pather.py @@ -604,7 +604,7 @@ def traverse_nodes( if active_skill == "": active_skill = char.default_move_skill if active_skill is not None: - char.select_skill(active_skill, mouse_click_type = "right") + char._activate_aura(active_skill) last_direction = None for i, node_idx in enumerate(path): diff --git a/src/ui_manager.py b/src/ui_manager.py index 96795ce40..b04e2a3ea 100644 --- a/src/ui_manager.py +++ b/src/ui_manager.py @@ -272,7 +272,7 @@ class ScreenObjects: ) ActiveWeaponBound=ScreenObject( ref=["ACTIVE_WEAPON_MAIN", "ACTIVE_WEAPON_OFFHAND"], - roi="active_weapon_tabs", + #roi="active_weapon_tabs", threshold=0.8, use_grayscale=True, best_match=True, From d3c0b6b4ee0ea6172abe6333b280d23fa975b806 Mon Sep 17 00:00:00 2001 From: mgleed Date: Tue, 21 Jun 2022 08:31:43 -0400 Subject: [PATCH 34/44] save as --- src/char/i_char.py | 16 +++++++++++--- src/char/paladin/fohdin.py | 6 +++--- src/target_detect.py | 30 +++++++++++--------------- src/utils/live-view-last-settings.json | 2 +- 4 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/char/i_char.py b/src/char/i_char.py index fe6ac866b..e4ccabe86 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -165,14 +165,24 @@ def on_capabilities_discovered(self, capabilities: CharacterCapabilities): """ @staticmethod - def _log_cast(skill_name: str, cast_pos_abs: tuple[float, float], spray: float, min_duration: float, aura: str): + def _log_cast(skill_name: str, cast_pos_abs: tuple[float, float], spray: float, spread_deg: float, min_duration: float, max_duration: float, aura: str): msg = f"Casting skill {skill_name}" if cast_pos_abs: msg += f" at screen coordinate {convert_abs_to_screen(cast_pos_abs)}" if spray: msg += f" with spray of {spray}" + if spread_deg: + msg += f" with spread of {spread_deg}" + if min_duration or max_duration: + msg += f" for " if min_duration: - msg += f" for {round(min_duration, 1)}s" + msg += f"{round(min_duration, 1)}" + if min_duration and max_duration: + msg += f" to " + if max_duration: + msg += f"{round(min_duration, 1)}" + if min_duration or max_duration: + msg += f" sec" if aura: msg += f" with {aura} active" Logger.debug(msg) @@ -239,7 +249,7 @@ def _cast_at_position( """ if not self._get_hotkey(skill_name): return False - + self._log_cast(skill_name, cast_pos_abs, spray, spread_deg, min_duration, max_duration, aura) if aura: self._activate_aura(aura) diff --git a/src/char/paladin/fohdin.py b/src/char/paladin/fohdin.py index c55631ef4..91799fff4 100644 --- a/src/char/paladin/fohdin.py +++ b/src/char/paladin/fohdin.py @@ -55,7 +55,7 @@ def _generic_foh_attack_sequence( cast_pos_abs: tuple[int, int] = (0, 0), min_duration: float = 0, max_duration: float = 15, - spray: float = 50, + spray: float = 20, spread_deg: float = 10, aura: str = "conviction" ) -> bool: @@ -98,7 +98,7 @@ def _generic_foh_attack_sequence( #FOHdin Attack Sequence Optimized for trash def _cs_attack_sequence(self, min_duration: float, max_duration: float): - self._generic_foh_attack_sequence(cast_pos_abs=(0, 0), min_duration = min_duration, max_duration = max_duration, spread=100) + self._generic_foh_attack_sequence(cast_pos_abs=(0, 0), min_duration = min_duration, max_duration = max_duration, spread_deg=100) self._activate_redemption_aura() def _cs_trash_mobs_attack_sequence(self): @@ -133,7 +133,7 @@ def kill_pindle(self) -> bool: self._pather.traverse_nodes([103], self, timeout=1.0, active_skill="conviction") cast_pos_abs = (pindle_pos_abs[0] * 0.9, pindle_pos_abs[1] * 0.9) - self._generic_foh_attack_sequence(cast_pos_abs=cast_pos_abs, min_duration=atk_len_dur, max_duration=atk_len_dur*3, spray=11) + self._generic_foh_attack_sequence(cast_pos_abs=cast_pos_abs, min_duration=atk_len_dur, max_duration=atk_len_dur*3, spread_deg=11) if self.capabilities.can_teleport_natively: self._pather.traverse_nodes_fixed("pindle_end", self) diff --git a/src/target_detect.py b/src/target_detect.py index 914bd7143..2c8c67123 100644 --- a/src/target_detect.py +++ b/src/target_detect.py @@ -67,6 +67,7 @@ def get_visible_targets( )) if targets: targets = sorted(targets, key=lambda obj: obj.distance) + Logger.debug(f"{len(targets)} targets detected, closest at {targets[0].center_abs} {targets[0].distance} px away") return targets def _bright_contrast(img: np.ndarray, brightness: int = 255, contrast: int = 127): @@ -248,22 +249,17 @@ def live_view(self): start_detecting_window() print("Move to d2r window and press f11") keyboard.wait("f11") - # l = LiveViewer() + l = LiveViewer() - masked_image = False - def _toggle_masked_image(): - global masked_image - masked_image = not masked_image - - while 1: - img = grab() - targets = get_visible_targets() - - display_img = img.copy() - - for target in targets: - x, y = target.center - cv2.rectangle(display_img, target.roi[:2], (target.roi[0] + target.roi[2], target.roi[1] + target.roi[3]), (0, 0, 255), 1) - cv2.imshow('test', display_img) - key = cv2.waitKey(1) +# while 1: +# img = grab() +# targets = get_visible_targets() +# +# display_img = img.copy() +# +# for target in targets: +# x, y = target.center +# cv2.rectangle(display_img, target.roi[:2], (target.roi[0] + target.roi[2], target.roi[1] + target.roi[3]), (0, 0, 255), 1) +# cv2.imshow('test', display_img) +# key = cv2.waitKey(1) diff --git a/src/utils/live-view-last-settings.json b/src/utils/live-view-last-settings.json index c119f0f25..b16461b7d 100644 --- a/src/utils/live-view-last-settings.json +++ b/src/utils/live-view-last-settings.json @@ -1 +1 @@ -{"erode": 0, "dilate": 0, "blur": 3, "lh": 110, "ls": 169, "lv": 50, "uh": 120, "us": 255, "uv": 255, "bright": 255, "contrast": 127, "thresh": 0, "invert": 0} \ No newline at end of file +{"erode": 0, "dilate": 0, "blur": 3, "lh": 0, "ls": 0, "lv": 0, "uh": 180, "us": 255, "uv": 255, "bright": 255, "contrast": 127, "thresh": 0, "invert": 0} \ No newline at end of file From 7e392e2fcdf58e33cdcc3191dc66a5bdd11b5e9b Mon Sep 17 00:00:00 2001 From: mgleed Date: Tue, 21 Jun 2022 22:28:43 -0400 Subject: [PATCH 35/44] save as --- assets/d2r_settings.json | 4 ++-- src/char/i_char.py | 12 ++++++------ src/char/paladin/fohdin.py | 2 +- src/pather.py | 7 +++---- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/assets/d2r_settings.json b/assets/d2r_settings.json index 516dad448..4a49f7197 100644 --- a/assets/d2r_settings.json +++ b/assets/d2r_settings.json @@ -43,8 +43,8 @@ "Item Name Display": 1, "Chat Background": 0, "Always Run": 1, - "Quick Cast Enabled": 0, - "Display Active Skill Bindings": 0, + "Quick Cast Enabled": 1, + "Display Active Skill Bindings": 1, "Lobby Wide Item Drop Enabled": 1, "Item Tooltip Hotkey Appender": 1 } diff --git a/src/char/i_char.py b/src/char/i_char.py index e4ccabe86..7571b4a82 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -379,12 +379,6 @@ def pick_up_item(self, pos: tuple[float, float], item_name: str = None, prev_cas def pre_move(self): pass - def _teleport_to_position(self, pos_monitor: tuple[float, float], cooldown: bool = True): - factor = Config().advanced_options["pathing_delay_factor"] - mouse.move(*pos_monitor, randomize=3, delay_factor=[(2+factor)/25, (4+factor)/25]) - wait(0.012, 0.02) - self._cast_teleport(cooldown = cooldown) - def _teleport_to_origin(self): """ Teleports to the origin @@ -393,6 +387,12 @@ def _teleport_to_origin(self): pos_m = convert_abs_to_monitor(random_abs) self._teleport_to_position(pos_monitor = pos_m, cooldown = True) + def _teleport_to_position(self, pos_monitor: tuple[float, float], cooldown: bool = True): + factor = Config().advanced_options["pathing_delay_factor"] + mouse.move(*pos_monitor, randomize=3, delay_factor=[(2+factor)/25, (4+factor)/25]) + wait(0.012, 0.02) + self._cast_teleport(cooldown = cooldown) + def _walk_to_position(self, pos_monitor: tuple[float, float], force_move: bool = False): factor = Config().advanced_options["pathing_delay_factor"] # in case we want to walk we actually want to move a bit before the point cause d2r will always "overwalk" diff --git a/src/char/paladin/fohdin.py b/src/char/paladin/fohdin.py index 91799fff4..9cf8cabc2 100644 --- a/src/char/paladin/fohdin.py +++ b/src/char/paladin/fohdin.py @@ -133,7 +133,7 @@ def kill_pindle(self) -> bool: self._pather.traverse_nodes([103], self, timeout=1.0, active_skill="conviction") cast_pos_abs = (pindle_pos_abs[0] * 0.9, pindle_pos_abs[1] * 0.9) - self._generic_foh_attack_sequence(cast_pos_abs=cast_pos_abs, min_duration=atk_len_dur, max_duration=atk_len_dur*3, spread_deg=11) + self._generic_foh_attack_sequence(cast_pos_abs=cast_pos_abs, min_duration=atk_len_dur, max_duration=atk_len_dur*3, spray = 20) if self.capabilities.can_teleport_natively: self._pather.traverse_nodes_fixed("pindle_end", self) diff --git a/src/pather.py b/src/pather.py index 8145b3716..fd326f7de 100644 --- a/src/pather.py +++ b/src/pather.py @@ -500,9 +500,9 @@ def _get_node(self, key: int, template: str): 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: str | list[tuple[float, float]], char: IChar) -> bool: + def traverse_nodes_fixed(self, key: str | list[tuple[float, float]], char: IChar, require_teleport: bool = False) -> bool: # this will check if character can teleport. for charged or native teleporters, it'll select teleport - if not (char.capabilities.can_teleport_natively and char.can_teleport()): + if require_teleport and not (char.capabilities.can_teleport_natively and char.can_teleport()): error_msg = "Teleport is required for static pathing" Logger.error(error_msg) raise ValueError(error_msg) @@ -668,9 +668,8 @@ def traverse_nodes( else: # Move the char x_m, y_m = convert_abs_to_monitor(node_pos_abs) - char.move((x_m, y_m), use_tp=use_tp, force_move=force_move) + last_move = char.move((x_m, y_m), use_tp=use_tp, force_move=force_move, last_move_time=last_move) last_direction = node_pos_abs - last_move = time.time() return True From 78205c20e97a072108d480c5aefe3717bba27b6f Mon Sep 17 00:00:00 2001 From: mgleed Date: Wed, 22 Jun 2022 15:40:19 -0400 Subject: [PATCH 36/44] wip --- src/char/i_char.py | 15 ++++++++++++--- src/char/paladin/fohdin.py | 9 ++++++--- src/pather.py | 7 +++---- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/char/i_char.py b/src/char/i_char.py index 7571b4a82..2e67a47ca 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -354,10 +354,12 @@ def _weapon_switch(self): def _switch_to_main_weapon(self): if self.main_weapon_equipped == False: self._weapon_switch() + wait(0.04, 0.08) def _switch_to_offhand_weapon(self): if self.main_weapon_equipped: self._weapon_switch() + wait(0.04, 0.08) def _force_move(self): self._key_press(self._get_hotkey("force_move")) @@ -389,7 +391,7 @@ def _teleport_to_origin(self): def _teleport_to_position(self, pos_monitor: tuple[float, float], cooldown: bool = True): factor = Config().advanced_options["pathing_delay_factor"] - mouse.move(*pos_monitor, randomize=3, delay_factor=[(2+factor)/25, (4+factor)/25]) + mouse.move(*pos_monitor, randomize=3, delay_factor=[(0+factor)/25, (2+factor)/25]) wait(0.012, 0.02) self._cast_teleport(cooldown = cooldown) @@ -430,13 +432,15 @@ def move( if use_tp and self.can_teleport(): # can_teleport() activates teleport hotkey if True # 7 frames is the fastest that teleport can be casted with 200 fcr on sorc self._teleport_to_position(pos_monitor, cooldown = False) + move_time = time.time() min_wait = get_cast_wait_time(class_base = self._base_class, skill_name = "teleport") + factor/25 # if there's still time remaining in cooldown, wait while time.time() - last_move_time < min_wait: wait(0.02) else: + move_time = time.time() self._walk_to_position(pos_monitor = pos_monitor, force_move=force_move) - return time.time() + return move_time def tp_town(self) -> bool: # will check if tp is available and select the skill @@ -462,7 +466,12 @@ def tp_town(self) -> bool: if skills.has_tps(): self._cast_town_portal() else: - return False + pos_m = convert_abs_to_monitor(Config().ui_pos["screen_width"]/2, Config().ui_pos["screen_height"]-5) + mouse.move(*pos_m) + if skills.has_tps(): + self._cast_town_portal() + else: + return False if (template_match := wait_until_visible(ScreenObjects.TownPortal, timeout=3)).valid: pos = template_match.center_monitor pos = (pos[0], pos[1] + 30) diff --git a/src/char/paladin/fohdin.py b/src/char/paladin/fohdin.py index 9cf8cabc2..46a14fe1c 100644 --- a/src/char/paladin/fohdin.py +++ b/src/char/paladin/fohdin.py @@ -35,10 +35,13 @@ def _cast_foh( cast_pos_abs: tuple[float, float], spray: float = 10, spread_deg: float = 10, + min_duration: float = 0, max_duration: float = 0, + teleport_frequency: float = 0, + use_target_detect: bool = False, aura: str = "conviction", ): - return self._cast_at_position(skill_name = "foh", cast_pos_abs = cast_pos_abs, spray = spray, spread_deg = spread_deg, max_duration = max_duration, aura = aura) + return self._cast_at_position(skill_name = "foh", cast_pos_abs = cast_pos_abs, spray = spray, spread_deg = spread_deg, min_duration = min_duration, max_duration = max_duration, use_target_detect = use_target_detect, teleport_frequency = teleport_frequency, aura = aura) def _cast_holy_bolt( self, @@ -194,10 +197,10 @@ def kill_shenk(self): wait(0.05, 0.1) # bypass mob detect first - self._cast_foh((0, 0), spray=11, min_duration = 2, aura = "conviction") + self._cast_foh((0, 0), spray=11, min_duration = 2, aura = "conviction", use_target_detect=False) # then do generic mob detect sequence diff = atk_len_dur if atk_len_dur <= 2 else (atk_len_dur - 2) - self._generic_foh_attack_sequence(min_duration=atk_len_dur - diff, max_duration=atk_len_dur*3 - diff, spray=10, target_detect=False) + self._generic_foh_attack_sequence(min_duration=atk_len_dur - diff, max_duration=atk_len_dur*3 - diff, spray=10) self._activate_cleanse_redemption() return True diff --git a/src/pather.py b/src/pather.py index fd326f7de..eb131df33 100644 --- a/src/pather.py +++ b/src/pather.py @@ -607,9 +607,9 @@ def traverse_nodes( char._activate_aura(active_skill) last_direction = None - for i, node_idx in enumerate(path): + last_move = time.time() + for _, node_idx in enumerate(path): continue_to_next_node = False - last_move = time.time() did_force_move = False teleport_count = 0 while not continue_to_next_node: @@ -642,9 +642,8 @@ def traverse_nodes( pos_abs = get_closest_non_hud_pixel(pos = pos_abs, pos_type="abs") Logger.debug(f"Pather: taking a random guess towards " + str(pos_abs)) x_m, y_m = convert_abs_to_monitor(pos_abs) - char.move((x_m, y_m), use_tp=use_tp, force_move=True) + last_move = char.move((x_m, y_m), use_tp=use_tp, force_move=True) did_force_move = True - last_move = time.time() # 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: From d3622a885c6c8197ff8c0aa8aac70bbeb2131b7b Mon Sep 17 00:00:00 2001 From: mgleed Date: Wed, 22 Jun 2022 19:35:28 -0400 Subject: [PATCH 37/44] wip --- src/char/i_char.py | 10 ++++++---- src/pather.py | 40 ++++++++++++++++++++++++++-------------- src/utils/misc.py | 26 ++++++++++++++++++++++++-- 3 files changed, 56 insertions(+), 20 deletions(-) diff --git a/src/char/i_char.py b/src/char/i_char.py index 2e67a47ca..cd242225f 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -419,6 +419,7 @@ def move( use_tp: bool = False, force_move: bool = False, last_move_time: float = time.time(), + skip_tp_cooldown: bool = False, ) -> float: """ Moves character to position. @@ -433,10 +434,11 @@ def move( # 7 frames is the fastest that teleport can be casted with 200 fcr on sorc self._teleport_to_position(pos_monitor, cooldown = False) move_time = time.time() - min_wait = get_cast_wait_time(class_base = self._base_class, skill_name = "teleport") + factor/25 - # if there's still time remaining in cooldown, wait - while time.time() - last_move_time < min_wait: - wait(0.02) + if not skip_tp_cooldown: + min_wait = get_cast_wait_time(class_base = self._base_class, skill_name = "teleport") + factor/25 + # if there's still time remaining in cooldown, wait + while time.time() - last_move_time < min_wait: + wait(0.02) else: move_time = time.time() self._walk_to_position(pos_monitor = pos_monitor, force_move=force_move) diff --git a/src/pather.py b/src/pather.py index eb131df33..7ae83a526 100644 --- a/src/pather.py +++ b/src/pather.py @@ -6,14 +6,13 @@ import cv2 import numpy as np 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 +from utils.misc import wait, image_diff from config import Config 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 import template_finder from char import IChar -from ui_manager import detect_screen_object, ScreenObjects, is_visible, select_screen_object_match, get_closest_non_hud_pixel +from ui_manager import detect_screen_object, ScreenObjects, get_hud_mask, is_visible, select_screen_object_match, get_closest_non_hud_pixel class Location: # A5 Town @@ -93,6 +92,12 @@ class Pather: def __init__(self): self._range_x = [-Config().ui_pos["center_x"] + 7, Config().ui_pos["center_x"] - 7] self._range_y = [-Config().ui_pos["center_y"] + 7, Config().ui_pos["center_y"] - Config().ui_pos["skill_bar_height"] - 33] + self._roi_middle_half = [round(x) for x in [ + Config().ui_pos["screen_width"]/4, + Config().ui_pos["screen_height"]/4, + Config().ui_pos["screen_width"]/2, + Config().ui_pos["screen_height"]/2 + ]] self._nodes = { # A5 town 0: {'A5_TOWN_0': (27, 249), 'A5_TOWN_1': (-92, -137), 'A5_TOWN_11': (-313, -177)}, @@ -500,6 +505,14 @@ def _get_node(self, key: int, template: str): 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]) + @staticmethod + def _wait_for_screen_update(img_pre: np.ndarray, last_move: float, roi: list = None, max_wait: float = 1.5, score_threshold: float = 0.15): + while (score := image_diff(img_pre, (img_post := grab(force_new=True)), roi = roi)) < score_threshold: + wait(0.02) + if (time.time() - last_move) > max_wait: + break + return img_post, score + def traverse_nodes_fixed(self, key: str | list[tuple[float, float]], char: IChar, require_teleport: bool = False) -> bool: # this will check if character can teleport. for charged or native teleporters, it'll select teleport if require_teleport and not (char.capabilities.can_teleport_natively and char.can_teleport()): @@ -520,13 +533,8 @@ def traverse_nodes_fixed(self, key: str | list[tuple[float, float]], char: IChar y_m += int(random.random() * 6 - 3) t0 = grab(force_new=True) last_move_time = char.move((x_m, y_m), use_tp=True, last_move_time=last_move_time) - t1 = grab(force_new=True) - # check difference between the two frames to determine if tele was good or not - diff = cv2.absdiff(t0, t1) - diff = cv2.cvtColor(diff, cv2.COLOR_BGR2GRAY) - _, mask = cv2.threshold(diff, 13, 255, cv2.THRESH_BINARY) - score = (float(np.sum(mask)) / mask.size) * (1/255.0) - if score > 0.15: + _, score = self._wait_for_screen_update(t0, last_move=last_move_time, roi = self._roi_middle_half) + if score >= 0.15: i += 1 else: stuck_count += 1 @@ -558,6 +566,7 @@ def find_abs_node_pos(self, node_idx: int, img: np.ndarray, threshold: float = 0 return node_pos_abs return None + def traverse_nodes( self, path: tuple[Location, Location] | list[int], @@ -608,15 +617,17 @@ def traverse_nodes( last_direction = None last_move = time.time() + first_move = True for _, node_idx in enumerate(path): continue_to_next_node = False did_force_move = False teleport_count = 0 while not continue_to_next_node: - img = grab(force_new=True) - elapsed = (time.time() - last_move) + if first_move: + img = grab(force_new=True) + first_move = False # Handle timeout - if elapsed > timeout: + if (elapsed := time.time() - last_move) > timeout: if is_visible(ScreenObjects.WaypointLabel, img): # sometimes bot opens waypoint menu, close it to find templates again Logger.debug("Opened wp, closing it again") @@ -669,7 +680,8 @@ def traverse_nodes( x_m, y_m = convert_abs_to_monitor(node_pos_abs) last_move = char.move((x_m, y_m), use_tp=use_tp, force_move=force_move, last_move_time=last_move) last_direction = node_pos_abs - + # wait until there's a change on screen + img, _ = self._wait_for_screen_update(img, last_move=last_move, roi = self._roi_middle_half) return True diff --git a/src/utils/misc.py b/src/utils/misc.py index e9b47bfa0..4411d1c12 100644 --- a/src/utils/misc.py +++ b/src/utils/misc.py @@ -94,11 +94,11 @@ def kill_thread(thread): ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0) Logger.error('Exception raise failure') -def cut_roi(img, roi): +def cut_roi(img: np.ndarray, roi: list) -> np.ndarray: x, y, w, h = roi return img[y:y+h, x:x+w] -def mask_by_roi(img, roi, type: str = "regular"): +def mask_by_roi(img: np.ndarray, roi: list, type: str = "regular"): x, y, w, h = roi if type == "regular": masked = np.zeros(img.shape, dtype=np.uint8) @@ -216,6 +216,28 @@ def image_is_equal(img1: np.ndarray, img2: np.ndarray) -> bool: return False return not(np.bitwise_xor(img1, img2).any()) +def apply_mask(img: np.ndarray, mask: np.ndarray) -> np.ndarray: + if img.shape[:][:][0] == mask.shape[:][:][0]: + return cv2.bitwise_and(img, img, mask = mask) + Logger.warning("apply_mask: Image shape is not equal, failed to apply to img") + return img + +def image_diff(img1: np.ndarray, img2: np.ndarray, roi: list = None, mask: np.ndarray = None, threshold: int = 13) -> float: + if img1.shape != img2.shape: + Logger.warning("image_diff: Image shape is not equal, failed to calculate diff") + return 0 + if mask is not None: + img1 = apply_mask(img1, mask) + img2 = apply_mask(img2, mask) + if roi is not None: + img1 = cut_roi(img1, roi) + img2 = cut_roi(img2, roi) + diff = cv2.absdiff(img1, img2) + diff = cv2.cvtColor(diff, cv2.COLOR_BGR2GRAY) + _, diff_mask = cv2.threshold(diff, threshold, 255, cv2.THRESH_BINARY) + score = (float(np.sum(diff_mask)) / diff_mask.size) * (1/255.0) + return score + @dataclass class BestMatchResult: match: str From afbb4f2f05429c924598e71ce79d92d77dbc31af Mon Sep 17 00:00:00 2001 From: mgleed Date: Thu, 23 Jun 2022 07:49:55 -0400 Subject: [PATCH 38/44] wip --- src/char/i_char.py | 58 +++++++++++++++++++++++----------------------- src/pather.py | 25 ++++++++++---------- 2 files changed, 42 insertions(+), 41 deletions(-) diff --git a/src/char/i_char.py b/src/char/i_char.py index cd242225f..75a2ec9fe 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -37,6 +37,8 @@ def __init__(self, skill_hotkeys: dict): self._base_class = "" self.capabilities = None self.main_weapon_equipped = None + self.last_cast_time = 0 + self.last_cast_skill = "" """ MOUSE AND KEYBOARD METHODS @@ -194,17 +196,25 @@ def _randomize_position(self, pos_abs: tuple[float, float], spray: float = 0, sp pos_abs = calculations.spray(pos_abs = pos_abs, r = spray) return get_closest_non_hud_pixel(pos_abs, "abs") - def _send_skill(self, skill_name: str, cooldown = True): - self._key_press(self._get_hotkey(skill_name)) + def _wait_for_cooldown(self): + min_wait = get_cast_wait_time(class_base = self._base_class, skill_name = self.last_cast_skill) + # if there's still time remaining in cooldown, wait + while (time.time() - self.last_cast_time) < (min_wait): + wait(0.02) + + def _send_skill(self, skill_name: str, cooldown: bool = True, hold_time: float | list | tuple | None = None): if cooldown: - wait(get_cast_wait_time(class_base = self._base_class, skill_name = skill_name)) + self._wait_for_cooldown() + self._key_press(self._get_hotkey(skill_name), hold_time = hold_time) + self.last_cast_time = time.time() + self.last_cast_skill = skill_name def _activate_aura(self, skill_name: str, delay: float | list | tuple | None = (0.04, 0.08)): if not self._get_hotkey(skill_name): return False if self._active_aura != skill_name: # if aura is already active, don't activate it again self._active_aura = skill_name - self._key_press(self._get_hotkey(skill_name), hold_time = delay) + self._send_skill(skill_name = skill_name, cooldown = False, hold_time = delay) return True def _cast_simple(self, skill_name: str, duration: float | list | tuple | None = None, cooldown = True) -> bool: @@ -218,7 +228,9 @@ def _cast_simple(self, skill_name: str, duration: float | list | tuple | None = self._send_skill(skill_name = skill_name, cooldown = cooldown) else: self._stand_still(True) - self._key_press(self._get_hotkey(skill_name), hold_time = duration) + self._send_skill(skill_name = skill_name, cooldown = cooldown, hold_time = duration) + self.last_cast_time = time.time() + self.last_cast_skill = skill_name self._stand_still(False) return True @@ -285,6 +297,8 @@ def _cast_at_position( if use_target_detect and (elapsed_time > min_duration) and not targets: break self._key_hold(self._get_hotkey(skill_name), False) + self.last_cast_time = time.time() + self.last_cast_skill = skill_name self._stand_still(False) else: random_abs = self._randomize_position(pos_abs = cast_pos_abs, spray = spray, spread_deg = spread_deg) @@ -349,7 +363,7 @@ def _cast_town_portal(self) -> bool: def _weapon_switch(self): if self.main_weapon_equipped is not None: self.main_weapon_equipped = not self.main_weapon_equipped - return self._key_press(self._get_hotkey("weapon_switch")) + return self._send_skill("weapon_switch", cooldown=False) def _switch_to_main_weapon(self): if self.main_weapon_equipped == False: @@ -362,7 +376,7 @@ def _switch_to_offhand_weapon(self): wait(0.04, 0.08) def _force_move(self): - self._key_press(self._get_hotkey("force_move")) + self._send_skill("force_move", cooldown=False) def _stand_still(self, enable: bool): if enable and not self._standing_still: @@ -382,18 +396,15 @@ def pre_move(self): pass def _teleport_to_origin(self): - """ - Teleports to the origin - """ random_abs = self._randomize_position(pos_abs = (0,0), spray = 5) pos_m = convert_abs_to_monitor(random_abs) - self._teleport_to_position(pos_monitor = pos_m, cooldown = True) + self._teleport_to_position(pos_monitor = pos_m) - def _teleport_to_position(self, pos_monitor: tuple[float, float], cooldown: bool = True): + def _teleport_to_position(self, pos_monitor: tuple[float, float]): factor = Config().advanced_options["pathing_delay_factor"] - mouse.move(*pos_monitor, randomize=3, delay_factor=[(0+factor)/25, (2+factor)/25]) + mouse.move(*pos_monitor, randomize=3, delay_factor=[(1+factor)/25, (2+factor)/25]) wait(0.012, 0.02) - self._cast_teleport(cooldown = cooldown) + self._cast_teleport() def _walk_to_position(self, pos_monitor: tuple[float, float], force_move: bool = False): factor = Config().advanced_options["pathing_delay_factor"] @@ -418,31 +429,19 @@ def move( pos_monitor: tuple[float, float], use_tp: bool = False, force_move: bool = False, - last_move_time: float = time.time(), - skip_tp_cooldown: bool = False, ) -> float: """ Moves character to position. :param pos_monitor: Position to move to (screen coordinates) :param use_tp: Use teleport if able to :param force_move: Use force_move hotkey to move if not teleporting - :param last_move_time: Time of last move. :return: Time of move completed. """ - factor = Config().advanced_options["pathing_delay_factor"] if use_tp and self.can_teleport(): # can_teleport() activates teleport hotkey if True - # 7 frames is the fastest that teleport can be casted with 200 fcr on sorc - self._teleport_to_position(pos_monitor, cooldown = False) - move_time = time.time() - if not skip_tp_cooldown: - min_wait = get_cast_wait_time(class_base = self._base_class, skill_name = "teleport") + factor/25 - # if there's still time remaining in cooldown, wait - while time.time() - last_move_time < min_wait: - wait(0.02) + self._teleport_to_position(pos_monitor) else: - move_time = time.time() self._walk_to_position(pos_monitor = pos_monitor, force_move=force_move) - return move_time + return time.time() def tp_town(self) -> bool: # will check if tp is available and select the skill @@ -468,7 +467,7 @@ def tp_town(self) -> bool: if skills.has_tps(): self._cast_town_portal() else: - pos_m = convert_abs_to_monitor(Config().ui_pos["screen_width"]/2, Config().ui_pos["screen_height"]-5) + pos_m = convert_abs_to_monitor(0, Config().ui_pos["screen_height"]/2 - 5) mouse.move(*pos_m) if skills.has_tps(): self._cast_town_portal() @@ -496,6 +495,7 @@ def _pre_buff_cta(self) -> bool: self._switch_to_offhand_weapon() self._cast_battle_command() self._cast_battle_orders() + self._wait_for_cooldown() self._switch_to_main_weapon() def pre_buff(self): diff --git a/src/pather.py b/src/pather.py index 7ae83a526..274a403f5 100644 --- a/src/pather.py +++ b/src/pather.py @@ -506,12 +506,16 @@ def _convert_rel_to_abs(rel_loc: tuple[float, float], pos_abs: tuple[float, floa return (rel_loc[0] + pos_abs[0], rel_loc[1] + pos_abs[1]) @staticmethod - def _wait_for_screen_update(img_pre: np.ndarray, last_move: float, roi: list = None, max_wait: float = 1.5, score_threshold: float = 0.15): + def _wait_for_screen_update(img_pre: np.ndarray, roi: list = None, timeout: float = 1.5, score_threshold: float = 0.15) -> tuple[np.ndarray, float, bool]: + start = time.perf_counter() + success = True while (score := image_diff(img_pre, (img_post := grab(force_new=True)), roi = roi)) < score_threshold: wait(0.02) - if (time.time() - last_move) > max_wait: + if (time.perf_counter() - start) > timeout: + success=False break - return img_post, score + # print(f"spent {time.perf_counter() - start} seconds waiting for window change") + return img_post, score, success def traverse_nodes_fixed(self, key: str | list[tuple[float, float]], char: IChar, require_teleport: bool = False) -> bool: # this will check if character can teleport. for charged or native teleporters, it'll select teleport @@ -526,14 +530,12 @@ def traverse_nodes_fixed(self, key: str | list[tuple[float, float]], char: IChar path = key i = 0 stuck_count = 0 - last_move_time = time.time() while i < len(path): x_m, y_m = convert_screen_to_monitor(path[i]) x_m += int(random.random() * 6 - 3) y_m += int(random.random() * 6 - 3) - t0 = grab(force_new=True) - last_move_time = char.move((x_m, y_m), use_tp=True, last_move_time=last_move_time) - _, score = self._wait_for_screen_update(t0, last_move=last_move_time, roi = self._roi_middle_half) + char.move((x_m, y_m), use_tp=True) + _, score, _ = self._wait_for_screen_update(img_pre = grab(force_new = True), roi = self._roi_middle_half) if score >= 0.15: i += 1 else: @@ -617,15 +619,14 @@ def traverse_nodes( last_direction = None last_move = time.time() - first_move = True + img = None for _, node_idx in enumerate(path): continue_to_next_node = False did_force_move = False teleport_count = 0 while not continue_to_next_node: - if first_move: + if img is None or not use_tp: img = grab(force_new=True) - first_move = False # Handle timeout if (elapsed := time.time() - last_move) > timeout: if is_visible(ScreenObjects.WaypointLabel, img): @@ -678,10 +679,10 @@ def traverse_nodes( else: # Move the char x_m, y_m = convert_abs_to_monitor(node_pos_abs) - last_move = char.move((x_m, y_m), use_tp=use_tp, force_move=force_move, last_move_time=last_move) + last_move = char.move((x_m, y_m), use_tp=use_tp, force_move=force_move) last_direction = node_pos_abs # wait until there's a change on screen - img, _ = self._wait_for_screen_update(img, last_move=last_move, roi = self._roi_middle_half) + img, _, _ = self._wait_for_screen_update(img, roi = self._roi_middle_half) return True From 6b2e2e48a0ba0ccb886b214d79cd4d341983bb00 Mon Sep 17 00:00:00 2001 From: mgleed Date: Thu, 23 Jun 2022 18:02:12 -0400 Subject: [PATCH 39/44] wip --- src/char/paladin/fohdin.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/char/paladin/fohdin.py b/src/char/paladin/fohdin.py index 46a14fe1c..fbe913e64 100644 --- a/src/char/paladin/fohdin.py +++ b/src/char/paladin/fohdin.py @@ -17,7 +17,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._pather.adapt_path((Location.A3_TRAV_START, Location.A3_TRAV_CENTER_STAIRS), [220, 221, 222, 903, 904, 905, 906]) - def _move_and_attack(self, abs_move: tuple[int, int], atk_len: float): + def _move_and_attack(self, abs_move: tuple[int, int], atk_len: float, aura: str = "concentration"): pos_m = convert_abs_to_monitor(abs_move) self.pre_move() self.move(pos_m, force_move=True) @@ -83,9 +83,9 @@ def _generic_foh_attack_sequence( # frame 6 can cast HB # frame 15 FOH able to be cast again # frame 16 FOH startup - self._key_press(self._get_hotkey("foh"), hold_time=3/25) + self._send_skill("foh", hold_time=3/25, cooldown=False) time.sleep(2/25) #total of 5 frame startup - self._key_press(self._get_hotkey("holy_bolt"), hold_time=(8/25)) # now at frame 6 + self._send_skill("holy_bolt", hold_time=8/25, cooldown=False) # now at frame 6 time.sleep(2/25) # now at frame 15 # if teleport frequency is set, teleport every teleport_frequency seconds if (elapsed_time - time_of_last_tp) >= 3.5: @@ -173,15 +173,8 @@ def kill_council(self) -> bool: def kill_eldritch(self) -> bool: eld_pos_abs = convert_screen_to_abs(Config().path["eldritch_end"][0]) atk_len_dur = float(Config().char["atk_len_eldritch"]) - self._generic_foh_attack_sequence(cast_pos_abs=eld_pos_abs, min_duration=atk_len_dur, max_duration=atk_len_dur*3, spray=70) - - # move to end node - pos_m = convert_abs_to_monitor((70, -200)) - self.pre_move() - self.move(pos_m, force_move=True) self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, timeout=0.1) - # check mobs one more time before pickit self._generic_foh_attack_sequence(cast_pos_abs=eld_pos_abs, max_duration=atk_len_dur, spray=70) self._activate_cleanse_redemption() From 0a1084283350139b06d770712d8c1f5cb2808209 Mon Sep 17 00:00:00 2001 From: mgleed Date: Fri, 24 Jun 2022 20:27:35 -0400 Subject: [PATCH 40/44] wip --- config/params.ini | 1 + src/char/i_char.py | 42 ++++++++++++++++++++---------------- src/char/tools/skill_data.py | 4 +++- src/inventory/personal.py | 5 +---- 4 files changed, 28 insertions(+), 24 deletions(-) diff --git a/config/params.ini b/config/params.ini index 48f2bdce2..e354d474d 100644 --- a/config/params.ini +++ b/config/params.ini @@ -47,6 +47,7 @@ order=run_pindle, run_eldritch_shenk type=light_sorc belt_rows=4 faster_cast_rate=105 +faster_cast_rate_offhand=105 extra_casting_frames=1 cta_available=0 ; safer_routines: enable for optional defensive maneuvers/etc during combat/runs at the cost of increased runtime (ex. hardcore players) diff --git a/src/char/i_char.py b/src/char/i_char.py index 75a2ec9fe..ac20ee8d2 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -37,8 +37,9 @@ def __init__(self, skill_hotkeys: dict): self._base_class = "" self.capabilities = None self.main_weapon_equipped = None - self.last_cast_time = 0 - self.last_cast_skill = "" + self._last_cast_time = 0 + self._last_cast_skill = "" + self._current_fcr = Config().char["faster_cast_rate"] """ MOUSE AND KEYBOARD METHODS @@ -58,13 +59,13 @@ def _handle_delay(delay: float | list | tuple | None = None): def _key_press(self, key: str, hold_time: float | list | tuple | None = None): if not hold_time: - keyboard.send(key) - else: - self._key_held[key] = True - keyboard.send(key, do_release=False) - self._handle_delay(hold_time) - keyboard.send(key, do_press=False) - self._key_held[key] = False + hold_time = 0.04 + self._key_held[key] = True + keyboard.send(key, do_release=False) + self._handle_delay(hold_time) + keyboard.send(key, do_press=False) + Logger.debug(f"Pressed key: {key} for {hold_time}s at {round(time.time(),3)}") + self._key_held[key] = False def _key_hold(self, key: str, enable: bool = True): if enable and not self._key_held[key]: @@ -197,24 +198,25 @@ def _randomize_position(self, pos_abs: tuple[float, float], spray: float = 0, sp return get_closest_non_hud_pixel(pos_abs, "abs") def _wait_for_cooldown(self): - min_wait = get_cast_wait_time(class_base = self._base_class, skill_name = self.last_cast_skill) + min_wait = get_cast_wait_time(class_base = self._base_class, skill_name = self._last_cast_skill) # if there's still time remaining in cooldown, wait - while (time.time() - self.last_cast_time) < (min_wait): + while (time.time() - self._last_cast_time) < (min_wait): wait(0.02) def _send_skill(self, skill_name: str, cooldown: bool = True, hold_time: float | list | tuple | None = None): if cooldown: self._wait_for_cooldown() self._key_press(self._get_hotkey(skill_name), hold_time = hold_time) - self.last_cast_time = time.time() - self.last_cast_skill = skill_name + self._last_cast_time = time.time() + self._last_cast_skill = skill_name def _activate_aura(self, skill_name: str, delay: float | list | tuple | None = (0.04, 0.08)): if not self._get_hotkey(skill_name): return False if self._active_aura != skill_name: # if aura is already active, don't activate it again self._active_aura = skill_name - self._send_skill(skill_name = skill_name, cooldown = False, hold_time = delay) + Logger.debug(f"Switch to aura {skill_name}") + self._send_skill(skill_name = skill_name, cooldown = True, hold_time = delay) return True def _cast_simple(self, skill_name: str, duration: float | list | tuple | None = None, cooldown = True) -> bool: @@ -229,8 +231,6 @@ def _cast_simple(self, skill_name: str, duration: float | list | tuple | None = else: self._stand_still(True) self._send_skill(skill_name = skill_name, cooldown = cooldown, hold_time = duration) - self.last_cast_time = time.time() - self.last_cast_skill = skill_name self._stand_still(False) return True @@ -297,8 +297,8 @@ def _cast_at_position( if use_target_detect and (elapsed_time > min_duration) and not targets: break self._key_hold(self._get_hotkey(skill_name), False) - self.last_cast_time = time.time() - self.last_cast_skill = skill_name + self._last_cast_time = time.time() + self._last_cast_skill = skill_name self._stand_still(False) else: random_abs = self._randomize_position(pos_abs = cast_pos_abs, spray = spray, spread_deg = spread_deg) @@ -363,15 +363,19 @@ def _cast_town_portal(self) -> bool: def _weapon_switch(self): if self.main_weapon_equipped is not None: self.main_weapon_equipped = not self.main_weapon_equipped - return self._send_skill("weapon_switch", cooldown=False) + equipped = "main" if self.main_weapon_equipped else "offhand" + Logger.debug(f"Switch to {equipped} weapon") + return self._send_skill("weapon_switch", cooldown=True) def _switch_to_main_weapon(self): if self.main_weapon_equipped == False: + self._current_fcr = Config().char["faster_cast_rate"] self._weapon_switch() wait(0.04, 0.08) def _switch_to_offhand_weapon(self): if self.main_weapon_equipped: + self._current_fcr = Config().char["facter_cast_rate_offhand"] self._weapon_switch() wait(0.04, 0.08) diff --git a/src/char/tools/skill_data.py b/src/char/tools/skill_data.py index b71e94930..38988e47d 100644 --- a/src/char/tools/skill_data.py +++ b/src/char/tools/skill_data.py @@ -89,5 +89,7 @@ def get_casting_frames(class_base: str, skill_name: str, fcr: int): else: return math.ceil(256 * _get_base_frames(class_base, skill_name) / math.floor(_get_animation_speed(class_base) * (100 + _efcr(fcr)) / 100)) - 1 -def get_cast_wait_time(class_base: str, skill_name: str, fcr: int = Config().char["faster_cast_rate"]): +def get_cast_wait_time(class_base: str, skill_name: str, fcr: int): + if skill_name in CHANNELED_SKILLS: + return (math.ceil(get_casting_frames(class_base, skill_name, fcr)/2) + Config().char["extra_casting_frames"]) * (1/25) return (get_casting_frames(class_base, skill_name, fcr) + Config().char["extra_casting_frames"]) * (1/25) \ No newline at end of file diff --git a/src/inventory/personal.py b/src/inventory/personal.py index 249d4a9dc..bf9dd1d66 100644 --- a/src/inventory/personal.py +++ b/src/inventory/personal.py @@ -74,10 +74,7 @@ def is_main_weapon_active(img: np.ndarray = None) -> bool | None: # inventory must be open img = grab() if img is None else img if (res := detect_screen_object(ScreenObjects.ActiveWeaponBound, img)).valid: - state = "main" in res.name.lower() - equipped = "Main" if state else "Offhand" - Logger.debug(f"{equipped} weapon is active") - return state + return "main" in res.name.lower() if not is_visible(ScreenObjects.InventoryBackground): Logger.warning("is_main_weapon_active(): Failed to detect active weapon, is inventory not open?") else: From 8c44bf159ab3a35fde0bd1d588e95d94c3c7e7b1 Mon Sep 17 00:00:00 2001 From: mgleed Date: Mon, 27 Jun 2022 15:17:25 -0400 Subject: [PATCH 41/44] wip --- src/char/i_char.py | 12 +++++++----- src/config.py | 1 + src/pather.py | 2 ++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/char/i_char.py b/src/char/i_char.py index ac20ee8d2..33e66b6b6 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -198,7 +198,7 @@ def _randomize_position(self, pos_abs: tuple[float, float], spray: float = 0, sp return get_closest_non_hud_pixel(pos_abs, "abs") def _wait_for_cooldown(self): - min_wait = get_cast_wait_time(class_base = self._base_class, skill_name = self._last_cast_skill) + min_wait = get_cast_wait_time(class_base = self._base_class, skill_name = self._last_cast_skill, fcr = self._current_fcr) # if there's still time remaining in cooldown, wait while (time.time() - self._last_cast_time) < (min_wait): wait(0.02) @@ -375,7 +375,7 @@ def _switch_to_main_weapon(self): def _switch_to_offhand_weapon(self): if self.main_weapon_equipped: - self._current_fcr = Config().char["facter_cast_rate_offhand"] + self._current_fcr = Config().char["faster_cast_rate_offhand"] self._weapon_switch() wait(0.04, 0.08) @@ -407,7 +407,7 @@ def _teleport_to_origin(self): def _teleport_to_position(self, pos_monitor: tuple[float, float]): factor = Config().advanced_options["pathing_delay_factor"] mouse.move(*pos_monitor, randomize=3, delay_factor=[(1+factor)/25, (2+factor)/25]) - wait(0.012, 0.02) + wait(0.04) self._cast_teleport() def _walk_to_position(self, pos_monitor: tuple[float, float], force_move: bool = False): @@ -450,9 +450,11 @@ def move( def tp_town(self) -> bool: # will check if tp is available and select the skill if not skills.has_tps(): - return False + pos_m = convert_abs_to_monitor(0, Config().ui_pos["screen_height"]/2 - 5) + mouse.move(*pos_m) + if not skills.has_tps(): + return False self._cast_town_portal() - roi_mouse_move = [ round(Config().ui_pos["screen_width"] * 0.3), 0, diff --git a/src/config.py b/src/config.py index 21c4d6a8e..cfdceba9f 100644 --- a/src/config.py +++ b/src/config.py @@ -193,6 +193,7 @@ def load_data(self): "battle_command": self._select_val("char", "battle_command"), "extra_casting_frames": int(self._select_val("char", "extra_casting_frames")), "faster_cast_rate": int(self._select_val("char", "faster_cast_rate")), + "faster_cast_rate_offhand": int(self._select_val("char", "faster_cast_rate_offhand")), "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")), diff --git a/src/pather.py b/src/pather.py index 274a403f5..95cd2831c 100644 --- a/src/pather.py +++ b/src/pather.py @@ -673,8 +673,10 @@ def traverse_nodes( # Find any template and calc node position from it node_pos_abs = self.find_abs_node_pos(node_idx, img, threshold=threshold) if node_pos_abs is not None: + Logger.debug(f"move to node {node_idx} at {node_pos_abs}") dist = math.dist(node_pos_abs, (0, 0)) if dist < Config().ui_pos["reached_node_dist"]: + Logger.debug(f"Continue to next node") continue_to_next_node = True else: # Move the char From aa444fe3996a89b7f1161298a19595404531e5ef Mon Sep 17 00:00:00 2001 From: mgleed Date: Mon, 27 Jun 2022 18:33:03 -0400 Subject: [PATCH 42/44] wip --- src/char/i_char.py | 8 +++----- src/char/paladin/fohdin.py | 4 ++-- src/pather.py | 6 +++--- src/run/pindle.py | 2 +- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/char/i_char.py b/src/char/i_char.py index 33e66b6b6..f0bacaf76 100644 --- a/src/char/i_char.py +++ b/src/char/i_char.py @@ -57,9 +57,7 @@ def _handle_delay(delay: float | list | tuple | None = None): except Exception as e: Logger.warning(f"Failed to delay with delay: {delay}. Exception: {e}") - def _key_press(self, key: str, hold_time: float | list | tuple | None = None): - if not hold_time: - hold_time = 0.04 + def _key_press(self, key: str, hold_time: float | list | tuple | None = 0.04): self._key_held[key] = True keyboard.send(key, do_release=False) self._handle_delay(hold_time) @@ -450,7 +448,7 @@ def move( def tp_town(self) -> bool: # will check if tp is available and select the skill if not skills.has_tps(): - pos_m = convert_abs_to_monitor(0, Config().ui_pos["screen_height"]/2 - 5) + pos_m = convert_abs_to_monitor((0, Config().ui_pos["screen_height"]/2 - 5)) mouse.move(*pos_m) if not skills.has_tps(): return False @@ -473,7 +471,7 @@ def tp_town(self) -> bool: if skills.has_tps(): self._cast_town_portal() else: - pos_m = convert_abs_to_monitor(0, Config().ui_pos["screen_height"]/2 - 5) + pos_m = convert_abs_to_monitor((0, Config().ui_pos["screen_height"]/2 - 5)) mouse.move(*pos_m) if skills.has_tps(): self._cast_town_portal() diff --git a/src/char/paladin/fohdin.py b/src/char/paladin/fohdin.py index fbe913e64..0d39d59ce 100644 --- a/src/char/paladin/fohdin.py +++ b/src/char/paladin/fohdin.py @@ -157,12 +157,12 @@ def kill_council(self) -> bool: # traverse to nodes and attack nodes = [225, 226, 300] for i, node in enumerate(nodes): - self._pather.traverse_nodes([node], self, timeout=2.2, do_pre_move = False, force_tp=(self.capabilities.can_teleport_natively or i > 0), use_tp_charge=(self.capabilities.can_teleport_natively or i > 0)) + self._pather.traverse_nodes([node], self, timeout=2.2, do_pre_move = False, force_tp=(i > 0)) cast_pos_abs = self._pather.find_abs_node_pos(node, img := grab()) or self._pather.find_abs_node_pos(906, img) or (-50, -50) self._generic_foh_attack_sequence(cast_pos_abs=cast_pos_abs, min_duration=atk_len_dur, max_duration=atk_len_dur*3, spray=80) # return to 226 and prepare for pickit - self._pather.traverse_nodes([226], self, timeout=2.2, do_pre_move = False, force_tp=True, use_tp_charge=True) + self._pather.traverse_nodes([226], self, timeout=2.2, do_pre_move = False, force_tp=True) cast_pos_abs = self._pather.find_abs_node_pos(226, img := grab()) or self._pather.find_abs_node_pos(906, img) or (-50, -50) self._generic_foh_attack_sequence(cast_pos_abs=cast_pos_abs, max_duration=atk_len_dur*3, spray=80) diff --git a/src/pather.py b/src/pather.py index ff3a6c982..34b356548 100644 --- a/src/pather.py +++ b/src/pather.py @@ -509,7 +509,7 @@ def _convert_rel_to_abs(rel_loc: tuple[float, float], pos_abs: tuple[float, floa def _wait_for_screen_update(img_pre: np.ndarray, roi: list = None, timeout: float = 1.5, score_threshold: float = 0.15) -> tuple[np.ndarray, float, bool]: start = time.perf_counter() success = True - while (score := image_diff(img_pre, (img_post := grab(force_new=True)), roi = roi)) < score_threshold: + while (score := image_diff(img_pre, (img_post := grab(force_new = True)), roi = roi)) < score_threshold: wait(0.02) if (time.perf_counter() - start) > timeout: success=False @@ -673,18 +673,18 @@ def traverse_nodes( # Find any template and calc node position from it node_pos_abs = self.find_abs_node_pos(node_idx, img, threshold=threshold) if node_pos_abs is not None: - Logger.debug(f"move to node {node_idx} at {node_pos_abs}") dist = math.dist(node_pos_abs, (0, 0)) if dist < Config().ui_pos["reached_node_dist"]: Logger.debug(f"Continue to next node") continue_to_next_node = True else: + Logger.debug(f"move to node {node_idx} at {node_pos_abs}") # Move the char x_m, y_m = convert_abs_to_monitor(node_pos_abs) last_move = char.move((x_m, y_m), use_tp=use_tp, force_move=force_move) last_direction = node_pos_abs # wait until there's a change on screen - img, _, _ = self._wait_for_screen_update(img, roi = self._roi_middle_half) + img, _, _ = self._wait_for_screen_update(img, roi = self._roi_middle_half, score_threshold=0.3) return True diff --git a/src/run/pindle.py b/src/run/pindle.py index 5eb6d07bf..da5d00410 100644 --- a/src/run/pindle.py +++ b/src/run/pindle.py @@ -31,7 +31,7 @@ def approach(self, start_loc: Location) -> bool | Location: loc = self._town_manager.go_to_act(5, start_loc) if not loc: return False - if not self._pather.traverse_nodes((loc, Location.A5_NIHLATHAK_PORTAL), self._char): + if not self._pather.traverse_nodes((loc, Location.A5_NIHLATHAK_PORTAL), self._char, force_move=True): return False wait(0.5, 0.6) found_loading_screen_func = lambda: loading.wait_for_loading_screen(2.0) From ff546705367e70311552aedafc93383bb5d48c71 Mon Sep 17 00:00:00 2001 From: mgleed Date: Wed, 29 Jun 2022 21:28:50 -0400 Subject: [PATCH 43/44] fix cleansing --- src/char/paladin/paladin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/char/paladin/paladin.py b/src/char/paladin/paladin.py index 6344a5e99..6c0604edd 100644 --- a/src/char/paladin/paladin.py +++ b/src/char/paladin/paladin.py @@ -35,4 +35,5 @@ def _cast_holy_shield(self) -> bool: return self._cast_simple(skill_name="holy_shield") def _activate_cleanse_redemption(self) -> bool: - return self._activate_cleansing_aura([0.3, 0.5]) or self._activate_redemption_aura([0.3, 0.5]) + self._activate_cleansing_aura([0.3, 0.5]) + return self._activate_redemption_aura([0.3, 0.5]) From c9f1405a4fc186513cee8040d7556ae9ec228168 Mon Sep 17 00:00:00 2001 From: mgleed Date: Wed, 29 Jun 2022 22:54:01 -0400 Subject: [PATCH 44/44] wip --- src/pather.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/pather.py b/src/pather.py index 34b356548..45ae506bb 100644 --- a/src/pather.py +++ b/src/pather.py @@ -507,6 +507,14 @@ def _convert_rel_to_abs(rel_loc: tuple[float, float], pos_abs: tuple[float, floa @staticmethod def _wait_for_screen_update(img_pre: np.ndarray, roi: list = None, timeout: float = 1.5, score_threshold: float = 0.15) -> tuple[np.ndarray, float, bool]: + """ + Waits for the screen to update. + :param img_pre: Image before the update + :param roi: Region of interest to be checked. If None, the whole screen is checked. + :param timeout: Timeout in seconds. + :param score_threshold: Threshold for the score. If the score is below this threshold, assume the screen has not updated. + :return: The image after the update, the score, and a boolean indicating if successful. + """ start = time.perf_counter() success = True while (score := image_diff(img_pre, (img_post := grab(force_new = True)), roi = roi)) < score_threshold: @@ -620,10 +628,12 @@ def traverse_nodes( last_direction = None last_move = time.time() img = None + last_node_pos_abs = None for _, node_idx in enumerate(path): continue_to_next_node = False did_force_move = False teleport_count = 0 + identical_count = 0 while not continue_to_next_node: if img is None or not use_tp: img = grab(force_new=True) @@ -677,14 +687,20 @@ def traverse_nodes( if dist < Config().ui_pos["reached_node_dist"]: Logger.debug(f"Continue to next node") continue_to_next_node = True + # if relative node position is roughly identical to previous attempt, try another screengrab + elif last_node_pos_abs is not None and math.dist(node_pos_abs, last_node_pos_abs) < 5 and identical_count <= 2: + img = grab(force_new=True) + Logger.debug(f"Identical node position {node_pos_abs} compared to previous {last_node_pos_abs}, trying another screengrab") + identical_count += 1 else: - Logger.debug(f"move to node {node_idx} at {node_pos_abs}") # Move the char x_m, y_m = convert_abs_to_monitor(node_pos_abs) last_move = char.move((x_m, y_m), use_tp=use_tp, force_move=force_move) last_direction = node_pos_abs # wait until there's a change on screen - img, _, _ = self._wait_for_screen_update(img, roi = self._roi_middle_half, score_threshold=0.3) + img, score, _ = self._wait_for_screen_update(img, roi = self._roi_middle_half, score_threshold=0.5) + Logger.debug(f"moved toward node {node_idx} at {node_pos_abs}, screen update score: {score}") + last_node_pos_abs = node_pos_abs return True