diff --git a/config/params.ini b/config/params.ini index 5b1b504..397d55a 100644 --- a/config/params.ini +++ b/config/params.ini @@ -150,6 +150,35 @@ shout=f3 war_cry=f5 find_item=f4 +; =========================== +; ==== Builds: Basic ==== +; =========================== +; Supported runs: UNTESTED (this is your job!) +; this will basically continuously left attack, unless right attack is available. +;if the buffs are not set they are not used (they are part of the prebuff setup) + +[basic] +teleport= +left_attack=f5 +right_attack=f2 +buff_1=f8 +buff_2=f3 + + +; =========================== +; ==== Builds: Basic Ranged==== +; =========================== +; Supported runs: UNTESTED (this is your job!) +; this will basically continuously left attack, unless right attack is available. +;if the buffs are not set they are not used (they are part of the prebuff setup) + +[basic_ranged] +teleport= +left_attack=f5 +right_attack=f2 +buff_1=f8 +buff_2=f3 + [dclone] ; Normally have known region IP (that we didn't block) for ip hunting diff --git a/src/bot.py b/src/bot.py index 1442777..e15b5ac 100644 --- a/src/bot.py +++ b/src/bot.py @@ -28,6 +28,8 @@ from char.sorceress import LightSorc, BlizzSorc, NovaSorc from char.trapsin import Trapsin from char.hammerdin import Hammerdin from char.barbarian import Barbarian +from char.basic import Basic +from char.basic_ranged import Basic_Ranged from run import Pindle, ShenkEld, Trav, Nihlatak, Arcane from town import TownManager, A1, A2, A3, A4, A5 @@ -61,6 +63,10 @@ class Bot: self._char: IChar = Trapsin(self._config.trapsin, self._config.char, self._screen, self._template_finder, self._ui_manager, self._pather) elif self._config.char["type"] == "barbarian": self._char: IChar = Barbarian(self._config.barbarian, self._config.char, self._screen, self._template_finder, self._ui_manager, self._pather) + elif self._config.char["type"] == "basic": + self._char: IChar = Basic(self._config.basic, self._config.char, self._screen, self._template_finder, self._ui_manager, self._pather) + elif self._config.char["type"] == "basic_ranged": + self._char: IChar = Basic_Ranged(self._config.basic, self._config.char, self._screen, self._template_finder, self._ui_manager, self._pather) else: Logger.error(f'{self._config.char["type"]} is not supported! Closing down bot.') os._exit(1) diff --git a/src/char/basic.py b/src/char/basic.py new file mode 100644 index 0000000..df14333 --- /dev/null +++ b/src/char/basic.py @@ -0,0 +1,154 @@ +import keyboard +from utils.custom_mouse import mouse +from char import IChar +from template_finder import TemplateFinder +from ui import UiManager +from pather import Pather +from logger import Logger +from screen import Screen +from utils.misc import wait, cut_roi +import time +from pather import Pather, Location + + +class Basic(IChar): + def __init__(self, skill_hotkeys, char_config, screen: Screen, template_finder: TemplateFinder, ui_manager: UiManager, pather: Pather): + Logger.info("Setting up Basic Character") + super().__init__(skill_hotkeys, char_config, screen, template_finder, ui_manager) + self._pather = pather + self._do_pre_move = True + # offset shenk final position further to the right and bottom + if self._skill_hotkeys["teleport"]: + self._pather.offset_node(149, [120, 70]) + + def _cast_attack_pattern(self, time_in_s: float): + keyboard.send(self._char_config["stand_still"], do_release=False) + wait(0.05, 0.1) + keyboard.send(self._skill_hotkeys["left_attack"]) + wait(0.05, 0.1) + keyboard.send(self._skill_hotkeys["right_attack"]) + wait(0.05, 0.1) + start = time.time() + while (time.time() - start) < time_in_s: + if self._ui_manager.is_right_skill_active(): + wait(0.05, 0.1) + mouse.click(button="right") + else: + wait(0.05, 0.1) + mouse.click(button="left") + wait(0.01, 0.05) + keyboard.send(self._char_config["stand_still"], do_press=False) + + def pre_buff(self): + if self._skill_hotkeys["buff_1"]: + keyboard.send(self._skill_hotkeys["buff_1"]) + wait(0.5, 0.15) + mouse.click(button="right") + wait(0.5, 0.15) + if self._skill_hotkeys["buff_2"]: + keyboard.send(self._skill_hotkeys["buff_2"]) + wait(0.5, 0.15) + mouse.click(button="right") + wait(0.5, 0.15) + + def pre_move(self): + # select teleport if available + super().pre_move() + + + def _move_and_attack(self, abs_move: tuple[int, int], atk_len: float): + pos_m = self._screen.convert_abs_to_monitor(abs_move) + self.pre_move() + self.move(pos_m, force_move=True) + self._cast_attack_pattern(atk_len) + + + +#this is where we kill bosses + + def kill_pindle(self) -> bool: + wait(0.1, 0.15) + if self.can_teleport(): + 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, time_out=1.0, do_pre_move=self._do_pre_move) + self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, time_out=0.1) + self._cast_attack_pattern(self._char_config["atk_len_pindle"]) + wait(0.1, 0.15) + return True + + def kill_eldritch(self) -> bool: + if self.can_teleport(): + 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, time_out=1.0, do_pre_move=self._do_pre_move) + wait(0.05, 0.1) + self._cast_attack_pattern(self._char_config["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, time_out=1.0, do_pre_move=self._do_pre_move) + wait(0.05, 0.1) + self._cast_attack_pattern(self._char_config["atk_len_shenk"]) + wait(0.1, 0.15) + return True + + def kill_council(self) -> bool: + # Check out the node screenshot in assets/templates/trav/nodes to see where each node is at + atk_len = self._char_config["atk_len_trav"] + # Go inside and war cry a bit + self._pather.traverse_nodes([228, 229], self, time_out=2.5, force_tp=True) + self._cast_attack_pattern(atk_len) + # Move a bit back and another round + self._move_and_attack((40, 20), atk_len) + # Here we have two different attack sequences depending if tele is available or not + if self.can_teleport(): + # Back to center stairs and more war cry + self._pather.traverse_nodes([226], self, time_out=2.5, force_tp=True) + self._cast_attack_pattern(atk_len) + # move a bit to the top + self._move_and_attack((65, -30), atk_len) + else: + # Stay inside and cast war cry again moving forward + self._move_and_attack((40, 10), atk_len) + self._move_and_attack((-40, -20), atk_len) + return True + + def kill_nihlatak(self, end_nodes: list[int]) -> bool: + # Move close to nilathak + self._pather.traverse_nodes(end_nodes, self, time_out=0.8, do_pre_move=False) + # move mouse to center (leftover from hammerdin) + pos_m = self._screen.convert_abs_to_monitor((0, 0)) + mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7]) + self._cast_attack_pattern(self._char_config["atk_len_nihlatak"] * 0.4) + self._cast_attack_pattern(0.8) + self._move_and_attack((30, 15), self._char_config["atk_len_nihlatak"] * 0.3) + self._cast_attack_pattern(0.8) + self._move_and_attack((-30, -15), self._char_config["atk_len_nihlatak"] * 0.4) + wait(0.1, 0.15) + self._cast_attack_pattern(1.2) + return True + +if __name__ == "__main__": + import os + import keyboard + keyboard.add_hotkey('f12', lambda: Logger.info('Force Exit (f12)') or os._exit(1)) + keyboard.wait("f11") + from config import Config + from ui.ui_manager import UiManager + config = Config() + screen = Screen(config.general["monitor"]) + t_finder = TemplateFinder(screen) + pather = Pather(screen, t_finder) + ui_manager = UiManager(screen, t_finder) + char = Basic(config.basic, config.char, screen, t_finder, ui_manager, pather) + char.kill_council() diff --git a/src/char/basic_ranged.py b/src/char/basic_ranged.py new file mode 100644 index 0000000..db5dccf --- /dev/null +++ b/src/char/basic_ranged.py @@ -0,0 +1,212 @@ +import keyboard +from utils.custom_mouse import mouse +from char import IChar +from template_finder import TemplateFinder +from ui import UiManager +from pather import Pather +from logger import Logger +from screen import Screen +from utils.misc import wait, cut_roi +import time +import random +import numpy as np +from pather import Pather, Location + + +class Basic_Ranged(IChar): + def __init__(self, skill_hotkeys, char_config, screen: Screen, template_finder: TemplateFinder, ui_manager: UiManager, pather: Pather): + Logger.info("Setting up Basic Ranged Character") + super().__init__(skill_hotkeys, char_config, screen, template_finder, ui_manager) + 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"]: + keyboard.send(self._skill_hotkeys["left_attack"]) + 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 = self._screen.convert_abs_to_monitor((x, y)) + mouse.move(*pos_m) + mouse.click(button="left") + + def _right_attack(self, cast_pos_abs: tuple[float, float], delay: tuple[float, float] = (0.2, 0.3), spray: float = 10): + if not self._skill_hotkeys["right_attack"]: + raise ValueError("You did not set right attack hotkey!") + keyboard.send(self._skill_hotkeys["right_attack"]) + 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 = self._screen.convert_abs_to_monitor((x, y)) + mouse.move(*cast_pos_monitor) + mouse.click(button="right") + + def pre_buff(self): + if self._skill_hotkeys["buff_1"]: + keyboard.send(self._skill_hotkeys["buff_1"]) + wait(0.5, 0.15) + mouse.click(button="right") + wait(0.5, 0.15) + if self._skill_hotkeys["buff_2"]: + keyboard.send(self._skill_hotkeys["buff_2"]) + wait(0.5, 0.15) + mouse.click(button="right") + wait(0.5, 0.15) + + +#bosses + + def kill_pindle(self) -> bool: + pindle_pos_abs = self._screen.convert_screen_to_abs(self._config.path["pindle_end"][0]) + cast_pos_abs = [pindle_pos_abs[0] * 0.9, pindle_pos_abs[1] * 0.9] + start = time.time() + keyboard.send(self._char_config["stand_still"], do_release=False) + while (time.time() - start) < self._char_config["atk_len_pindle"]: + if self._ui_manager.is_right_skill_active(): + wait(0.05, 0.1) + self._right_attack(cast_pos_abs, spray=11) + else: + wait(0.05, 0.1) + self._left_attack(cast_pos_abs, spray=11) + keyboard.send(self._char_config["stand_still"], do_press=False) + wait(self._cast_duration, self._cast_duration + 0.2) + # Move to items + self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, time_out=1.4, force_tp=True) + return True + + + def kill_eldritch(self) -> bool: + eld_pos_abs = self._screen.convert_screen_to_abs(self._config.path["eldritch_end"][0]) + cast_pos_abs = [eld_pos_abs[0] * 0.9, eld_pos_abs[1] * 0.9] + start = time.time() + keyboard.send(self._char_config["stand_still"], do_release=False) + while (time.time() - start) < self._char_config["atk_len_eldritch"]: + if self._ui_manager.is_right_skill_active(): + wait(0.05, 0.1) + self._right_attack(cast_pos_abs, spray=11) + else: + wait(0.05, 0.1) + self._left_attack(cast_pos_abs, spray=11) + keyboard.send(self._char_config["stand_still"], do_press=False) + wait(self._cast_duration, self._cast_duration + 0.2) + # Move to items + self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, time_out=1.4, force_tp=True) + return True + + def kill_shenk(self) -> bool: + shenk_pos_abs = self._pather.find_abs_node_pos(149, self._screen.grab()) + if shenk_pos_abs is None: + shenk_pos_abs = self._screen.convert_screen_to_abs(self._config.path["shenk_end"][0]) + cast_pos_abs = [shenk_pos_abs[0] * 0.9, shenk_pos_abs[1] * 0.9] + start = time.time() + keyboard.send(self._char_config["stand_still"], do_release=False) + while (time.time() - start) < self._char_config["atk_len_shenk"]: + if self._ui_manager.is_right_skill_active(): + wait(0.05, 0.1) + self._right_attack(cast_pos_abs, spray=11) + else: + wait(0.05, 0.1) + self._left_attack(cast_pos_abs, spray=11) + keyboard.send(self._char_config["stand_still"], do_press=False) + wait(self._cast_duration, self._cast_duration + 0.2) + # Move to items + self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, time_out=1.4, force_tp=True) + return True + + def kill_council(self) -> bool: + atk_len_trav_2 = int(self._char_config["atk_len_trav"] / 2) + # Check out the node screenshot in assets/templates/trav/nodes to see where each node is at + # Go inside cast stuff in general direction + self._pather.offset_node(229, [250, 130]) + self._pather.traverse_nodes([228, 229], self, time_out=2.5, force_tp=True) + self._pather.offset_node(229, [-250, -130]) + atk_pos_abs = self._pather.find_abs_node_pos(230, self._screen.grab()) + if atk_pos_abs is None: + Logger.debug("Could not find node [230]. Using static attack coordinates instead.") + atk_pos_abs = [-300, -200] + else: + atk_pos_abs = [atk_pos_abs[0], atk_pos_abs[1] + 70] + cast_pos_abs = np.array([atk_pos_abs[0] * 0.9, atk_pos_abs[1] * 0.9]) + self._left_attack(cast_pos_abs, spray=80) + for _ in range(atk_len_trav_2): + if self._ui_manager.is_right_skill_active(): + self._right_attack(cast_pos_abs, spray=11) + else: + self._left_attack(cast_pos_abs, spray=11) + # move a bit back + pos_m = self._screen.convert_abs_to_monitor((160, 30)) + self.pre_move() + self.move(pos_m, force_move=True) + atk_pos_abs = self._pather.find_abs_node_pos(229, self._screen.grab()) + if atk_pos_abs is None: + Logger.debug("Could not find node [229]. Using static attack coordinates instead.") + atk_pos_abs = [-200, -80] + for _ in range(atk_len_trav_2): + if self._ui_manager.is_right_skill_active(): + self._right_attack(cast_pos_abs, spray=11) + else: + self._left_attack(cast_pos_abs, spray=11) + # Move outside + # Move a bit back and another round + self._pather.traverse_nodes([226], self, time_out=2.5, force_tp=True) + cast_pos_abs = np.array([-300, -100]) + for _ in range(atk_len_trav_2): + if self._ui_manager.is_right_skill_active(): + self._right_attack(cast_pos_abs, spray=11) + else: + self._left_attack(cast_pos_abs, spray=11) + # move a bit back + pos_m = self._screen.convert_abs_to_monitor((100, 0)) + cast_pos_abs = self._pather.find_abs_node_pos(229, self._screen.grab()) + if cast_pos_abs is not None: + self.pre_move() + self.move(pos_m, force_move=True) + for _ in range(atk_len_trav_2): + if self._ui_manager.is_right_skill_active(): + self._right_attack(cast_pos_abs, spray=11) + else: + self._left_attack(cast_pos_abs, spray=11) + return True + + def kill_nihlatak(self, end_nodes: list[int]) -> bool: + # Find nilhlatak position + delay = [0.2, 0.3] + atk_len = int(self._char_config["atk_len_nihlatak"]) + for i in range(atk_len): + nihlatak_pos_abs = self._pather.find_abs_node_pos(end_nodes[-1], self._screen.grab()) + if nihlatak_pos_abs is None: + return False + cast_pos_abs = np.array([nihlatak_pos_abs[0] * 0.9, nihlatak_pos_abs[1] * 0.9]) + self._left_attack(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) + tele_pos_abs = unit_vector(rotate_vec(cast_pos_abs, rot_deg)) * 100 + pos_m = self._screen.convert_abs_to_monitor(tele_pos_abs) + self.pre_move() + self.move(pos_m) + else: + self._right_attack(cast_pos_abs, spray=60) + # Move to items + wait(self._cast_duration, self._cast_duration + 0.2) + self._pather.traverse_nodes(end_nodes, self, time_out=0.8) + return True + + +if __name__ == "__main__": + import os + import keyboard + from screen import Screen + from template_finder import TemplateFinder + from pather import Pather + keyboard.add_hotkey('f12', lambda: Logger.info('Force Exit (f12)') or os._exit(1)) + keyboard.wait("f11") + from config import Config + from ui import UiManager + config = Config() + screen = Screen(config.general["monitor"]) + t_finder = TemplateFinder(screen) + pather = Pather(screen, t_finder) + ui_manager = UiManager(screen, t_finder) + char = LightSorc(config.light_sorc, config.char, screen, t_finder, ui_manager, pather) + char.kill_council() diff --git a/src/config.py b/src/config.py index 14abd22..f52ce90 100644 --- a/src/config.py +++ b/src/config.py @@ -156,6 +156,16 @@ class Config: self.barbarian = self._config["barbarian"] if "barbarian" in self._custom: self.barbarian.update(self._custom["barbarian"]) + + # Basic config + self.basic = self._config["basic"] + if "basic" in self._custom: + self.basic.update(self._custom["basic"]) + + # Basic Ranged config + self.basic = self._config["basic_ranged"] + if "basic_ranged" in self._custom: + self.basic_ranged.update(self._custom["basic_ranged"]) self.advanced_options = { "pathing_delay_factor": min(max(int(self._select_val("advanced_options", "pathing_delay_factor")), 1), 10),