Compare commits

..
25 Commits
Author SHA1 Message Date
aeon0 54af636d8f Doc formating 2021-11-07 21:50:19 +01:00
aeon0 41c05cc614 fix(path): Add another template to avoid getting stuck at shenk 2021-11-07 21:33:11 +01:00
aeon0 183bae8583 feat(release): Create empty custom.ini for each release 2021-11-07 21:25:25 +01:00
aeon0 6f190c8065 docs(md): Update docs 2021-11-07 21:22:18 +01:00
aeon0 6260a0ce30 fix(pickit): Workaround for pickup with flames, do shenk pickit twice 2021-11-07 21:07:53 +01:00
aeon0 35df3bae7b fix(move): Do not slow walk on teleport 2021-11-07 21:06:11 +01:00
aeon0 26c5ee9de5 add another template to avoid getting stuck 2021-11-07 21:04:53 +01:00
aeon0 1cfde7f560 fix(hammerdin): Recast in set intervall to come back from interrupt 2021-11-07 21:04:38 +01:00
aeon0 282f7a5606 fix(char): Remove useless import that might lead to issues in exe 2021-11-07 20:49:28 +01:00
aeon0 1a148afcfb Bump version to 0.1.3-rc0 2021-11-07 16:02:46 +01:00
aeon0 a91a91dc7c feat(path): Ref pathing shenk 2021-11-07 15:56:41 +01:00
aeon0 648d89ff28 feat(path): Refpathing to Eldritch 2021-11-07 13:52:18 +01:00
aeon0 489cc4ec36 feat(path): Refpoint pathing for pindle 2021-11-07 12:43:35 +01:00
aeon0 fddd72c3a5 Merge branch 'master' of https://github.com/aeon0/botty 2021-11-07 10:58:58 +01:00
aeon0 5abe2fb33e fix(death): Make sure keys are released when killing main bot thread 2021-11-07 10:57:20 +01:00
aeon0 df12dbd57c update docs 2021-11-07 01:39:37 +01:00
aeon0 9a3b054248 Merge pull request #3 from jagarop/master
add zstandard
2021-11-07 01:38:07 +01:00
aeon0 26246f141b Merge branch 'master' into master 2021-11-07 01:37:53 +01:00
aeon0 8d1f84062d doc(dev): Add note about user being a variable 2021-11-07 00:49:48 +01:00
aeon0 a7d31775f1 fix(start): Robustify game start with new disabled play button 2021-11-07 00:40:33 +01:00
jagarop 7c52b05d10 Update development.md 2021-11-06 20:09:59 -03:00
jagarop a7d7a96268 add zstandard 2021-11-06 20:06:29 -03:00
aeon0 8e48dfe3d0 Bump version to v0.1.3 2021-11-06 16:47:22 +01:00
aeon0 c89b35ff33 fix(ui): Game creation 2021-11-06 16:46:54 +01:00
aeon0 0d6ab59277 fix(bot): Reset of runs with specific shenk eldritch setup 2021-11-06 16:25:54 +01:00
65 changed files with 286 additions and 210 deletions
+7 -7
View File
@@ -6,14 +6,14 @@ Join the [**Discord Channel**](https://discord.gg/CnkfsjqN) for help and discuss
[![Watch the video](assets/docs/video_thumbnail.png)](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).
Binary file not shown.

After

Width:  |  Height:  |  Size: 930 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 910 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 919 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 865 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 884 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 899 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 890 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 935 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 933 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 921 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 909 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 940 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 954 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 949 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 907 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 885 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 921 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

+9 -2
View File
@@ -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"/>
+1
View File
@@ -14,3 +14,4 @@ dependencies:
- keyboard
- graphviz
- beautifultable
- zstandard
+3
View File
@@ -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}/")
+22 -13
View File
@@ -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")
+22 -24
View File
@@ -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)
+8 -8
View File
@@ -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
+38 -39
View File
@@ -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__":
+3
View File
@@ -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]:
+3
View File
@@ -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
+101 -70
View File
@@ -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")
+33 -7
View File
@@ -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)
+31 -36
View File
@@ -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
View File
@@ -1 +1 @@
__version__ = '0.1.1'
__version__ = '0.1.3-rc0'
+4 -3
View File
@@ -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