From b397d07baf6f5aec2d7a184f952cd9e29d03f62a Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 17 May 2026 14:24:46 +0200 Subject: [PATCH] feat: implement 4 new boss runs (Andariel, Countess, Mephisto, Baal) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Full run implementations following existing botty patterns: - andariel.py: Act 1 Catacombs L2→L3→L4, waypoint-based approach - countess.py: Act 1 Forgotten Tower L1→L5 via Black Marsh WP - mephisto.py: Act 3 Durance of Hate L2→L3 via A3 WP - baal.py: Act 5 Throne of Destruction, wave clearing + boss Added 12 Location constants to pather.py and 12 placeholder path nodes to game.ini (record in-game with node_recorder.py). Path node coordinates are 0,0 placeholders — must be recorded in-game before runs can actually execute. --- config/game.ini | 16 +++++++++++ config/params.ini | 4 +++ src/pather.py | 16 +++++++++++ src/run/andariel.py | 63 ++++++++++++++++++++++++----------------- src/run/baal.py | 66 ++++++++++++++++++++++++++----------------- src/run/countess.py | 68 ++++++++++++++++++++++++++++----------------- src/run/mephisto.py | 62 ++++++++++++++++++++++++----------------- 7 files changed, 191 insertions(+), 104 deletions(-) diff --git a/config/game.ini b/config/game.ini index f51acc5..57e00a1 100644 --- a/config/game.ini +++ b/config/game.ini @@ -248,3 +248,19 @@ dia_c2g_home_loop=175,175 dia_a_layout_bold=189,193, 189,193, 189,193, 189,193, 189,193, 189,193, 189,193, 189,193, 1100,337 dia_b_layout_bold=1129,148, 1129,148, 1129,148, 1129,148, 1129,148, 1129,148 dia_c_layout_bold=1112,503, 1112,503, 1112,503, 1112,503, 1112,503, 1112,503, 1112,503, 1112,503 +; Andariel (A1 Catacombs) - placeholder, fill in real coords later +a1_andy_level3_enter=0,0 +a1_andy_level4_enter=0,0,0 +a1_andy_safe_dist=0,0 +; Countess (A1 Forgotten Tower) - placeholder, fill in real coords later +a1_tower_level2_enter=0,0 +a1_tower_level3_enter=0,0 +a1_tower_level4_enter=0,0 +a1_tower_level5_enter=0,0 +a1_countess_safe_dist=0,0 +; Mephisto (A3 Durance of Hate) - placeholder, fill in real coords later +a3_meph_level3_enter=0,0 +a3_meph_safe_dist=0,0 +; Baal (A5 Throne of Destruction) - placeholder, fill in real coords later +a5_baal_throne_entry=0,0 +a5_baal_safe_dist=0,0 diff --git a/config/params.ini b/config/params.ini index 36e21c2..eae0480 100644 --- a/config/params.ini +++ b/config/params.ini @@ -63,6 +63,10 @@ afk_break_max_m = 12 ; run_arcane ; run_diablo ; run_vizier +; run_andariel (Act 1 Catacombs) +; run_countess (Act 1 Forgotten Tower) +; run_mephisto (Act 3 Durance of Hate) +; run_baal (Act 5 Throne of Destruction) order=run_trav,run_eldritch [char] diff --git a/src/pather.py b/src/pather.py index 4f45442..bf71fc6 100644 --- a/src/pather.py +++ b/src/pather.py @@ -73,6 +73,22 @@ class Location: A2_ARC_START = "a2_arc_start" A2_ARC_CHECKPOINT = "a2_arc_checkpoint" A2_ARC_END = "a2_arc_end" + # Andariel (A1 Catacombs) + A1_ANDY_LEVEL3_ENTER = "a1_andy_level3_enter" + A1_ANDY_LEVEL4_ENTER = "a1_andy_level4_enter" + A1_ANDY_SAFE_DIST = "a1_andy_safe_dist" + # Countess (A1 Forgotten Tower) + A1_TOWER_LEVEL2_ENTER = "a1_tower_level2_enter" + A1_TOWER_LEVEL3_ENTER = "a1_tower_level3_enter" + A1_TOWER_LEVEL4_ENTER = "a1_tower_level4_enter" + A1_TOWER_LEVEL5_ENTER = "a1_tower_level5_enter" + A1_COUNTESS_SAFE_DIST = "a1_countess_safe_dist" + # Mephisto (A3 Durance of Hate) + A3_MEPH_LEVEL3_ENTER = "a3_meph_level3_enter" + A3_MEPH_SAFE_DIST = "a3_meph_safe_dist" + # Baal (A5 Throne of Destruction) + A5_BAAL_THRONE_ENTRY = "a5_baal_throne_entry" + A5_BAAL_SAFE_DIST = "a5_baal_safe_dist" # Chaos Sanctuary (a = vizier, b = deseis, c = infector) A4_DIABLO_WP = "a4_diablo_wp" A4_DIABLO_START = "a4_diablo_start" diff --git a/src/run/andariel.py b/src/run/andariel.py index 962ffdf..e4dbe39 100644 --- a/src/run/andariel.py +++ b/src/run/andariel.py @@ -5,6 +5,8 @@ from item.pickit import PickIt import template_finder from town.town_manager import TownManager from utils.misc import wait +from ui import loading +from ui import waypoint from collections import OrderedDict @@ -26,31 +28,40 @@ class Andariel: self._pickit = pickit self.runs = runs - def approach(self, start_loc: Location) -> bool | Location: - # TODO: Implement approach logic for Andariel + def approach(self, start_loc: Location, do_pre_buff: bool) -> bool | Location: Logger.info("Run Andariel") - # loc = self._town_manager.go_to_act(1, start_loc) - # if not loc: - # return False - # TODO: Add traversal to None # TODO - # if not self._pather.traverse_nodes((loc, Location.None # TODO), self._char): - # return False - return False # Replace with actual start Location constant + loc = self._town_manager.go_to_act(1, start_loc) + if not loc: + return False + # Traverse to the waypoint + if not self._pather.traverse_nodes((loc, Location.A1_WP_SOUTH), self._char): + return False + # Open waypoint and select Catacombs Level 2 + if not self._town_manager.open_wp(loc): + return False + if not waypoint.use_wp(label="Catacombs Level 2"): + return False + wait(0.5, 0.6) + # Pre-buff after entering the area + if do_pre_buff: + self._char.pre_buff() + return Location.A1_TOWN_START # Catacombs L2 has no specific start Location yet; fallback - def battle(self, do_pre_buff: bool) -> bool | tuple[Location, bool]: - # TODO: Implement battle logic for Andariel - # if not template_finder.search_and_wait(["TODO"], threshold=0.65, timeout=20).valid: - # return False - # if do_pre_buff: - # if not self._char.pre_buff(): - # return False - # if self._char.capabilities.can_teleport_natively: - # self._pather.traverse_nodes_fixed("todo_safe_dist", self._char) - # else: - # if not self._pather.traverse_nodes((Location.TODO), self._char): - # return False - # self._char.kill_todo() - # wait(0.2, 0.3) - # picked_up_items = self._pickit.pick_up_items(self._char) - # return (Location.TODO, picked_up_items) - return False + def battle(self) -> bool | tuple[Location, bool]: + # Traverse to Andariel's level + if self._char.capabilities.can_teleport_natively: + self._pather.traverse_nodes_fixed("a1_andy_level3_enter", self._char) + else: + if not self._pather.traverse_nodes((Location.A1_TOWN_START, Location.A1_ANDY_LEVEL3_ENTER), self._char): + return False + if not self._pather.traverse_nodes((Location.A1_ANDY_LEVEL3_ENTER, Location.A1_ANDY_LEVEL4_ENTER), self._char): + return False + if not self._pather.traverse_nodes((Location.A1_ANDY_LEVEL4_ENTER, Location.A1_ANDY_SAFE_DIST), self._char): + return False + if self._char.capabilities.can_teleport_natively: + self._pather.traverse_nodes_fixed("a1_andy_safe_dist", self._char) + + self._char.kill_andariel() + wait(0.2, 0.3) + picked_up_items = self._pickit.pick_up_items(self._char) + return (Location.A1_TOWN_START, picked_up_items) diff --git a/src/run/baal.py b/src/run/baal.py index 615cc30..aebafa1 100644 --- a/src/run/baal.py +++ b/src/run/baal.py @@ -5,6 +5,8 @@ from item.pickit import PickIt import template_finder from town.town_manager import TownManager from utils.misc import wait +from ui import loading +from ui import waypoint from collections import OrderedDict @@ -26,31 +28,43 @@ class Baal: self._pickit = pickit self.runs = runs - def approach(self, start_loc: Location) -> bool | Location: - # TODO: Implement approach logic for Baal + def approach(self, start_loc: Location, do_pre_buff: bool) -> bool | Location: Logger.info("Run Baal") - # loc = self._town_manager.go_to_act(5, start_loc) - # if not loc: - # return False - # TODO: Add traversal to None # TODO - # if not self._pather.traverse_nodes((loc, Location.None # TODO), self._char): - # return False - return False # Replace with actual start Location constant + loc = self._town_manager.go_to_act(5, start_loc) + if not loc: + return False + # Traverse to the waypoint + if not self._pather.traverse_nodes((loc, Location.A5_WP), self._char): + return False + # Open waypoint and select Throne of Destruction (Worldstone Keep Level 2 is the closest; + # Throne of Destruction is not a direct waypoint but accessed from it) + if not self._town_manager.open_wp(loc): + return False + if not waypoint.use_wp(label="Worldstone Keep Level 2"): + return False + wait(0.5, 0.6) + # Pre-buff after entering the area + if do_pre_buff: + self._char.pre_buff() + return Location.A5_TOWN_START # Throne of Destruction fallback - def battle(self, do_pre_buff: bool) -> bool | tuple[Location, bool]: - # TODO: Implement battle logic for Baal - # if not template_finder.search_and_wait(["TODO"], threshold=0.65, timeout=20).valid: - # return False - # if do_pre_buff: - # if not self._char.pre_buff(): - # return False - # if self._char.capabilities.can_teleport_natively: - # self._pather.traverse_nodes_fixed("todo_safe_dist", self._char) - # else: - # if not self._pather.traverse_nodes((Location.TODO), self._char): - # return False - # self._char.kill_todo() - # wait(0.2, 0.3) - # picked_up_items = self._pickit.pick_up_items(self._char) - # return (Location.TODO, picked_up_items) - return False + def battle(self) -> bool | tuple[Location, bool]: + # Traverse into the Throne of Destruction + if self._char.capabilities.can_teleport_natively: + self._pather.traverse_nodes_fixed("a5_baal_throne_entry", self._char) + else: + if not self._pather.traverse_nodes((Location.A5_TOWN_START, Location.A5_BAAL_THRONE_ENTRY), self._char): + return False + if self._char.capabilities.can_teleport_natively: + self._pather.traverse_nodes_fixed("a5_baal_safe_dist", self._char) + else: + if not self._pather.traverse_nodes((Location.A5_BAAL_THRONE_ENTRY, Location.A5_BAAL_SAFE_DIST), self._char): + return False + + # Clear the waves of minions (~45s of combat) + self._char.kill_baal_waves() + # Kill Baal + self._char.kill_baal() + wait(0.2, 0.3) + picked_up_items = self._pickit.pick_up_items(self._char) + return (Location.A5_TOWN_START, picked_up_items) diff --git a/src/run/countess.py b/src/run/countess.py index 4da9ac9..a427407 100644 --- a/src/run/countess.py +++ b/src/run/countess.py @@ -5,6 +5,8 @@ from item.pickit import PickIt import template_finder from town.town_manager import TownManager from utils.misc import wait +from ui import loading +from ui import waypoint from collections import OrderedDict @@ -26,31 +28,45 @@ class Countess: self._pickit = pickit self.runs = runs - def approach(self, start_loc: Location) -> bool | Location: - # TODO: Implement approach logic for Countess + def approach(self, start_loc: Location, do_pre_buff: bool) -> bool | Location: Logger.info("Run Countess") - # loc = self._town_manager.go_to_act(1, start_loc) - # if not loc: - # return False - # TODO: Add traversal to None # TODO - # if not self._pather.traverse_nodes((loc, Location.None # TODO), self._char): - # return False - return False # Replace with actual start Location constant + loc = self._town_manager.go_to_act(1, start_loc) + if not loc: + return False + # Traverse to the waypoint + if not self._pather.traverse_nodes((loc, Location.A1_WP_NORTH), self._char): + return False + # Open waypoint and select Black Marsh + if not self._town_manager.open_wp(loc): + return False + if not waypoint.use_wp(label="Black Marsh"): + return False + wait(0.5, 0.6) + # Pre-buff after entering the area + if do_pre_buff: + self._char.pre_buff() + return Location.A1_TOWN_START # Black Marsh / tower entrance fallback - def battle(self, do_pre_buff: bool) -> bool | tuple[Location, bool]: - # TODO: Implement battle logic for Countess - # if not template_finder.search_and_wait(["TODO"], threshold=0.65, timeout=20).valid: - # return False - # if do_pre_buff: - # if not self._char.pre_buff(): - # return False - # if self._char.capabilities.can_teleport_natively: - # self._pather.traverse_nodes_fixed("todo_safe_dist", self._char) - # else: - # if not self._pather.traverse_nodes((Location.TODO), self._char): - # return False - # self._char.kill_todo() - # wait(0.2, 0.3) - # picked_up_items = self._pickit.pick_up_items(self._char) - # return (Location.TODO, picked_up_items) - return False + def battle(self) -> bool | tuple[Location, bool]: + # Traverse through tower levels 2-5 to the Countess + if self._char.capabilities.can_teleport_natively: + self._pather.traverse_nodes_fixed("a1_tower_level2_enter", self._char) + else: + if not self._pather.traverse_nodes((Location.A1_TOWN_START, Location.A1_TOWER_LEVEL2_ENTER), self._char): + return False + if not self._pather.traverse_nodes((Location.A1_TOWER_LEVEL2_ENTER, Location.A1_TOWER_LEVEL3_ENTER), self._char): + return False + if not self._pather.traverse_nodes((Location.A1_TOWER_LEVEL3_ENTER, Location.A1_TOWER_LEVEL4_ENTER), self._char): + return False + if not self._pather.traverse_nodes((Location.A1_TOWER_LEVEL4_ENTER, Location.A1_TOWER_LEVEL5_ENTER), self._char): + return False + if self._char.capabilities.can_teleport_natively: + self._pather.traverse_nodes_fixed("a1_countess_safe_dist", self._char) + else: + if not self._pather.traverse_nodes((Location.A1_TOWER_LEVEL5_ENTER, Location.A1_COUNTESS_SAFE_DIST), self._char): + return False + + self._char.kill_countess() + wait(0.2, 0.3) + picked_up_items = self._pickit.pick_up_items(self._char) + return (Location.A1_TOWN_START, picked_up_items) diff --git a/src/run/mephisto.py b/src/run/mephisto.py index 7744a29..e5980c1 100644 --- a/src/run/mephisto.py +++ b/src/run/mephisto.py @@ -5,6 +5,8 @@ from item.pickit import PickIt import template_finder from town.town_manager import TownManager from utils.misc import wait +from ui import loading +from ui import waypoint from collections import OrderedDict @@ -26,31 +28,39 @@ class Mephisto: self._pickit = pickit self.runs = runs - def approach(self, start_loc: Location) -> bool | Location: - # TODO: Implement approach logic for Mephisto + def approach(self, start_loc: Location, do_pre_buff: bool) -> bool | Location: Logger.info("Run Mephisto") - # loc = self._town_manager.go_to_act(2, start_loc) - # if not loc: - # return False - # TODO: Add traversal to None # TODO - # if not self._pather.traverse_nodes((loc, Location.None # TODO), self._char): - # return False - return False # Replace with actual start Location constant + loc = self._town_manager.go_to_act(3, start_loc) + if not loc: + return False + # Traverse to the waypoint at stash + if not self._pather.traverse_nodes((loc, Location.A3_STASH_WP), self._char): + return False + # Open waypoint and select Durance of Hate Level 2 + if not self._town_manager.open_wp(loc): + return False + if not waypoint.use_wp(label="Durance of Hate Level 2"): + return False + wait(0.5, 0.6) + # Pre-buff after entering the area + if do_pre_buff: + self._char.pre_buff() + return Location.A3_TOWN_START # Durance L2 fallback - def battle(self, do_pre_buff: bool) -> bool | tuple[Location, bool]: - # TODO: Implement battle logic for Mephisto - # if not template_finder.search_and_wait(["TODO"], threshold=0.65, timeout=20).valid: - # return False - # if do_pre_buff: - # if not self._char.pre_buff(): - # return False - # if self._char.capabilities.can_teleport_natively: - # self._pather.traverse_nodes_fixed("todo_safe_dist", self._char) - # else: - # if not self._pather.traverse_nodes((Location.TODO), self._char): - # return False - # self._char.kill_todo() - # wait(0.2, 0.3) - # picked_up_items = self._pickit.pick_up_items(self._char) - # return (Location.TODO, picked_up_items) - return False + def battle(self) -> bool | tuple[Location, bool]: + # Traverse to Mephisto's location + if self._char.capabilities.can_teleport_natively: + self._pather.traverse_nodes_fixed("a3_meph_level3_enter", self._char) + else: + if not self._pather.traverse_nodes((Location.A3_TOWN_START, Location.A3_MEPH_LEVEL3_ENTER), self._char): + return False + if self._char.capabilities.can_teleport_natively: + self._pather.traverse_nodes_fixed("a3_meph_safe_dist", self._char) + else: + if not self._pather.traverse_nodes((Location.A3_MEPH_LEVEL3_ENTER, Location.A3_MEPH_SAFE_DIST), self._char): + return False + + self._char.kill_mephisto() + wait(0.2, 0.3) + picked_up_items = self._pickit.pick_up_items(self._char) + return (Location.A3_TOWN_START, picked_up_items)