Support Hammerdin without Engima (#136)

This commit is contained in:
aeon0
2021-11-28 14:33:09 +01:00
committed by GitHub
parent 28a455d964
commit bfb8c16fa0
14 changed files with 78 additions and 36 deletions

View File

@@ -12,8 +12,8 @@ And please. I urge you to actually read that README! It will make your life a lo
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>
**Note**: There have been issues reported with image sharpening being truned on via the graphic card settings itself outside of D2R. Try turning it off when running botty.
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.
**Note**: There have been issues reported with image sharpening being truned on via the graphic card settings itself outside of D2R. Try turning it off when running botty. Also HDR should be truned off.
### 2) Supported builds
@@ -115,11 +115,12 @@ thunder_storm | Optional Hotkey for thunder storm
[hammerdin] | Descriptions
--------------------------------|---------------------------------------------
teleport | Required Hotkey for teleport
teleport | Optional Hotkey for teleport. If left empty hammerdin will run instead of teleport.
concentration | Required Hotkey for Concentration
redemption | Optional Hotkey for redemption
holy_shield | Hotkey for Holy Shield
blessed_hammer | Hotkey for Blessed Hammer
redemption | Optional Hotkey for redemption
vigor | Optional Hotkey for vigor
[advanced_options] | Descriptions
--------------------------------|---------------------------------------------

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

BIN
assets/templates/vigor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -91,6 +91,8 @@ item_height_min=9
item_width_min=30
; pather
reached_node_dist=150
min_walk_dist=38
max_walk_dist=190
[ui_roi_1920_1080]
; all rois are in [left, top, width, height] format

View File

@@ -69,9 +69,10 @@ telekinesis=
[hammerdin]
teleport=f1
concentration=f2
redemption=
holy_shield=f4
blessed_hammer=f5
vigor=
redemption=
[advanced_options]
;===== don't touch unless you know what you're doing =====

View File

@@ -16,6 +16,11 @@ class Hammerdin(IChar):
Logger.info("Setting up Hammerdin")
super().__init__(skill_hotkeys, char_config, screen, template_finder, ui_manager)
self._pather = pather
self._do_pre_move = True
# In case we have a running pala, we want to switch to concentration when moving to the boss
# ass most likely we will click on some mobs and already cast hammers
if not self._skill_hotkeys["teleport"]:
self._do_pre_move = False
def pre_buff(self):
if self._char_config["cta_available"]:
@@ -30,7 +35,8 @@ class Hammerdin(IChar):
wait(0.05, 0.15)
keyboard.send(self._char_config["stand_still"], do_release=False)
wait(0.05, 0.15)
keyboard.send(self._skill_hotkeys["blessed_hammer"])
if self._skill_hotkeys["blessed_hammer"]:
keyboard.send(self._skill_hotkeys["blessed_hammer"])
wait(0.05, 0.15)
mouse.press(button="left")
start = time.time()
@@ -56,7 +62,10 @@ class Hammerdin(IChar):
if self._config.char["static_path_pindle"]:
self._pather.traverse_nodes_fixed("pindle_end", self)
else:
self._pather.traverse_nodes(Location.PINDLE_SAVE_DIST, Location.PINDLE_END, self, time_out=0.5)
if not self._do_pre_move:
keyboard.send(self._skill_hotkeys["concentration"])
wait(0.05, 0.15)
self._pather.traverse_nodes(Location.PINDLE_SAVE_DIST, Location.PINDLE_END, self, time_out=1.0, do_pre_move=self._do_pre_move)
self._cast_hammers(1)
# pindle sometimes knocks back, get back in
self._pather.traverse_nodes(Location.PINDLE_SAVE_DIST, Location.PINDLE_END, self, time_out=0.1)
@@ -69,7 +78,10 @@ class Hammerdin(IChar):
if self._config.char["static_path_eldritch"]:
self._pather.traverse_nodes_fixed("eldritch_end", self)
else:
self._pather.traverse_nodes(Location.ELDRITCH_SAVE_DIST, Location.ELDRITCH_END, self, time_out=0.5)
if not self._do_pre_move:
keyboard.send(self._skill_hotkeys["concentration"])
wait(0.05, 0.15)
self._pather.traverse_nodes(Location.ELDRITCH_SAVE_DIST, Location.ELDRITCH_END, self, time_out=1.0, do_pre_move=self._do_pre_move)
wait(0.05, 0.1)
self._cast_hammers(self._char_config["atk_len_eldritch"])
wait(0.05, 0.15)
@@ -77,13 +89,26 @@ class Hammerdin(IChar):
return True
def kill_shenk(self):
self._pather.traverse_nodes(Location.SHENK_SAVE_DIST, Location.SHENK_END, self, time_out=0.2)
if not self._do_pre_move:
keyboard.send(self._skill_hotkeys["concentration"])
wait(0.05, 0.15)
self._pather.traverse_nodes(Location.SHENK_SAVE_DIST, Location.SHENK_END, self, time_out=1.0, do_pre_move=self._do_pre_move)
wait(0.05, 0.1)
self._cast_hammers(self._char_config["atk_len_shenk"])
wait(0.05, 0.15)
self._do_redemption()
return True
def pre_move(self):
# select teleport if available
super().pre_move()
# in case teleport hotkey is not set or teleport can not be used, use vigor if set
should_cast_vigor = self._skill_hotkeys["vigor"] and not self._ui_manager.is_right_skill_selected(["VIGOR"])
can_teleport = self._skill_hotkeys["teleport"] and self._ui_manager.is_right_skill_active()
if should_cast_vigor and not can_teleport:
keyboard.send(self._skill_hotkeys["vigor"])
wait(0.15, 0.25)
if __name__ == "__main__":
import os
@@ -98,10 +123,6 @@ if __name__ == "__main__":
pather = Pather(screen, t_finder)
ui_manager = UiManager(screen, t_finder)
char = Hammerdin(config.hammerdin, config.char, screen, t_finder, ui_manager, pather)
# for i in range(20):
# char.pre_buff()
# time.sleep(1.5)
# char.tp_town()
char.select_by_template("A5_WP")
wait(1.0)
ui_manager.use_wp(4, 1)
char.pre_buff()
pather.traverse_nodes(Location.ELDRITCH_START, Location.ELDRITCH_SAVE_DIST, char)
char.kill_eldritch()

View File

@@ -11,6 +11,7 @@ from logger import Logger
import time
from typing import Dict, Tuple, Union, List
from config import Config
import random
def abstract(f):
@@ -55,12 +56,15 @@ class IChar:
return True
return False
def move(self, pos_monitor: Tuple[float, float], force_tp: bool = False):
if force_tp or not self._ui_manager.is_teleport_selected():
def pre_move(self):
# if teleport hotkey is set and if teleport is not already selected
if self._skill_hotkeys["teleport"] and not self._ui_manager.is_right_skill_selected(["TELE_ACTIVE", "TELE_INACTIVE"]):
keyboard.send(self._skill_hotkeys["teleport"])
wait(0.15, 0.25)
def move(self, pos_monitor: Tuple[float, float], force_tp: bool = False):
factor = self._config.advanced_options["pathing_delay_factor"]
if force_tp or self._ui_manager.can_teleport():
if self._skill_hotkeys["teleport"] and (force_tp or self._ui_manager.is_right_skill_active()):
mouse.move(pos_monitor[0], pos_monitor[1], randomize=3, delay_factor=[factor*0.1, factor*0.14])
wait(0.012, 0.02)
mouse.click(button="right")
@@ -70,7 +74,12 @@ class IChar:
pos_screen = self._screen.convert_monitor_to_screen(pos_monitor)
pos_abs = self._screen.convert_screen_to_abs(pos_screen)
dist = math.dist(pos_abs, (0, 0))
adjust_factor = (dist - 50) / dist
min_wd = self._config.ui_pos["min_walk_dist"]
if self._config.char["slow_walk"]:
max_wd = dist
else:
max_wd = random.randint(int(self._config.ui_pos["max_walk_dist"] * 0.65), self._config.ui_pos["max_walk_dist"])
adjust_factor = max(max_wd, min(min_wd, dist - 50)) / dist
pos_abs = [int(pos_abs[0] * adjust_factor), int(pos_abs[1] * adjust_factor)]
x, y = self._screen.convert_abs_to_monitor(pos_abs)
mouse.move(x, y, randomize=5, delay_factor=[factor*0.1, factor*0.14])

View File

@@ -91,6 +91,9 @@ class Config:
self.hammerdin = self._config["hammerdin"]
if "hammerdin" in self._custom:
self.hammerdin.update(self._custom["hammerdin"])
if not self.hammerdin["teleport"]:
self.char["static_path_pindle"] = False
self.char["static_path_eldritch"] = False
self.advanced_options = {
"pathing_delay_factor": min(max(int(self._select_val("advanced_options", "pathing_delay_factor")),1),10),

View File

@@ -77,7 +77,7 @@ class Pather:
120: {"ELDRITCH_0": (439, 36), "ELDRITCH_1": (-461, 114)},
121: {"ELDRITCH_1": (-493, -155), "ELDRITCH_2": (616, 257), "ELDRITCH_3": (-137, 297)},
122: {"ELDRITCH_2": (530, -218), "ELDRITCH_3": (-223, -178)},
123: {"ELDRITCH_3": (-148, -498), "ELDRITCH_2": (604, -538), "ELDRITCH_4": (-163, -283)},
123: {"ELDRITCH_3": (-148, -498+120), "ELDRITCH_2": (604, -538+120), "ELDRITCH_4": (-163+70, -283+120)},
# Shenk
140: {"SHENK_0": (-224, -340), "SHENK_17": (-750, 353), "SHENK_15": (120, 20), "SHENK_1": (667, -242)},
141: {"SHENK_0": (-194, 66), "SHENK_17": (-780, 792), "SHENK_15": (116, 440), "SHENK_1": (696, 161), "SHENK_2": (-251, -51)},
@@ -88,8 +88,7 @@ class Pather:
146: {"SHENK_12": (408, 166), "SHENK_9": (-497, -216), "SHENK_8": (-108, 387)},
147: {"SHENK_16": (290, -150), "SHENK_9": (-100, 208), "SHENK_10": (-646, 100)},
148: {"SHENK_16": (967, 95), "SHENK_9": (451, 395), "SHENK_10": (-97, 282), "SHENK_11": (-459, 208)},
149: {"SHENK_11": (532, 602), "SHENK_10": (882, 682), "SHENK_13": (730, 36)},
149: {"SHENK_11": (532-140, 642-50), "SHENK_10": (882-140, 682-50), "SHENK_13": (730-140, 36-50)},
}
self._paths = {
# A5 Town
@@ -161,7 +160,8 @@ class Pather:
def _convert_rel_to_abs(rel_loc: Tuple[float, float], pos_abs: Tuple[float, float]) -> Tuple[float, float]:
return (rel_loc[0] + pos_abs[0], rel_loc[1] + pos_abs[1])
def traverse_nodes_fixed(self, key: str, char):
def traverse_nodes_fixed(self, key: str, char: IChar):
char.pre_move()
path = self._config.path[key]
for pos in path:
x_m, y_m = self._screen.convert_screen_to_monitor(pos)
@@ -212,7 +212,7 @@ class Pather:
return node_pos_abs
return None
def traverse_nodes(self, start_location: Location, end_location: Location, char: IChar, time_out: float = 7, force_tp: bool = False) -> bool:
def traverse_nodes(self, start_location: Location, end_location: Location, char: IChar, time_out: float = 7, force_tp: bool = False, do_pre_move: bool = True) -> bool:
"""
Traverse from one location to another
:param start_location: Location the char is starting at
@@ -223,6 +223,8 @@ class Pather:
"""
Logger.debug(f"Traverse from {start_location} to {end_location}")
path = self._paths[(start_location, end_location)]
if do_pre_move:
char.pre_move()
for i, node_idx in enumerate(path):
continue_to_next_node = False
last_move = time.time()
@@ -264,13 +266,14 @@ if __name__ == "__main__":
keyboard.wait("f11")
from config import Config
from char.sorceress import Sorceress
from char.hammerdin import Hammerdin
from ui_manager import UiManager
config = Config()
screen = Screen(config.general["monitor"])
t_finder = TemplateFinder(screen)
pather = Pather(screen, t_finder)
ui_manager = UiManager(screen, t_finder)
char = Sorceress(config.sorceress, config.char, screen, t_finder, ui_manager, pather)
char = Hammerdin(config.hammerdin, config.char, screen, t_finder, ui_manager, pather)
# pather.traverse_nodes_fixed("pindle_save_dist", char)
# pather.traverse_nodes(Location.SHENK_START, Location.SHENK_SAVE_DIST, char)
pather._display_all_nodes_debug(filter="SHENK_17")
# pather.traverse_nodes(Location.A5_TOWN_START, Location.NIHLATHAK_PORTAL, char)
pather._display_all_nodes_debug(filter="SHENK")

View File

@@ -40,6 +40,7 @@ class PickIt:
start = prev_cast_start = time.time()
time_out = False
picked_up_items = []
char.pre_move()
while not time_out:
if (time.time() - start) > 24:
time_out = True

View File

@@ -92,6 +92,7 @@ class TemplateFinder:
"MERC": [load_template(f"assets/templates{res_str}/merc.png", 1.0), 1.0],
"TELE_ACTIVE": [load_template(f"assets/templates{res_str}/tele_active.png", 1.0), 1.0],
"TELE_INACTIVE": [load_template(f"assets/templates{res_str}/tele_inactive.png", 1.0), 1.0],
"VIGOR": [load_template(f"assets/templates{res_str}/vigor.png", 1.0), 1.0],
"REPAIR_BTN": [load_template(f"assets/templates{res_str}/repair_btn.png", 1.0), 1.0],
"TP_TOMB": [load_template(f"assets/templates{res_str}/tp_tomb.png", 1.0), 1.0],
"SUPER_HEALING_POTION": [load_template(f"assets/templates{res_str}/super_healing_potion.png", 1.0), 1.0],

View File

@@ -12,6 +12,7 @@ from logger import Logger
from utils.misc import wait, cut_roi, color_filter, send_discord
from config import Config
from item_finder import ItemFinder
from typing import List
class UiManager():
@@ -41,9 +42,9 @@ class UiManager():
wait(0.4, 0.5)
mouse.click(button="left")
def can_teleport(self) -> bool:
def is_right_skill_active(self) -> bool:
"""
:return: Bool if teleport is red/available or not. Teleport skill must be selected on right skill slot when calling the function.
:return: Bool if skill is red/available or not. Skill must be selected on right skill slot when calling the function.
"""
roi = [
self._config.ui_pos["skill_right_x"] - (self._config.ui_pos["skill_width"] // 2),
@@ -55,9 +56,9 @@ class UiManager():
avg = np.average(img)
return avg > 75.0
def is_teleport_selected(self) -> bool:
def is_right_skill_selected(self, template_list: List[str]) -> bool:
"""
:return: Bool if teleport is currently the selected skill on the right skill slot.
:return: Bool if skill is currently the selected skill on the right skill slot.
"""
roi = [
self._config.ui_pos["skill_right_x"] - (self._config.ui_pos["skill_width"] // 2),
@@ -65,10 +66,9 @@ class UiManager():
self._config.ui_pos["skill_width"],
self._config.ui_pos["skill_height"]
]
if self._template_finder.search("TELE_ACTIVE", self._screen.grab(), threshold=0.94, roi=roi)[0]:
return True
if self._template_finder.search("TELE_INACTIVE", self._screen.grab(), threshold=0.94, roi=roi)[0]:
return True
for template in template_list:
if self._template_finder.search(template, self._screen.grab(), threshold=0.94, roi=roi)[0]:
return True
return False
def is_overburdened(self) -> bool: