feat: implement 4 new boss runs (Andariel, Countess, Mephisto, Baal)

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.
This commit is contained in:
Alex
2026-05-17 14:24:46 +02:00
parent eee99c069b
commit b397d07baf
7 changed files with 191 additions and 104 deletions

View File

@@ -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

View File

@@ -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]

View File

@@ -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"

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)