Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b33135f704 | ||
|
|
67e03fc938 | ||
|
|
3c39cfa5d9 | ||
|
|
be1f1ce08c | ||
|
|
7b7815a021 | ||
|
|
5b70c24c7b | ||
|
|
636745f47b | ||
|
|
4d083bee83 | ||
|
|
fe8c6d5de0 | ||
|
|
857052c75d | ||
|
|
bf7ce99c5f | ||
|
|
b6260327b7 | ||
|
|
8a85f38819 | ||
|
|
c2bd99af18 | ||
|
|
5e43da1f02 |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 55 KiB |
@@ -109,6 +109,7 @@ repair_btn=318,473,90,80
|
||||
inventory=860,343,394,165
|
||||
vendor_stash=29,84,400,400
|
||||
skill_right=664,673,41,41
|
||||
skill_left=574,673,41,41
|
||||
hero_selection_logo=0,0,367,553
|
||||
window_ingame_ref=600,650,90,90
|
||||
no_pickup=5,530,240,65
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
; There is detailed documentation for each parameter in the README.md
|
||||
[general]
|
||||
name=Botty
|
||||
saved_games_folder=
|
||||
monitor=0
|
||||
max_game_length_s=320
|
||||
custom_message_hook=
|
||||
logg_lvl=debug
|
||||
max_game_length_s=380
|
||||
resume_key=f11
|
||||
exit_key=f12
|
||||
auto_settings_key=f9
|
||||
graphic_debugger_key=f10
|
||||
logg_lvl=debug
|
||||
randomize_runs=0
|
||||
difficulty=hell
|
||||
custom_message_hook=
|
||||
discord_status_count=20
|
||||
info_screenshots=1
|
||||
loot_screenshots=0
|
||||
saved_games_folder=
|
||||
|
||||
[routes]
|
||||
run_trav=0
|
||||
@@ -24,48 +24,56 @@ run_shenk=0
|
||||
run_nihlatak=0
|
||||
|
||||
[char]
|
||||
; ==========================
|
||||
; ==== Mandatory Fields ====
|
||||
; ==========================
|
||||
; These configs have to be alligned with your d2r settings and char build
|
||||
type=sorceress
|
||||
casting_frames=11
|
||||
stash_gold=1
|
||||
gold_trav_only=0
|
||||
use_merc=1
|
||||
atk_len_trav=2.0
|
||||
atk_len_pindle=3.0
|
||||
atk_len_eldritch=3.0
|
||||
atk_len_shenk=4.0
|
||||
atk_len_nihlatak=4.0
|
||||
num_loot_columns=5
|
||||
take_health_potion=0.8
|
||||
take_mana_potion=0.6
|
||||
take_rejuv_potion_health=0.4
|
||||
take_rejuv_potion_mana=0.1
|
||||
heal_merc=0.5
|
||||
heal_rejuv_merc=0.2
|
||||
chicken=0.2
|
||||
merc_chicken=0
|
||||
show_items=alt
|
||||
inventory_screen=i
|
||||
;note: stand_still can not be the default "shift" as it would interfere with merc healing
|
||||
stand_still=capslock
|
||||
force_move=e
|
||||
tp=f9
|
||||
belt_rows=4
|
||||
;note: stand_still can not be the default "shift" as it would interfere with merc healing
|
||||
stand_still=capslock
|
||||
;note: this is different from the default hotkey as "~" is for many keyboards not reachable without also pressing altgr
|
||||
show_belt=k
|
||||
potion1=1
|
||||
potion2=2
|
||||
potion3=3
|
||||
potion4=4
|
||||
belt_rejuv_columns=2
|
||||
belt_hp_columns=1
|
||||
belt_mp_columns=1
|
||||
pre_buff_every_run=0
|
||||
cta_available=0
|
||||
weapon_switch=x
|
||||
battle_orders=f7
|
||||
battle_command=f8
|
||||
; ==========================
|
||||
; ==== Optional configs ====
|
||||
; ==========================
|
||||
stash_gold=1
|
||||
gold_trav_only=0
|
||||
use_merc=1
|
||||
; Attack length for barbarians should be as high as 8-10 and even 10-12 for trav/shenk
|
||||
atk_len_trav=3.0
|
||||
atk_len_pindle=3.0
|
||||
atk_len_eldritch=3.0
|
||||
atk_len_shenk=4.0
|
||||
atk_len_nihlatak=4.0
|
||||
take_health_potion=0.8
|
||||
take_mana_potion=0.6
|
||||
take_rejuv_potion_health=0.42
|
||||
take_rejuv_potion_mana=0.1
|
||||
heal_merc=0.5
|
||||
heal_rejuv_merc=0.2
|
||||
chicken=0.35
|
||||
merc_chicken=0
|
||||
belt_rejuv_columns=2
|
||||
belt_hp_columns=1
|
||||
belt_mp_columns=1
|
||||
pre_buff_every_run=0
|
||||
|
||||
; Supported runs: Pindle, Eldritch, Shenk
|
||||
; Supported runs: Trav, Pindle, Eld, Shenk, Nihlathak
|
||||
[sorceress]
|
||||
teleport=f1
|
||||
skill_left=f2
|
||||
@@ -74,8 +82,9 @@ frozen_armor=
|
||||
energy_shield=
|
||||
thunder_storm=
|
||||
telekinesis=
|
||||
static=
|
||||
|
||||
; Supported runs: Trav, Pindle, Eld, Shenk, Nihlatak (tele only)
|
||||
; Supported runs: Trav, Pindle, Eld, Shenk, Nihlathak (tele only)
|
||||
[hammerdin]
|
||||
teleport=f1
|
||||
concentration=f2
|
||||
@@ -94,6 +103,16 @@ shadow_warrior=
|
||||
lightning_sentry=f2
|
||||
death_sentry=f3
|
||||
|
||||
; Supported runs: Trav, Pindle, Eld, Shenk, Nihlatak (tele only)
|
||||
; Make sure leap hotkey is set if you do not have Enigma
|
||||
; Ensure Battle Order/Command hotkeys are set above in the [char] section
|
||||
[barbarian]
|
||||
teleport=
|
||||
leap=f6
|
||||
shout=f3
|
||||
war_cry=f5
|
||||
find_item=f4
|
||||
|
||||
[dclone]
|
||||
; Normally have known region IP (that we didn't block) for ip hunting
|
||||
; eg: EU Server expected IP would be 37.244.11.xxx or 37.244.48.xxx and US Server expected IP would be 158.115.222.xxx or 158.115.221.xxx
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
;==================================================================;
|
||||
|
||||
;potions
|
||||
misc_rejuvenation_potion=1
|
||||
misc_rejuvenation_potion=0
|
||||
misc_full_rejuvenation_potion=1
|
||||
misc_super_healing_potion=1
|
||||
misc_super_mana_potion=1
|
||||
misc_greater_healing_potion=0
|
||||
misc_greater_mana_potion=0
|
||||
|
||||
;other
|
||||
misc_tp_scroll=0
|
||||
@@ -83,9 +85,9 @@ rune_16_io=0
|
||||
rune_17_lum=0
|
||||
rune_18_ko=0
|
||||
rune_19_fal=0
|
||||
rune_20_lem=1
|
||||
rune_20_lem=0
|
||||
rune_21_pul=1
|
||||
rune_22_um=2
|
||||
rune_22_um=1
|
||||
rune_23_mal=2
|
||||
rune_24_ist=2
|
||||
rune_25_gul=2
|
||||
@@ -249,11 +251,11 @@ rare_griffon_headdress=0
|
||||
rare_alpha_helm=0
|
||||
|
||||
;elite
|
||||
rare_earth_spirit=1
|
||||
rare_dream_spirit=1
|
||||
rare_sky_spirit=1
|
||||
rare_sun_spirit=1
|
||||
rare_blood_spirit=1
|
||||
rare_earth_spirit=0
|
||||
rare_dream_spirit=0
|
||||
rare_sky_spirit=0
|
||||
rare_sun_spirit=0
|
||||
rare_blood_spirit=0
|
||||
|
||||
|
||||
;---------------------------------------;
|
||||
@@ -317,11 +319,11 @@ rare_sharkskin_boots=0
|
||||
rare_demonhide_boots=0
|
||||
|
||||
;elite
|
||||
rare_myrmidon_greaves=1
|
||||
rare_mirrored_boots=1
|
||||
rare_boneweave_boots=1
|
||||
rare_scarabshell_boots=1
|
||||
rare_wyrmhide_boots=1
|
||||
rare_myrmidon_greaves=0
|
||||
rare_mirrored_boots=0
|
||||
rare_boneweave_boots=0
|
||||
rare_scarabshell_boots=0
|
||||
rare_wyrmhide_boots=0
|
||||
|
||||
|
||||
;---------------------------------------;
|
||||
@@ -343,11 +345,11 @@ rare_battle_gauntlets=0
|
||||
rare_war_gauntlets=0
|
||||
|
||||
;elite
|
||||
rare_bramble_mitts=1
|
||||
rare_vampirebone_gloves=1
|
||||
rare_vambraces=1
|
||||
rare_crusader_gauntlets=1
|
||||
rare_ogre_gauntlets=1
|
||||
rare_bramble_mitts=0
|
||||
rare_vampirebone_gloves=0
|
||||
rare_vambraces=0
|
||||
rare_crusader_gauntlets=0
|
||||
rare_ogre_gauntlets=0
|
||||
|
||||
|
||||
;---------------------------------------;
|
||||
@@ -440,11 +442,11 @@ white_superior_scarab_husk=0
|
||||
white_superior_wyrmhide=0
|
||||
|
||||
;ethereal-socketed
|
||||
eth_armor_archon_plate=1
|
||||
eth_armor_dusk_shroud=1
|
||||
eth_armor_archon_plate=0
|
||||
eth_armor_dusk_shroud=0
|
||||
eth_armor_lacquered_plate=0
|
||||
eth_armor_light_plate=0
|
||||
eth_armor_mage_plate=1
|
||||
eth_armor_mage_plate=0
|
||||
eth_armor_sacred_armor=0
|
||||
eth_armor_shadow_plate=0
|
||||
eth_armor_great_hauberk=0
|
||||
@@ -457,14 +459,14 @@ eth_armor_kraken=0
|
||||
;superior ethereal-socketed
|
||||
eth_superior_archon_plate=1
|
||||
eth_superior_dusk_shroud=1
|
||||
eth_superior_lacquered_plate=1
|
||||
eth_superior_lacquered_plate=3
|
||||
eth_superior_light_plate=0
|
||||
eth_superior_mage_plate=1
|
||||
eth_superior_sacred_armor=1
|
||||
eth_superior_shadow_plate=1
|
||||
eth_superior_sacred_armor=3
|
||||
eth_superior_shadow_plate=3
|
||||
eth_superior_great_hauberk=0
|
||||
eth_superior_wyrmhide=1
|
||||
eth_superior_boneweave=1
|
||||
eth_superior_wyrmhide=0
|
||||
eth_superior_boneweave=0
|
||||
|
||||
|
||||
;---------------------------------------;
|
||||
@@ -488,10 +490,10 @@ white_monarch=0
|
||||
|
||||
;superior white
|
||||
white_superior_crown_shield=0
|
||||
white_superior_monarch=1
|
||||
white_superior_monarch=0
|
||||
|
||||
;ethereal
|
||||
eth_shield_monarch=1
|
||||
eth_shield_monarch=0
|
||||
eth_shield_troll_nest=0
|
||||
|
||||
|
||||
@@ -531,10 +533,10 @@ eth_akaran_rondache=0
|
||||
eth_royal_shield=0
|
||||
|
||||
;superior ethereal-socketed
|
||||
eth_superior_vortex_shield=0
|
||||
eth_superior_vortex_shield=1
|
||||
eth_superior_royal_shield=0
|
||||
eth_superior_sacred_targe=1
|
||||
eth_superior_sacred_rondache=0
|
||||
eth_superior_sacred_rondache=1
|
||||
eth_superior_kurast_shield=0
|
||||
eth_superior_zakarum_shield=0
|
||||
eth_superior_akaran_targe=0
|
||||
@@ -779,13 +781,13 @@ uniq_corpsemourn=0
|
||||
uniq_the_gladiators_bane=0
|
||||
uniq_leviathan=0
|
||||
uniq_arkaines_valor=0
|
||||
uniq_tyraels_might=1
|
||||
uniq_armor_duriel_shell=1
|
||||
uniq_tyraels_might=0
|
||||
uniq_armor_duriel_shell=0
|
||||
uniq_armor_guardian_angel=0
|
||||
uniq_armor_ormus_robes=1
|
||||
uniq_armor_shaftstop=0
|
||||
uniq_armor_skin_of_the_vipermagi=1
|
||||
uniq_armor_skullder_ire=1
|
||||
uniq_armor_skin_of_the_vipermagi=0
|
||||
uniq_armor_skullder_ire=0
|
||||
|
||||
;------------------------------;
|
||||
;======== UNIQUE BELTS ========;
|
||||
@@ -796,8 +798,8 @@ uniq_glooms_trap=0
|
||||
uniq_belt_arachnid_mesh=2
|
||||
uniq_belt_goldwrap=1
|
||||
uniq_belt_razortail=0
|
||||
uniq_belt_snowclash=1
|
||||
uniq_belt_string_of_ears=1
|
||||
uniq_belt_snowclash=0
|
||||
uniq_belt_string_of_ears=0
|
||||
uniq_belt_thundergod_vigor=0
|
||||
uniq_belt_verdungo_hearty_cord=1
|
||||
|
||||
@@ -808,12 +810,12 @@ uniq_hotspur=0
|
||||
uniq_infernostride=0
|
||||
uniq_boots_goblin_toe=0
|
||||
uniq_boots_gore_rider=0
|
||||
uniq_boots_marrowwalk=1
|
||||
uniq_boots_marrowwalk=0
|
||||
uniq_boots_sandstorm_trek=1
|
||||
uniq_boots_shadow_dancer=1
|
||||
uniq_boots_silkweave=1
|
||||
uniq_boots_shadow_dancer=0
|
||||
uniq_boots_silkweave=0
|
||||
uniq_boots_war_traveler=2
|
||||
uniq_boots_waterwalk=1
|
||||
uniq_boots_waterwalk=0
|
||||
|
||||
;------------------------------;
|
||||
;======= UNIQUE GLOVES ========;
|
||||
@@ -834,7 +836,7 @@ uniq_peasant_crown=0
|
||||
uniq_crown_of_thieves=0
|
||||
uniq_helm_andariel_visage=2
|
||||
uniq_helm_crown_of_ages=2
|
||||
uniq_helm_giant_skull=1
|
||||
uniq_helm_giant_skull=0
|
||||
uniq_helm_griffon_eye=2
|
||||
uniq_helm_harlequin_crest=2
|
||||
uniq_helm_kira_guardian=0
|
||||
@@ -870,7 +872,7 @@ uniq_gheeds_fortune=0
|
||||
;------------------------------;
|
||||
;= UNIQUE NECROMANCER SHIELDS =;
|
||||
;------------------------------;
|
||||
uniq_offhand_boneflame=1
|
||||
uniq_offhand_boneflame=0
|
||||
uniq_offhand_darkforce_spawn=1
|
||||
uniq_offhand_homunculus=1
|
||||
|
||||
@@ -879,8 +881,8 @@ uniq_offhand_homunculus=1
|
||||
;------------------------------;
|
||||
uniq_umes_lament=0
|
||||
uniq_arm_of_king_leoric=0
|
||||
uniq_suicide_branch=1
|
||||
uniq_weapon_boneshade=1
|
||||
uniq_suicide_branch=0
|
||||
uniq_weapon_boneshade=0
|
||||
uniq_weapon_death_web=2
|
||||
|
||||
;------------------------------;
|
||||
@@ -925,7 +927,7 @@ uniq_weapon_gimmershred=0
|
||||
;------------------------------;
|
||||
uniq_weapon_death_fathom=2
|
||||
uniq_weapon_eschuta_temper=2
|
||||
uniq_weapon_the_oculus=2
|
||||
uniq_weapon_the_oculus=0
|
||||
|
||||
;------------------------------;
|
||||
;====== UNIQUE POLEARMS =======;
|
||||
|
||||
@@ -24,6 +24,7 @@ from death_manager import DeathManager
|
||||
from char.sorceress import Sorceress
|
||||
from char.trapsin import Trapsin
|
||||
from char.hammerdin import Hammerdin
|
||||
from char.barbarian import Barbarian
|
||||
from run import Pindle, ShenkEld, Trav, Nihlatak
|
||||
from town import TownManager, A3, A4, A5
|
||||
|
||||
@@ -51,6 +52,8 @@ class Bot:
|
||||
self._char: IChar = Hammerdin(self._config.hammerdin, self._config.char, self._screen, self._template_finder, self._ui_manager, self._pather)
|
||||
elif self._config.char["type"] == "trapsin":
|
||||
self._char: IChar = Trapsin(self._config.trapsin, self._config.char, self._screen, self._template_finder, self._ui_manager, self._pather)
|
||||
elif self._config.char["type"] == "barbarian":
|
||||
self._char: IChar = Barbarian(self._config.barbarian, 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)
|
||||
@@ -234,6 +237,7 @@ class Bot:
|
||||
merc_alive = self._template_finder.search(["MERC_A2","MERC_A1","MERC_A5","MERC_A3"], self._screen.grab(), threshold=0.9, roi=self._config.ui_roi["merc_icon"]).valid
|
||||
if not merc_alive and self._config.char["use_merc"]:
|
||||
Logger.info("Resurrect merc")
|
||||
self._game_stats.log_merc_death()
|
||||
self._curr_loc = self._town_manager.resurrect(self._curr_loc)
|
||||
if not self._curr_loc:
|
||||
return self.trigger_or_stop("end_game", failed=True)
|
||||
@@ -294,6 +298,7 @@ class Bot:
|
||||
def on_run_pindle(self):
|
||||
res = False
|
||||
self._do_runs["run_pindle"] = False
|
||||
self._game_stats.update_location("Pindle")
|
||||
self._curr_loc = self._pindle.approach(self._curr_loc)
|
||||
if self._curr_loc:
|
||||
res = self._pindle.battle(not self._pre_buffed)
|
||||
@@ -302,14 +307,16 @@ class Bot:
|
||||
def on_run_shenk(self):
|
||||
res = False
|
||||
self._do_runs["run_shenk"] = False
|
||||
self._game_stats.update_location("Shenk")
|
||||
self._curr_loc = self._shenk.approach(self._curr_loc)
|
||||
if self._curr_loc:
|
||||
res = self._shenk.battle(self._route_config["run_shenk"], not self._pre_buffed)
|
||||
res = self._shenk.battle(self._route_config["run_shenk"], not self._pre_buffed, self._game_stats)
|
||||
self._ending_run_helper(res)
|
||||
|
||||
def on_run_trav(self):
|
||||
res = False
|
||||
self._do_runs["run_trav"] = False
|
||||
self._game_stats.update_location("Travincal")
|
||||
self._curr_loc = self._trav.approach(self._curr_loc)
|
||||
if self._curr_loc:
|
||||
res = self._trav.battle(not self._pre_buffed)
|
||||
@@ -318,6 +325,7 @@ class Bot:
|
||||
def on_run_nihlatak(self):
|
||||
res = False
|
||||
self._do_runs["run_nihlatak"] = False
|
||||
self._game_stats.update_location("Nihlatak")
|
||||
self._curr_loc = self._nihlatak.approach(self._curr_loc)
|
||||
if self._curr_loc:
|
||||
res = self._nihlatak.battle(not self._pre_buffed)
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
import keyboard
|
||||
from utils.custom_mouse import mouse
|
||||
from char import IChar
|
||||
from template_finder import TemplateFinder
|
||||
from ui import UiManager
|
||||
from pather import Pather
|
||||
from logger import Logger
|
||||
from screen import Screen
|
||||
from utils.misc import wait, cut_roi, is_in_roi
|
||||
import time
|
||||
from pather import Pather, Location
|
||||
|
||||
|
||||
class Barbarian(IChar):
|
||||
def __init__(self, skill_hotkeys, char_config, screen: Screen, template_finder: TemplateFinder, ui_manager: UiManager, pather: Pather):
|
||||
Logger.info("Setting up Barbarian")
|
||||
super().__init__(skill_hotkeys, char_config, screen, template_finder, ui_manager)
|
||||
self._pather = pather
|
||||
self._do_pre_move = True
|
||||
# offset shenk final position further to the right and bottom
|
||||
self._pather.offset_node(149, [120, 70])
|
||||
# In case we have a running barb, we want to switch to ???? when moving to the boss
|
||||
# as most likely we will click on some mobs
|
||||
if not self._skill_hotkeys["teleport"]:
|
||||
self._do_pre_move = False
|
||||
|
||||
def _cast_war_cry(self, time_in_s: float):
|
||||
# keyboard.send(self._skill_hotkeys["concentration"])
|
||||
# wait(0.05, 0.1)
|
||||
keyboard.send(self._char_config["stand_still"], do_release=False)
|
||||
wait(0.05, 0.1)
|
||||
if self._skill_hotkeys["war_cry"]:
|
||||
keyboard.send(self._skill_hotkeys["war_cry"])
|
||||
wait(0.05, 0.1)
|
||||
start = time.time()
|
||||
while (time.time() - start) < time_in_s:
|
||||
wait(0.06, 0.08)
|
||||
mouse.press(button="right")
|
||||
wait(0.5, 0.7)
|
||||
mouse.release(button="right")
|
||||
wait(0.01, 0.05)
|
||||
keyboard.send(self._char_config["stand_still"], do_press=False)
|
||||
|
||||
def _do_hork(self, hork_time: int):
|
||||
# Outburst's fine work below
|
||||
# save current skill img
|
||||
wait(0.1)
|
||||
skill_before = cut_roi(self._screen.grab(), self._config.ui_roi["skill_right"])
|
||||
wait(0.5)
|
||||
keyboard.send(self._char_config["weapon_switch"])
|
||||
wait(0.5)
|
||||
#hork
|
||||
if self._skill_hotkeys["find_item"]:
|
||||
keyboard.send(self._skill_hotkeys["find_item"])
|
||||
wait(0.5, 0.15)
|
||||
mouse.move(637, 354)
|
||||
wait(0.5, 0.15)
|
||||
mouse.press(button="right")
|
||||
wait(hork_time)
|
||||
mouse.release(button="right")
|
||||
wait(1)
|
||||
keyboard.send(self._char_config["weapon_switch"])
|
||||
wait(0.5, 0.15)
|
||||
# Make sure that we are back at the previous skill
|
||||
skill_after = cut_roi(self._screen.grab(), self._config.ui_roi["skill_right"])
|
||||
_, max_val, _, _ = cv2.minMaxLoc(cv2.matchTemplate(skill_after, skill_before, cv2.TM_CCOEFF_NORMED))
|
||||
if max_val < 0.96:
|
||||
Logger.warning("Failed to switch weapon, try again")
|
||||
wait(1.2)
|
||||
skill_after = cut_roi(self._screen.grab(), self._config.ui_roi["skill_right"])
|
||||
_, max_val, _, _ = cv2.minMaxLoc(cv2.matchTemplate(skill_after, skill_before, cv2.TM_CCOEFF_NORMED))
|
||||
if max_val < 0.96:
|
||||
keyboard.send(self._char_config["weapon_switch"])
|
||||
wait(0.4)
|
||||
else:
|
||||
Logger.warning("Turns out weapon switch just took a long time. You ever considered getting a new internet provider or to upgrade your pc?")
|
||||
|
||||
def pre_buff(self):
|
||||
# keyboard.send(self._char_config["weapon_switch"])
|
||||
# wait(0.3, 0.35)
|
||||
keyboard.send(self._char_config["battle_command"])
|
||||
wait(0.08, 0.19)
|
||||
mouse.click(button="right")
|
||||
wait(self._cast_duration + 0.08, self._cast_duration + 0.1)
|
||||
keyboard.send(self._char_config["battle_orders"])
|
||||
wait(0.08, 0.19)
|
||||
mouse.click(button="right")
|
||||
wait(self._cast_duration + 0.08, self._cast_duration + 0.1)
|
||||
keyboard.send(self._skill_hotkeys["shout"])
|
||||
wait(0.08, 0.19)
|
||||
mouse.click(button="right")
|
||||
wait(self._cast_duration + 0.08, self._cast_duration + 0.1)
|
||||
# keyboard.send(self._char_config["weapon_switch"])
|
||||
wait(0.3, 0.35)
|
||||
|
||||
def pre_move(self):
|
||||
# select teleport if available
|
||||
super().pre_move()
|
||||
# in case teleport hotkey is not set or teleport can not be used, use leap if set
|
||||
should_cast_leap = self._skill_hotkeys["leap"] and not self._ui_manager.is_left_skill_selected(["LEAP"])
|
||||
can_teleport = self._skill_hotkeys["teleport"] and self._ui_manager.is_right_skill_active()
|
||||
if should_cast_leap and not can_teleport:
|
||||
keyboard.send(self._skill_hotkeys["leap"])
|
||||
wait(0.15, 0.25)
|
||||
|
||||
def _move_and_attack(self, abs_move: tuple[int, int], atk_len: float):
|
||||
pos_m = self._screen.convert_abs_to_monitor(abs_move)
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
self._cast_war_cry(atk_len)
|
||||
|
||||
def kill_pindle(self) -> bool:
|
||||
wait(0.1, 0.15)
|
||||
if self.can_teleport():
|
||||
self._pather.traverse_nodes_fixed("pindle_end", self)
|
||||
else:
|
||||
if not self._do_pre_move:
|
||||
# keyboard.send(self._skill_hotkeys["concentration"])
|
||||
# wait(0.05, 0.15)
|
||||
self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, time_out=1.0, do_pre_move=self._do_pre_move)
|
||||
self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, time_out=0.1)
|
||||
self._cast_war_cry(self._char_config["atk_len_pindle"])
|
||||
wait(0.1, 0.15)
|
||||
self._do_hork(4)
|
||||
return True
|
||||
|
||||
def kill_eldritch(self) -> bool:
|
||||
if self.can_teleport():
|
||||
self._pather.traverse_nodes_fixed("eldritch_end", self)
|
||||
else:
|
||||
if not self._do_pre_move:
|
||||
# keyboard.send(self._skill_hotkeys["concentration"])
|
||||
# wait(0.05, 0.15)
|
||||
self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, time_out=1.0, do_pre_move=self._do_pre_move)
|
||||
wait(0.05, 0.1)
|
||||
self._cast_war_cry(self._char_config["atk_len_eldritch"])
|
||||
wait(0.1, 0.15)
|
||||
self._do_hork(4)
|
||||
return True
|
||||
|
||||
def kill_shenk(self):
|
||||
# if not self._do_pre_move:
|
||||
# keyboard.send(self._skill_hotkeys["concentration"])
|
||||
# wait(0.05, 0.15)
|
||||
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, time_out=1.0, do_pre_move=self._do_pre_move)
|
||||
wait(0.05, 0.1)
|
||||
self._cast_war_cry(self._char_config["atk_len_shenk"])
|
||||
wait(0.1, 0.15)
|
||||
self._do_hork(7)
|
||||
return True
|
||||
|
||||
def kill_council(self) -> bool:
|
||||
# Check out the node screenshot in assets/templates/trav/nodes to see where each node is at
|
||||
atk_len = self._char_config["atk_len_trav"]
|
||||
# Go inside and war cry a bit
|
||||
self._pather.traverse_nodes([228, 229], self, time_out=2.5, force_tp=True)
|
||||
self._cast_war_cry(atk_len)
|
||||
# Move a bit back and another round
|
||||
self._move_and_attack((40, 20), atk_len)
|
||||
# Here we have two different attack sequences depending if tele is available or not
|
||||
if self.can_teleport():
|
||||
# Back to center stairs and more war cry
|
||||
self._pather.traverse_nodes([226], self, time_out=2.5, force_tp=True)
|
||||
self._cast_war_cry(atk_len)
|
||||
# move a bit to the top
|
||||
self._move_and_attack((65, -30), atk_len)
|
||||
else:
|
||||
# Stay inside and cast war cry again moving forward
|
||||
self._move_and_attack((40, 10), atk_len)
|
||||
self._move_and_attack((-40, -20), atk_len)
|
||||
self._do_hork(5)
|
||||
return True
|
||||
|
||||
def kill_nihlatak(self, end_nodes: list[int]) -> bool:
|
||||
# Move close to nilathak
|
||||
self._pather.traverse_nodes(end_nodes, self, time_out=0.8, do_pre_move=False)
|
||||
# move mouse to center (leftover from hammerdin)
|
||||
pos_m = self._screen.convert_abs_to_monitor((0, 0))
|
||||
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
|
||||
self._cast_war_cry(self._char_config["atk_len_nihlatak"] * 0.4)
|
||||
self._cast_war_cry(0.8)
|
||||
self._move_and_attack((30, 15), self._char_config["atk_len_nihlatak"] * 0.3)
|
||||
self._cast_war_cry(0.8)
|
||||
self._move_and_attack((-30, -15), self._char_config["atk_len_nihlatak"] * 0.4)
|
||||
wait(0.1, 0.15)
|
||||
self._cast_war_cry(1.2)
|
||||
self._do_hork(5)
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
import os
|
||||
import keyboard
|
||||
keyboard.add_hotkey('f12', lambda: Logger.info('Force Exit (f12)') or os._exit(1))
|
||||
keyboard.wait("f11")
|
||||
from config import Config
|
||||
from ui_manager import UiManager
|
||||
config = Config()
|
||||
screen = Screen(config.general["monitor"])
|
||||
t_finder = TemplateFinder(screen)
|
||||
pather = Pather(screen, t_finder)
|
||||
ui_manager = UiManager(screen, t_finder)
|
||||
char = Barbarian(config.barbarian, config.char, screen, t_finder, ui_manager, pather)
|
||||
char.kill_council()
|
||||
@@ -23,7 +23,7 @@ class Hammerdin(IChar):
|
||||
self._do_pre_move = False
|
||||
|
||||
def _cast_hammers(self, time_in_s: float, aura: str = "concentration"):
|
||||
if aura in self._skill_hotkeys:
|
||||
if aura in self._skill_hotkeys and self._skill_hotkeys[aura]:
|
||||
keyboard.send(self._skill_hotkeys[aura])
|
||||
wait(0.05, 0.1)
|
||||
keyboard.send(self._char_config["stand_still"], do_release=False)
|
||||
|
||||
@@ -149,13 +149,13 @@ class IChar:
|
||||
keyboard.send(self._char_config["weapon_switch"])
|
||||
wait(0.3, 0.35)
|
||||
keyboard.send(self._char_config["battle_command"])
|
||||
wait(0.08, 0.19)
|
||||
wait(0.1, 0.19)
|
||||
mouse.click(button="right")
|
||||
wait(self._cast_duration + 0.13, self._cast_duration + 0.16)
|
||||
wait(self._cast_duration + 0.16, self._cast_duration + 0.18)
|
||||
keyboard.send(self._char_config["battle_orders"])
|
||||
wait(0.08, 0.19)
|
||||
wait(0.1, 0.19)
|
||||
mouse.click(button="right")
|
||||
wait(self._cast_duration + 0.13, self._cast_duration + 0.16)
|
||||
wait(self._cast_duration + 0.16, self._cast_duration + 0.18)
|
||||
keyboard.send(self._char_config["weapon_switch"])
|
||||
wait(0.3, 0.35)
|
||||
# Make sure that we are back at the previous skill
|
||||
|
||||
@@ -6,11 +6,12 @@ from ui import UiManager
|
||||
from pather import Pather
|
||||
from logger import Logger
|
||||
from screen import Screen
|
||||
from utils.misc import wait
|
||||
from utils.misc import wait, rotate_vec, unit_vector
|
||||
import random
|
||||
import time
|
||||
from typing import Tuple
|
||||
from pather import Location, Pather
|
||||
import numpy as np
|
||||
|
||||
|
||||
class Sorceress(IChar):
|
||||
@@ -55,11 +56,20 @@ class Sorceress(IChar):
|
||||
mouse.click(button="right")
|
||||
wait(self._cast_duration)
|
||||
|
||||
def _cast_static(self):
|
||||
if self._skill_hotkeys["static_field"]:
|
||||
keyboard.send(self._skill_hotkeys["static_field"])
|
||||
wait(0.1, 0.13)
|
||||
start = time.time()
|
||||
while time.time() - start < 1.4:
|
||||
mouse.click(button="right")
|
||||
wait(self._cast_duration)
|
||||
|
||||
def _left_attack(self, cast_pos_abs: Tuple[float, float], delay: float, spray: int = 10):
|
||||
keyboard.send(self._char_config["stand_still"], do_release=False)
|
||||
if self._skill_hotkeys["skill_left"]:
|
||||
keyboard.send(self._skill_hotkeys["skill_left"])
|
||||
for _ in range(6):
|
||||
for _ in range(5):
|
||||
x = cast_pos_abs[0] + (random.random() * 2*spray - spray)
|
||||
y = cast_pos_abs[1] + (random.random() * 2*spray - spray)
|
||||
cast_pos_monitor = self._screen.convert_abs_to_monitor((x, y))
|
||||
@@ -69,7 +79,7 @@ class Sorceress(IChar):
|
||||
mouse.release(button="left")
|
||||
keyboard.send(self._char_config["stand_still"], do_press=False)
|
||||
|
||||
def _main_attack(self, cast_pos_abs: Tuple[float, float], delay: float, spray: float = 10):
|
||||
def _right_attack(self, cast_pos_abs: Tuple[float, float], delay: float, spray: float = 10):
|
||||
keyboard.send(self._skill_hotkeys["skill_right"])
|
||||
x = cast_pos_abs[0] + (random.random() * 2*spray - spray)
|
||||
y = cast_pos_abs[1] + (random.random() * 2*spray - spray)
|
||||
@@ -88,7 +98,7 @@ class Sorceress(IChar):
|
||||
if pindle_pos_abs is not None:
|
||||
cast_pos_abs = [pindle_pos_abs[0] * 0.9, pindle_pos_abs[1] * 0.9]
|
||||
for _ in range(int(self._char_config["atk_len_pindle"])):
|
||||
self._main_attack(cast_pos_abs, delay, 11)
|
||||
self._right_attack(cast_pos_abs, delay, 11)
|
||||
self._left_attack(cast_pos_abs, delay, 11)
|
||||
wait(self._cast_duration, self._cast_duration + 0.2)
|
||||
# Move to items
|
||||
@@ -109,7 +119,7 @@ class Sorceress(IChar):
|
||||
if eld_pos_abs is not None:
|
||||
cast_pos_abs = [eld_pos_abs[0] * 0.9, eld_pos_abs[1] * 0.9]
|
||||
for _ in range(int(self._char_config["atk_len_eldritch"])):
|
||||
self._main_attack(cast_pos_abs, delay, 90)
|
||||
self._right_attack(cast_pos_abs, delay, 90)
|
||||
self._left_attack(cast_pos_abs, delay, 90)
|
||||
wait(self._cast_duration, self._cast_duration + 0.2)
|
||||
# Move to items
|
||||
@@ -130,7 +140,7 @@ class Sorceress(IChar):
|
||||
if shenk_pos_abs is not None:
|
||||
cast_pos_abs = [shenk_pos_abs[0] * 0.9, shenk_pos_abs[1] * 0.9]
|
||||
for _ in range(int(self._char_config["atk_len_shenk"])):
|
||||
self._main_attack(cast_pos_abs, delay, 90)
|
||||
self._right_attack(cast_pos_abs, delay, 90)
|
||||
self._left_attack(cast_pos_abs, delay, 90)
|
||||
wait(self._cast_duration, self._cast_duration + 0.2)
|
||||
# Move to items
|
||||
@@ -138,6 +148,67 @@ class Sorceress(IChar):
|
||||
return True
|
||||
return False
|
||||
|
||||
def kill_council(self) -> bool:
|
||||
delay = [0.2, 0.3]
|
||||
# Check out the node screenshot in assets/templates/trav/nodes to see where each node is at
|
||||
# Go inside cast stuff in general direction
|
||||
self._pather.traverse_nodes([228, 229], self, time_out=2.5, force_tp=True)
|
||||
atk_pos_abs = self._pather.find_abs_node_pos(230, self._screen.grab())
|
||||
if atk_pos_abs is None:
|
||||
atk_pos_abs = [-300, -200]
|
||||
cast_pos_abs = np.array([atk_pos_abs[0] * 0.9, atk_pos_abs[1] * 0.9])
|
||||
self._right_attack(cast_pos_abs, delay, 80)
|
||||
self._cast_static()
|
||||
self._left_attack(cast_pos_abs, delay, 80)
|
||||
self._right_attack((0, 0), delay, 30)
|
||||
self._left_attack(cast_pos_abs, delay, 80)
|
||||
# move a bit back
|
||||
pos_m = self._screen.convert_abs_to_monitor((160, 30))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
atk_pos_abs = self._pather.find_abs_node_pos(229, self._screen.grab())
|
||||
if atk_pos_abs is None:
|
||||
atk_pos_abs = [-200, -80]
|
||||
self._left_attack(cast_pos_abs, delay, 60)
|
||||
self._right_attack(cast_pos_abs, delay, 60)
|
||||
self._right_attack((0, 0), delay, 60)
|
||||
# Move outside
|
||||
# Move a bit back and another round
|
||||
self._pather.traverse_nodes([226], self, time_out=2.5, force_tp=True)
|
||||
cast_pos_abs = np.array([-300, -100])
|
||||
self._left_attack(cast_pos_abs, delay, 60)
|
||||
self._right_attack(cast_pos_abs, delay, 60)
|
||||
self._cast_static()
|
||||
self._right_attack((0, 0), delay, 30)
|
||||
wait(0.4)
|
||||
# move a bit back
|
||||
pos_m = self._screen.convert_abs_to_monitor((100, -100))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
self._right_attack((0, 0), delay, 80)
|
||||
return True
|
||||
|
||||
def kill_nihlatak(self, end_nodes: list[int]) -> bool:
|
||||
# Find nilhlatak position
|
||||
delay = [0.2, 0.3]
|
||||
atk_sequences = int(self._char_config["atk_len_nihlatak"])
|
||||
for i in range(atk_sequences):
|
||||
nihlatak_pos_abs = self._pather.find_abs_node_pos(end_nodes[-1], self._screen.grab())
|
||||
if nihlatak_pos_abs is not None:
|
||||
cast_pos_abs = np.array([nihlatak_pos_abs[0] * 0.9, nihlatak_pos_abs[1] * 0.9])
|
||||
self._right_attack(cast_pos_abs, delay, 90)
|
||||
self._left_attack(cast_pos_abs, delay, 90)
|
||||
# Do some tele "dancing" after each sequence
|
||||
if i < atk_sequences - 1:
|
||||
rot_deg = random.randint(-10, 10) if i % 2 == 0 else random.randint(170, 190)
|
||||
tele_pos_abs = unit_vector(rotate_vec(cast_pos_abs, rot_deg)) * 100
|
||||
pos_m = self._screen.convert_abs_to_monitor(tele_pos_abs)
|
||||
self.pre_move()
|
||||
self.move(pos_m)
|
||||
# Move to items
|
||||
self._pather.traverse_nodes(end_nodes, self, time_out=0.8)
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import os
|
||||
@@ -152,5 +223,5 @@ if __name__ == "__main__":
|
||||
pather = Pather(screen, t_finder)
|
||||
ui_manager = UiManager(screen, t_finder)
|
||||
char = Sorceress(config.sorceress, config.char, screen, t_finder, ui_manager, pather)
|
||||
char.pre_buff()
|
||||
pather.traverse_nodes_fixed("trav_safe_dist", char)
|
||||
#char.pre_buff()
|
||||
char.kill_council()
|
||||
|
||||
@@ -52,7 +52,7 @@ class Trapsin(IChar):
|
||||
keyboard.send(self._char_config["stand_still"], do_press=False)
|
||||
|
||||
|
||||
def _main_attack(self, cast_pos_abs: Tuple[float, float], delay: float, spray: float = 10):
|
||||
def _right_attack(self, cast_pos_abs: Tuple[float, float], delay: float, spray: float = 10):
|
||||
keyboard.send(self._skill_hotkeys["lightning_sentry"])
|
||||
x = cast_pos_abs[0] + (random.random() * 2*spray - spray)
|
||||
y = cast_pos_abs[1] + (random.random() * 2*spray - spray)
|
||||
@@ -77,7 +77,7 @@ class Trapsin(IChar):
|
||||
if pindle_pos_abs is not None:
|
||||
cast_pos_abs = [pindle_pos_abs[0] * 0.9, pindle_pos_abs[1] * 0.9]
|
||||
for _ in range(int(self._char_config["atk_len_pindle"])):
|
||||
self._main_attack(cast_pos_abs, delay, 11)
|
||||
self._right_attack(cast_pos_abs, delay, 11)
|
||||
self._left_attack(cast_pos_abs, delay, 11)
|
||||
wait(self._cast_duration, self._cast_duration + 0.2)
|
||||
# Move to items
|
||||
@@ -98,7 +98,7 @@ class Trapsin(IChar):
|
||||
if eld_pos_abs is not None:
|
||||
cast_pos_abs = [eld_pos_abs[0] * 0.9, eld_pos_abs[1] * 0.9]
|
||||
for _ in range(int(self._char_config["atk_len_eldritch"])):
|
||||
self._main_attack(cast_pos_abs, delay, 90)
|
||||
self._right_attack(cast_pos_abs, delay, 90)
|
||||
self._left_attack(cast_pos_abs, delay, 90)
|
||||
wait(self._cast_duration, self._cast_duration + 0.2)
|
||||
# Move to items
|
||||
@@ -119,7 +119,7 @@ class Trapsin(IChar):
|
||||
if shenk_pos_abs is not None:
|
||||
cast_pos_abs = [shenk_pos_abs[0] * 0.9, shenk_pos_abs[1] * 0.9]
|
||||
for _ in range(int(self._char_config["atk_len_shenk"])):
|
||||
self._main_attack(cast_pos_abs, delay, 90)
|
||||
self._right_attack(cast_pos_abs, delay, 90)
|
||||
self._left_attack(cast_pos_abs, delay, 90)
|
||||
wait(self._cast_duration, self._cast_duration + 0.2)
|
||||
# Move to items
|
||||
|
||||
@@ -112,6 +112,10 @@ class Config:
|
||||
self.trapsin = self._config["trapsin"]
|
||||
if "trapsin" in self._custom:
|
||||
self.trapsin.update(self._custom["trapsin"])
|
||||
|
||||
self.barbarian = self._config["barbarian"]
|
||||
if "barbarian" in self._custom:
|
||||
self.barbarian.update(self._custom["barbarian"])
|
||||
|
||||
self.advanced_options = {
|
||||
"pathing_delay_factor": min(max(int(self._select_val("advanced_options", "pathing_delay_factor")), 1), 10),
|
||||
|
||||
@@ -21,8 +21,20 @@ class GameStats:
|
||||
self._game_counter = 0
|
||||
self._chicken_counter = 0
|
||||
self._death_counter = 0
|
||||
self._merc_death_counter = 0
|
||||
self._runs_failed = 0
|
||||
self._failed_game_time = 0
|
||||
self._location = None
|
||||
|
||||
def update_location(self, loc: str):
|
||||
if self._location != loc:
|
||||
self._location = str(loc)
|
||||
|
||||
def get_location_msg(self):
|
||||
if self._location is not None:
|
||||
return f" at {self._location}"
|
||||
else:
|
||||
return ""
|
||||
|
||||
def _send_message_thread(self, msg: str):
|
||||
if self._config.general["custom_message_hook"]:
|
||||
@@ -33,21 +45,25 @@ class GameStats:
|
||||
send_message_thread.daemon = True
|
||||
send_message_thread.start()
|
||||
|
||||
def log_item_pickup(self, item_name: str, send_message: bool, area: str = None):
|
||||
def log_item_pickup(self, item_name: str, send_message: bool):
|
||||
self._picked_up_items.append(item_name)
|
||||
if send_message:
|
||||
msg = f"{self._config.general['name']}: Found {item_name}"
|
||||
if area is not None:
|
||||
msg += f" at {area}"
|
||||
msg = f"{self._config.general['name']}: Found {item_name}{self.get_location_msg()}"
|
||||
self._send_message_thread(msg)
|
||||
|
||||
def log_death(self):
|
||||
self._death_counter += 1
|
||||
self._send_message_thread(f"{self._config.general['name']}: You have died")
|
||||
msg = f"{self._config.general['name']}: You have died{self.get_location_msg()}"
|
||||
self._send_message_thread(msg)
|
||||
|
||||
def log_chicken(self):
|
||||
self._chicken_counter += 1
|
||||
self._send_message_thread(f"{self._config.general['name']}: You have chickened")
|
||||
msg = f"{self._config.general['name']}: You have chickened{self.get_location_msg()}"
|
||||
self._send_message_thread(msg)
|
||||
|
||||
def log_merc_death(self):
|
||||
self._merc_death_counter += 1
|
||||
self._send_message_thread(f"{self._config.general['name']}: Merc has died")
|
||||
|
||||
def log_start_game(self):
|
||||
if self._game_counter > 0:
|
||||
@@ -107,6 +123,7 @@ class GameStats:
|
||||
Avg Game Length: {avg_length_str}
|
||||
Chickens: {self._chicken_counter}
|
||||
Deaths: {self._death_counter}
|
||||
Merc deaths: {self._merc_death_counter}
|
||||
Failed runs: {self._runs_failed}
|
||||
''')
|
||||
return msg
|
||||
@@ -126,4 +143,4 @@ class GameStats:
|
||||
|
||||
if __name__ == "__main__":
|
||||
game_stats = GameStats()
|
||||
game_stats.log_item_pickup("rune_12", True, "shenk")
|
||||
game_stats.log_item_pickup("rune_12", True)
|
||||
|
||||
@@ -23,11 +23,11 @@ class PickIt:
|
||||
self._config = Config()
|
||||
self._last_closest_item: Item = None
|
||||
|
||||
def pick_up_items(self, char: IChar, area: str = None) -> bool:
|
||||
def pick_up_items(self, char: IChar, is_at_trav: bool = False) -> bool:
|
||||
"""
|
||||
Pick up all items with specified char
|
||||
:param char: The character used to pick up the item
|
||||
:param area: Specify area where item was picked up for logging
|
||||
:param is_at_trav: Dirty hack to reduce gold pickup only to trav area, should be removed once we can determine the amount of gold reliably
|
||||
:return: Bool if any items were picked up or not. (Does not account for picking up scrolls and pots)
|
||||
"""
|
||||
found_nothing = 0
|
||||
@@ -64,8 +64,7 @@ class PickIt:
|
||||
item_list = [x for x in item_list if "rejuvenation_potion" not in x.name]
|
||||
|
||||
# TODO: Hacky solution for trav only gold pickup, hope we can soon read gold ammount and filter by that...
|
||||
at_trav = area is not None and "trav" in area.lower()
|
||||
if self._config.char["gold_trav_only"] and not at_trav:
|
||||
if self._config.char["gold_trav_only"] and not is_at_trav:
|
||||
item_list = [x for x in item_list if "misc_gold" not in x.name]
|
||||
|
||||
if len(item_list) == 0:
|
||||
@@ -131,7 +130,7 @@ class PickIt:
|
||||
# send log to discord
|
||||
if found_items and closest_item.name not in picked_up_items:
|
||||
Logger.info(f"Picking up: {closest_item.name} ({closest_item.score*100:.1f}% confidence)")
|
||||
self._game_stats.log_item_pickup(closest_item.name, self._config.items[closest_item.name] == 2, area)
|
||||
self._game_stats.log_item_pickup(closest_item.name, self._config.items[closest_item.name] == 2)
|
||||
picked_up_items.append(closest_item.name)
|
||||
else:
|
||||
char.pre_move()
|
||||
|
||||
@@ -244,7 +244,7 @@ class Pather:
|
||||
if score > 0.15:
|
||||
i += 1
|
||||
else:
|
||||
Logger.debug(f"Teleport cancel detected. Try same teleport action again. ({score})")
|
||||
Logger.debug(f"Teleport cancel detected. Try same teleport action again. ({score:.4f})")
|
||||
# if type(key) == str and ("_save_dist" in key or "_end" in key):
|
||||
# cv2.imwrite(f"./info_screenshots/nil_path_{key}_" + time.strftime("%Y%m%d_%H%M%S") + ".png", self._screen.grab())
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ class Nihlatak:
|
||||
if not self._char.select_by_template(["NI1_STAIRS", "NI1_STAIRS_2", "NI1_STAIRS_3", "NI1_STAIRS_4"], found_loading_screen_func, threshold=0.63, time_out=4):
|
||||
return False
|
||||
# Wait until templates in lvl 2 entrance are found
|
||||
if not self._template_finder.search_and_wait(["NI2_SEARCH_0", "NI2_SEARCH_1"], threshold=0.8, time_out=20).valid:
|
||||
if not self._template_finder.search_and_wait(["NI2_SEARCH_0", "NI2_SEARCH_1", "NI2_SEARCH_2"], threshold=0.8, time_out=20).valid:
|
||||
return False
|
||||
@dataclass
|
||||
class EyeCheckData:
|
||||
@@ -67,7 +67,7 @@ class Nihlatak:
|
||||
end_nodes: list[int]
|
||||
|
||||
check_arr = [
|
||||
EyeCheckData(["NI2_A_SAFE_DIST", "NI2_A_NOATTACK"], "ni2_circle_a", [500], [501]),
|
||||
EyeCheckData(["NI2_A_SAFE_DIST", "NI2_A_NOATTACK", "NI2_A_SAFE_DIST_2", "NI2_A_NOATTACK_2"], "ni2_circle_a", [500], [501]),
|
||||
EyeCheckData(["NI2_B_SAFE_DIST", "NI2_B_NOATTACK", "NI2_B_NOATTACK_2"], "ni2_circle_b", [505], [506]),
|
||||
EyeCheckData(["NI2_C_SAFE_DIST", "NI2_C_NOATTACK"], "ni2_circle_c", [510], [511]),
|
||||
EyeCheckData(["NI2_D_SAFE_DIST", "NI2_D_NOATTACK"], "ni2_circle_d", [515], [516, 517]),
|
||||
|
||||
@@ -56,5 +56,5 @@ class Pindle:
|
||||
return False
|
||||
self._char.kill_pindle()
|
||||
wait(0.2, 0.3)
|
||||
picked_up_items = self._pickit.pick_up_items(self._char, "Pindle")
|
||||
picked_up_items = self._pickit.pick_up_items(self._char)
|
||||
return (Location.A5_PINDLE_END, picked_up_items)
|
||||
|
||||
@@ -37,8 +37,9 @@ class ShenkEld:
|
||||
self._ui_manager.use_wp(5, 1)
|
||||
return Location.A5_ELDRITCH_START
|
||||
|
||||
def battle(self, do_shenk: bool, do_pre_buff: bool) -> Union[bool, tuple[Location, bool]]:
|
||||
def battle(self, do_shenk: bool, do_pre_buff: bool, game_stats) -> Union[bool, tuple[Location, bool]]:
|
||||
# Eldritch
|
||||
game_stats.update_location("Eldritch")
|
||||
if not self._template_finder.search_and_wait(["ELDRITCH_0", "ELDRITCH_START"], threshold=0.65, time_out=20).valid:
|
||||
return False
|
||||
if do_pre_buff:
|
||||
@@ -51,11 +52,12 @@ class ShenkEld:
|
||||
self._char.kill_eldritch()
|
||||
loc = Location.A5_ELDRITCH_END
|
||||
wait(0.2, 0.3)
|
||||
picked_up_items = self._pickit.pick_up_items(self._char, "Eldritch")
|
||||
picked_up_items = self._pickit.pick_up_items(self._char)
|
||||
|
||||
# Shenk
|
||||
if do_shenk:
|
||||
Logger.info("Run Shenk")
|
||||
game_stats.update_location("Shenk")
|
||||
self._curr_loc = Location.A5_SHENK_START
|
||||
# No force move, otherwise we might get stuck at stairs!
|
||||
if not self._pather.traverse_nodes((Location.A5_SHENK_START, Location.A5_SHENK_SAFE_DIST), self._char):
|
||||
@@ -63,6 +65,6 @@ class ShenkEld:
|
||||
self._char.kill_shenk()
|
||||
loc = Location.A5_SHENK_END
|
||||
wait(1.9, 2.4) # sometimes merc needs some more time to kill shenk...
|
||||
picked_up_items |= self._pickit.pick_up_items(self._char, "Shenk")
|
||||
picked_up_items |= self._pickit.pick_up_items(self._char)
|
||||
|
||||
return (loc, picked_up_items)
|
||||
|
||||
@@ -49,14 +49,14 @@ class Trav:
|
||||
if not self._pather.traverse_nodes((Location.A3_TRAV_START, Location.A3_TRAV_CENTER_STAIRS), self._char, force_move=True):
|
||||
return False
|
||||
self._char.kill_council()
|
||||
picked_up_items = self._pickit.pick_up_items(self._char, "Travincal")
|
||||
picked_up_items = self._pickit.pick_up_items(self._char, is_at_trav=True)
|
||||
wait(0.2, 0.3)
|
||||
# If we can teleport we want to move back inside and also check loot there
|
||||
if self._char.can_teleport():
|
||||
if not self._pather.traverse_nodes([229], self._char, time_out=2.5):
|
||||
self._pather.traverse_nodes([228, 229], self._char, time_out=2.5)
|
||||
picked_up_items |= self._pickit.pick_up_items(self._char, "Travincal")
|
||||
picked_up_items |= self._pickit.pick_up_items(self._char, is_at_trav=True)
|
||||
# if we picked up items lets make sure we go back to the center to not hide the tp
|
||||
if picked_up_items:
|
||||
self._pather.traverse_nodes([229], self._char, time_out=2.5)
|
||||
self._pather.traverse_nodes([230], self._char, time_out=2.5)
|
||||
return (Location.A3_TRAV_CENTER_STAIRS, picked_up_items)
|
||||
|
||||
@@ -64,6 +64,8 @@ class Screen:
|
||||
found_offsets = True
|
||||
break
|
||||
if not found_offsets:
|
||||
if self._config.general["info_screenshots"]:
|
||||
cv2.imwrite("./info_screenshots/error_d2r_window_now_found_" + time.strftime("%Y%m%d_%H%M%S") + ".png", self.grab())
|
||||
Logger.error("Could not find D2R logo at hero selection or template for ingame, shutting down")
|
||||
Logger.error(f"The max score that could be found was: ({debug_max_val*100:.1f}% confidence)")
|
||||
raise RuntimeError("Could not determine window offset. Please make sure you have the D2R window " +
|
||||
|
||||
@@ -156,4 +156,4 @@ if __name__ == "__main__":
|
||||
a4 = A4(screen, template_finder, pather, char, npc_manager)
|
||||
a3 = A3(screen, template_finder, pather, char, npc_manager)
|
||||
town_manager = TownManager(template_finder, ui_manager, item_finder, a3, a4, a5)
|
||||
print(town_manager.stash(Location.A3_STASH_WP))
|
||||
print(town_manager.stash(Location.A5_STASH))
|
||||
|
||||
@@ -24,7 +24,9 @@ class BeltManager:
|
||||
"misc_rejuvenation_potion": "rejuv",
|
||||
"misc_full_rejuvenation_potion": "rejuv",
|
||||
"misc_super_healing_potion": "health",
|
||||
"misc_super_mana_potion": "mana"
|
||||
"misc_greater_healing_potion": "health",
|
||||
"misc_super_mana_potion": "mana",
|
||||
"misc_greater_mana_potion": "mana"
|
||||
}
|
||||
|
||||
def get_pot_needs(self):
|
||||
@@ -160,7 +162,7 @@ class BeltManager:
|
||||
pot_positions = []
|
||||
for column, row in itertools.product(range(num_loot_columns), range(4)):
|
||||
center_pos, slot_img = UiManager.get_slot_pos_and_img(self._config, img, column, row)
|
||||
found = self._template_finder.search(["SUPER_HEALING_POTION", "SUPER_MANA_POTION", "FULL_REJUV_POTION", "REJUV_POTION"], slot_img, threshold=0.9).valid
|
||||
found = self._template_finder.search(["GREATER_HEALING_POTION", "GREATER_MANA_POTION", "SUPER_HEALING_POTION", "SUPER_MANA_POTION", "FULL_REJUV_POTION", "REJUV_POTION"], slot_img, threshold=0.9).valid
|
||||
if found:
|
||||
pot_positions.append(center_pos)
|
||||
keyboard.press("shift")
|
||||
|
||||
@@ -77,6 +77,15 @@ class UiManager():
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_left_skill_selected(self, template_list: List[str]) -> bool:
|
||||
"""
|
||||
:return: Bool if skill is currently the selected skill on the left skill slot.
|
||||
"""
|
||||
for template in template_list:
|
||||
if self._template_finder.search(template, self._screen.grab(), threshold=0.87, roi=self._config.ui_roi["skill_left"]).valid:
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_overburdened(self) -> bool:
|
||||
"""
|
||||
:return: Bool if the last pick up overburdened your char. Must be called right after picking up an item.
|
||||
@@ -157,19 +166,16 @@ class UiManager():
|
||||
# it returns a bool value (True or False) if the button was found, and the position of it
|
||||
# roi = Region of interest. It reduces the search area and can be adapted within game.ini
|
||||
# by running >> python src/screen.py you can visualize all of the currently set region of interests
|
||||
found_btn = self._template_finder.search(["PLAY_BTN","PLAY_BTN_GRAY"], img, roi=self._config.ui_roi["offline_btn"], threshold=0.8, best_match=True)
|
||||
found_btn = self._template_finder.search(["PLAY_BTN", "PLAY_BTN_GRAY"], img, roi=self._config.ui_roi["offline_btn"], threshold=0.8, best_match=True)
|
||||
if found_btn.name == "PLAY_BTN":
|
||||
# We need to convert the position to monitor coordinates (e.g. if someone is using 2 monitors or windowed mode)
|
||||
x, y = self._screen.convert_screen_to_monitor(found_btn.position)
|
||||
Logger.debug(f"Found Play Btn")
|
||||
# move the mouse to the play button and randomize the position a bit. +-35 pixel in x direction, +-7 pixel in y direction
|
||||
mouse.move(x, y, randomize=[35, 7], delay_factor=[1.0, 1.8])
|
||||
wait(0.1, 0.15)
|
||||
# click!
|
||||
mouse.click(button="left")
|
||||
break
|
||||
else:
|
||||
# Might be in online mode?
|
||||
found_btn = self._template_finder.search("PLAY_BTN", img, roi=self._config.ui_roi["online_btn"], threshold=0.8)
|
||||
if found_btn.valid:
|
||||
Logger.error("Botty only works for single player. Please switch to offline mode and restart botty!")
|
||||
@@ -305,7 +311,7 @@ class UiManager():
|
||||
Logger.debug("Found inventory gold btn")
|
||||
# stash gold
|
||||
if self._config.char["stash_gold"]:
|
||||
inventory_no_gold = self._template_finder.search("INVENTORY_NO_GOLD", self._screen.grab(), roi=self._config.ui_roi["inventory_gold"], threshold=0.97)
|
||||
inventory_no_gold = self._template_finder.search("INVENTORY_NO_GOLD", self._screen.grab(), roi=self._config.ui_roi["inventory_gold"], threshold=0.83)
|
||||
if inventory_no_gold.valid:
|
||||
Logger.debug("Skipping gold stashing")
|
||||
else:
|
||||
@@ -320,10 +326,14 @@ class UiManager():
|
||||
wait(0.4, 0.6)
|
||||
keyboard.send("enter") #if stash already full of gold just nothing happens -> gold stays on char -> no popup window
|
||||
wait(1.0, 1.2)
|
||||
inventory_no_gold = self._template_finder.search("INVENTORY_NO_GOLD", self._screen.grab(), roi=self._config.ui_roi["inventory_gold"], threshold=0.97)
|
||||
# move cursor away from button to interfere with screen grab
|
||||
mouse.move(-120, 0, absolute=False, randomize=15)
|
||||
inventory_no_gold = self._template_finder.search("INVENTORY_NO_GOLD", self._screen.grab(), roi=self._config.ui_roi["inventory_gold"], threshold=0.83)
|
||||
if not inventory_no_gold.valid:
|
||||
Logger.info("Stash tab is full of gold, selecting next stash tab.")
|
||||
self._curr_stash["gold"] += 1
|
||||
if self._config.general["info_screenshots"]:
|
||||
cv2.imwrite("./info_screenshots/info_gold_stash_full_" + time.strftime("%Y%m%d_%H%M%S") + ".png", self._screen.grab())
|
||||
if self._curr_stash["gold"] > 3:
|
||||
# turn of gold pickup
|
||||
self._config.char["stash_gold"] = False
|
||||
@@ -537,4 +547,4 @@ if __name__ == "__main__":
|
||||
template_finder = TemplateFinder(screen)
|
||||
item_finder = ItemFinder(config)
|
||||
ui_manager = UiManager(screen, template_finder)
|
||||
ui_manager.stash_all_items(5, item_finder)
|
||||
ui_manager.start_game()
|
||||
|
||||
@@ -6,6 +6,7 @@ from logger import Logger
|
||||
import cv2
|
||||
from typing import List, Tuple
|
||||
import os
|
||||
from math import cos, sin, dist
|
||||
|
||||
|
||||
def wait(min_seconds, max_seconds = None):
|
||||
@@ -65,3 +66,11 @@ def list_files_in_folder(path: str):
|
||||
for name in files:
|
||||
r.append(os.path.join(root, name))
|
||||
return r
|
||||
|
||||
def rotate_vec(vec: np.ndarray, deg: float) -> np.ndarray:
|
||||
theta = np.deg2rad(deg)
|
||||
rot_matrix = np.array([[cos(theta), -sin(theta)], [sin(theta), cos(theta)]])
|
||||
return np.dot(rot_matrix, vec)
|
||||
|
||||
def unit_vector(vec: np.ndarray) -> np.ndarray:
|
||||
return vec / dist(vec, (0, 0))
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = '0.4.4-dev'
|
||||
__version__ = '0.5.1-dev'
|
||||