Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01bdc022ec | ||
|
|
06b0d4fb27 | ||
|
|
c751259319 | ||
|
|
b1113b8f5a | ||
|
|
efced03d83 | ||
|
|
e69a197135 | ||
|
|
90869ebfbb | ||
|
|
b9ccc733df | ||
|
|
0635497a47 | ||
|
|
8279332579 | ||
|
|
21dc086ebd | ||
|
|
b80888aca6 | ||
|
|
d59fbc0cd3 | ||
|
|
652823189c | ||
|
|
1f1437ce1d |
@@ -12,3 +12,4 @@ run.dist/
|
||||
run.onefile-build/
|
||||
generated/
|
||||
botty_v*/
|
||||
custom.ini
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# <img src="assets/docs/header_green.png" width="370">
|
||||
|
||||
Simple Pixelbot for Diablo 2 Resurrected written in python and opencv. Obviously only use it in offline mode as it is against the TOS of Blizzard to use it in online mode!
|
||||
Simple Pixelbot for Diablo 2 Resurrected written in python and opencv. Obviously only use it in offline mode as it is against the TOS of Blizzard to use it in online mode! [**Download here**](https://github.com/aeon0/botty/releases)
|
||||
|
||||
Join the [**Discord Channel**](https://discord.gg/CnkfsjqN) for help and discussions.
|
||||
|
||||
@@ -28,6 +28,8 @@ All settings will automatically be set when you execute `run.exe` and press the
|
||||
- Item Label Display: Hold
|
||||
- Large Font Mode: on
|
||||
|
||||
**Note**: There have been issues reported with image sharpening being truned on via the graphic card settings itself outside of D2R. Try turning it off when running the bot.
|
||||
|
||||
### 2) Supported builds
|
||||
#### Soceress
|
||||
You can put any skills on left and right attack and see if it works out. E.g. Glacial Spike on left attack and Blizzard or right attack.
|
||||
@@ -45,6 +47,17 @@ Download the a prebuilt release [here](https://github.com/aeon0/botty/releases).
|
||||
Check out the [development.md](development.md) docu for infos on how to build from source and details of the project structure and code.
|
||||
|
||||
## param.ini
|
||||
To ease the switch to new botty versions, you can also overwrite any of the param.ini fields in a __custom.ini__ file. When a new version of botty is released you just copy the file to the new version without having to port all your __param.ini__ changes to the new version. Example:
|
||||
```ini
|
||||
; custom.ini - overwrites 3 params in the param.ini
|
||||
[general]
|
||||
monitor=1
|
||||
|
||||
[routes]
|
||||
run_pindle=1
|
||||
run_shenk=0
|
||||
```
|
||||
|
||||
[general] | Descriptions
|
||||
--------------------------------|---------------------------------------------
|
||||
monitor | Select on which monitor D2R is running in case multiple are available
|
||||
@@ -62,6 +75,7 @@ run_shenk | Run shenk in each new game. Select "1" to run it "0" to leave it out
|
||||
--------------------------------|---------------------------------------------
|
||||
type | Build type. Currently only "sorceress" or "hammerdin" is supported
|
||||
casting_frames | Depending on your char and fcr you will have a specific casting frame count. Check it here: https://diablo2.diablowiki.net/Breakpoints and fill in the right number. Determines how much delay there is after each teleport for example.
|
||||
slow_walk | With this set to 1 the char will have a large delay for each running action in town. Set this to 1 if you keep getting stuck during traversing town.
|
||||
num_loot_columns | Number of columns in inventory used for loot (from left!). Remaining space can be used for charms
|
||||
take_health_potion | Health percentage when healing potion will be used
|
||||
take_mana_potion | Mana percentage when mana potion will be used. Currently belt managment is not very clever and it is safest to only pick up health pots and make sure mana reg is enough for pindle to not need mana pots.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
+4
-2
@@ -1,7 +1,7 @@
|
||||
# Dev Docu
|
||||
|
||||
## Dependencies
|
||||
- Install miniconda (https://docs.conda.io/en/latest/miniconda.html)
|
||||
- Install miniconda (https://docs.conda.io/en/latest/miniconda.html). Note: You will have to check "Add conda to my PATH environment variable" in order to access the conda command in the cmd.
|
||||
- Install git (https://gitforwindows.org/)
|
||||
|
||||
## Getting started
|
||||
@@ -9,16 +9,18 @@
|
||||
git clone https://github.com/aeon0/botty.git
|
||||
cd botty
|
||||
conda env create environment.yml
|
||||
conda activate botty
|
||||
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
|
||||
Edit System environment variables -> Enfironment Vriables... -> PYTHONPATH -> C:\Users\\$USER\miniconda3\envs\botty\lib\site-packages\cv2
|
||||
```python
|
||||
# building .exe and bundeling all needed resource into one folder
|
||||
python release.py
|
||||
```
|
||||
For changelog run: `git log <PREVIOUS_TAG>..HEAD --oneline --decorate`
|
||||
|
||||
## Folder Structure
|
||||
**/**</br>
|
||||
|
||||
@@ -8,11 +8,13 @@ color_checker_key=f10
|
||||
|
||||
[routes]
|
||||
run_pindle=1
|
||||
run_eldritch=1
|
||||
run_shenk=1
|
||||
|
||||
[char]
|
||||
type=hammerdin
|
||||
casting_frames=11
|
||||
slow_walk=0
|
||||
num_loot_columns=6
|
||||
take_health_potion=0.8
|
||||
take_mana_potion=0.6
|
||||
|
||||
+25
-24
@@ -34,16 +34,19 @@ 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._item_finder, self._ui_manager)
|
||||
self._char: IChar = Sorceress(self._config.sorceress, self._config.char, self._screen, self._template_finder, self._ui_manager)
|
||||
elif self._config.char["type"] == "hammerdin":
|
||||
self._char: IChar = Hammerdin(self._config.hammerdin, self._config.char, self._screen, self._template_finder, self._item_finder, self._ui_manager)
|
||||
self._char: IChar = Hammerdin(self._config.hammerdin, self._config.char, self._screen, self._template_finder, self._ui_manager)
|
||||
else:
|
||||
Logger.error(f'{self._config.char["type"]} is not supported! Closing down bot.')
|
||||
os._exit(1)
|
||||
self._route_config = self._config.routes
|
||||
if self._route_config["run_shenk"] and not self._route_config["run_eldritch"]:
|
||||
Logger.error("Running shenk without eldtritch is not supported. Either run none, both or eldritch only.")
|
||||
os._exit(1)
|
||||
self._do_runs = {
|
||||
"run_pindle": self._route_config["run_pindle"],
|
||||
"run_shenk": self._route_config["run_shenk"]
|
||||
"run_shenk": self._route_config["run_shenk"] or self._route_config["run_eldritch"]
|
||||
}
|
||||
self._picked_up_items = False
|
||||
self._tp_is_up = False
|
||||
@@ -89,9 +92,9 @@ class Bot:
|
||||
wait(delay, delay + 5.0)
|
||||
Logger.info("Start new game")
|
||||
self._timer = time.time()
|
||||
found, _ = self._template_finder.search_and_wait("D2_LOGO_HS", time_out=100)
|
||||
found, _ = self._template_finder.search_and_wait("D2_LOGO_HS", time_out=70)
|
||||
if not found:
|
||||
Logger.error("Something went wrong here, probably died. Exit game and closing down bot.")
|
||||
Logger.error("Something went wrong here, bot is unsure about current location. Exit game and closing down bot.")
|
||||
self._ui_manager.save_and_exit()
|
||||
os._exit(1)
|
||||
self._ui_manager.start_hell_game()
|
||||
@@ -117,14 +120,12 @@ class Bot:
|
||||
# TODO: If tp is up we always go back into the portal...
|
||||
if not self._tp_is_up and (self._health_manager.get_health(img) < 0.6 or self._health_manager.get_mana(img) < 0.3):
|
||||
Logger.info("Need some healing first. Go talk to Malah")
|
||||
success = self._pather.traverse_nodes(self._curr_location, Location.MALAH, self._char)
|
||||
if not success:
|
||||
if not self._pather.traverse_nodes(self._curr_location, Location.MALAH, self._char):
|
||||
self.trigger("end_game")
|
||||
return
|
||||
self._curr_location = Location.MALAH
|
||||
self._npc_manager.open_npc_menu(Npc.MALAH)
|
||||
success = self._pather.traverse_nodes(self._curr_location, Location.A5_TOWN_START, self._char)
|
||||
if not success:
|
||||
if not self._pather.traverse_nodes(self._curr_location, Location.A5_TOWN_START, self._char):
|
||||
self.trigger("end_game")
|
||||
return
|
||||
self._curr_location = Location.A5_TOWN_START
|
||||
@@ -132,15 +133,13 @@ class Bot:
|
||||
# Stash stuff
|
||||
if self._picked_up_items:
|
||||
Logger.info("Stashing picked up items")
|
||||
success = self._pather.traverse_nodes(self._curr_location, Location.A5_STASH, self._char)
|
||||
if not success:
|
||||
if not self._pather.traverse_nodes(self._curr_location, Location.A5_STASH, self._char):
|
||||
self.trigger("end_game")
|
||||
return
|
||||
self._curr_location = Location.A5_STASH
|
||||
time.sleep(1.5)
|
||||
# sometimes waypoint is opened and stash not found because of that, check for that
|
||||
found, _ = self._template_finder.search("WAYPOINT_MENU", self._screen.grab())
|
||||
if found:
|
||||
if self._template_finder.search("WAYPOINT_MENU", self._screen.grab())[0]:
|
||||
keyboard.send("esc")
|
||||
if self._char.select_by_template("A5_STASH"):
|
||||
self._ui_manager.stash_all_items(self._config.char["num_loot_columns"])
|
||||
@@ -153,13 +152,12 @@ class Bot:
|
||||
merc_alive, _ = self._template_finder.search("MERC", self._screen.grab(), threshold=0.9, roi=[0, 0, 200, 200])
|
||||
if not merc_alive:
|
||||
Logger.info("Reviveing merc")
|
||||
success = self._pather.traverse_nodes(self._curr_location, Location.QUAL_KEHK, self._char)
|
||||
if not success:
|
||||
if not self._pather.traverse_nodes(self._curr_location, Location.QUAL_KEHK, self._char):
|
||||
self.trigger("end_game")
|
||||
return
|
||||
self._curr_location = Location.QUAL_KEHK
|
||||
self._npc_manager.open_npc_menu(Npc.QUAL_KEHK)
|
||||
self._npc_manager.press_npc_btn(Npc.QUAL_KEHK, "resurrect")
|
||||
if self._npc_manager.open_npc_menu(Npc.QUAL_KEHK):
|
||||
self._npc_manager.press_npc_btn(Npc.QUAL_KEHK, "resurrect")
|
||||
time.sleep(2)
|
||||
|
||||
# Start a new run
|
||||
@@ -205,7 +203,7 @@ 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=15)[0]
|
||||
self.success &= bot._template_finder.search_and_wait("PINDLE_STONE", time_out=20)[0]
|
||||
if not self.success:
|
||||
return
|
||||
bot._char.pre_buff()
|
||||
@@ -235,7 +233,9 @@ class Bot:
|
||||
bot._char.select_by_template("A5_WP")
|
||||
wait(1.0)
|
||||
bot._ui_manager.use_wp(4, 1)
|
||||
bot._template_finder.search_and_wait("SHENK_FLAME")
|
||||
self.success = bot._template_finder.search_and_wait("SHENK_FLAME", time_out=20)[0]
|
||||
if not self.success:
|
||||
return
|
||||
bot._char.pre_buff()
|
||||
# eldritch
|
||||
bot._pather.traverse_nodes_fixed("ELDRITCH", bot._char)
|
||||
@@ -243,11 +243,12 @@ class Bot:
|
||||
wait(0.5)
|
||||
bot._picked_up_items = bot._pickit.pick_up_items(bot._char)
|
||||
# shenk
|
||||
bot._pather.traverse_nodes_fixed("SHENK", bot._char)
|
||||
wait(0.15, 0.2)
|
||||
bot._char.kill_shenk(bot._pather.get_fixed_path("SHENK")[1])
|
||||
wait(0.5)
|
||||
bot._picked_up_items |= bot._pickit.pick_up_items(bot._char)
|
||||
if bot._route_config["run_shenk"]:
|
||||
bot._pather.traverse_nodes_fixed("SHENK", bot._char)
|
||||
wait(0.15, 0.2)
|
||||
bot._char.kill_shenk(bot._pather.get_fixed_path("SHENK")[1])
|
||||
wait(0.5)
|
||||
bot._picked_up_items |= bot._pickit.pick_up_items(bot._char)
|
||||
self.success = True
|
||||
return
|
||||
run = RunShenk()
|
||||
|
||||
@@ -11,12 +11,13 @@ from screen import Screen
|
||||
from utils.misc import wait
|
||||
import random
|
||||
import time
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
class Hammerdin(IChar):
|
||||
def __init__(self, skill_hotkeys, char_config, screen: Screen, template_finder: TemplateFinder, item_finder: ItemFinder, ui_manager: UiManager):
|
||||
def __init__(self, skill_hotkeys, char_config, screen: Screen, template_finder: TemplateFinder, ui_manager: UiManager):
|
||||
Logger.info("Setting up Hammerdin")
|
||||
super().__init__(skill_hotkeys, char_config, screen, template_finder, item_finder, ui_manager)
|
||||
super().__init__(skill_hotkeys, char_config, screen, template_finder, ui_manager)
|
||||
|
||||
def pre_buff(self):
|
||||
keyboard.send(self._skill_hotkeys["holy_shield"])
|
||||
@@ -37,7 +38,7 @@ class Hammerdin(IChar):
|
||||
keyboard.send(self._char_config["weapon_switch"])
|
||||
wait(0.25, 0.3)
|
||||
|
||||
def _cast_hammers(self, time_in_s):
|
||||
def _cast_hammers(self, time_in_s: float):
|
||||
keyboard.send(self._char_config["stand_still"], do_release=False)
|
||||
wait(0.05)
|
||||
keyboard.send(self._skill_hotkeys["blessed_hammer"])
|
||||
@@ -55,7 +56,7 @@ class Hammerdin(IChar):
|
||||
keyboard.send(self._skill_hotkeys["redemption"])
|
||||
wait(1.5, 2.0)
|
||||
|
||||
def kill_pindle(self, pindle_pos_screen):
|
||||
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))
|
||||
@@ -65,7 +66,7 @@ class Hammerdin(IChar):
|
||||
wait(0.1, 0.15)
|
||||
self._do_redemption()
|
||||
|
||||
def kill_shenk(self, shenk_pos_screen):
|
||||
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)
|
||||
@@ -77,7 +78,7 @@ class Hammerdin(IChar):
|
||||
wait(0.1, 0.15)
|
||||
self._do_redemption()
|
||||
|
||||
def kill_eldritch(self, eldritch_pos_screen):
|
||||
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))
|
||||
|
||||
+13
-16
@@ -12,6 +12,8 @@ import math
|
||||
import keyboard
|
||||
from logger import Logger
|
||||
import time
|
||||
from typing import Dict, Tuple
|
||||
from config import Config
|
||||
|
||||
|
||||
def abstract(f):
|
||||
@@ -20,27 +22,26 @@ def abstract(f):
|
||||
return _decorator
|
||||
|
||||
class IChar:
|
||||
def __init__(self, skill_hotkeys, char_config, screen: Screen, template_finder: TemplateFinder, item_finder: ItemFinder, ui_manager: UiManager):
|
||||
def __init__(self, skill_hotkeys: Dict, char_config: Dict, screen: Screen, template_finder: TemplateFinder, ui_manager: UiManager):
|
||||
self._skill_hotkeys = skill_hotkeys
|
||||
self._char_config = char_config
|
||||
self._template_finder = template_finder
|
||||
self._item_finder = item_finder
|
||||
self._ui_manager = ui_manager
|
||||
self._screen = screen
|
||||
self._config = Config()
|
||||
|
||||
def select_by_template(self, template_type: str) -> bool:
|
||||
Logger.debug(f"Select {template_type}")
|
||||
success, screen_loc = self._template_finder.search_and_wait(template_type, time_out=10)
|
||||
if success:
|
||||
x_m, y_m = self._screen.convert_screen_to_monitor(screen_loc)
|
||||
# TODO: check if telekinse can be used
|
||||
custom_mouse.move(x_m, y_m, duration=(random.random() * 0.1 + 0.2))
|
||||
wait(0.1, 0.2)
|
||||
custom_mouse.move(x_m, y_m, duration=0.3)
|
||||
wait(0.3, 0.4)
|
||||
mouse.click(button="left")
|
||||
return True
|
||||
return False
|
||||
|
||||
def move(self, pos_monitor):
|
||||
def move(self, pos_monitor: Tuple[float, float]):
|
||||
if not self._ui_manager.is_teleport_selected():
|
||||
keyboard.send(self._skill_hotkeys["teleport"])
|
||||
wait(0.1, 0.2)
|
||||
@@ -54,7 +55,7 @@ class IChar:
|
||||
pos_screen = self._screen.convert_monitor_to_screen(pos_monitor)
|
||||
pos_abs = self._screen.convert_screen_to_abs(pos_screen)
|
||||
dist = math.dist(pos_abs, (0, 0))
|
||||
adjust_factor = (dist - 60) / dist
|
||||
adjust_factor = (dist - 50) / dist
|
||||
pos_abs = [int(pos_abs[0] * adjust_factor), int(pos_abs[1] * adjust_factor)]
|
||||
x, y = self._screen.convert_abs_to_monitor(pos_abs)
|
||||
custom_mouse.move(x, y, duration=(random.random() * 0.03 + 0.05))
|
||||
@@ -68,8 +69,7 @@ class IChar:
|
||||
mouse.click(button="right")
|
||||
custom_mouse.move(1400 + random.randrange(0, 60), 300 + random.randrange(0, 60), duration=0.1)
|
||||
time.sleep(1.4) # takes quite a while for tp to be visible
|
||||
# TODO: 1920x1080 specific params
|
||||
roi=[530, 180, 820, 600]
|
||||
roi = self._config.ui_roi["tp_search"]
|
||||
start = time.time()
|
||||
while (time.time() - start) < 7:
|
||||
img = self._screen.grab()
|
||||
@@ -81,14 +81,11 @@ class IChar:
|
||||
x, y = self._screen.convert_screen_to_monitor(pos)
|
||||
# Note: Template is top of portal, thus move the y-position a bit to the bottom
|
||||
# Also move a bit left and right to get rid of possibly highlight other things such as items
|
||||
custom_mouse.move(x - 20 * random.random() * 3, y + random.random() * 5, duration=0.1)
|
||||
custom_mouse.move(x + 20 * random.random() * 3, y + random.random() * 5, duration=0.02)
|
||||
custom_mouse.move(x + random.random() * 3, y + random.random() * 5, duration=0.02)
|
||||
wait(0.09, 0.11)
|
||||
custom_mouse.move(x - 20, y, duration=0.13, randomize=5)
|
||||
custom_mouse.move(x + 20, y, duration=0.05, randomize=5)
|
||||
custom_mouse.move(x, y, duration=0.05, randomize=5)
|
||||
wait(0.1, 0.14)
|
||||
mouse.click(button="left")
|
||||
# # we sometimes pick up items instead... just to be safe
|
||||
# wait(0.09, 0.11)
|
||||
# mouse.click(button="left")
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@@ -10,12 +10,13 @@ from logger import Logger
|
||||
from screen import Screen
|
||||
from utils.misc import wait
|
||||
import random
|
||||
from typing import Tuple
|
||||
|
||||
|
||||
class Sorceress(IChar):
|
||||
def __init__(self, skill_hotkeys, char_config, screen: Screen, template_finder: TemplateFinder, item_finder: ItemFinder, ui_manager: UiManager):
|
||||
def __init__(self, skill_hotkeys, char_config, screen: Screen, template_finder: TemplateFinder, ui_manager: UiManager):
|
||||
Logger.info("Setting up Sorceress")
|
||||
super().__init__(skill_hotkeys, char_config, screen, template_finder, item_finder, ui_manager)
|
||||
super().__init__(skill_hotkeys, char_config, screen, template_finder, ui_manager)
|
||||
|
||||
def pre_buff(self):
|
||||
keyboard.send(self._skill_hotkeys["frozen_armor"])
|
||||
@@ -36,7 +37,7 @@ class Sorceress(IChar):
|
||||
keyboard.send(self._char_config["weapon_switch"])
|
||||
wait(0.25, 0.3)
|
||||
|
||||
def _left_attack(self, cast_pos, delay, spray = 10):
|
||||
def _left_attack(self, cast_pos: Tuple[float, float], delay: float, spray: int = 10):
|
||||
keyboard.send(self._char_config["stand_still"], do_release=False)
|
||||
custom_mouse.move(cast_pos[0], cast_pos[1], duration=(random.random() * 0.05 + 0.15))
|
||||
keyboard.send(self._skill_hotkeys["skill_left"])
|
||||
@@ -48,7 +49,7 @@ class Sorceress(IChar):
|
||||
wait(delay[0], delay[1])
|
||||
keyboard.send(self._char_config["stand_still"], do_press=False)
|
||||
|
||||
def _main_attack(self, cast_pos, delay, spray = 10):
|
||||
def _main_attack(self, cast_pos: Tuple[float, float], delay: float, spray: float = 10):
|
||||
keyboard.send(self._skill_hotkeys["skill_right"])
|
||||
x = cast_pos[0] + (random.random() * 2*spray - spray)
|
||||
y = cast_pos[1] + (random.random() * 2*spray - spray)
|
||||
@@ -56,7 +57,7 @@ class Sorceress(IChar):
|
||||
mouse.click(button="right")
|
||||
wait(delay[0], delay[1])
|
||||
|
||||
def kill_pindle(self, pindle_pos_screen):
|
||||
def kill_pindle(self, pindle_pos_screen: Tuple[float, float]):
|
||||
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]
|
||||
@@ -75,7 +76,7 @@ class Sorceress(IChar):
|
||||
blizzard_cast_pos = self._screen.convert_abs_to_monitor([0, 0])
|
||||
self._main_attack(blizzard_cast_pos, delay)
|
||||
|
||||
def kill_shenk(self, shenk_pos_screen):
|
||||
def kill_shenk(self, shenk_pos_screen: Tuple[float, float]):
|
||||
delay = [0.2, 0.3]
|
||||
pos_abs = self._screen.convert_screen_to_abs(shenk_pos_screen)
|
||||
cast_pos_abs = [pos_abs[0] * 0.9, pos_abs[1] * 0.9]
|
||||
@@ -95,7 +96,7 @@ class Sorceress(IChar):
|
||||
custom_mouse.move(pos_monitor[0], pos_monitor[1], duration=(random.random() * 0.05 + 0.15))
|
||||
mouse.click(button="right")
|
||||
|
||||
def kill_eldritch(self, eldritch_pos_screen):
|
||||
def kill_eldritch(self, eldritch_pos_screen: Tuple[float, float]):
|
||||
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]
|
||||
@@ -126,6 +127,6 @@ 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, None, ui_manager)
|
||||
char = Sorceress(config.sorceress, config.char, screen, t_finder, ui_manager)
|
||||
# char.pre_buff()
|
||||
char.tp_town()
|
||||
|
||||
+65
-42
@@ -1,64 +1,87 @@
|
||||
import configparser
|
||||
import numpy as np
|
||||
import os
|
||||
|
||||
|
||||
class Config:
|
||||
def _select_val(self, section: str, key: str = None):
|
||||
if section in self._custom and key in self._custom[section]:
|
||||
return self._custom[section][key]
|
||||
elif section in self._config:
|
||||
return self._config[section][key]
|
||||
else:
|
||||
return self._ui_config[section][key]
|
||||
|
||||
def __init__(self):
|
||||
config = configparser.ConfigParser()
|
||||
config.read('params.ini')
|
||||
ui_config = configparser.ConfigParser()
|
||||
ui_config.read('ui.ini')
|
||||
self._config = configparser.ConfigParser()
|
||||
self._config.read('params.ini')
|
||||
self._ui_config = configparser.ConfigParser()
|
||||
self._ui_config.read('ui.ini')
|
||||
self._custom = configparser.ConfigParser()
|
||||
if os.path.exists('custom.ini'):
|
||||
self._custom.read('custom.ini')
|
||||
|
||||
self.general = {
|
||||
"monitor": int(config["general"]["monitor"]),
|
||||
"min_game_length_s": float(config["general"]["min_game_length_s"]),
|
||||
"exit_key": config["general"]["exit_key"],
|
||||
"resume_key": config["general"]["resume_key"],
|
||||
"auto_settings_key": config["general"]["auto_settings_key"],
|
||||
"color_checker_key": config["general"]["color_checker_key"],
|
||||
}
|
||||
self.routes = {
|
||||
"run_pindle": bool(int(config["routes"]["run_pindle"])),
|
||||
"run_shenk": bool(int(config["routes"]["run_shenk"])) if "run_shenk" in config["routes"] else False,
|
||||
"monitor": int(self._select_val("general", "monitor")),
|
||||
"min_game_length_s": float(self._select_val("general", "min_game_length_s")),
|
||||
"exit_key": self._select_val("general", "exit_key"),
|
||||
"resume_key": self._select_val("general", "resume_key"),
|
||||
"auto_settings_key": self._select_val("general", "auto_settings_key"),
|
||||
"color_checker_key": self._select_val("general", "color_checker_key"),
|
||||
}
|
||||
|
||||
self.routes = {}
|
||||
for key in self._config["routes"]:
|
||||
self.routes[key] = bool(int(self._select_val("routes", key)))
|
||||
|
||||
self.char = {
|
||||
"type": config["char"]["type"],
|
||||
"show_items": config["char"]["show_items"],
|
||||
"inventory_screen": config["char"]["inventory_screen"],
|
||||
"stand_still": config["char"]["stand_still"],
|
||||
"num_loot_columns": int(config["char"]["num_loot_columns"]),
|
||||
"take_health_potion": float(config["char"]["take_health_potion"]),
|
||||
"take_mana_potion": float(config["char"]["take_mana_potion"]),
|
||||
"heal_merc": float(config["char"]["heal_merc"]),
|
||||
"chicken": float(config["char"]["chicken"]),
|
||||
"tp": config["char"]["tp"],
|
||||
"show_belt": config["char"]["show_belt"],
|
||||
"potion1": config["char"]["potion1"],
|
||||
"potion2": config["char"]["potion2"],
|
||||
"potion3": config["char"]["potion3"],
|
||||
"potion4": config["char"]["potion4"],
|
||||
"cta_available": bool(int(config["char"]["cta_available"])),
|
||||
"weapon_switch": config["char"]["weapon_switch"],
|
||||
"battle_orders": config["char"]["battle_orders"],
|
||||
"battle_command": config["char"]["battle_command"],
|
||||
"casting_frames": int(config["char"]["casting_frames"])
|
||||
"type": self._select_val("char", "type"),
|
||||
"show_items": self._select_val("char", "show_items"),
|
||||
"inventory_screen": self._select_val("char", "inventory_screen"),
|
||||
"stand_still": self._select_val("char", "stand_still"),
|
||||
"num_loot_columns": int(self._select_val("char", "num_loot_columns")),
|
||||
"take_health_potion": float(self._select_val("char", "take_health_potion")),
|
||||
"take_mana_potion": float(self._select_val("char", "take_mana_potion")),
|
||||
"heal_merc": float(self._select_val("char", "heal_merc")),
|
||||
"chicken": float(self._select_val("char", "chicken")),
|
||||
"tp": self._select_val("char", "tp"),
|
||||
"show_belt": self._select_val("char", "show_belt"),
|
||||
"potion1": self._select_val("char", "potion1"),
|
||||
"potion2": self._select_val("char", "potion2"),
|
||||
"potion3": self._select_val("char", "potion3"),
|
||||
"potion4": self._select_val("char", "potion4"),
|
||||
"cta_available": bool(int(self._select_val("char", "cta_available"))),
|
||||
"weapon_switch": self._select_val("char", "weapon_switch"),
|
||||
"battle_orders": self._select_val("char", "battle_orders"),
|
||||
"battle_command": self._select_val("char", "battle_command"),
|
||||
"casting_frames": int(self._select_val("char", "casting_frames")),
|
||||
"slow_walk": bool(int(self._select_val("char", "slow_walk"))),
|
||||
}
|
||||
|
||||
self.sorceress = config["sorceress"]
|
||||
self.sorceress = dict(self._config["sorceress"])
|
||||
if "sorceress" in self._custom:
|
||||
self.sorceress.update(dict(self._custom["sorceress"]))
|
||||
|
||||
self.hammerdin = config["hammerdin"]
|
||||
self.hammerdin = self._config["hammerdin"]
|
||||
if "hammerdin" in self._custom:
|
||||
self.hammerdin.update(self._custom["hammerdin"])
|
||||
|
||||
self.items = {}
|
||||
for key in config["items"]:
|
||||
self.items[key] = bool(int(config["items"][key]))
|
||||
for key in self._config["items"]:
|
||||
self.items[key] = bool(int(self._select_val("items", key)))
|
||||
|
||||
self.colors = {}
|
||||
for key in ui_config["colors"]:
|
||||
self.colors[key] = np.split(np.array([int(x) for x in ui_config["colors"][key].split(",")]), 2)
|
||||
for key in self._ui_config["colors"]:
|
||||
self.colors[key] = np.split(np.array([int(x) for x in self._select_val("colors", key).split(",")]), 2)
|
||||
|
||||
self.ui_pos = {}
|
||||
for key in ui_config["ui_pos_1920_1080"]:
|
||||
self.ui_pos[key] = int(ui_config["ui_pos_1920_1080"][key])
|
||||
for key in self._ui_config["ui_pos_1920_1080"]:
|
||||
self.ui_pos[key] = int(self._select_val("ui_pos_1920_1080", key))
|
||||
|
||||
self.ui_roi = {}
|
||||
for key in self._ui_config["ui_roi_1920_1080"]:
|
||||
self.ui_roi[key] = np.array([int(x) for x in self._select_val("ui_roi_1920_1080", key).split(",")])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
config = Config()
|
||||
|
||||
@@ -9,6 +9,7 @@ import cv2
|
||||
from logger import Logger
|
||||
import time
|
||||
import random
|
||||
from threading import Thread
|
||||
|
||||
|
||||
class DeathManager:
|
||||
@@ -17,7 +18,6 @@ class DeathManager:
|
||||
self._screen = screen
|
||||
self._template_finder = template_finder
|
||||
_, self._you_have_died_filtered = color_filter(cv2.imread("assets/templates/you_have_died.png"), self._config.colors["red"])
|
||||
self._search_roi = [self._config.ui_pos["death_roi_left"], self._config.ui_pos["death_roi_top"], self._config.ui_pos["death_roi_width"], self._config.ui_pos["death_roi_height"]]
|
||||
self._died = False
|
||||
self._do_monitor = False
|
||||
|
||||
@@ -34,11 +34,11 @@ class DeathManager:
|
||||
mouse.click(button="left")
|
||||
self._died = False
|
||||
|
||||
def start_monitor(self, run_thread):
|
||||
def start_monitor(self, run_thread: Thread):
|
||||
self._do_monitor = True
|
||||
while self._do_monitor:
|
||||
time.sleep(1.0) # no need to do this too frequent, when we died we are not in a hurry...
|
||||
roi_img = cut_roi(self._screen.grab(), self._search_roi)
|
||||
roi_img = cut_roi(self._screen.grab(), self._config.ui_roi["death"])
|
||||
_, filtered_roi_img = color_filter(roi_img, self._config.colors["red"])
|
||||
res = cv2.matchTemplate(filtered_roi_img, self._you_have_died_filtered, cv2.TM_CCOEFF_NORMED)
|
||||
_, max_val, _, _ = cv2.minMaxLoc(res)
|
||||
|
||||
+10
-9
@@ -9,6 +9,7 @@ from screen import Screen
|
||||
import numpy as np
|
||||
import time
|
||||
from config import Config
|
||||
from threading import Thread
|
||||
|
||||
|
||||
class HealthManager:
|
||||
@@ -48,7 +49,7 @@ class HealthManager:
|
||||
keyboard.send(self._config.char[key])
|
||||
break
|
||||
|
||||
def get_health(self, img):
|
||||
def get_health(self, img: np.ndarray) -> float:
|
||||
health_rec = [self._config.ui_pos["health_left"], self._config.ui_pos["health_top"], self._config.ui_pos["health_width"], self._config.ui_pos["health_height"]]
|
||||
health_img = cut_roi(img, health_rec)
|
||||
# red mask
|
||||
@@ -61,14 +62,14 @@ class HealthManager:
|
||||
health_percentage_green = (float(np.sum(mask)) / mask.size) * (1/255.0)
|
||||
return max(health_percentage, health_percentage_green)
|
||||
|
||||
def get_mana(self, img):
|
||||
def get_mana(self, img: np.ndarray) -> float:
|
||||
mana_rec = [self._config.ui_pos["mana_left"], self._config.ui_pos["mana_top"], self._config.ui_pos["mana_width"], self._config.ui_pos["mana_height"]]
|
||||
mana_img = cut_roi(img, mana_rec)
|
||||
mask, _ = color_filter(mana_img, [np.array([117, 120, 20]), np.array([121, 255, 255])])
|
||||
mana_percentage = (float(np.sum(mask)) / mask.size) * (1/255.0)
|
||||
return mana_percentage
|
||||
|
||||
def get_merc_health(self, img):
|
||||
def get_merc_health(self, img: np.ndarray) -> float:
|
||||
health_rec = [self._config.ui_pos["merc_health_left"], self._config.ui_pos["merc_health_top"], self._config.ui_pos["merc_health_width"], self._config.ui_pos["merc_health_height"]]
|
||||
merc_health_img = cut_roi(img, health_rec)
|
||||
merc_health_img = cv2.cvtColor(merc_health_img, cv2.COLOR_BGR2GRAY)
|
||||
@@ -76,15 +77,15 @@ class HealthManager:
|
||||
merc_health_percentage = (float(np.sum(health_tresh)) / health_tresh.size) * (1/255.0)
|
||||
return merc_health_percentage
|
||||
|
||||
def start_monitor(self, run_thread):
|
||||
def start_monitor(self, run_thread: Thread):
|
||||
Logger.debug("Start health monitoring")
|
||||
self._do_monitor = True
|
||||
self._did_chicken = False
|
||||
start = time.time()
|
||||
while self._do_monitor:
|
||||
time.sleep(0.1)
|
||||
img = self._screen.grab()
|
||||
roi = [700, 650, 460, 250]
|
||||
is_loading_black_roi = np.average(img[:, 0:500]) < 1.0
|
||||
is_loading_black_roi = np.average(img[:, 0:self._config.ui_roi["loading_left_black"][2]]) < 1.0
|
||||
if not is_loading_black_roi:
|
||||
# check health
|
||||
health_percentage = self.get_health(img)
|
||||
@@ -92,7 +93,8 @@ class HealthManager:
|
||||
if health_percentage < self._config.char["take_health_potion"] and last_drink > 2.5:
|
||||
self._drink_poition(img, "health")
|
||||
self._last_health = time.time()
|
||||
elif health_percentage < self._config.char["chicken"]:
|
||||
# give the chicken a 4 sec delay to give time for a healing pot and avoid endless loop of chicken
|
||||
elif health_percentage < self._config.char["chicken"] and (time.time() - start) > 4:
|
||||
Logger.warning("Trying to chicken!")
|
||||
cv2.imwrite("info_debug_chicken.png", img)
|
||||
self._ui_manager.save_and_exit()
|
||||
@@ -107,8 +109,7 @@ class HealthManager:
|
||||
self._drink_poition(img, "mana")
|
||||
self._last_mana = time.time()
|
||||
# check merc
|
||||
roi = [0, 0, 150, 150]
|
||||
merc_alive, _ = self._template_finder.search("MERC", img, roi=roi)
|
||||
merc_alive, _ = self._template_finder.search("MERC", img, roi=self._config.ui_roi["merc_icon"])
|
||||
if merc_alive:
|
||||
merc_health_percentage = self.get_merc_health(img)
|
||||
last_drink = time.time() - self._last_merc_healh
|
||||
|
||||
+5
-12
@@ -39,22 +39,18 @@ class NpcManager:
|
||||
}
|
||||
|
||||
def open_npc_menu(self, npc_key: Npc) -> bool:
|
||||
# TODO: 1920x1080 specific. Cut off bottom skill bar
|
||||
roi = [0, 0, 1920, 1000]
|
||||
roi = self._config.ui_roi["cut_skill_bar"]
|
||||
start = time.time()
|
||||
while (time.time() - start) < 40:
|
||||
while (time.time() - start) < 35:
|
||||
img = self._screen.grab()
|
||||
for key in self._npcs[npc_key]["template_group"]:
|
||||
# TODO: 1920x1080 specific params
|
||||
res, pos = self._template_finder.search(key, img, threshold=0.35, roi=[0, 0, 1920, 1000])
|
||||
res, pos = self._template_finder.search(key, img, threshold=0.35, roi=roi)
|
||||
if res:
|
||||
x_m, y_m = self._screen.convert_screen_to_monitor(pos)
|
||||
custom_mouse.move(x_m, y_m, duration=0.05)
|
||||
time.sleep(0.2)
|
||||
_, filtered_inp_w = color_filter(self._screen.grab(), self._config.colors["white"])
|
||||
_, filtered_inp_g = color_filter(self._screen.grab(), self._config.colors["gold"])
|
||||
# TODO: 1920x1080 specific params
|
||||
# roi = [max(0, pos[0] - 200), max(0, pos[1] - 250), 400, 250]
|
||||
res_w, _ = self._template_finder.search(self._npcs[npc_key]["name_tag_white"], filtered_inp_w, 0.92, roi=roi)
|
||||
res_g, _ = self._template_finder.search(self._npcs[npc_key]["name_tag_gold"], filtered_inp_g, 0.92, roi=roi)
|
||||
if res_w:
|
||||
@@ -69,11 +65,8 @@ class NpcManager:
|
||||
return False
|
||||
|
||||
def press_npc_btn(self, npc_key: Npc, action_btn_key: str):
|
||||
# click resurrect btn
|
||||
_, filtered_inp = color_filter(self._screen.grab(), self._config.colors["white"])
|
||||
# TODO: 1920x1080 specific params
|
||||
roi = [300, 0, 1400, 500]
|
||||
res, pos = self._template_finder.search(self._npcs[npc_key]["action_btns"][action_btn_key], filtered_inp, 0.92, roi=roi)
|
||||
res, pos = self._template_finder.search(self._npcs[npc_key]["action_btns"][action_btn_key], filtered_inp, 0.92, roi=self._config.ui_roi["cut_skill_bar"])
|
||||
if res:
|
||||
x_m, y_m = self._screen.convert_screen_to_monitor(pos)
|
||||
custom_mouse.move(x_m, y_m, duration=0.1)
|
||||
@@ -85,7 +78,7 @@ class NpcManager:
|
||||
keyboard.send("esc")
|
||||
|
||||
|
||||
# Testing: Stand close to Qual-Kehk and run
|
||||
# Testing: Stand close to Qual-Kehk or Malah and run
|
||||
if __name__ == "__main__":
|
||||
from screen import Screen
|
||||
from config import Config
|
||||
|
||||
+70
-29
@@ -7,8 +7,11 @@ import keyboard
|
||||
import time
|
||||
import os
|
||||
import random
|
||||
from typing import Tuple
|
||||
from typing import Tuple, List
|
||||
import cv2
|
||||
from config import Config
|
||||
from utils.misc import wait, is_in_roi
|
||||
import numpy as np
|
||||
|
||||
|
||||
class Location:
|
||||
@@ -21,17 +24,23 @@ class Location:
|
||||
|
||||
|
||||
class Pather:
|
||||
"""
|
||||
Traverses 'dynamic' pathes with reference templates and relative coordinates or statically recorded pathes.
|
||||
Check utils/node_recorder.py to generate templates/refpoints and relative coordinates to nodes. Once you have refpoints and
|
||||
nodes you can specify in which order this nodes should be traversed in self._paths.
|
||||
"""
|
||||
|
||||
def __init__(self, screen: Screen, template_finder: TemplateFinder):
|
||||
self._config = Config()
|
||||
self._screen = screen
|
||||
self._template_finder = template_finder
|
||||
# TODO: params based on 1920x1080 (in rel coordinates to ref point)
|
||||
self._range_x = [-950, 950]
|
||||
self._range_y = [-530, 440]
|
||||
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._nodes = {
|
||||
# A5 town
|
||||
0: {"A5_TOWN_0": (110-70, 373), "A5_TOWN_1": (-68-70, -205)},
|
||||
1: {"A5_TOWN_0": (-466, 287), "A5_TOWN_1": (-644, -291), "A5_TOWN_0.5": (717, 349)},
|
||||
2: {"A5_TOWN_0": (-552, 42), "A5_TOWN_0.5": (659-25, 90+20)},
|
||||
2: {"A5_TOWN_0": (-552, 42-100), "A5_TOWN_0.5": (659-25, 90+20-100)},
|
||||
3: {"A5_TOWN_1": (-414, 141), "A5_TOWN_2": (728, -90)},
|
||||
4: {"A5_TOWN_1": (-701, 400), "A5_TOWN_2": (440, 169), "A5_TOWN_3": (-400, -208), "A5_TOWN_4": (243, -244)},
|
||||
5: {"A5_TOWN_2": (555-100, 429-100), "A5_TOWN_3": (-285-100, 51-100), "A5_TOWN_4": (358-100, 15-100)},
|
||||
@@ -68,7 +77,7 @@ class Pather:
|
||||
def get_fixed_path(self, key: str):
|
||||
return self._fixed_tele_path[key]
|
||||
|
||||
def _draw_debug(self, img, node_pos_abs_list, ref_pos_abs_list):
|
||||
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)
|
||||
@@ -81,7 +90,7 @@ class Pather:
|
||||
cv2.imshow("x", img)
|
||||
cv2.waitKey(1)
|
||||
|
||||
def _display_all_nodes(self):
|
||||
def _display_all_nodes_debug(self):
|
||||
while 1:
|
||||
img = self._screen.grab()
|
||||
for node_idx in self._nodes:
|
||||
@@ -103,15 +112,10 @@ class Pather:
|
||||
cv2.imshow("debug", img)
|
||||
cv2.waitKey(1)
|
||||
|
||||
@staticmethod
|
||||
def _convert_rel_to_abs(rel_loc, pos_abs):
|
||||
@staticmethod
|
||||
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 _check_abs_range_to_screen(self, pos_abs: Tuple[float, float]) -> Tuple[float, float]:
|
||||
is_in_x_range = self._range_x[0] < pos_abs[0] < self._range_x[1]
|
||||
is_in_y_range = self._range_y[0] < pos_abs[1] < self._range_y[1]
|
||||
return is_in_x_range and is_in_y_range
|
||||
|
||||
def traverse_nodes_fixed(self, key: str, char: IChar):
|
||||
path = self._fixed_tele_path[key][0]
|
||||
for pos in path:
|
||||
@@ -120,7 +124,43 @@ class Pather:
|
||||
y_m += int(random.random() * 6 - 3)
|
||||
char.move((x_m, y_m))
|
||||
|
||||
def _adjust_abs_range_to_screen(self, abs_pos: Tuple[float, float]) -> Tuple[float, float]:
|
||||
"""
|
||||
Adjust an absolute coordinate so it will not go out of screen or click on any ui which will not move the char
|
||||
:param abs_pos: Absolute position of the desired position to move to
|
||||
:return: Absolute position of a valid position that can be clicked on
|
||||
"""
|
||||
f = 1.0
|
||||
# Check for x-range
|
||||
if abs_pos[0] > self._range_x[1]:
|
||||
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
|
||||
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))
|
||||
return abs_pos
|
||||
|
||||
def traverse_nodes(self, start_location: Location, end_location: Location, char: IChar, debug: bool = False) -> 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
|
||||
:return: Bool if traversed succesfull or False if it got stuck
|
||||
"""
|
||||
Logger.debug(f"Traverse from {start_location} to {end_location}")
|
||||
path = self._paths[(start_location, end_location)]
|
||||
for i, node_idx in enumerate(path):
|
||||
@@ -137,7 +177,7 @@ class Pather:
|
||||
last_move = time.time()
|
||||
else:
|
||||
cv2.imwrite("info_pather_got_stuck.png", img)
|
||||
Logger.error("Got stuck exit")
|
||||
Logger.error("Got stuck exit pather")
|
||||
return False
|
||||
_debug_node_pos_abs_list = []
|
||||
_debug_ref_pos_abs_list = []
|
||||
@@ -154,17 +194,18 @@ class Pather:
|
||||
_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)
|
||||
if self._check_abs_range_to_screen(node_pos_abs):
|
||||
dist = math.dist(node_pos_abs, (0, 0))
|
||||
# TODO: param based on 1920x1080
|
||||
if dist < 150:
|
||||
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()
|
||||
break
|
||||
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
|
||||
return True
|
||||
|
||||
|
||||
@@ -181,7 +222,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, None, ui_manager)
|
||||
char = Sorceress(config.sorceress, config.char, screen, t_finder, ui_manager)
|
||||
# pather.traverse_nodes_fixed("PINDLE", char)
|
||||
# pather.traverse_nodes(Location.QUAL_KEHK, Location.NIHLATHAK_PORTAL, char, debug=True)
|
||||
pather._display_all_nodes()
|
||||
pather.traverse_nodes(Location.A5_TOWN_START, Location.MALAH, char, debug=True)
|
||||
# pather._display_all_nodes()
|
||||
|
||||
+8
-4
@@ -19,6 +19,11 @@ class PickIt:
|
||||
self._config = Config()
|
||||
|
||||
def pick_up_items(self, char: IChar) -> bool:
|
||||
"""
|
||||
Pick up all items with specified char
|
||||
:param char: The character used to pick up the item
|
||||
:return: Bool if any items were picked up or not. (Does not account for picking up scrolls and pots)
|
||||
"""
|
||||
found_items = False
|
||||
keyboard.send(self._config.char["show_items"], do_press=True, do_release=False)
|
||||
time.sleep(1.0) # sleep needed here to give d2r time to display items on screen on keypress
|
||||
@@ -41,16 +46,15 @@ class PickIt:
|
||||
if closest_item.dist > item.dist:
|
||||
closest_item = item
|
||||
x_m, y_m = self._screen.convert_screen_to_monitor(closest_item.center)
|
||||
# TODO: 1920x1080 specific param
|
||||
if closest_item.dist < 400:
|
||||
if closest_item.dist < self._config.ui_pos["item_dist"]:
|
||||
# no need to stash poitions and scrolls
|
||||
if "potion" not in closest_item.name and "tp_scroll" != closest_item.name:
|
||||
found_items = True
|
||||
Logger.info(f"Picking up {closest_item.name}")
|
||||
custom_mouse.move(x_m, y_m, duration=(random.random() * 0.01 + 0.02))
|
||||
custom_mouse.move(x_m, y_m, duration=(random.random() * 0.03 + 0.08))
|
||||
time.sleep(0.1)
|
||||
mouse.click(button="left")
|
||||
time.sleep(0.6)
|
||||
time.sleep(0.5)
|
||||
if self._ui_manager.is_overburdened():
|
||||
Logger.warning("Inventory full, skipping pickit!")
|
||||
# TODO: should go back to town and stash stuff then go back to picking up more stuff
|
||||
|
||||
@@ -10,6 +10,7 @@ from utils.remove_mouse_flags import remove_mouse_flag
|
||||
from utils.auto_settings import adjust_settings
|
||||
import threading
|
||||
from beautifultable import BeautifulTable
|
||||
import time
|
||||
|
||||
|
||||
def start_bot():
|
||||
@@ -54,6 +55,7 @@ if __name__ == "__main__":
|
||||
elif keyboard.is_pressed(config.general['color_checker_key']):
|
||||
run_color_checker()
|
||||
break
|
||||
time.sleep(0.02)
|
||||
|
||||
remove_flags_thread.join()
|
||||
print("Press Enter to exit ...")
|
||||
|
||||
@@ -5,10 +5,13 @@ import cv2
|
||||
import time
|
||||
from logger import Logger
|
||||
from typing import Tuple
|
||||
from config import Config
|
||||
import os
|
||||
|
||||
|
||||
class Screen:
|
||||
"""Grabs images from screen and converts differnt coordinate systems to each other"""
|
||||
|
||||
def __init__(self, monitor: int = 0):
|
||||
self._sct = mss()
|
||||
monitor_idx = monitor + 1 # sct saves the whole screen (including both monitors if available at index 0, then monitor 1 at 1 and 2 at 2)
|
||||
@@ -18,7 +21,12 @@ class Screen:
|
||||
if monitor_idx >= len(self._sct.monitors):
|
||||
Logger.warning("Monitor index not available! Choose a smaller number for 'monitor' in the param.ini. Forcing value to 0 for now.")
|
||||
monitor_idx = 1
|
||||
config = Config()
|
||||
self._monitor_roi = self._sct.monitors[monitor_idx]
|
||||
# For windowed screens it is expected to always have them at the top left edge and adjust offset_top then
|
||||
self._monitor_roi["top"] += config.ui_pos["offset_top"]
|
||||
self._monitor_roi["width"] = config.ui_pos["screen_width"]
|
||||
self._monitor_roi["height"] = config.ui_pos["screen_height"]
|
||||
|
||||
def convert_monitor_to_screen(self, screen_coord: Tuple[float, float]) -> Tuple[float, float]:
|
||||
return (screen_coord[0] - self._monitor_roi["left"], screen_coord[1] - self._monitor_roi["top"])
|
||||
|
||||
+22
-2
@@ -5,6 +5,7 @@ from typing import Tuple, Union, List
|
||||
import numpy as np
|
||||
from logger import Logger
|
||||
import time
|
||||
from config import Config
|
||||
|
||||
|
||||
def load_template(path, scale_factor):
|
||||
@@ -14,9 +15,14 @@ def load_template(path, scale_factor):
|
||||
|
||||
class TemplateFinder:
|
||||
def __init__(self, screen: Screen, scale_factor: float = 0.5):
|
||||
"""
|
||||
: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._screen = screen
|
||||
self._scale_factor = scale_factor
|
||||
self._config = Config()
|
||||
self._templates = {
|
||||
# Templates for node in A5 Town
|
||||
"A5_TOWN_0": [load_template("assets/templates/a5_town/a5_town_0.png", self._scale_factor), self._scale_factor],
|
||||
@@ -45,6 +51,7 @@ class TemplateFinder:
|
||||
"D2_LOGO_HS": [load_template("assets/templates/d2_logo_hs.png", 1.0), 1.0],
|
||||
"LOADING": [load_template("assets/templates/loading.png", 1.0), 1.0],
|
||||
"PLAY_BTN": [load_template("assets/templates/play_btn.png", 1.0), 1.0],
|
||||
"PLAY_BTN_GRAY": [load_template("assets/templates/play_btn_gray.png", 1.0), 1.0],
|
||||
"HELL_BTN": [load_template("assets/templates/hell_btn.png", 1.0), 1.0],
|
||||
"SAVE_AND_EXIT": [load_template("assets/templates/save_and_exit.png", 1.0), 1.0],
|
||||
"SERVER_ISSUES": [load_template("assets/templates/server_issues.png", 1.0), 1.0],
|
||||
@@ -79,6 +86,14 @@ class TemplateFinder:
|
||||
return self._templates[key][0]
|
||||
|
||||
def search(self, ref: Union[str, np.ndarray], inp_img: np.ndarray, threshold: float = 0.7, roi: List[float] = None) -> Tuple[bool, Tuple[float, float]]:
|
||||
"""
|
||||
Search for a template in an image
|
||||
:param ref: Either key of a already loaded template or a image which is used as template
|
||||
:param inp_img: Image in which the template will be searched
|
||||
:param threshold: Threshold which determines if a template is found or not
|
||||
:param roi: Region of Interest of the inp_img to restrict search area. Format [left, top, width, height]
|
||||
:return: Returns found flag and the position as [bool, [x, y]]. If not found, position will be None. Position in image space.
|
||||
"""
|
||||
if roi is None:
|
||||
# if no roi is provided roi = full inp_img
|
||||
roi = [0, 0, inp_img.shape[1], inp_img.shape[0]]
|
||||
@@ -109,12 +124,17 @@ class TemplateFinder:
|
||||
return False, None
|
||||
|
||||
def search_and_wait(self, ref: str, roi: List[float] = None, time_out: float = None, threshold: float = 0.7) -> Tuple[bool, Tuple[float, float]]:
|
||||
"""
|
||||
Helper function that will loop and keep searching for a template
|
||||
:param ref: Key of template which has been loaded beforehand
|
||||
:param time_out: After this amount of time the search will stop and it will return [False, None]
|
||||
Rest of params same as TemplateFinder.search()
|
||||
"""
|
||||
Logger.debug(f"Waiting for Template {ref}")
|
||||
start = time.time()
|
||||
while 1:
|
||||
img = self._screen.grab()
|
||||
# TODO: 1920x1080 specific params
|
||||
is_loading_black_roi = np.average(img[:, 0:500]) < 1.0
|
||||
is_loading_black_roi = np.average(img[:, 0:self._config.ui_roi["loading_left_black"][2]]) < 1.0
|
||||
success, pos = self.search(ref, img, roi=roi, threshold=threshold)
|
||||
if not is_loading_black_roi and success:
|
||||
return True, pos
|
||||
|
||||
+154
-87
@@ -1,8 +1,9 @@
|
||||
from screen import Screen
|
||||
from template_finder import TemplateFinder
|
||||
import mouse
|
||||
from utils import custom_mouse, custom_keyboard
|
||||
import keyboard # currently needed for a workaround
|
||||
from typing import Tuple
|
||||
from utils import custom_mouse
|
||||
import keyboard
|
||||
import random
|
||||
import time
|
||||
import cv2
|
||||
@@ -16,36 +17,50 @@ from utils.misc import color_filter
|
||||
|
||||
|
||||
class UiManager():
|
||||
"""Everything that is clicking on some static 2D UI or is checking anything in regard to it should be placed here."""
|
||||
|
||||
def __init__(self, screen: Screen, template_finder: TemplateFinder):
|
||||
self._config = Config()
|
||||
self._template_finder = template_finder
|
||||
self._screen = screen
|
||||
self._curr_stash = 0 # 0: personal, 1: shared1, 2: shared2, 3: shared3
|
||||
|
||||
def use_wp(self, act, idx):
|
||||
pos_act_btn = (self._config.ui_pos["wp_act_i_btn_x"] + self._config.ui_pos["wp_act_btn_width"] * act, self._config.ui_pos["wp_act_i_btn_y"])
|
||||
x, y = self._screen.convert_screen_to_monitor(pos_act_btn)
|
||||
custom_mouse.move(x, y, duration=0.4, randomize=5)
|
||||
mouse.click(button="left")
|
||||
wait(0.35, 0.4)
|
||||
def use_wp(self, act: int, idx: int):
|
||||
"""
|
||||
Use Waypoint. The menu must be opened when calling the function.
|
||||
:param act: Index of the act from left. Note that it start at 0. e.g. Act5 -> act=4
|
||||
:param idx: Index of the waypoint from top. Note that it start at 0.
|
||||
"""
|
||||
# Note: We are currently only in act 5, thus no need to click here.
|
||||
# pos_act_btn = (self._config.ui_pos["wp_act_i_btn_x"] + self._config.ui_pos["wp_act_btn_width"] * act, self._config.ui_pos["wp_act_i_btn_y"])
|
||||
# x, y = self._screen.convert_screen_to_monitor(pos_act_btn)
|
||||
# custom_mouse.move(x, y, duration=0.4, randomize=8)
|
||||
# mouse.click(button="left")
|
||||
# wait(0.3, 0.4)
|
||||
pos_wp_btn = (self._config.ui_pos["wp_first_btn_x"], self._config.ui_pos["wp_first_btn_y"] + self._config.ui_pos["wp_btn_height"] * idx)
|
||||
x, y = self._screen.convert_screen_to_monitor(pos_wp_btn)
|
||||
custom_mouse.move(x, y, duration=0.4, randomize=5)
|
||||
custom_mouse.move(x, y, duration=0.4, randomize=12)
|
||||
wait(0.3, 0.4)
|
||||
mouse.click(button="left")
|
||||
|
||||
def can_teleport(self):
|
||||
img = self._screen.grab()
|
||||
def can_teleport(self) -> bool:
|
||||
"""
|
||||
:return: Bool if teleport is red/available or not. Teleport skill must be selected on right skill slot when calling the function.
|
||||
"""
|
||||
roi = [
|
||||
self._config.ui_pos["skill_right_x"] - (self._config.ui_pos["skill_width"] // 2),
|
||||
self._config.ui_pos["skill_y"] - (self._config.ui_pos["skill_height"] // 2),
|
||||
self._config.ui_pos["skill_width"],
|
||||
self._config.ui_pos["skill_height"]
|
||||
]
|
||||
img = cut_roi(img, roi)
|
||||
img = cut_roi(self._screen.grab(), roi)
|
||||
avg = np.average(img)
|
||||
return avg > 75.0
|
||||
|
||||
def is_teleport_selected(self):
|
||||
def is_teleport_selected(self) -> bool:
|
||||
"""
|
||||
:return: Bool if teleport is currently the selected skill on the right skill slot.
|
||||
"""
|
||||
roi = [
|
||||
self._config.ui_pos["skill_right_x"] - (self._config.ui_pos["skill_width"] // 2),
|
||||
self._config.ui_pos["skill_y"] - (self._config.ui_pos["skill_height"] // 2),
|
||||
@@ -58,11 +73,11 @@ class UiManager():
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_overburdened(self):
|
||||
#TODO: 1920x1080 specific roi
|
||||
roi = [17, 765, 470, 90]
|
||||
img = self._screen.grab()
|
||||
img = cut_roi(img, roi)
|
||||
def is_overburdened(self) -> bool:
|
||||
"""
|
||||
:return: Bool if the last pick up overburdened your char. Must be called right after picking up an item.
|
||||
"""
|
||||
img = cut_roi(self._screen.grab(), self._config.ui_roi["is_overburdened"])
|
||||
_, filtered_img = color_filter(img, self._config.colors["gold"])
|
||||
templates = [cv2.imread("assets/templates/inventory_full_msg_0.png"), cv2.imread("assets/templates/inventory_full_msg_1.png")]
|
||||
for template in templates:
|
||||
@@ -74,7 +89,12 @@ class UiManager():
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def potion_type(img):
|
||||
def potion_type(img: np.ndarray) -> str:
|
||||
"""
|
||||
Based on cut out image from belt, determines what type of potion it is. TODO: Add rejuv support
|
||||
:param img: Cut out image of a belt slot
|
||||
:return: Any of ["empty", "health", "mana"]
|
||||
"""
|
||||
avg_brightness = np.average(img)
|
||||
if avg_brightness < 47:
|
||||
return "empty"
|
||||
@@ -89,7 +109,10 @@ class UiManager():
|
||||
return "empty"
|
||||
|
||||
def check_free_belt_spots(self) -> bool:
|
||||
# currently only checks if a whole row is free
|
||||
"""
|
||||
Check if any column in the belt is free (only checking the bottom row)
|
||||
:return: Bool if any column in belt is free
|
||||
"""
|
||||
img = self._screen.grab()
|
||||
for i in range(4):
|
||||
roi = [
|
||||
@@ -104,128 +127,168 @@ class UiManager():
|
||||
return True
|
||||
return False
|
||||
|
||||
def save_and_exit(self):
|
||||
def save_and_exit(self) -> bool:
|
||||
"""
|
||||
Performes save and exit action from within game
|
||||
:return: Bool if action was successful
|
||||
"""
|
||||
start = time.time()
|
||||
while (time.time() - start) < 10:
|
||||
while (time.time() - start) < 15:
|
||||
keyboard.send("esc")
|
||||
wait(0.05)
|
||||
wait(0.1)
|
||||
exit_btn_pos = (self._config.ui_pos["save_and_exit_x"], self._config.ui_pos["save_and_exit_y"])
|
||||
found, _ = self._template_finder.search_and_wait("SAVE_AND_EXIT", roi=[750, 350, 410, 260], time_out=2)
|
||||
found, _ = self._template_finder.search_and_wait("SAVE_AND_EXIT", roi=self._config.ui_roi["save_and_exit"], time_out=7)
|
||||
if found:
|
||||
x_m, y_m = self._screen.convert_screen_to_monitor(exit_btn_pos)
|
||||
custom_mouse.move(x_m, y_m, duration=random.random()*0.05 + 0.15, randomize=10)
|
||||
custom_mouse.move(x_m, y_m, duration=0.2, randomize=12)
|
||||
wait(0.1)
|
||||
mouse.click(button="left")
|
||||
break
|
||||
return True
|
||||
return False
|
||||
|
||||
def start_hell_game(self) -> bool:
|
||||
"""
|
||||
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)
|
||||
|
||||
def start_hell_game(self):
|
||||
# expects to be in hero selection screen
|
||||
Logger.debug(f"Searching for Play Btn...")
|
||||
# TODO: roi is with respect to 1920x1080
|
||||
roi = [390, 760, 1150, 300]
|
||||
while 1:
|
||||
_, pos = self._template_finder.search_and_wait("PLAY_BTN", roi=roi)
|
||||
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 currently creating a game in online mode!")
|
||||
custom_mouse.move(x, y, duration=(random.random() * 0.2 + 0.5), randomize=5)
|
||||
mouse.click(button="left")
|
||||
break
|
||||
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
|
||||
|
||||
Logger.debug("Searching for Hell Btn...")
|
||||
# TODO: roi is with respect to 1920x1080
|
||||
roi = [550, 100, 1000, 800]
|
||||
while 1:
|
||||
_, pos = self._template_finder.search_and_wait("HELL_BTN", roi=roi)
|
||||
# sanity x y check based on 1920x1080
|
||||
# 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]:
|
||||
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 + 0.5), randomize=5)
|
||||
mouse.click(button="left")
|
||||
break
|
||||
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]:
|
||||
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)
|
||||
mouse.click(button="left")
|
||||
else:
|
||||
Logger.debug("Sanity position check on hell btn failed")
|
||||
return False
|
||||
|
||||
# check for server issue
|
||||
time.sleep(1.0)
|
||||
wait(2.0)
|
||||
server_issue, _ = self._template_finder.search("SERVER_ISSUES", self._screen.grab())
|
||||
if server_issue:
|
||||
Logger.warning("Server issue. waiting 20s")
|
||||
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 + 0.5), randomize=5)
|
||||
custom_mouse.move(x, y, duration=(random.random() * 0.4 + 1.0), randomize=5)
|
||||
mouse.click(button="left")
|
||||
wait(1, 2)
|
||||
keyboard.send("esc")
|
||||
wait(16, 18)
|
||||
self.start_hell_game()
|
||||
wait(18, 22)
|
||||
return self.start_hell_game()
|
||||
else:
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _slot_has_item(slot_img: np.ndarray) -> bool:
|
||||
"""
|
||||
Check if a specific slot in the inventory has an item or not based on color
|
||||
:param slot_img: Image of the slot
|
||||
:return: Bool if there is an item or not
|
||||
"""
|
||||
slot_img = cv2.cvtColor(slot_img, cv2.COLOR_BGR2HSV)
|
||||
avg_brightness = np.average(slot_img[:, :, 2])
|
||||
# TODO: magic param, move to param file (Could go as low as 11.0, but better be save, otherwise bot will stop cause "stash is full" if fails)
|
||||
return avg_brightness > 16.0
|
||||
|
||||
def _get_slot_pos_and_img(self, img: np.ndarray, column: int, row: int):
|
||||
def _get_slot_pos_and_img(self, img: np.ndarray, column: int, row: int) -> Tuple[Tuple[int, int], np.ndarray]:
|
||||
"""
|
||||
Get the pos and img of a specific slot position in Inventory. Inventory must be open in the image.
|
||||
:param img: Image from screen.grab() not cut
|
||||
:param column: Column in the Inventory
|
||||
:param row: Row in the Inventory
|
||||
:return: Returns position and image of the cut area as such: [[x, y], img]
|
||||
"""
|
||||
top_left_slot = (self._config.ui_pos["inventory_top_left_slot_x"], self._config.ui_pos["inventory_top_left_slot_y"])
|
||||
slot_width = self._config.ui_pos["slot_width"]
|
||||
slot_height= self._config.ui_pos["slot_height"]
|
||||
slot = (top_left_slot[0] + slot_width * column, top_left_slot[1] + slot_height * row)
|
||||
min_x = slot[0] + 7
|
||||
max_x = slot[0] + slot_width - 7
|
||||
min_y = slot[1] + 7
|
||||
max_y = slot[1] + slot_height - 7
|
||||
# decrease size to make sure not to have any borders of the slot in the image
|
||||
offset_w = int(slot_width * 0.12)
|
||||
offset_h = int(slot_height * 0.12)
|
||||
min_x = slot[0] + offset_w
|
||||
max_x = slot[0] + slot_width - offset_w
|
||||
min_y = slot[1] + offset_h
|
||||
max_y = slot[1] + slot_height - offset_h
|
||||
slot_img = img[min_y:max_y, min_x:max_x]
|
||||
center_pos = (int(slot[0] + (slot_width // 2)), int(slot[1] + (slot_height // 2)))
|
||||
return center_pos, slot_img
|
||||
|
||||
def _inventory_has_items(self, img, num_loot_columns) -> bool:
|
||||
def _inventory_has_items(self, img, num_loot_columns: int) -> bool:
|
||||
"""
|
||||
Check if Inventory has any items
|
||||
:param img: Img from screen.grab() with inventory open
|
||||
:param num_loot_columns: Number of columns to check from left
|
||||
:return: Bool if inventory still has items or not
|
||||
"""
|
||||
for column, row in itertools.product(range(num_loot_columns), range(4)):
|
||||
_, slot_img = self._get_slot_pos_and_img(img, column, row)
|
||||
if self._slot_has_item(slot_img):
|
||||
return True
|
||||
return False
|
||||
|
||||
def stash_all_items(self, num_loot_columns):
|
||||
def stash_all_items(self, num_loot_columns: int):
|
||||
"""
|
||||
Stashing all items in inventory. Stash UI must be open when calling the function.
|
||||
:param num_loot_columns: Number of columns used for loot from left
|
||||
"""
|
||||
# TODO: Do not stash portal scrolls and potions but throw them out of inventory on the ground!
|
||||
# then the pickit check for potions and belt free can also be removed
|
||||
Logger.debug("Searching for inventory gold btn...")
|
||||
#TODO: 1920x1080 specific params
|
||||
gold_btn_pos = [self._config.ui_pos["inventory_gold_btn_x"], self._config.ui_pos["inventory_gold_btn_y"]]
|
||||
inventory_roi = [gold_btn_pos[0] - 120, gold_btn_pos[1] - 60, 400, 150]
|
||||
self._template_finder.search_and_wait("INVENTORY_GOLD_BTN", roi=inventory_roi)
|
||||
if not self._template_finder.search_and_wait("INVENTORY_GOLD_BTN", roi=self._config.ui_roi["gold_btn"], time_out=20)[0]:
|
||||
Logger.error("Could not determine to be in stash menu. Continue...")
|
||||
return
|
||||
Logger.debug("Found inventory gold btn")
|
||||
# select the start stash
|
||||
personal_stash_pos = (self._config.ui_pos["stash_personal_btn_x"], self._config.ui_pos["stash_personal_btn_y"])
|
||||
stash_btn_width = self._config.ui_pos["stash_btn_width"]
|
||||
next_stash_pos = (personal_stash_pos[0] + stash_btn_width * self._curr_stash, personal_stash_pos[1])
|
||||
x_m, y_m = self._screen.convert_screen_to_monitor(next_stash_pos)
|
||||
custom_mouse.move(x_m, y_m, duration=(random.random() * 0.2 + 0.6), randomize=5)
|
||||
custom_mouse.move(x_m, y_m, duration=0.7, randomize=15)
|
||||
mouse.click(button="left")
|
||||
wait(0.3, 0.4)
|
||||
# stash stuff
|
||||
# TODO: Currenlty doesnt work with PostMessage (custom_keyboard) not sure why?!? investigate
|
||||
keyboard.send('ctrl', do_release=False)
|
||||
for column, row in itertools.product(range(num_loot_columns), range(4)):
|
||||
img = self._screen.grab()
|
||||
slot_pos, slot_img = self._get_slot_pos_and_img(img, column, row)
|
||||
if self._slot_has_item(slot_img):
|
||||
x_m, y_m = self._screen.convert_screen_to_monitor(slot_pos)
|
||||
custom_mouse.move(x_m, y_m, duration=(random.random() * 0.1 + 0.2), randomize=3)
|
||||
custom_mouse.move(x_m, y_m, duration=(random.random() * 0.2 + 0.3), randomize=5)
|
||||
wait(0.1, 0.15)
|
||||
mouse.click(button="left")
|
||||
wait(0.4, 0.6)
|
||||
keyboard.send('ctrl', do_press=False)
|
||||
Logger.debug("Check if stash is full")
|
||||
time.sleep(0.6)
|
||||
# move mouse awai from inventory
|
||||
# move mouse away from inventory, for some reason it was sometimes included in the grabed img
|
||||
top_left_slot = (self._config.ui_pos["inventory_top_left_slot_x"], self._config.ui_pos["inventory_top_left_slot_y"])
|
||||
move_to = (top_left_slot[0] - 250, top_left_slot[1] - 250)
|
||||
x, y = self._screen.convert_screen_to_monitor(move_to)
|
||||
@@ -244,11 +307,14 @@ class UiManager():
|
||||
return self.stash_all_items(num_loot_columns)
|
||||
|
||||
Logger.debug("Done stashing")
|
||||
wait(0.5, 0.6)
|
||||
wait(0.4, 0.5)
|
||||
keyboard.send("esc")
|
||||
|
||||
def fill_up_belt_from_inventory(self, num_loot_columns):
|
||||
# Find all pots in the inventory
|
||||
def fill_up_belt_from_inventory(self, num_loot_columns: int):
|
||||
"""
|
||||
Fill up your belt with pots from the inventory e.g. after death. It will open and close invetory by itself!
|
||||
:param num_loot_columns: Number of columns used for loot from left
|
||||
"""
|
||||
keyboard.send(self._config.char["inventory_screen"])
|
||||
wait(0.7, 1.0)
|
||||
img = self._screen.grab()
|
||||
@@ -262,8 +328,8 @@ class UiManager():
|
||||
keyboard.press("shift")
|
||||
for pos in pot_positions:
|
||||
x, y = self._screen.convert_screen_to_monitor(pos)
|
||||
custom_mouse.move(x, y, duration=0.15, randomize=3)
|
||||
wait(0.1)
|
||||
custom_mouse.move(x, y, duration=0.15, randomize=5)
|
||||
wait(0.2, 0.3)
|
||||
mouse.click(button="left")
|
||||
wait(0.3, 0.4)
|
||||
keyboard.release("shift")
|
||||
@@ -281,6 +347,7 @@ if __name__ == "__main__":
|
||||
screen = Screen(config.general["monitor"])
|
||||
template_finder = TemplateFinder(screen)
|
||||
ui_manager = UiManager(screen, template_finder)
|
||||
ui_manager.start_hell_game()
|
||||
# ui_manager.stash_all_items(6)
|
||||
# ui_manager.use_wp(4, 1)
|
||||
ui_manager.fill_up_belt_from_inventory(10)
|
||||
# ui_manager.fill_up_belt_from_inventory(10)
|
||||
|
||||
+7
-6
@@ -3,7 +3,7 @@ import random
|
||||
import ctypes
|
||||
from logger import Logger
|
||||
import cv2
|
||||
import uuid
|
||||
from typing import List, Tuple
|
||||
|
||||
|
||||
def wait(min_seconds, max_seconds = None):
|
||||
@@ -12,11 +12,6 @@ def wait(min_seconds, max_seconds = None):
|
||||
time.sleep(random.random() * (max_seconds - min_seconds) + min_seconds)
|
||||
return
|
||||
|
||||
def get_mac():
|
||||
mac_num = hex(uuid.getnode()).replace('0x', '').upper()
|
||||
mac = ':'.join(mac_num[i: i + 2] for i in range(0, 11, 2))
|
||||
return mac
|
||||
|
||||
def kill_thread(thread):
|
||||
thread_id = thread.ident
|
||||
res = ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, ctypes.py_object(SystemExit))
|
||||
@@ -28,6 +23,12 @@ def cut_roi(img, roi):
|
||||
x, y, width, height = roi
|
||||
return img[y:y+height, x:x+width]
|
||||
|
||||
def is_in_roi(roi: List[float], pos: Tuple[float, float]):
|
||||
x, y, w, h = roi
|
||||
is_in_x_range = x < pos[0] < x + w
|
||||
is_in_y_range = y < pos[1] < y + h
|
||||
return is_in_x_range and is_in_y_range
|
||||
|
||||
def color_filter(img, color_range):
|
||||
hsv_img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
|
||||
color_mask = cv2.inRange(hsv_img, color_range[0], color_range[1])
|
||||
|
||||
@@ -3,14 +3,21 @@
|
||||
; h_min, s_min, v_min, h_max, s_max, v_max
|
||||
white=0,0,240,2,2,255
|
||||
gray=0,0,119,2,2,126
|
||||
blue=119,120,252,121,126,255
|
||||
blue=119,113,252,121,127,255
|
||||
green=59,250,251,61,255,255
|
||||
yellow=29,128,251,31,137,255
|
||||
gold=22,80,201,24,89,216
|
||||
gold=22,80,201,24,89,225
|
||||
orange=20,251,251,23,255,255
|
||||
red=4,251,206,7,255,213
|
||||
|
||||
[ui_pos_1920_1080]
|
||||
screen_width=1920
|
||||
screen_height=1080
|
||||
offset_top=0
|
||||
center_x=960
|
||||
center_y=540
|
||||
; some distance
|
||||
skill_bar_height=100
|
||||
; game creation / ending buttons
|
||||
play_x_online=800
|
||||
play_x_offline=960
|
||||
@@ -69,3 +76,22 @@ wp_act_btn_width=100
|
||||
wp_first_btn_x=340
|
||||
wp_first_btn_y=193
|
||||
wp_btn_height=70
|
||||
; pickit
|
||||
item_dist=400
|
||||
; pather
|
||||
reached_node_dist=150
|
||||
|
||||
[ui_roi_1920_1080]
|
||||
; 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
|
||||
gold_btn=1390,736,400,150
|
||||
health_globe=260,890,320,190
|
||||
mana_globe=1350,890,320,190
|
||||
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
|
||||
Reference in New Issue
Block a user