Recover from being stuck (#46)
* recover from being stuck * remove shutdown within bot.py
This commit is contained in:
@@ -53,6 +53,7 @@ res | Resolution settings can be any of [1920_1080, 1280_720]
|
||||
offset_top | Your D2R windows offset from top of the screen (including the window bar). For fullscreen leave at 0.
|
||||
offset_left | Your D2R window offset from left of screen. For fullscreen leave at 0.
|
||||
min_game_length_s | Games must have at least this length, will wait in hero selection for if game is too quick (to avoid server connection issues)
|
||||
max_game_length_s | Botty will attempt to stop whatever its doing and try to restart a new game. Note if this fails, botty will attempt to shut down D2R and Bnet
|
||||
exit_key | Pressing this key (anywhere), will force botty to shut down
|
||||
resume_key | After starting the exe botty will wait for this keypress to atually start botting away
|
||||
color_checker_key | Pressing this key will start a debug mode to check if the color filtering works with your settings. It also includes the item search and marks items it would pick up with red circles
|
||||
|
||||
@@ -4,6 +4,7 @@ res=1920_1080
|
||||
offset_top=0
|
||||
offset_left=0
|
||||
min_game_length_s=65
|
||||
max_game_length_s=320
|
||||
resume_key=f11
|
||||
exit_key=f12
|
||||
auto_settings_key=f9
|
||||
|
||||
+41
-34
@@ -14,7 +14,7 @@ from health_manager import HealthManager
|
||||
from death_manager import DeathManager
|
||||
from npc_manager import NpcManager, Npc
|
||||
from pickit import PickIt
|
||||
from utils.misc import wait, send_discord, close_down_d2
|
||||
from utils.misc import kill_thread, wait
|
||||
import keyboard
|
||||
import threading
|
||||
import time
|
||||
@@ -57,6 +57,9 @@ class Bot:
|
||||
self._timer = None
|
||||
self._tps_left = 20
|
||||
self._pre_buffed = 0
|
||||
self._stopping = False
|
||||
self._pausing = False
|
||||
self._current_threads = []
|
||||
|
||||
self._states=['hero_selection', 'a5_town', 'pindle', 'shenk']
|
||||
self._transitions = [
|
||||
@@ -81,6 +84,23 @@ class Bot:
|
||||
def start(self):
|
||||
self.trigger('create_game')
|
||||
|
||||
def stop(self):
|
||||
self._stopping = True
|
||||
for t in self._current_threads:
|
||||
kill_thread(t)
|
||||
|
||||
def toggle_pause(self):
|
||||
self._pausing = not self._pausing
|
||||
Logger.info(f"Pause at next state change...") if self._pausing else Logger.info(f"Resume")
|
||||
|
||||
def trigger_or_stop(self, name: str):
|
||||
if self._pausing:
|
||||
Logger.info("Botty is now pausing")
|
||||
while self._pausing:
|
||||
time.sleep(0.2)
|
||||
if not self._stopping:
|
||||
self.trigger(name)
|
||||
|
||||
def current_game_length(self):
|
||||
if self._timer is None:
|
||||
return 0
|
||||
@@ -99,19 +119,6 @@ class Bot:
|
||||
break
|
||||
return not found_unfinished_run
|
||||
|
||||
def _shut_down(self):
|
||||
Logger.error("Something went wrong here, bot is unsure about current location. Closing down bot.")
|
||||
if self._config.general["custom_discord_hook"] != "":
|
||||
send_discord_thread = threading.Thread(
|
||||
target=send_discord,
|
||||
args=("Botty got stuck and can not resume", self._config.general["custom_discord_hook"])
|
||||
)
|
||||
send_discord_thread.daemon = True
|
||||
send_discord_thread.start()
|
||||
self._ui_manager.save_and_exit()
|
||||
close_down_d2()
|
||||
os._exit(1)
|
||||
|
||||
def on_create_game(self):
|
||||
if self._timer is not None:
|
||||
delay = self._config.general["min_game_length_s"] - (time.time() - self._timer)
|
||||
@@ -120,20 +127,16 @@ class Bot:
|
||||
wait(delay, delay + 5.0)
|
||||
Logger.info("Start new game")
|
||||
self._timer = time.time()
|
||||
found, _ = self._template_finder.search_and_wait("D2_LOGO_HS", time_out=70)
|
||||
if not found:
|
||||
self._shut_down()
|
||||
self._template_finder.search_and_wait("D2_LOGO_HS")
|
||||
self._ui_manager.start_game()
|
||||
found, _ = self._template_finder.search_and_wait(["A5_TOWN_1", "A5_TOWN_0"], time_out=50)
|
||||
if not found:
|
||||
self._shut_down()
|
||||
self._template_finder.search_and_wait(["A5_TOWN_1", "A5_TOWN_0"])
|
||||
self._tp_is_up = False
|
||||
self._curr_location = Location.A5_TOWN_START
|
||||
# Make sure these keys are released
|
||||
keyboard.release(self._config.char["stand_still"])
|
||||
wait(0.05, 0.15)
|
||||
keyboard.release(self._config.char["show_items"])
|
||||
self.trigger("maintenance")
|
||||
self.trigger_or_stop("maintenance")
|
||||
|
||||
def on_maintenance(self):
|
||||
time.sleep(0.6)
|
||||
@@ -152,12 +155,12 @@ class Bot:
|
||||
if not self._tp_is_up and (self._health_manager.get_health(img) < 0.6 or self._health_manager.get_mana(img) < 0.3):
|
||||
Logger.info("Need some healing first. Go talk to Malah")
|
||||
if not self._pather.traverse_nodes(self._curr_location, Location.MALAH, self._char):
|
||||
self.trigger("end_game")
|
||||
self.trigger_or_stop("end_game")
|
||||
return
|
||||
self._curr_location = Location.MALAH
|
||||
self._npc_manager.open_npc_menu(Npc.MALAH)
|
||||
if not self._pather.traverse_nodes(self._curr_location, Location.A5_TOWN_START, self._char):
|
||||
self.trigger("end_game")
|
||||
self.trigger_or_stop("end_game")
|
||||
return
|
||||
self._curr_location = Location.A5_TOWN_START
|
||||
|
||||
@@ -165,7 +168,7 @@ class Bot:
|
||||
if self._picked_up_items:
|
||||
Logger.info("Stashing picked up items")
|
||||
if not self._pather.traverse_nodes(self._curr_location, Location.A5_STASH, self._char):
|
||||
self.trigger("end_game")
|
||||
self.trigger_or_stop("end_game")
|
||||
return
|
||||
self._curr_location = Location.A5_STASH
|
||||
time.sleep(1.5)
|
||||
@@ -182,7 +185,7 @@ class Bot:
|
||||
if self._tps_left < 4:
|
||||
Logger.info("Repairing and buying tps at Lazurk")
|
||||
if not self._pather.traverse_nodes(self._curr_location, Location.LARZUK, self._char):
|
||||
self.trigger("end_game")
|
||||
self.trigger_or_stop("end_game")
|
||||
return
|
||||
self._curr_location = Location.LARZUK
|
||||
self._npc_manager.open_npc_menu(Npc.LARZUK)
|
||||
@@ -198,7 +201,7 @@ class Bot:
|
||||
if not merc_alive:
|
||||
Logger.info("Reviveing merc")
|
||||
if not self._pather.traverse_nodes(self._curr_location, Location.QUAL_KEHK, self._char):
|
||||
self.trigger("end_game")
|
||||
self.trigger_or_stop("end_game")
|
||||
return
|
||||
self._curr_location = Location.QUAL_KEHK
|
||||
if self._npc_manager.open_npc_menu(Npc.QUAL_KEHK):
|
||||
@@ -209,22 +212,25 @@ class Bot:
|
||||
started_run = False
|
||||
for key in self._do_runs:
|
||||
if self._do_runs[key]:
|
||||
self.trigger(key)
|
||||
self.trigger_or_stop(key)
|
||||
started_run = True
|
||||
break
|
||||
if not started_run:
|
||||
self.trigger("end_game")
|
||||
self.trigger_or_stop("end_game")
|
||||
|
||||
def _start_run(self, key, run):
|
||||
Logger.info(f"{key}")
|
||||
self._do_runs[key] = False
|
||||
run_thread = threading.Thread(target=run.doit, args=(self,))
|
||||
run_thread.start()
|
||||
self._current_threads.append(run_thread)
|
||||
# Set up monitoring
|
||||
health_monitor_thread = threading.Thread(target=self._health_manager.start_monitor, args=(run_thread,))
|
||||
health_monitor_thread.start()
|
||||
self._current_threads.append(health_monitor_thread)
|
||||
death_monitor_thread = threading.Thread(target=self._death_manager.start_monitor, args=(run_thread,))
|
||||
death_monitor_thread.start()
|
||||
self._current_threads.append(death_monitor_thread)
|
||||
run_thread.join()
|
||||
# Run done, lets stop health monitoring and death monitoring
|
||||
self._health_manager.stop_monitor()
|
||||
@@ -236,10 +242,11 @@ class Bot:
|
||||
self._death_manager.stop_monitor()
|
||||
death_monitor_thread.join()
|
||||
|
||||
self._current_threads = []
|
||||
if self._death_manager.died() or self._health_manager.did_chicken() or self.is_last_run() or not run.success:
|
||||
self.trigger("end_game")
|
||||
self.trigger_or_stop("end_game")
|
||||
else:
|
||||
self.trigger("end_run")
|
||||
self.trigger_or_stop("end_run")
|
||||
|
||||
def on_run_pindle(self):
|
||||
class RunPindle:
|
||||
@@ -368,7 +375,7 @@ class Bot:
|
||||
if self._config.general["randomize_runs"]:
|
||||
self.shuffle_runs()
|
||||
wait(0.2, 0.5)
|
||||
self.trigger("create_game")
|
||||
self.trigger_or_stop("create_game")
|
||||
|
||||
def on_end_run(self):
|
||||
success = self._char.tp_town()
|
||||
@@ -378,11 +385,11 @@ class Bot:
|
||||
if success:
|
||||
self._tp_is_up = True
|
||||
self._curr_location = Location.A5_TOWN_START
|
||||
self.trigger("maintenance")
|
||||
self.trigger_or_stop("maintenance")
|
||||
else:
|
||||
self.trigger("end_game")
|
||||
self.trigger_or_stop("end_game")
|
||||
else:
|
||||
self.trigger("end_game")
|
||||
self.trigger_or_stop("end_game")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -30,6 +30,7 @@ class Config:
|
||||
"offset_top": int(self._select_val("general", "offset_top")),
|
||||
"offset_left": int(self._select_val("general", "offset_left")),
|
||||
"min_game_length_s": float(self._select_val("general", "min_game_length_s")),
|
||||
"max_game_length_s": float(self._select_val("general", "max_game_length_s")),
|
||||
"exit_key": self._select_val("general", "exit_key"),
|
||||
"resume_key": self._select_val("general", "resume_key"),
|
||||
"auto_settings_key": self._select_val("general", "auto_settings_key"),
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
from death_manager import DeathManager
|
||||
from screen import Screen
|
||||
from template_finder import TemplateFinder
|
||||
from config import Config
|
||||
from death_manager import DeathManager
|
||||
from ui_manager import UiManager
|
||||
import keyboard
|
||||
import time
|
||||
|
||||
|
||||
class GameRecovery:
|
||||
def __init__(self):
|
||||
self._config = Config()
|
||||
self._screen = Screen(self._config.general["monitor"])
|
||||
self._template_finder = TemplateFinder(self._screen)
|
||||
self._death_manager = DeathManager(self._screen, self._template_finder)
|
||||
self._ui_manager = UiManager(self._screen, self._template_finder)
|
||||
|
||||
def go_to_hero_selection(self):
|
||||
# first lets just see if you might already be at hero selection
|
||||
found, _ = self._template_finder.search_and_wait("D2_LOGO_HS", time_out=1, take_ss=False)
|
||||
if found:
|
||||
return True
|
||||
# would have been too easy, maybe we have died?
|
||||
died = self._death_manager.handle_death_screen()
|
||||
if died:
|
||||
return self._ui_manager.save_and_exit()
|
||||
# we must be ingame, but maybe we are at vendor or on stash, press "esc" until we find a save and exit btn (max 5 times)
|
||||
for _ in range(5):
|
||||
keyboard.press("esc")
|
||||
time.sleep(1)
|
||||
templates = ["SAVE_AND_EXIT_NO_HIGHLIGHT","SAVE_AND_EXIT_HIGHLIGHT"]
|
||||
found, _ = self._template_finder.search_and_wait(templates, roi=self._config.ui_roi["save_and_exit"], time_out=1.5, take_ss=False)
|
||||
if found:
|
||||
keyboard.press("esc")
|
||||
time.sleep(1)
|
||||
return self._ui_manager.save_and_exit()
|
||||
return False
|
||||
+30
-10
@@ -1,4 +1,5 @@
|
||||
from bot import Bot
|
||||
from game_recovery import GameRecovery
|
||||
from logger import Logger
|
||||
import keyboard
|
||||
import os
|
||||
@@ -6,17 +7,38 @@ from config import Config
|
||||
from utils.color_checker import run_color_checker
|
||||
from version import __version__
|
||||
from utils.auto_settings import adjust_settings
|
||||
from utils.misc import kill_thread, send_discord, close_down_d2
|
||||
import threading
|
||||
from beautifultable import BeautifulTable
|
||||
import time
|
||||
import logging
|
||||
|
||||
|
||||
def start_bot(bot):
|
||||
try:
|
||||
bot.start()
|
||||
except KeyboardInterrupt:
|
||||
Logger.info('Exit (ctrl+c)') or exit()
|
||||
def run_bot(config: Config):
|
||||
game_recovery = GameRecovery()
|
||||
bot = Bot()
|
||||
bot_thread = threading.Thread(target=bot.start)
|
||||
bot_thread.start()
|
||||
do_restart = False
|
||||
keyboard.add_hotkey(config.general["exit_key"], lambda: Logger.info(f'Force Exit') or os._exit(1))
|
||||
keyboard.add_hotkey(config.general['resume_key'], lambda: bot.toggle_pause())
|
||||
while 1:
|
||||
if bot.current_game_length() > config.general["max_game_length_s"]:
|
||||
Logger.info("Max game length reached. Attempting to restart botty!")
|
||||
bot.stop()
|
||||
kill_thread(bot_thread)
|
||||
if game_recovery.go_to_hero_selection():
|
||||
do_restart = False
|
||||
break
|
||||
time.sleep(0.04)
|
||||
bot_thread.join()
|
||||
if do_restart:
|
||||
run_bot(config)
|
||||
else:
|
||||
Logger.error("Botty could not recover from a max game length violation. Shutting down everything.")
|
||||
if config.general["custom_discord_hook"]:
|
||||
send_discord("Botty got stuck and can not resume", config.general["custom_discord_hook"])
|
||||
close_down_d2()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
@@ -36,7 +58,7 @@ if __name__ == "__main__":
|
||||
table = BeautifulTable()
|
||||
table.rows.append([config.general['auto_settings_key'], "Adjust D2R settings"])
|
||||
table.rows.append([config.general['color_checker_key'], "Color test mode "])
|
||||
table.rows.append([config.general['resume_key'], "Start bot"])
|
||||
table.rows.append([config.general['resume_key'], "Start / Pause Botty"])
|
||||
table.rows.append([config.general['exit_key'], "Stop bot"])
|
||||
table.columns.header = ["hotkey", "action"]
|
||||
print(table)
|
||||
@@ -44,9 +66,8 @@ if __name__ == "__main__":
|
||||
|
||||
while 1:
|
||||
if keyboard.is_pressed(config.general['resume_key']):
|
||||
bot = Bot()
|
||||
bot_thread = threading.Thread(target=start_bot, args=(bot,))
|
||||
bot_thread.start()
|
||||
run_bot(config)
|
||||
break
|
||||
if keyboard.is_pressed(config.general['auto_settings_key']):
|
||||
adjust_settings()
|
||||
elif keyboard.is_pressed(config.general['color_checker_key']):
|
||||
@@ -54,6 +75,5 @@ if __name__ == "__main__":
|
||||
break
|
||||
time.sleep(0.02)
|
||||
|
||||
bot_thread.join()
|
||||
print("Press Enter to exit ...")
|
||||
input()
|
||||
|
||||
Reference in New Issue
Block a user