Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54af636d8f | ||
|
|
41c05cc614 | ||
|
|
183bae8583 | ||
|
|
6f190c8065 | ||
|
|
6260a0ce30 | ||
|
|
35df3bae7b | ||
|
|
26c5ee9de5 | ||
|
|
1cfde7f560 | ||
|
|
282f7a5606 | ||
|
|
1a148afcfb | ||
|
|
a91a91dc7c | ||
|
|
648d89ff28 | ||
|
|
489cc4ec36 | ||
|
|
fddd72c3a5 | ||
|
|
5abe2fb33e | ||
|
|
df12dbd57c | ||
|
|
9a3b054248 | ||
|
|
26246f141b | ||
|
|
8d1f84062d | ||
|
|
a7d31775f1 | ||
|
|
7c52b05d10 | ||
|
|
a7d7a96268 | ||
|
|
8e48dfe3d0 | ||
|
|
c89b35ff33 | ||
|
|
0d6ab59277 |
@@ -6,14 +6,14 @@ Join the [**Discord Channel**](https://discord.gg/CnkfsjqN) for help and discuss
|
||||
|
||||
[](https://vimeo.com/641410429)
|
||||
|
||||
## Supported features
|
||||
## What it does
|
||||
- Run Pindle, Eldtritch, Shenk
|
||||
- Pickit with per item config.
|
||||
- Pickit with per item config
|
||||
- Stash picked up items (using all 4 stashes)
|
||||
- Prebuff
|
||||
- Revive Merc if dead
|
||||
- Heal at Malah if needed
|
||||
- Take potions and chicken if in trouble during fights
|
||||
- Take potions (health and mana) and chicken if in trouble during fights
|
||||
- Check for death. In this case start another game and pick up corpse.
|
||||
- Supported builds: Sorceress (Blizz, Light, Meteor), Hammerdin
|
||||
- Debug color checker to easily verify your settings
|
||||
@@ -43,6 +43,10 @@ Open up D2R and wait till you are at the hero selection screen. Make sure the ch
|
||||
### 5) Start Botty
|
||||
Download the a prebuilt release [here](https://github.com/aeon0/botty/releases). Start `run.exe` in the botty folder, go to D2R window and press the "resume" key. Move to your D2R window and press "f11". You can always force stop botty with f12.
|
||||
|
||||
## Color Test Mode
|
||||
To check if you graphic settings are good and if the bot would pick up items there is a **Color Test Mode** built in. Start botty and press F10 (Default key). This will open up a (mostly black) window. Start a game in D2R and throw some items of different type on the ground. If you now bring forward the debug window all items should show up with their names while the background is black. If you throw an item on the ground that should be picked up, it will have a red circle.</br>
|
||||
<img src="assets/docs/color_checker.png" width="400">
|
||||
|
||||
## Development
|
||||
Check out the [development.md](development.md) docu for infos on how to build from source and details of the project structure and code.
|
||||
|
||||
@@ -114,9 +118,5 @@ blessed_hammer | Hotkey for Blessed Hammer
|
||||
--------------------------------|---------------------------------------------
|
||||
item_type | Select "1" if item should be picked up, "0" if not.
|
||||
|
||||
## Some distant future plans
|
||||
- Running chaos with AI algo
|
||||
- Emulating Keyboard and Mouse with Arduino
|
||||
|
||||
## Support this project
|
||||
This project is free. Support it by contributing in any technical way, giving feedback, PRs or by submitting issues. That being said, I am not above accepting some pixel currency :) So if you want to send some fg my way to keep my dopamine high, here is my jsp: [jojo22](https://forums.d2jsp.org/user.php?i=768967).
|
||||
|
||||
|
After Width: | Height: | Size: 930 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 910 KiB |
|
After Width: | Height: | Size: 919 KiB |
|
After Width: | Height: | Size: 920 KiB |
|
After Width: | Height: | Size: 865 KiB |
|
After Width: | Height: | Size: 884 KiB |
|
After Width: | Height: | Size: 899 KiB |
|
After Width: | Height: | Size: 890 KiB |
|
After Width: | Height: | Size: 859 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 935 KiB |
|
After Width: | Height: | Size: 933 KiB |
|
After Width: | Height: | Size: 921 KiB |
|
After Width: | Height: | Size: 909 KiB |
|
After Width: | Height: | Size: 940 KiB |
|
After Width: | Height: | Size: 954 KiB |
|
After Width: | Height: | Size: 949 KiB |
|
After Width: | Height: | Size: 907 KiB |
|
After Width: | Height: | Size: 885 KiB |
|
After Width: | Height: | Size: 921 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 111 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 129 KiB |
@@ -14,8 +14,12 @@ python src/run.py
|
||||
```
|
||||
|
||||
## Building from source
|
||||
If you want to build a .exe from source you will have to first add the cv2 path to your PYTHONPATH:</br>
|
||||
Edit System environment variables -> Enfironment Vriables... -> PYTHONPATH -> C:\Users\\$USER\miniconda3\envs\botty\lib\site-packages\cv2
|
||||
If you want to build a .exe from source you will have to first add the cv2 path to your PYTHONPATH in System variables or User variables for current user:</br>
|
||||
Edit the system environment variables -> Environment Variables... -> New... </br>
|
||||
Variable name: PYTHONPATH </br>
|
||||
Variable value: C:\Users\YOUR_WINDOWS_USER_NAME\miniconda3\envs\botty\Lib\site-packages\cv2 </br>
|
||||
|
||||
If you installed your miniconda in another location you will of course have ot change it for that one.
|
||||
```python
|
||||
# building .exe and bundeling all needed resource into one folder
|
||||
python release.py
|
||||
@@ -44,6 +48,9 @@ Want to implement a new char or build. Check this folder out. You will have to i
|
||||
**src/char**</br>
|
||||
Utilities functions and scripts e.g. for easily creating templates to traverse nodes and automatically generate code for it</br>
|
||||
|
||||
## Adding Items
|
||||
To add items you can check the **assets/items** folder. Screenshot whatever you want to pick up in the same way (all settings must be as if you ran the bot). Then add the filename to the param.ini [items] section (e.g. if boots_rare.png add boots_rare=1)
|
||||
|
||||
## State Diagram
|
||||
The core logic of the bot is determined by a state machine with these states and transations. The bot.py which contains all of the transitions should have little implementation code which should be hidden as much as possible in the "manager" classes.
|
||||
<img src="assets/docs/state_diagram.png" width="550"/>
|
||||
|
||||
@@ -14,3 +14,4 @@ dependencies:
|
||||
- keyboard
|
||||
- graphviz
|
||||
- beautifultable
|
||||
- zstandard
|
||||
|
||||
@@ -34,6 +34,9 @@ installer_cmd = f"python -m nuitka --mingw --standalone --onefile --plugin-enabl
|
||||
os.system(installer_cmd)
|
||||
|
||||
os.system(f"mkdir {botty_dir}")
|
||||
|
||||
with open(f"{botty_dir}/custom.ini", "w") as f:
|
||||
f.write("; Add parameters you want to overwrite from param.ini here")
|
||||
shutil.copy("run.exe", f"{botty_dir}/")
|
||||
shutil.copy("ui.ini", f"{botty_dir}/")
|
||||
shutil.copy("params.ini", f"{botty_dir}/")
|
||||
|
||||
@@ -34,9 +34,9 @@ class Bot:
|
||||
self._npc_manager = NpcManager(self._screen, self._template_finder)
|
||||
self._pickit = PickIt(self._screen, self._item_finder, self._ui_manager)
|
||||
if self._config.char["type"] == "sorceress":
|
||||
self._char: IChar = Sorceress(self._config.sorceress, self._config.char, self._screen, self._template_finder, self._ui_manager)
|
||||
self._char: IChar = Sorceress(self._config.sorceress, self._config.char, self._screen, self._template_finder, self._ui_manager, self._pather)
|
||||
elif self._config.char["type"] == "hammerdin":
|
||||
self._char: IChar = Hammerdin(self._config.hammerdin, self._config.char, self._screen, self._template_finder, self._ui_manager)
|
||||
self._char: IChar = Hammerdin(self._config.hammerdin, self._config.char, self._screen, self._template_finder, self._ui_manager, self._pather)
|
||||
else:
|
||||
Logger.error(f'{self._config.char["type"]} is not supported! Closing down bot.')
|
||||
os._exit(1)
|
||||
@@ -101,7 +101,9 @@ class Bot:
|
||||
self._template_finder.search_and_wait("A5_TOWN_1")
|
||||
self._tp_is_up = False
|
||||
self._curr_location = Location.A5_TOWN_START
|
||||
keyboard.send(self._config.char["stand_still"]) # just in case the key is still locked
|
||||
# Make sure these keys are released
|
||||
keyboard.release(self._config.char["stand_still"])
|
||||
keyboard.release(self._config.char["show_items"])
|
||||
self.trigger("maintenance")
|
||||
|
||||
def on_maintenance(self):
|
||||
@@ -203,13 +205,13 @@ class Bot:
|
||||
bot._curr_location = Location.NIHLATHAK_PORTAL
|
||||
wait(0.2, 0.4)
|
||||
self.success &= bot._char.select_by_template("A5_RED_PORTAL")
|
||||
self.success &= bot._template_finder.search_and_wait("PINDLE_STONE", time_out=20)[0]
|
||||
self.success &= bot._template_finder.search_and_wait("PINDLE_0", time_out=20)[0]
|
||||
if not self.success:
|
||||
return
|
||||
bot._char.pre_buff()
|
||||
wait(0.2, 0.4)
|
||||
bot._pather.traverse_nodes_fixed("PINDLE", bot._char)
|
||||
bot._char.kill_pindle(bot._pather.get_fixed_path("PINDLE")[1])
|
||||
bot._pather.traverse_nodes(Location.PINDLE_START, Location.PINDLE_SAVE_DIST, bot._char)
|
||||
bot._char.kill_pindle()
|
||||
wait(1.5, 1.8)
|
||||
bot._picked_up_items = bot._pickit.pick_up_items(bot._char)
|
||||
# in order to move away for items and such to have a clear tp, move to the end of the hall
|
||||
@@ -233,21 +235,26 @@ class Bot:
|
||||
bot._char.select_by_template("A5_WP")
|
||||
wait(1.0)
|
||||
bot._ui_manager.use_wp(4, 1)
|
||||
self.success = bot._template_finder.search_and_wait("SHENK_FLAME", time_out=20)[0]
|
||||
self.success = bot._template_finder.search_and_wait("ELDRITCH_0", time_out=20)[0]
|
||||
if not self.success:
|
||||
return
|
||||
bot._char.pre_buff()
|
||||
# eldritch
|
||||
bot._pather.traverse_nodes_fixed("ELDRITCH", bot._char)
|
||||
bot._char.kill_eldritch(bot._pather.get_fixed_path("ELDRITCH")[1])
|
||||
bot._pather.traverse_nodes(Location.ELDRITCH_START, Location.ELDRITCH_SAVE_DIST, bot._char)
|
||||
bot._char.kill_eldritch()
|
||||
wait(0.5)
|
||||
bot._picked_up_items = bot._pickit.pick_up_items(bot._char)
|
||||
# shenk
|
||||
if bot._route_config["run_shenk"]:
|
||||
bot._pather.traverse_nodes_fixed("SHENK", bot._char)
|
||||
self.success = bot._pather.traverse_nodes(Location.SHENK_START, Location.SHENK_SAVE_DIST, bot._char)
|
||||
if not self.success:
|
||||
return
|
||||
wait(0.15, 0.2)
|
||||
bot._char.kill_shenk(bot._pather.get_fixed_path("SHENK")[1])
|
||||
bot._char.kill_shenk()
|
||||
wait(0.5)
|
||||
# do two pickups on shenk because flames sometimes mess up item search
|
||||
bot._picked_up_items |= bot._pickit.pick_up_items(bot._char)
|
||||
wait(1.5, 1.8)
|
||||
bot._picked_up_items |= bot._pickit.pick_up_items(bot._char)
|
||||
self.success = True
|
||||
return
|
||||
@@ -279,8 +286,10 @@ class Bot:
|
||||
if self._timer is not None:
|
||||
elapsed_time = time.time() - self._timer
|
||||
Logger.info(f"End game. Elapsed time: {elapsed_time:.2f}s")
|
||||
for key in self._do_runs:
|
||||
self._do_runs[key] = self._route_config[key]
|
||||
self._do_runs = {
|
||||
"run_pindle": self._route_config["run_pindle"],
|
||||
"run_shenk": self._route_config["run_shenk"] or self._route_config["run_eldritch"]
|
||||
}
|
||||
wait(0.2, 0.5)
|
||||
self.trigger("create_game")
|
||||
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
import keyboard
|
||||
import mouse
|
||||
from utils import custom_mouse
|
||||
from char.i_char import IChar
|
||||
from template_finder import TemplateFinder
|
||||
from item_finder import ItemFinder
|
||||
from ui_manager import UiManager
|
||||
from pather import Pather
|
||||
from logger import Logger
|
||||
from screen import Screen
|
||||
from utils.misc import wait
|
||||
import random
|
||||
import time
|
||||
from typing import Tuple
|
||||
from pather import Pather, Location
|
||||
|
||||
|
||||
class Hammerdin(IChar):
|
||||
def __init__(self, skill_hotkeys, char_config, screen: Screen, template_finder: TemplateFinder, ui_manager: UiManager):
|
||||
def __init__(self, skill_hotkeys, char_config, screen: Screen, template_finder: TemplateFinder, ui_manager: UiManager, pather: Pather):
|
||||
Logger.info("Setting up Hammerdin")
|
||||
super().__init__(skill_hotkeys, char_config, screen, template_finder, ui_manager)
|
||||
self._pather = pather
|
||||
|
||||
def pre_buff(self):
|
||||
keyboard.send(self._skill_hotkeys["holy_shield"])
|
||||
@@ -47,8 +45,14 @@ class Hammerdin(IChar):
|
||||
wait(0.05, 0.1)
|
||||
mouse.press(button="left")
|
||||
start = time.time()
|
||||
i = 0
|
||||
while (time.time() - start) < time_in_s:
|
||||
time.sleep(0.05)
|
||||
wait(0.04, 0.06)
|
||||
i += 1
|
||||
if i % 20 == 0:
|
||||
mouse.release(button="left")
|
||||
time.sleep(0.01)
|
||||
mouse.press(button="left")
|
||||
mouse.release(button="left")
|
||||
keyboard.send(self._char_config["stand_still"], do_press=False)
|
||||
|
||||
@@ -56,33 +60,27 @@ class Hammerdin(IChar):
|
||||
keyboard.send(self._skill_hotkeys["redemption"])
|
||||
wait(1.5, 2.0)
|
||||
|
||||
def kill_pindle(self, pindle_pos_screen: Tuple[float, float]):
|
||||
pos_monitor = self._screen.convert_screen_to_monitor(pindle_pos_screen)
|
||||
keyboard.send(self._skill_hotkeys["teleport"])
|
||||
custom_mouse.move(pos_monitor[0], pos_monitor[1], duration=(random.random() * 0.05 + 0.15))
|
||||
mouse.click(button="right")
|
||||
def kill_pindle(self) -> bool:
|
||||
wait(0.1, 0.15)
|
||||
self._cast_hammers(7)
|
||||
self._pather.traverse_nodes(Location.PINDLE_SAVE_DIST, Location.PINDLE_END, self, time_out=2.0)
|
||||
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.2)
|
||||
self._cast_hammers(6)
|
||||
wait(0.1, 0.15)
|
||||
self._do_redemption()
|
||||
return True
|
||||
|
||||
def kill_shenk(self, shenk_pos_screen: Tuple[float, float]):
|
||||
pos_monitor = self._screen.convert_screen_to_monitor(shenk_pos_screen)
|
||||
keyboard.send(self._skill_hotkeys["teleport"])
|
||||
wait(0.05)
|
||||
custom_mouse.move(pos_monitor[0], pos_monitor[1], duration=(random.random() * 0.05 + 0.15))
|
||||
wait(0.02)
|
||||
mouse.click(button="right")
|
||||
def kill_eldritch(self) -> bool:
|
||||
self._pather.traverse_nodes(Location.ELDRITCH_SAVE_DIST, Location.ELDRITCH_END, self, time_out=0.2)
|
||||
wait(0.1, 0.15)
|
||||
self._cast_hammers(6)
|
||||
wait(0.1, 0.15)
|
||||
self._do_redemption()
|
||||
return True
|
||||
|
||||
def kill_eldritch(self, eldritch_pos_screen: Tuple[float, float]):
|
||||
pos_monitor = self._screen.convert_screen_to_monitor(eldritch_pos_screen)
|
||||
keyboard.send(self._skill_hotkeys["teleport"])
|
||||
custom_mouse.move(pos_monitor[0], pos_monitor[1], duration=(random.random() * 0.05 + 0.15))
|
||||
mouse.click(button="right")
|
||||
def kill_shenk(self):
|
||||
self._pather.traverse_nodes(Location.SHENK_SAVE_DIST, Location.SHENK_END, self, time_out=0.2)
|
||||
wait(0.1, 0.15)
|
||||
self._cast_hammers(6)
|
||||
wait(0.1, 0.15)
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
from typing import Tuple
|
||||
from cv2 import threshold
|
||||
import mouse
|
||||
from utils import custom_mouse
|
||||
from template_finder import TemplateFinder
|
||||
from item_finder import ItemFinder
|
||||
from ui_manager import UiManager
|
||||
from screen import Screen
|
||||
from utils.misc import wait
|
||||
@@ -29,6 +27,8 @@ class IChar:
|
||||
self._ui_manager = ui_manager
|
||||
self._screen = screen
|
||||
self._config = Config()
|
||||
self._last_tp = time.time()
|
||||
self._cast_duration = self._char_config["casting_frames"] * 0.04 + 0.02
|
||||
|
||||
def select_by_template(self, template_type: str) -> bool:
|
||||
Logger.debug(f"Select {template_type}")
|
||||
@@ -48,8 +48,7 @@ class IChar:
|
||||
if self._ui_manager.can_teleport():
|
||||
custom_mouse.move(pos_monitor[0], pos_monitor[1], duration=(random.random() * 0.01 + 0.06))
|
||||
mouse.click(button="right")
|
||||
wait_min = self._char_config["casting_frames"] * 0.036
|
||||
wait(wait_min, wait_min + 0.02)
|
||||
time.sleep(self._cast_duration)
|
||||
else:
|
||||
# in case we want to walk we actually want to move a bit before the point cause d2r will always "overwalk"
|
||||
pos_screen = self._screen.convert_monitor_to_screen(pos_monitor)
|
||||
@@ -61,9 +60,10 @@ class IChar:
|
||||
custom_mouse.move(x, y, duration=(random.random() * 0.03 + 0.05))
|
||||
mouse.click(button="left")
|
||||
wait(0.02, 0.03)
|
||||
if self._config.char["slow_walk"]:
|
||||
wait(1.2, 1.4)
|
||||
|
||||
def tp_town(self):
|
||||
# TODO: Make robust against no more tps
|
||||
keyboard.send(self._char_config["tp"])
|
||||
wait(0.05, 0.1)
|
||||
mouse.click(button="right")
|
||||
@@ -94,13 +94,13 @@ class IChar:
|
||||
pass
|
||||
|
||||
@abstract
|
||||
def kill_pindle(self, pindle_pos_screen):
|
||||
def kill_pindle(self) -> bool:
|
||||
pass
|
||||
|
||||
@abstract
|
||||
def kill_shenk(self, shenk_pos_screen):
|
||||
def kill_shenk(self) -> bool:
|
||||
pass
|
||||
|
||||
@abstract
|
||||
def kill_eldritch(self, eldritch_pos_screen):
|
||||
def kill_eldritch(self) -> bool:
|
||||
pass
|
||||
|
||||
@@ -11,12 +11,14 @@ from screen import Screen
|
||||
from utils.misc import wait
|
||||
import random
|
||||
from typing import Tuple
|
||||
from pather import Location, Pather
|
||||
|
||||
|
||||
class Sorceress(IChar):
|
||||
def __init__(self, skill_hotkeys, char_config, screen: Screen, template_finder: TemplateFinder, ui_manager: UiManager):
|
||||
def __init__(self, skill_hotkeys, char_config, screen: Screen, template_finder: TemplateFinder, ui_manager: UiManager, pather: Pather):
|
||||
Logger.info("Setting up Sorceress")
|
||||
super().__init__(skill_hotkeys, char_config, screen, template_finder, ui_manager)
|
||||
self._pather = pather
|
||||
|
||||
def pre_buff(self):
|
||||
keyboard.send(self._skill_hotkeys["frozen_armor"])
|
||||
@@ -57,31 +59,30 @@ class Sorceress(IChar):
|
||||
mouse.click(button="right")
|
||||
wait(delay[0], delay[1])
|
||||
|
||||
def kill_pindle(self, pindle_pos_screen: Tuple[float, float]):
|
||||
def kill_pindle(self) -> bool:
|
||||
delay = [0.2, 0.3]
|
||||
pindle_pos_abs = self._screen.convert_screen_to_abs(pindle_pos_screen)
|
||||
cast_pos_abs = [pindle_pos_abs[0] * 0.9, pindle_pos_abs[1] * 0.9]
|
||||
cast_pos_monitor = self._screen.convert_abs_to_monitor(cast_pos_abs)
|
||||
pindle_pos_monitor = self._screen.convert_screen_to_monitor(pindle_pos_screen)
|
||||
self._main_attack(cast_pos_monitor, delay)
|
||||
self._left_attack(cast_pos_monitor, delay)
|
||||
self._main_attack(cast_pos_monitor, delay)
|
||||
self._left_attack(cast_pos_monitor, delay)
|
||||
wait(0.1, 0.15)
|
||||
# Move to items
|
||||
keyboard.send(self._skill_hotkeys["teleport"])
|
||||
custom_mouse.move(pindle_pos_monitor[0], pindle_pos_monitor[1], duration=(random.random() * 0.05 + 0.15))
|
||||
mouse.click(button="right")
|
||||
wait(0.1, 0.15)
|
||||
blizzard_cast_pos = self._screen.convert_abs_to_monitor([0, 0])
|
||||
self._main_attack(blizzard_cast_pos, delay)
|
||||
pindle_pos_abs = self._pather.find_abs_node_pos(104, self._screen.grab())
|
||||
if pindle_pos_abs is not None:
|
||||
cast_pos_abs = [pindle_pos_abs[0] * 0.9, pindle_pos_abs[1] * 0.9]
|
||||
cast_pos_monitor = self._screen.convert_abs_to_monitor(cast_pos_abs)
|
||||
self._main_attack(cast_pos_monitor, delay)
|
||||
self._left_attack(cast_pos_monitor, delay)
|
||||
self._main_attack(cast_pos_monitor, delay)
|
||||
self._left_attack(cast_pos_monitor, delay)
|
||||
wait(0.1, 0.15)
|
||||
# Move to items
|
||||
self._pather.traverse_nodes(Location.PINDLE_SAVE_DIST, Location.PINDLE_END, self)
|
||||
wait(0.1, 0.15)
|
||||
blizzard_cast_pos = self._screen.convert_abs_to_monitor([0, 0])
|
||||
self._main_attack(blizzard_cast_pos, delay)
|
||||
return True
|
||||
return False
|
||||
|
||||
def kill_shenk(self, shenk_pos_screen: Tuple[float, float]):
|
||||
def kill_shenk(self):
|
||||
delay = [0.2, 0.3]
|
||||
pos_abs = self._screen.convert_screen_to_abs(shenk_pos_screen)
|
||||
pos_abs = self._pather.find_abs_node_pos(149, self._screen.grab())
|
||||
cast_pos_abs = [pos_abs[0] * 0.9, pos_abs[1] * 0.9]
|
||||
cast_pos_monitor = self._screen.convert_abs_to_monitor(cast_pos_abs)
|
||||
pos_monitor = self._screen.convert_screen_to_monitor(shenk_pos_screen)
|
||||
# TODO: Not sure if we need so much attacks... maybe add a "number_attack_sequenze" to the param.ini
|
||||
self._main_attack(cast_pos_monitor, delay, 90)
|
||||
self._left_attack(cast_pos_monitor, delay, 90)
|
||||
@@ -92,26 +93,24 @@ class Sorceress(IChar):
|
||||
self._left_attack(cast_pos_monitor, delay, 90)
|
||||
wait(0.2, 0.3)
|
||||
# Move to items
|
||||
keyboard.send(self._skill_hotkeys["teleport"])
|
||||
custom_mouse.move(pos_monitor[0], pos_monitor[1], duration=(random.random() * 0.05 + 0.15))
|
||||
mouse.click(button="right")
|
||||
self._pather.traverse_nodes(Location.SHENK_SAVE_DIST, Location.SHENK_END, self, time_out=2.0)
|
||||
|
||||
def kill_eldritch(self, eldritch_pos_screen: Tuple[float, float]):
|
||||
def kill_eldritch(self) -> bool:
|
||||
delay = [0.2, 0.3]
|
||||
pos_abs = self._screen.convert_screen_to_abs(eldritch_pos_screen)
|
||||
cast_pos_abs = [pos_abs[0] * 0.9, pos_abs[1] * 0.9]
|
||||
cast_pos_monitor = self._screen.convert_abs_to_monitor(cast_pos_abs)
|
||||
pos_monitor = self._screen.convert_screen_to_monitor(eldritch_pos_screen)
|
||||
self._main_attack(cast_pos_monitor, delay, 90)
|
||||
self._left_attack(cast_pos_monitor, delay, 90)
|
||||
self._main_attack(cast_pos_monitor, delay, 90)
|
||||
self._main_attack(cast_pos_monitor, delay, 90)
|
||||
self._left_attack(cast_pos_monitor, delay, 90)
|
||||
wait(0.2, 0.3)
|
||||
# Move to items
|
||||
keyboard.send(self._skill_hotkeys["teleport"])
|
||||
custom_mouse.move(pos_monitor[0], pos_monitor[1], duration=(random.random() * 0.05 + 0.15))
|
||||
mouse.click(button="right")
|
||||
pos_abs = self._pather.find_abs_node_pos(123, self._screen.grab())
|
||||
if pos_abs is not None:
|
||||
cast_pos_abs = [pos_abs[0] * 0.9, pos_abs[1] * 0.9]
|
||||
cast_pos_monitor = self._screen.convert_abs_to_monitor(cast_pos_abs)
|
||||
self._main_attack(cast_pos_monitor, delay, 90)
|
||||
self._left_attack(cast_pos_monitor, delay, 90)
|
||||
self._main_attack(cast_pos_monitor, delay, 90)
|
||||
self._main_attack(cast_pos_monitor, delay, 90)
|
||||
self._left_attack(cast_pos_monitor, delay, 90)
|
||||
wait(0.2, 0.3)
|
||||
# Move to items
|
||||
self._pather.traverse_nodes(Location.ELDRITCH_SAVE_DIST, Location.ELDRITCH_END, self, time_out=2.0)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -46,6 +46,9 @@ class DeathManager:
|
||||
Logger.warning("You have died! Waiting 30 sec to make sure chicken does not do any funny during with this logic.")
|
||||
# first wait a bit to make sure health manager is done with its chicken stuff which obviously failed
|
||||
kill_thread(run_thread)
|
||||
# clean up key presses that might be pressed in the run_thread
|
||||
keyboard.release(self._config.char["stand_still"])
|
||||
keyboard.release(self._config.char["show_items"])
|
||||
time.sleep(30)
|
||||
self._died = True
|
||||
if self._template_finder.search("D2_LOGO_HS", self._screen.grab())[0]:
|
||||
|
||||
@@ -100,6 +100,9 @@ class HealthManager:
|
||||
self._ui_manager.save_and_exit()
|
||||
self._did_chicken = True
|
||||
kill_thread(run_thread)
|
||||
# clean up key presses that might be pressed in the run_thread
|
||||
keyboard.release(self._config.char["stand_still"])
|
||||
keyboard.release(self._config.char["show_items"])
|
||||
self._do_monitor = False
|
||||
break
|
||||
# check mana
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from char.i_char import IChar
|
||||
from screen import Screen
|
||||
from template_finder import TemplateFinder
|
||||
from logger import Logger
|
||||
@@ -15,12 +16,25 @@ import numpy as np
|
||||
|
||||
|
||||
class Location:
|
||||
# A5 Town
|
||||
A5_TOWN_START = "a5_town_start"
|
||||
A5_STASH = "a5_stash"
|
||||
A5_WP = "a5_wp"
|
||||
QUAL_KEHK = "qual_kehk"
|
||||
MALAH = "malah"
|
||||
NIHLATHAK_PORTAL = "nihlathak_portal"
|
||||
# Pindle
|
||||
PINDLE_START = "pindle_start"
|
||||
PINDLE_SAVE_DIST = "pindle_save_dist"
|
||||
PINDLE_END = "pindle_end"
|
||||
# Eldritch
|
||||
ELDRITCH_START = "eldritch_start"
|
||||
ELDRITCH_SAVE_DIST = "eldritch_save_dist"
|
||||
ELDRITCH_END = "eldritch_end"
|
||||
# Shenk
|
||||
SHENK_START = "shenk_start"
|
||||
SHENK_SAVE_DIST = "shenk_save_dist"
|
||||
SHENK_END = "shenk_end"
|
||||
|
||||
|
||||
class Pather:
|
||||
@@ -35,7 +49,7 @@ class Pather:
|
||||
self._screen = screen
|
||||
self._template_finder = template_finder
|
||||
self._range_x = [-self._config.ui_pos["center_x"] + 10, self._config.ui_pos["center_x"] - 10]
|
||||
self._range_y = [-self._config.ui_pos["center_y"] + 10, self._config.ui_pos["center_y"] - self._config.ui_pos["skill_bar_height"] - 10]
|
||||
self._range_y = [-self._config.ui_pos["center_y"] + 10, self._config.ui_pos["center_y"] - self._config.ui_pos["skill_bar_height"] - 50]
|
||||
self._nodes = {
|
||||
# A5 town
|
||||
0: {"A5_TOWN_0": (110-70, 373), "A5_TOWN_1": (-68-70, -205)},
|
||||
@@ -52,8 +66,32 @@ class Pather:
|
||||
12: {"A5_TOWN_8": (-209, -294), "A5_TOWN_9": (-248, 130)},
|
||||
13: {"A5_TOWN_3": (262, 210)},
|
||||
14: {"A5_TOWN_3": (694, 282)},
|
||||
# Pindle
|
||||
100: {"PINDLE_0": (-146, -60), "PINDLE_1": (-19, 335), "PINDLE_2": (-549, 127)},
|
||||
101: {"PINDLE_1": (557, -68), "PINDLE_2": (27, -276), "PINDLE_3": (-185, 391)},
|
||||
102: {"PINDLE_3": (334, 132), "PINDLE_4": (142, 323)},
|
||||
103: {"PINDLE_3": (593, -113), "PINDLE_4": (401, 78)},
|
||||
104: {"PINDLE_4": (1076, -176), "PINDLE_3": (1264, -366), "PINDLE_5": (-280, 356), "PINDLE_6": (-700, 133)},
|
||||
# Eldritch
|
||||
120: {"ELDRITCH_0": (439, 236), "ELDRITCH_1": (-461, 314)},
|
||||
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)},
|
||||
# Shenk
|
||||
140: {"SHENK_0": (-224, -340), "SHENK_15": (120, 20), "SHENK_1": (667, -242)},
|
||||
141: {"SHENK_0": (-194, 66), "SHENK_15": (116, 440), "SHENK_1": (696, 161), "SHENK_2": (-251, -51)},
|
||||
142: {"SHENK_1": (876, 564), "SHENK_2": (-78, 352), "SHENK_3": (535, -194), "SHENK_4": (-665, -155)},
|
||||
143: {"SHENK_2": (212, 758),"SHENK_3": (823, 209), "SHENK_4": (-377, 248), "SHENK_6": (-508, -103)},
|
||||
144: {"SHENK_6": (-162, 185), "SHENK_7": (721, 226)},
|
||||
145: {"SHENK_12": (146, -200), "SHENK_7": (1204, 558), "SHENK_6": (314, 520), "SHENK_8": (-367, 27)},
|
||||
146: {"SHENK_12": (408, 166), "SHENK_9": (-497, -216), "SHENK_8": (-108, 387)},
|
||||
147: {"SHENK_9": (-100, 208), "SHENK_10": (-646, 100)},
|
||||
148: {"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), "SHENK_14": (510, 343)},
|
||||
|
||||
}
|
||||
self._paths = {
|
||||
# A5 Town
|
||||
(Location.A5_TOWN_START, Location.NIHLATHAK_PORTAL): [3, 4, 5, 6, 8, 9],
|
||||
(Location.A5_TOWN_START, Location.A5_STASH): [3, 4, 5],
|
||||
(Location.A5_TOWN_START, Location.A5_WP): [3, 4],
|
||||
@@ -65,49 +103,44 @@ class Pather:
|
||||
(Location.A5_STASH, Location.A5_WP): [],
|
||||
(Location.QUAL_KEHK, Location.NIHLATHAK_PORTAL): [12, 11, 10, 6, 8, 9],
|
||||
(Location.QUAL_KEHK, Location.A5_WP): [12, 11, 10, 6],
|
||||
# Pindle
|
||||
(Location.PINDLE_START, Location.PINDLE_SAVE_DIST): [100, 101, 102, 103],
|
||||
(Location.PINDLE_SAVE_DIST, Location.PINDLE_END): [104],
|
||||
# Eldritch
|
||||
(Location.ELDRITCH_START, Location.ELDRITCH_SAVE_DIST): [120, 121, 122],
|
||||
(Location.ELDRITCH_SAVE_DIST, Location.ELDRITCH_END): [123],
|
||||
# Shenk
|
||||
(Location.SHENK_START, Location.SHENK_SAVE_DIST): [140, 141, 142, 143, 144, 145, 146, 147, 148],
|
||||
(Location.SHENK_SAVE_DIST, Location.SHENK_END): [149],
|
||||
}
|
||||
self._fixed_tele_path = {
|
||||
# 0: path to boss, 1: location of boss
|
||||
"PINDLE": ([(1382, 53), (1685, 105), (1429, 122)], (1533, 327)),
|
||||
"PINDLE_END": ([(600, 40)], None),
|
||||
"ELDRITCH": ([(978, 95), (845, 109)], (1012, 42)),
|
||||
"SHENK": ([(798, 869), (1112, 882), (1220, 860), (1330, 869), (1502, 836), (1247, 887), (1258, 901), (1463, 814), (1351, 778)], [1815, 772])
|
||||
"PINDLE_END": ([(600, 40)], None), # to move away from items
|
||||
}
|
||||
|
||||
def get_fixed_path(self, key: str):
|
||||
return self._fixed_tele_path[key]
|
||||
|
||||
def _draw_debug(self, img: np.ndarray, node_pos_abs_list: List, ref_pos_abs_list: List):
|
||||
for node_pos_abs in node_pos_abs_list:
|
||||
pos_screen = self._screen.convert_abs_to_screen(node_pos_abs)
|
||||
cv2.circle(img, pos_screen, 10, (255, 0, 0), 10)
|
||||
for ref_pos_screen in ref_pos_abs_list:
|
||||
pos_screen = self._screen.convert_abs_to_screen(ref_pos_screen)
|
||||
cv2.circle(img, pos_screen, 10, (0, 255, 0), 10)
|
||||
img = cv2.resize(img, None, fx=0.5, fy=0.5)
|
||||
cv2.namedWindow("x")
|
||||
cv2.moveWindow("x", 2000, 30)
|
||||
cv2.imshow("x", img)
|
||||
cv2.waitKey(1)
|
||||
|
||||
def _display_all_nodes_debug(self):
|
||||
def _display_all_nodes_debug(self, filter: str = None):
|
||||
while 1:
|
||||
img = self._screen.grab()
|
||||
for node_idx in self._nodes:
|
||||
for template_type in self._nodes[node_idx]:
|
||||
success, ref_pos_screen = self._template_finder.search(template_type, img)
|
||||
if success:
|
||||
# Get reference position of template in abs coordinates
|
||||
ref_pos_abs = self._screen.convert_screen_to_abs(ref_pos_screen)
|
||||
# Calc the abs node position with the relative coordinates (relative to ref)
|
||||
node_pos_rel = self._nodes[node_idx][template_type]
|
||||
node_pos_abs = self._convert_rel_to_abs(node_pos_rel, ref_pos_abs)
|
||||
if self._check_abs_range_to_screen(node_pos_abs):
|
||||
if filter is None or filter in template_type:
|
||||
success, ref_pos_screen = self._template_finder.search(template_type, img)
|
||||
if success:
|
||||
# Get reference position of template in abs coordinates
|
||||
ref_pos_abs = self._screen.convert_screen_to_abs(ref_pos_screen)
|
||||
# Calc the abs node position with the relative coordinates (relative to ref)
|
||||
node_pos_rel = self._nodes[node_idx][template_type]
|
||||
node_pos_abs = self._convert_rel_to_abs(node_pos_rel, ref_pos_abs)
|
||||
node_pos_abs = self._adjust_abs_range_to_screen(node_pos_abs)
|
||||
x, y = self._screen.convert_abs_to_screen(node_pos_abs)
|
||||
cv2.circle(img, (x, y), 5, (255, 0, 0), 3)
|
||||
cv2.putText(img, str(node_idx), (x, y), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2, cv2.LINE_AA)
|
||||
x, y = self._screen.convert_abs_to_monitor(ref_pos_abs)
|
||||
cv2.circle(img, (x, y), 5, (0, 255, 0), 3)
|
||||
cv2.putText(img, template_type, (x, y), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2, cv2.LINE_AA)
|
||||
img = cv2.resize(img, None, fx=0.5, fy=0.5)
|
||||
cv2.imshow("debug", img)
|
||||
cv2.waitKey(1)
|
||||
@@ -116,7 +149,7 @@ 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: IChar):
|
||||
def traverse_nodes_fixed(self, key: str, char):
|
||||
path = self._fixed_tele_path[key][0]
|
||||
for pos in path:
|
||||
x_m, y_m = self._screen.convert_screen_to_monitor(pos)
|
||||
@@ -136,29 +169,44 @@ class Pather:
|
||||
f = min(f, abs(self._range_x[1] / float(abs_pos[0])))
|
||||
elif abs_pos[0] < self._range_x[0]:
|
||||
f = min(f, abs(self._range_x[0] / float(abs_pos[0])))
|
||||
# Check top y-range
|
||||
# Check y-range
|
||||
if abs_pos[1] > self._range_y[1]:
|
||||
f = min(f, abs(self._range_y[1] / float(abs_pos[1])))
|
||||
if abs_pos[1] < self._range_y[0]:
|
||||
f = min(f, abs(self._range_y[0] / float(abs_pos[1])))
|
||||
# check bottom y-range + globe roi which will also not allow a movement
|
||||
range_y_bottom = self._range_y[1]
|
||||
screen_pos = self._screen.convert_abs_to_screen(abs_pos)
|
||||
if is_in_roi(self._config.ui_roi["mana_globe"], screen_pos) or is_in_roi(self._config.ui_roi["health_globe"], screen_pos):
|
||||
# convert any of health or mana roi top coordinate to abs (x-coordinate is just a dummy 0 value)
|
||||
range_y_bottom = self._screen.convert_screen_to_abs((0, self._config.ui_roi["mana_globe"][1]))[1]
|
||||
if abs_pos[1] > range_y_bottom:
|
||||
f = min(f, abs(range_y_bottom / float(abs_pos[1])))
|
||||
# Scale the position by the factor f
|
||||
if f < 1.0:
|
||||
abs_pos = (int(abs_pos[0] * f), int(abs_pos[1] * f))
|
||||
# Check if adjusted position is "inside globe"
|
||||
screen_pos = self._screen.convert_abs_to_screen(abs_pos)
|
||||
if is_in_roi(self._config.ui_roi["mana_globe"], screen_pos) or is_in_roi(self._config.ui_roi["health_globe"], screen_pos):
|
||||
# convert any of health or mana roi top coordinate to abs (x-coordinate is just a dummy 0 value)
|
||||
new_range_y_bottom = self._screen.convert_screen_to_abs((0, self._config.ui_roi["mana_globe"][1]))[1]
|
||||
f = abs(new_range_y_bottom / float(abs_pos[1]))
|
||||
abs_pos = (int(abs_pos[0] * f), int(abs_pos[1] * f))
|
||||
return abs_pos
|
||||
|
||||
def traverse_nodes(self, start_location: Location, end_location: Location, char: IChar, debug: bool = False) -> bool:
|
||||
def find_abs_node_pos(self, node_idx: int, img: np.ndarray) -> Tuple[float, float]:
|
||||
node = self._nodes[node_idx]
|
||||
for template_type in node:
|
||||
success, ref_pos_screen = self._template_finder.search(template_type, img)
|
||||
if success:
|
||||
# Get reference position of template in abs coordinates
|
||||
ref_pos_abs = self._screen.convert_screen_to_abs(ref_pos_screen)
|
||||
# Calc the abs node position with the relative coordinates (relative to ref)
|
||||
node_pos_rel = node[template_type]
|
||||
node_pos_abs = self._convert_rel_to_abs(node_pos_rel, ref_pos_abs)
|
||||
node_pos_abs = self._adjust_abs_range_to_screen(node_pos_abs)
|
||||
return node_pos_abs
|
||||
return None
|
||||
|
||||
def traverse_nodes(self, start_location: Location, end_location: Location, char: IChar, time_out: float = 7) -> bool:
|
||||
"""
|
||||
Traverse from one location to another
|
||||
:param start_location: Location the char is starting at
|
||||
:param end_location: Location the char is supposed to end up
|
||||
:param char: Char that is traversing the nodes
|
||||
:param debug: Debug mode will display some images and stuff
|
||||
:param time_out: Timeout in second. If no more move was found in that time it will cancel traverse
|
||||
:return: Bool if traversed succesfull or False if it got stuck
|
||||
"""
|
||||
Logger.debug(f"Traverse from {start_location} to {end_location}")
|
||||
@@ -168,7 +216,7 @@ class Pather:
|
||||
last_move = time.time()
|
||||
while not continue_to_next_node:
|
||||
img = self._screen.grab()
|
||||
if (time.time() - last_move) > 7:
|
||||
if (time.time() - last_move) > time_out:
|
||||
success, _ = self._template_finder.search("WAYPOINT_MENU", img)
|
||||
if success:
|
||||
# sometimes bot opens waypoint menu, close it to find templates again
|
||||
@@ -179,33 +227,16 @@ class Pather:
|
||||
cv2.imwrite("info_pather_got_stuck.png", img)
|
||||
Logger.error("Got stuck exit pather")
|
||||
return False
|
||||
_debug_node_pos_abs_list = []
|
||||
_debug_ref_pos_abs_list = []
|
||||
node = self._nodes[node_idx]
|
||||
for template_type in node:
|
||||
success, ref_pos_screen = self._template_finder.search(template_type, img)
|
||||
if success:
|
||||
# Get reference position of template in abs coordinates
|
||||
ref_pos_abs = self._screen.convert_screen_to_abs(ref_pos_screen)
|
||||
_debug_ref_pos_abs_list.append(ref_pos_abs)
|
||||
# Calc the abs node position with the relative coordinates (relative to ref)
|
||||
node_pos_rel = node[template_type]
|
||||
node_pos_abs = self._convert_rel_to_abs(node_pos_rel, ref_pos_abs)
|
||||
_debug_node_pos_abs_list.append(node_pos_abs)
|
||||
if debug:
|
||||
self._draw_debug(img, _debug_node_pos_abs_list, _debug_ref_pos_abs_list)
|
||||
node_pos_abs = self._adjust_abs_range_to_screen(node_pos_abs)
|
||||
dist = math.dist(node_pos_abs, (0, 0))
|
||||
if dist < self._config.ui_pos["reached_node_dist"]:
|
||||
continue_to_next_node = True
|
||||
else:
|
||||
# Move the char
|
||||
x_m, y_m = self._screen.convert_abs_to_monitor(node_pos_abs)
|
||||
char.move((x_m, y_m))
|
||||
last_move = time.time()
|
||||
if self._config.char["slow_walk"]:
|
||||
wait(1.2, 1.4)
|
||||
break
|
||||
node_pos_abs = self.find_abs_node_pos(node_idx, img)
|
||||
if node_pos_abs is not None:
|
||||
dist = math.dist(node_pos_abs, (0, 0))
|
||||
if dist < self._config.ui_pos["reached_node_dist"]:
|
||||
continue_to_next_node = True
|
||||
else:
|
||||
# Move the char
|
||||
x_m, y_m = self._screen.convert_abs_to_monitor(node_pos_abs)
|
||||
char.move((x_m, y_m))
|
||||
last_move = time.time()
|
||||
return True
|
||||
|
||||
|
||||
@@ -222,7 +253,7 @@ if __name__ == "__main__":
|
||||
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)
|
||||
char = Sorceress(config.sorceress, config.char, screen, t_finder, ui_manager, pather)
|
||||
# pather.traverse_nodes_fixed("PINDLE", char)
|
||||
pather.traverse_nodes(Location.A5_TOWN_START, Location.MALAH, char, debug=True)
|
||||
# pather._display_all_nodes()
|
||||
# pather.traverse_nodes(Location.PINDLE_START, Location.PINDLE_END, char)
|
||||
pather._display_all_nodes_debug(filter="SHENK")
|
||||
|
||||
@@ -19,7 +19,7 @@ class TemplateFinder:
|
||||
:param screen: Screen object
|
||||
:param scale_factor: Scale factor that is used for templates. Note: UI and NPC templates will always have scale of 1.0
|
||||
"""
|
||||
self.debug_last_score = -1.0
|
||||
self.last_score = -1.0
|
||||
self._screen = screen
|
||||
self._scale_factor = scale_factor
|
||||
self._config = Config()
|
||||
@@ -36,10 +36,36 @@ class TemplateFinder:
|
||||
"A5_TOWN_7": [load_template("assets/templates/a5_town/a5_town_7.png", self._scale_factor), self._scale_factor],
|
||||
"A5_TOWN_8": [load_template("assets/templates/a5_town/a5_town_8.png", self._scale_factor), self._scale_factor],
|
||||
"A5_TOWN_9": [load_template("assets/templates/a5_town/a5_town_9.png", self._scale_factor), self._scale_factor],
|
||||
# Template Pindle area (currently only used to check if we are at pindle, the walking is done statically)
|
||||
"PINDLE_STONE": [load_template("assets/templates/pindle_stone.png", self._scale_factor), self._scale_factor],
|
||||
# Template Shenk area (currently only used to check if we are at shenk at wp, the walking is done statically)
|
||||
"SHENK_FLAME": [load_template("assets/templates/shenk_flame.png", self._scale_factor), self._scale_factor],
|
||||
# Templates for nod at Pindle
|
||||
"PINDLE_0": [load_template("assets/templates/pindle/pindle_0.png", self._scale_factor), self._scale_factor],
|
||||
"PINDLE_1": [load_template("assets/templates/pindle/pindle_1.png", self._scale_factor), self._scale_factor],
|
||||
"PINDLE_2": [load_template("assets/templates/pindle/pindle_2.png", self._scale_factor), self._scale_factor],
|
||||
"PINDLE_3": [load_template("assets/templates/pindle/pindle_3.png", self._scale_factor), self._scale_factor],
|
||||
"PINDLE_4": [load_template("assets/templates/pindle/pindle_4.png", self._scale_factor), self._scale_factor],
|
||||
"PINDLE_5": [load_template("assets/templates/pindle/pindle_5.png", self._scale_factor), self._scale_factor],
|
||||
"PINDLE_6": [load_template("assets/templates/pindle/pindle_6.png", self._scale_factor), self._scale_factor],
|
||||
# Templates for nodes to Eldritch
|
||||
"ELDRITCH_0": [load_template("assets/templates/eldritch/eldritch_0.png", self._scale_factor), self._scale_factor],
|
||||
"ELDRITCH_1": [load_template("assets/templates/eldritch/eldritch_1.png", self._scale_factor), self._scale_factor],
|
||||
"ELDRITCH_2": [load_template("assets/templates/eldritch/eldritch_2.png", self._scale_factor), self._scale_factor],
|
||||
"ELDRITCH_3": [load_template("assets/templates/eldritch/eldritch_3.png", self._scale_factor), self._scale_factor],
|
||||
"ELDRITCH_4": [load_template("assets/templates/eldritch/eldritch_4.png", self._scale_factor), self._scale_factor],
|
||||
# Templates for nodes to Shenk (from Eldritch)
|
||||
"SHENK_0": [load_template("assets/templates/shenk/shenk_0.png", self._scale_factor), self._scale_factor],
|
||||
"SHENK_1": [load_template("assets/templates/shenk/shenk_1.png", self._scale_factor), self._scale_factor],
|
||||
"SHENK_2": [load_template("assets/templates/shenk/shenk_2.png", self._scale_factor), self._scale_factor],
|
||||
"SHENK_3": [load_template("assets/templates/shenk/shenk_3.png", self._scale_factor), self._scale_factor],
|
||||
"SHENK_4": [load_template("assets/templates/shenk/shenk_4.png", self._scale_factor), self._scale_factor],
|
||||
"SHENK_6": [load_template("assets/templates/shenk/shenk_6.png", self._scale_factor), self._scale_factor],
|
||||
"SHENK_7": [load_template("assets/templates/shenk/shenk_7.png", self._scale_factor), self._scale_factor],
|
||||
"SHENK_8": [load_template("assets/templates/shenk/shenk_8.png", self._scale_factor), self._scale_factor],
|
||||
"SHENK_9": [load_template("assets/templates/shenk/shenk_9.png", self._scale_factor), self._scale_factor],
|
||||
"SHENK_10": [load_template("assets/templates/shenk/shenk_10.png", self._scale_factor), self._scale_factor],
|
||||
"SHENK_11": [load_template("assets/templates/shenk/shenk_11.png", self._scale_factor), self._scale_factor],
|
||||
"SHENK_12": [load_template("assets/templates/shenk/shenk_12.png", self._scale_factor), self._scale_factor],
|
||||
"SHENK_13": [load_template("assets/templates/shenk/shenk_13.png", self._scale_factor), self._scale_factor],
|
||||
"SHENK_14": [load_template("assets/templates/shenk/shenk_14.png", self._scale_factor), self._scale_factor],
|
||||
"SHENK_15": [load_template("assets/templates/shenk/shenk_15.png", self._scale_factor), self._scale_factor],
|
||||
# Template Selectables
|
||||
"A5_STASH": [load_template("assets/templates/a5_stash.png", self._scale_factor), self._scale_factor],
|
||||
"A5_WP": [load_template("assets/templates/a5_wp.png", self._scale_factor), self._scale_factor],
|
||||
@@ -116,7 +142,7 @@ class TemplateFinder:
|
||||
if img.shape[0] > template.shape[0] and img.shape[1] > template.shape[1]:
|
||||
res = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED)
|
||||
_, max_val, _, max_pos = cv2.minMaxLoc(res)
|
||||
self.debug_last_score = max_val
|
||||
self.last_score = max_val
|
||||
if max_val > threshold:
|
||||
ref_point = (max_pos[0] + int(template.shape[1] * 0.5) + rx, max_pos[1] + int(template.shape[0] * 0.5) + ry)
|
||||
ref_point = (int(ref_point[0] * (1.0 / scale)), int(ref_point[1] * (1.0 / scale)))
|
||||
@@ -153,7 +179,7 @@ if __name__ == "__main__":
|
||||
while 1:
|
||||
img = screen.grab()
|
||||
success, pos = template_finder.search("BLUE_PORTAL", img, threshold=0.67)
|
||||
print(template_finder.debug_last_score)
|
||||
print(template_finder.last_score)
|
||||
if success:
|
||||
cv2.circle(img, pos, 7, (255, 0, 0), thickness=5)
|
||||
img = cv2.resize(img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_NEAREST)
|
||||
|
||||
@@ -151,46 +151,41 @@ class UiManager():
|
||||
Starting a game in hell mode. Will wait and retry on server connection issue.
|
||||
:return: Bool if action was successful
|
||||
"""
|
||||
while self._template_finder.search("PLAY_BTN_GRAY", self._screen.grab(), roi=self._config.ui_roi["play_btn"], threshold=0.95)[0]:
|
||||
time.sleep(2)
|
||||
|
||||
Logger.debug(f"Searching for Play Btn...")
|
||||
found, pos = self._template_finder.search_and_wait("PLAY_BTN", roi=self._config.ui_roi["play_btn"], time_out=8)
|
||||
if not found:
|
||||
return False
|
||||
# sanity x, y check and determine if offline or online
|
||||
x_range_offline = [self._config.ui_pos["play_x_offline"] - 50, self._config.ui_pos["play_x_offline"] + 50]
|
||||
x_range_online = [self._config.ui_pos["play_x_online"] - 50, self._config.ui_pos["play_x_online"] + 50]
|
||||
y_range = [self._config.ui_pos["play_y"] - 50, self._config.ui_pos["play_y"] + 50]
|
||||
in_offline_range = x_range_offline[0] < pos[0] < x_range_offline[1]
|
||||
in_online_range = x_range_online[0] < pos[0] < x_range_online[1]
|
||||
mode_info = "online mode" if in_online_range else "offline mode"
|
||||
if (in_offline_range or in_online_range) and y_range[0] < pos[1] < y_range[1]:
|
||||
pos = [pos[0], self._config.ui_pos["play_y"]]
|
||||
x, y = self._screen.convert_screen_to_monitor(pos)
|
||||
Logger.debug(f"Found Play Btn ({mode_info}) -> clicking it")
|
||||
if mode_info == "online mode":
|
||||
Logger.warning("You are creating a game in online mode!")
|
||||
custom_mouse.move(x, y, duration=(random.random() * 0.2 + 0.5), randomize=5)
|
||||
mouse.click(button="left")
|
||||
else:
|
||||
Logger.debug("Sanity position check on play btn failed")
|
||||
return False
|
||||
while 1:
|
||||
img = self._screen.grab()
|
||||
# search offline btn
|
||||
found_off, _ = self._template_finder.search("PLAY_BTN", img, roi=self._config.ui_roi["play_btn_offline"], threshold=0.8)
|
||||
# search online btn with enabled and disabled version
|
||||
found_on, _ = self._template_finder.search("PLAY_BTN", img, roi=self._config.ui_roi["play_btn_online"], threshold=0.8)
|
||||
score_enabled = self._template_finder.last_score
|
||||
self._template_finder.search("PLAY_BTN_GRAY", img, roi=self._config.ui_roi["play_btn_online"], threshold=0.8)
|
||||
score_disabled = self._template_finder.last_score
|
||||
found_on = found_on and score_enabled > score_disabled
|
||||
if found_off or found_on:
|
||||
x_s = self._config.ui_pos["play_x_offline"] if found_off else self._config.ui_pos["play_x_online"]
|
||||
pos = [x_s, self._config.ui_pos["play_y"]]
|
||||
x, y = self._screen.convert_screen_to_monitor(pos)
|
||||
mode_info = "offline" if found_off else "online"
|
||||
Logger.debug(f"Found Play Btn ({mode_info}) -> clicking it")
|
||||
if mode_info == "online":
|
||||
Logger.warning("You are creating a game in online mode!")
|
||||
custom_mouse.move(x, y, duration=(random.random() * 0.2 + 0.4), randomize=5)
|
||||
mouse.click(button="left")
|
||||
break
|
||||
time.sleep(3.0)
|
||||
|
||||
Logger.debug("Searching for Hell Btn...")
|
||||
found, pos = self._template_finder.search_and_wait("HELL_BTN", roi=self._config.ui_roi["hell_btn"], time_out=8)
|
||||
if not found:
|
||||
return False
|
||||
# sanity x y check. Note: not checking y range as it often detects nightmare button as hell btn, not sure why
|
||||
x_range = [self._config.ui_pos["hell_x"] - 50, self._config.ui_pos["hell_x"] + 50]
|
||||
if x_range[0] < pos[0] < x_range[1]:
|
||||
while 1:
|
||||
found, pos = self._template_finder.search_and_wait("HELL_BTN", roi=self._config.ui_roi["hell_btn"], time_out=8)
|
||||
if not found:
|
||||
Logger.debug("Could not find hell btn, try from start again")
|
||||
return self.start_hell_game()
|
||||
# sanity x y check. Note: not checking y range as it often detects nightmare button as hell btn, not sure why
|
||||
x, y = self._screen.convert_screen_to_monitor((self._config.ui_pos["hell_x"], self._config.ui_pos["hell_y"]))
|
||||
Logger.debug("Found Hell Btn -> clicking it")
|
||||
custom_mouse.move(x, y, duration=(random.random() * 0.2 + 1.0), randomize=5)
|
||||
custom_mouse.move(x, y, duration=(random.random() * 0.1 + 0.4), randomize=5)
|
||||
mouse.click(button="left")
|
||||
else:
|
||||
Logger.debug("Sanity position check on hell btn failed")
|
||||
return False
|
||||
break
|
||||
|
||||
# check for server issue
|
||||
wait(2.0)
|
||||
@@ -198,7 +193,7 @@ class UiManager():
|
||||
if server_issue:
|
||||
Logger.warning("Server connection issue. waiting 20s")
|
||||
x, y = self._screen.convert_screen_to_monitor((self._config.ui_pos["issue_occured_ok_x"], self._config.ui_pos["issue_occured_ok_y"]))
|
||||
custom_mouse.move(x, y, duration=(random.random() * 0.4 + 1.0), randomize=5)
|
||||
custom_mouse.move(x, y, duration=(random.random() * 0.1 + 0.4), randomize=5)
|
||||
mouse.click(button="left")
|
||||
wait(1, 2)
|
||||
keyboard.send("esc")
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = '0.1.1'
|
||||
__version__ = '0.1.3-rc0'
|
||||
|
||||
@@ -85,8 +85,9 @@ reached_node_dist=150
|
||||
; all rois are in [left, top, width, height] format
|
||||
is_overburdened=17,765,470,90
|
||||
save_and_exit=750,350,410,260
|
||||
play_btn=390,760,1150,300
|
||||
hell_btn=550,100,1000,800
|
||||
play_btn_online=640,920,320,100
|
||||
play_btn_offline=800,940,320,100
|
||||
hell_btn=840,550,245,70
|
||||
gold_btn=1390,736,400,150
|
||||
health_globe=260,890,320,190
|
||||
mana_globe=1350,890,320,190
|
||||
@@ -94,4 +95,4 @@ cut_skill_bar=0,0,1920,980
|
||||
merc_icon=0,0,150,150
|
||||
loading_left_black=0,0,500,1080
|
||||
death=770,290,380,75
|
||||
tp_search=530,180,820,600
|
||||
tp_search=530,180,820,600
|
||||
|
||||