Auto offset (#120)
* automatically calculate offsets, not-yet working * space * space * float to int * working * clean * clean2 * auto adjust windowed mode depending on screen res and botty res * clean screen instance, fix tests * remove test png Co-authored-by: aeon0 <[email protected]>
This commit is contained in:
committed by
GitHub
co-authored by
aeon0
parent
e9c877029d
commit
fdcdd90b43
@@ -9,7 +9,7 @@ And please. I urge you to actually read that README! It will make your life a lo
|
||||
[](https://streamable.com/67h9ay)
|
||||
|
||||
## Getting started
|
||||
Botty only supports English language and screen resolutions 1920x1080 and 1280x720. If your monitor has a higher resolution, you need to either reduce it to one of the two resolutions or set D2R to windowed mode and adjust offset_left and offset_top.
|
||||
Botty only supports English language! Botty is currently working in 1080p or 720p and will try to adjust the D2R settings accordingly depending on your monitor res and your botty setting for "res" in the [general] section.
|
||||
|
||||
### 1) Graphics and Gameplay Settings
|
||||
All settings will automatically be set when you execute `run.exe` and press the hotkey for "Adjust D2R settings" (default f9). Note that D2R should not run during this process, or if it does you will have to restart afterwards. It is not a 100% thing, in rare cases you might still have to fiddle around with your brightness. I suggest using the "Graphic Debugger" to verify your settings. Also, there are sample screenshots of how graphics should look like: <a href="/assets/docs/sample_graphics.png">Example 1</a>, <a href="/assets/docs/sample_graphics_2.png">Example 2</a></br>
|
||||
@@ -54,8 +54,6 @@ run_shenk=0
|
||||
name | Name used in terminal and discord messages
|
||||
monitor | Select on which monitor D2R is running in case multiple are available
|
||||
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.
|
||||
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
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@@ -3,8 +3,6 @@
|
||||
name=Botty
|
||||
monitor=0
|
||||
res=1280_720
|
||||
offset_top=160
|
||||
offset_left=320
|
||||
max_game_length_s=320
|
||||
resume_key=f11
|
||||
exit_key=f12
|
||||
|
||||
+6
-4
@@ -26,11 +26,11 @@ import random
|
||||
|
||||
|
||||
class Bot:
|
||||
def __init__(self):
|
||||
def __init__(self, screen: Screen):
|
||||
self._screen = screen
|
||||
self._config = Config()
|
||||
self._game_stats = GameStats()
|
||||
self._game_recovery = GameRecovery()
|
||||
self._screen = Screen(self._config.general["monitor"])
|
||||
self._game_recovery = GameRecovery(self._screen)
|
||||
self._template_finder = TemplateFinder(self._screen)
|
||||
self._item_finder = ItemFinder()
|
||||
self._ui_manager = UiManager(self._screen, self._template_finder)
|
||||
@@ -375,7 +375,9 @@ if __name__ == "__main__":
|
||||
import keyboard
|
||||
keyboard.add_hotkey("f12", lambda: os._exit(1))
|
||||
keyboard.wait("f11")
|
||||
bot = Bot()
|
||||
config = Config()
|
||||
screen = Screen(config.general["monitor"])
|
||||
bot = Bot(screen)
|
||||
bot.state = "a5_town"
|
||||
bot._curr_location = Location.A5_TOWN_START
|
||||
bot.on_maintenance()
|
||||
|
||||
+2
-4
@@ -28,8 +28,6 @@ class Config:
|
||||
"name": self._select_val("general", "name"),
|
||||
"monitor": int(self._select_val("general", "monitor")),
|
||||
"res": self._select_val("general", "res"),
|
||||
"offset_top": int(self._select_val("general", "offset_top")),
|
||||
"offset_left": int(self._select_val("general", "offset_left")),
|
||||
"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"),
|
||||
@@ -93,7 +91,7 @@ class Config:
|
||||
self.hammerdin = self._config["hammerdin"]
|
||||
if "hammerdin" in self._custom:
|
||||
self.hammerdin.update(self._custom["hammerdin"])
|
||||
|
||||
|
||||
self.advanced_options = {
|
||||
"pathing_delay_factor": min(max(int(self._select_val("advanced_options", "pathing_delay_factor")),1),10),
|
||||
"template_threshold": float(self._select_val("advanced_options", "template_threshold")),
|
||||
@@ -150,7 +148,7 @@ if __name__ == "__main__":
|
||||
# cv2.imwrite(f"./assets/items/{k}.png", img)
|
||||
# else:
|
||||
# print(f"{attrib}_{base_name}=1")
|
||||
|
||||
|
||||
for filename in os.listdir(f'assets/items'):
|
||||
filename = filename.lower()
|
||||
if filename.endswith('.png'):
|
||||
|
||||
@@ -9,9 +9,9 @@ import time
|
||||
|
||||
|
||||
class GameRecovery:
|
||||
def __init__(self):
|
||||
def __init__(self, screen: Screen):
|
||||
self._config = Config()
|
||||
self._screen = Screen(self._config.general["monitor"])
|
||||
self._screen = screen
|
||||
self._template_finder = TemplateFinder(self._screen)
|
||||
self._death_manager = DeathManager(self._screen, self._template_finder)
|
||||
self._ui_manager = UiManager(self._screen, self._template_finder)
|
||||
@@ -48,5 +48,7 @@ class GameRecovery:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
game_recovery = GameRecovery()
|
||||
config = Config()
|
||||
screen = Screen(config.general["monitor"])
|
||||
game_recovery = GameRecovery(screen)
|
||||
game_recovery.go_to_hero_selection()
|
||||
|
||||
+14
-4
@@ -1,5 +1,6 @@
|
||||
from bot import Bot
|
||||
from game_recovery import GameRecovery
|
||||
from screen import Screen
|
||||
from logger import Logger
|
||||
import keyboard
|
||||
import os
|
||||
@@ -13,11 +14,13 @@ from beautifultable import BeautifulTable
|
||||
import time
|
||||
import logging
|
||||
import cv2
|
||||
import traceback
|
||||
|
||||
|
||||
def run_bot(config: Config):
|
||||
game_recovery = GameRecovery()
|
||||
bot = Bot()
|
||||
screen = Screen(config.general["monitor"])
|
||||
game_recovery = GameRecovery(screen)
|
||||
bot = Bot(screen)
|
||||
bot_thread = threading.Thread(target=bot.start)
|
||||
bot_thread.start()
|
||||
do_restart = False
|
||||
@@ -42,8 +45,7 @@ def run_bot(config: Config):
|
||||
send_discord(f"{config.general['name']} got stuck and can not resume", config.general["custom_discord_hook"])
|
||||
os._exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
def main():
|
||||
config = Config(print_warnings=True)
|
||||
if config.general["logg_lvl"] == "info":
|
||||
Logger.init(logging.INFO)
|
||||
@@ -84,5 +86,13 @@ if __name__ == "__main__":
|
||||
break
|
||||
time.sleep(0.02)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# To avoid cmd just closing down, except any errors and add a input() to the end
|
||||
try:
|
||||
main()
|
||||
except:
|
||||
print("RUNTIME ERROR:")
|
||||
traceback.print_exc()
|
||||
print("Press Enter to exit ...")
|
||||
input()
|
||||
|
||||
+22
-9
@@ -5,6 +5,7 @@ import time
|
||||
from logger import Logger
|
||||
from typing import Tuple
|
||||
from config import Config
|
||||
from utils.misc import load_template
|
||||
import os
|
||||
|
||||
|
||||
@@ -20,15 +21,27 @@ class Screen:
|
||||
if monitor_idx >= len(self._sct.monitors):
|
||||
Logger.warning("Monitor index not available! Choose a smaller number for 'monitor' in the param.ini. Forcing value to 0 for now.")
|
||||
monitor_idx = 1
|
||||
config = Config()
|
||||
self._config = Config()
|
||||
self._monitor_roi = self._sct.monitors[monitor_idx]
|
||||
# For windowed screens it is expected to always have them at the top left edge and adjust offset_top then
|
||||
self._monitor_roi["top"] += config.general["offset_top"]
|
||||
self._monitor_roi["left"] += config.general["offset_left"]
|
||||
self._monitor_roi["width"] = config.ui_pos["screen_width"]
|
||||
self._monitor_roi["height"] = config.ui_pos["screen_height"]
|
||||
self._monitor_x_range = (self._monitor_roi["left"] + 10, self._monitor_roi["left"] + self._monitor_roi["width"] - 10)
|
||||
self._monitor_y_range = (self._monitor_roi["top"] + 10, self._monitor_roi["top"] + self._monitor_roi["height"] - 10)
|
||||
# auto find offests
|
||||
res_str = "" if self._config.general['res'] == "1920_1080" else "_1280_720"
|
||||
template = load_template(f"assets/templates{res_str}/main_menu_top_left.png", 1.0)
|
||||
img = self.grab()
|
||||
self._sct = mss()
|
||||
res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED)
|
||||
_, max_val, _, max_pos = cv2.minMaxLoc(res)
|
||||
if max_val > 0.9:
|
||||
offset_left, offset_top = max_pos
|
||||
Logger.debug(f"Set offsets: left {offset_left}px, top {offset_top}px")
|
||||
self._monitor_roi["top"] += offset_top
|
||||
self._monitor_roi["left"] += offset_left
|
||||
self._monitor_x_range = (self._monitor_roi["left"] + 10, self._monitor_roi["left"] + self._monitor_roi["width"] - 10)
|
||||
self._monitor_y_range = (self._monitor_roi["top"] + 10, self._monitor_roi["top"] + self._monitor_roi["height"] - 10)
|
||||
self._monitor_roi["width"] = self._config.ui_pos["screen_width"]
|
||||
self._monitor_roi["height"] = self._config.ui_pos["screen_height"]
|
||||
else:
|
||||
Logger.error("Could not find top left corner of window to set offset, shutting down")
|
||||
raise RuntimeError("Could not determine window offset")
|
||||
|
||||
def convert_monitor_to_screen(self, screen_coord: Tuple[float, float]) -> Tuple[float, float]:
|
||||
return (screen_coord[0] - self._monitor_roi["left"], screen_coord[1] - self._monitor_roi["top"])
|
||||
@@ -62,7 +75,7 @@ if __name__ == "__main__":
|
||||
while 1:
|
||||
start = time.time()
|
||||
test_img = screen.grab().copy()
|
||||
print(time.time() - start)
|
||||
# print(time.time() - start)
|
||||
|
||||
show_roi = True
|
||||
show_pt = True
|
||||
|
||||
@@ -12,9 +12,9 @@ import screen
|
||||
from config import Config
|
||||
from logger import Logger
|
||||
from npc_manager import NpcManager, Npc
|
||||
from template_finder import TemplateFinder, load_template
|
||||
from template_finder import TemplateFinder
|
||||
from utils.custom_mouse import mouse
|
||||
from utils.misc import wait
|
||||
from utils.misc import wait, load_template
|
||||
|
||||
|
||||
|
||||
|
||||
+4
-10
@@ -6,15 +6,9 @@ from logger import Logger
|
||||
import time
|
||||
import os
|
||||
from config import Config
|
||||
from utils.misc import load_template
|
||||
|
||||
|
||||
def load_template(path, scale_factor):
|
||||
if os.path.isfile(path):
|
||||
template_img = cv2.imread(path)
|
||||
template_img = cv2.resize(template_img, None, fx=scale_factor, fy=scale_factor, interpolation=cv2.INTER_NEAREST)
|
||||
return template_img
|
||||
return None
|
||||
|
||||
class TemplateFinder:
|
||||
def __init__(self, screen: Screen, scale_factor: float = None):
|
||||
"""
|
||||
@@ -145,12 +139,12 @@ class TemplateFinder:
|
||||
return self._templates[key][0]
|
||||
|
||||
def search(
|
||||
self,
|
||||
self,
|
||||
ref: Union[str, np.ndarray],
|
||||
inp_img: np.ndarray,
|
||||
threshold: float = None,
|
||||
threshold: float = None,
|
||||
roi: List[float] = None,
|
||||
normalize_monitor: bool = False,
|
||||
normalize_monitor: bool = False,
|
||||
) -> Tuple[bool, Tuple[float, float]]:
|
||||
"""
|
||||
Search for a template in an image
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
import json
|
||||
import os
|
||||
from config import Config
|
||||
from mss import mss
|
||||
|
||||
|
||||
def adjust_settings():
|
||||
# You might not belive it, but there are cases where users press f9 and just dont read any console output and think everything is done
|
||||
# so for now, removing this warning as it seems there has never been anybody actually backing up the original settings anyway...
|
||||
# print("Warning: This will overwrite some of your graphics and gameplay settings. D2R must not be running during this action! Continue with Enter...")
|
||||
# input()
|
||||
# find monitor res
|
||||
config = Config()
|
||||
sct = mss()
|
||||
monitor_idx = config.general["monitor"] + 1 # sct saves the whole screen (including both monitors if available at index 0, then monitor 1 at 1 and 2 at 2)
|
||||
if len(sct.monitors) == 1:
|
||||
print("How do you not have a monitor connected?!")
|
||||
os._exit(1)
|
||||
if monitor_idx >= len(sct.monitors):
|
||||
monitor_idx = 1
|
||||
monitor_res = f"{sct.monitors[monitor_idx]['width']}_{sct.monitors[monitor_idx]['height']}"
|
||||
# Get D2r folder
|
||||
d2_saved_games = f"C:\\Users\\{os.getlogin()}\\Saved Games\\Diablo II Resurrected"
|
||||
if not os.path.exists(d2_saved_games):
|
||||
@@ -20,6 +33,17 @@ def adjust_settings():
|
||||
new_settings = json.load(f)
|
||||
for key in new_settings:
|
||||
curr_settings[key] = new_settings[key]
|
||||
# catch error where user sets a higher botty res than the monitor
|
||||
if monitor_res == "1280_720" and config.general["res"] == "1920_1080":
|
||||
print("ERROR: You can not set 'res' to 1920_1080 while your monitor is in 1280_720")
|
||||
return
|
||||
# In case monitor res is at 720p, force fullscreen
|
||||
if monitor_res == config.general["res"]:
|
||||
print(f"Detected param res and monitor res to be the same ({monitor_res}). Forcing fullscreen mode.")
|
||||
curr_settings["Window Mode"] = 1
|
||||
if monitor_res == "1920_1080":
|
||||
# most template screenshots where take with this setting in 1080p:
|
||||
curr_settings["Anti Aliasing"] = 1
|
||||
# write back to settings.json
|
||||
with open(d2_saved_games + "\\Settings.json", 'w') as outfile:
|
||||
json.dump(curr_settings, outfile)
|
||||
|
||||
@@ -5,6 +5,7 @@ from logger import Logger
|
||||
import cv2
|
||||
from typing import List, Tuple
|
||||
import requests
|
||||
import os
|
||||
from version import __version__
|
||||
|
||||
|
||||
@@ -49,3 +50,10 @@ def hms(seconds: int):
|
||||
m = seconds % 3600 // 60
|
||||
s = seconds % 3600 % 60
|
||||
return '{:02d}:{:02d}:{:02d}'.format(h, m, s)
|
||||
|
||||
def load_template(path, scale_factor):
|
||||
if os.path.isfile(path):
|
||||
template_img = cv2.imread(path)
|
||||
template_img = cv2.resize(template_img, None, fx=scale_factor, fy=scale_factor, interpolation=cv2.INTER_NEAREST)
|
||||
return template_img
|
||||
return None
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from screen import Screen
|
||||
import cv2
|
||||
from config import Config
|
||||
from template_finder import TemplateFinder, load_template
|
||||
from template_finder import TemplateFinder
|
||||
from utils.misc import load_template
|
||||
import mouse
|
||||
import keyboard
|
||||
import os
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 264 KiB |
+11
-1
@@ -1,5 +1,14 @@
|
||||
from logger import Logger
|
||||
from bot import Bot
|
||||
from screen import Screen
|
||||
from config import Config
|
||||
import cv2
|
||||
|
||||
|
||||
class ScreenMock(Screen):
|
||||
def grab(self):
|
||||
img = cv2.imread("test/hero_select.png")
|
||||
return img
|
||||
|
||||
|
||||
class TestSmoke:
|
||||
@@ -11,4 +20,5 @@ class TestSmoke:
|
||||
Logger.remove_file_logger()
|
||||
|
||||
def test_smoke(self):
|
||||
bot = Bot()
|
||||
screen = ScreenMock()
|
||||
bot = Bot(screen)
|
||||
|
||||
Reference in New Issue
Block a user