Recover from being stuck (#46)

* recover from being stuck

* remove shutdown within bot.py
This commit is contained in:
aeon0
2021-11-17 22:49:38 +01:00
committed by GitHub
parent 8f772daf86
commit db994eeee4
6 changed files with 112 additions and 44 deletions
+1
View File
@@ -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
+1
View File
@@ -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
View File
@@ -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__":
+1
View File
@@ -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"),
+38
View File
@@ -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
View File
@@ -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()