Resolve merge conflicts, keeping local Win10 specific changes

This commit is contained in:
FiskenPoul
2026-06-05 21:47:11 +02:00
parent 7dcbb5284b
commit 95f520e968
22 changed files with 800 additions and 738 deletions
+1 -1
View File
@@ -15,7 +15,7 @@
"VSync": 1, "VSync": 1,
"Framerate Cap": 60, "Framerate Cap": 60,
"Framerate Target": 0, "Framerate Target": 0,
"Window Mode": 0, "Window Mode": 1,
"Graphic Presets": 4, "Graphic Presets": 4,
"Texture Quality": 4, "Texture Quality": 4,
"Texture Anisotropy": 0, "Texture Anisotropy": 0,
+542 -429
View File
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -100,8 +100,7 @@ mana_slice=961,610,7,101
cut_skill_bar=0,0,1280,653 cut_skill_bar=0,0,1280,653
reduce_to_center=120,60,1040,540 reduce_to_center=120,60,1040,540
search_npcs=120,0,1040,620 search_npcs=120,0,1040,620
merc_icon=5,5,60,60 merc_icon=10,9,56,56
merc_panel=0,0,450,100
loading_left_black=0,0,350,720 loading_left_black=0,0,350,720
death=444,198,397,71 death=444,198,397,71
tp_search=353,120,547,400 tp_search=353,120,547,400
+45 -90
View File
@@ -8,19 +8,9 @@
; bnet_pass=${BOTTY_BNET_PASS} ; bnet_pass=${BOTTY_BNET_PASS}
; char_name=${BOTTY_CHAR_NAME} ; char_name=${BOTTY_CHAR_NAME}
; difficulty: game difficulty to create ("normal", "nightmare", "hell") ; difficulty: game difficulty to create ("normal", "nightmare", "hell")
; difficulty=hell
; Hammerdin difficulty guide (CTA build with Conviction aura):
; NORMAL - Very easy. 1-2h Hammer kills most packs. No CTA needed.
; NIGHTMARE - Recommended starting point. Bosses hit ~100-200 dmg.
; CTA + Conviction drops resists. Need decent armor (ED/HR).
; Gear targets: ~200 AR, ~15 ED, ~30% HR, ~50% FCR on hammer.
; HELL - Bosses hit 400-800+ dmg per swing. Conviction is mandatory.
; Gear targets: ~350+ AR, ~20+ ED, ~50%+ HR, ~60%+ FCR,
; ~150% IAS on weapon. Full Rejuv belt recommended.
; If you chicken/die repeatedly, drop to Nightmare first.
difficulty=nightmare
; name: bot profile name used in logs/messages and mod launch option replacement ; name: bot profile name used in logs/messages and mod launch option replacement
name=fistman name=fiskenersej
; randomize_runs: 0 = run in listed order, 1 = shuffle run order ; randomize_runs: 0 = run in listed order, 1 = shuffle run order
randomize_runs=0 randomize_runs=0
; target_tz: target Terror Zone id (leave as default unless you know the mapping) ; target_tz: target Terror Zone id (leave as default unless you know the mapping)
@@ -32,7 +22,7 @@ level_max_steps=20
; Set to 1 to enable auto-login and auto-launch of D2R on startup. ; Set to 1 to enable auto-login and auto-launch of D2R on startup.
; Credentials below are ONLY used when auto_login=1. ; Credentials below are ONLY used when auto_login=1.
auto_login=1 auto_login=0
bnet_name=${BOTTY_BNET_NAME} bnet_name=${BOTTY_BNET_NAME}
bnet_pass=${BOTTY_BNET_PASS} bnet_pass=${BOTTY_BNET_PASS}
@@ -42,13 +32,13 @@ char_name=${BOTTY_CHAR_NAME}
; messaging ; messaging
; custom_loot_message_hook: optional separate webhook for loot notifications ; custom_loot_message_hook: optional separate webhook for loot notifications
custom_loot_message_hook=${BOTTY_CUSTOM_LOOT_MESSAGE_HOOK} custom_loot_message_hook=https://discord.com/api/webhooks/1510992232939913297/20rbnGmpTWMKBBL6JN0JaE897jLGOmHl6zclJ1AO82o2dtD67YHfAKMD-e4G8DWvUOXV
; custom_message_hook: main webhook for status/death/chicken messages ; custom_message_hook: main webhook for status/death/chicken messages
custom_message_hook=${BOTTY_CUSTOM_MESSAGE_HOOK} custom_message_hook=https://discord.com/api/webhooks/1510992232939913297/20rbnGmpTWMKBBL6JN0JaE897jLGOmHl6zclJ1AO82o2dtD67YHfAKMD-e4G8DWvUOXV
; discord_log_chicken: 1 = send chicken/death style notifications ; discord_log_chicken: 1 = send chicken/death style notifications
discord_log_chicken=1 discord_log_chicken=1
; discord_status_runs: send periodic status every X completed runs (blank/0 disables) ; discord_status_runs: send periodic status every X completed runs (blank/0 disables)
discord_status_runs=10 discord_status_runs=100
; discord_status_count: legacy fallback, send periodic status every X games (blank/0 disables) ; discord_status_count: legacy fallback, send periodic status every X games (blank/0 disables)
discord_status_count=20 discord_status_count=20
; message_api_type: "" disables messaging, "discord" or "generic_api" ; message_api_type: "" disables messaging, "discord" or "generic_api"
@@ -66,14 +56,10 @@ d2r_path=C:\Program Files (x86)\Diablo II Resurrected
; max_consecutive_fails: stop bot after this many failed runs in a row ; max_consecutive_fails: stop bot after this many failed runs in a row
max_consecutive_fails=5 max_consecutive_fails=5
; max_game_length_s: emergency timeout per run/game ; max_game_length_s: emergency timeout per run/game
max_game_length_s=600 max_game_length_s=160
; auto_downgrade_threshold: if combined chickens+deaths exceed this number within 1 hour,
; bot automatically lowers difficulty by one tier (hell->nightmare->normal) and restarts.
; Set to 0 to disable.
auto_downgrade_threshold=40
; if you set this field to 1, botty will attempt to restart d2 after a crash or failure ; if you set this field to 1, botty will attempt to restart d2 after a crash or failure
restart_d2r_when_stuck=1 restart_d2r_when_stuck=0
; hardcore: 1 enables hardcore-safe assumptions in some routines ; hardcore: 1 enables hardcore-safe assumptions in some routines
hardcore=0 hardcore=0
@@ -181,7 +167,7 @@ skill_mistake_chance = 0.015
; run_countess (Act 1 Forgotten Tower) ; run_countess (Act 1 Forgotten Tower)
; run_mephisto (Act 3 Durance of Hate) ; run_mephisto (Act 3 Durance of Hate)
; run_baal (Act 5 Throne of Destruction) ; run_baal (Act 5 Throne of Destruction)
order=run_pindle, run_eldritch_shenk, run_nihlathak, run_arcane, run_diablo, run_vizier, run_trav order=run_trav
[char] [char]
; ========================== ; ==========================
@@ -194,9 +180,9 @@ type=hammerdin
; belt_rows: in-game belt size (2/3/4) ; belt_rows: in-game belt size (2/3/4)
belt_rows=4 belt_rows=4
; casting_frames: your char cast breakpoint (affects action timing) ; casting_frames: your char cast breakpoint (affects action timing)
casting_frames=8 casting_frames=11
; cta_casting_frames: cast breakpoint on CTA swap (if used) ; cta_casting_frames: cast breakpoint on CTA swap (if used)
cta_casting_frames=8 cta_casting_frames=11
; attack_frames: base attack animation timing for non-cast attacks ; attack_frames: base attack animation timing for non-cast attacks
attack_frames=15 attack_frames=15
; cta_available: 1 if Call to Arms swap exists, else 0 ; cta_available: 1 if Call to Arms swap exists, else 0
@@ -225,13 +211,13 @@ show_items=alt
; stand_still cannot be the default "shift" as it would interfere with merc healing ; stand_still cannot be the default "shift" as it would interfere with merc healing
stand_still=capslock stand_still=capslock
; teleport: leave empty if you can't use ; teleport: leave empty if you can't use
teleport=b teleport=f4
town_portal=6 town_portal=f8
; call to arms settings: ; call to arms settings:
; weapon_switch/battle_orders/battle_command only used when cta_available=1 ; weapon_switch/battle_orders/battle_command only used when cta_available=1
weapon_switch=w weapon_switch=w
battle_orders=7 battle_orders=f6
battle_command=8 battle_command=f5
; ========================== ; ==========================
; ==== Optional configs ==== ; ==== Optional configs ====
@@ -240,43 +226,13 @@ stash_gold=1
use_merc=1 use_merc=1
; Attack length for barbarians should be as high as 8-10 and even 10-12 for trav/shenk ; Attack length for barbarians should be as high as 8-10 and even 10-12 for trav/shenk
;
; Hammerdin attack lengths (seconds of hammer spam per boss):
; atk_len_trav = 3.0 (Council of 3 - 3 council members, easy)
; atk_len_pindle = 8.0 (Pindle + Eldritch + Shenk - longest fight)
; Pindle stats by difficulty:
; Normal: 1064-1588 HP, 75% fire, 70% poison
; Nightmare: 4773-5859 HP, 100% poison
; Hell: 13094-16070 HP, 75% fire, 50% cold, 33% light, 100% poison
; With Conviction (-33% res), Hell Pindle = 50% fire, 17% cold, 0% light, 67% poison
; CTA adds +100% dmg to hammer. Need ~60%+ FCR and good IAS to kill in 8s.
; atk_len_nihlathak = 4.0 (Nihlathak - single boss)
; atk_len_eldritch = 3.0 (Eldritch only - single boss)
; atk_len_shenk = 4.0 (Shenk only - single boss)
; atk_len_arc = 2.5 (Summoner in Arcane - single boss)
; atk_len_diablo = 10.0 (Diablo in CS - longest single boss)
; atk_len_countess = 3.0 (Countess - single boss, easy)
; atk_len_andariel = 4.0 (Andariel - single boss)
; atk_len_mephisto = 12.0 (Mephisto - single boss, high HP)
; atk_len_baal = 10.0 (Baal - single boss)
; atk_len_baal_waves = 30.0 (Baal's spawn waves before boss)
;
; Chaos Sanctuary (Diablo run) individual fights:
; atk_len_cs_trashmobs = 2.0 (Trash packs in CS)
; atk_len_diablo_vizier = 2.0 (Vizier of Chaos - seal boss A)
; atk_len_diablo_infector = 4.0 (Infector of Souls - seal boss C)
; atk_len_diablo_deseis = 5.0 (Lord De Seis - seal boss B, hardest seal)
;
; Increase these if your hammer kills slower (low IAS/FCR).
; Decrease if your hammer kills faster (high IAS/FCR, good gear).
; If you die during a fight, the attack length is too long for your defense.
atk_len_arc=2.5 atk_len_arc=2.5
atk_len_eldritch=3.0 atk_len_eldritch=3.0
atk_len_nihlathak=4.0 atk_len_nihlathak=4.0
atk_len_pindle=8.0 atk_len_pindle=3.0
atk_len_shenk=4.0 atk_len_shenk=4.0
atk_len_diablo=10.0 atk_len_diablo=3.0
atk_len_trav=3.0 atk_len_trav=5.0
; Boss run attack lengths (per-character defaults in kill_* methods) ; Boss run attack lengths (per-character defaults in kill_* methods)
; Adjust these if your build is faster/slower against these bosses ; Adjust these if your build is faster/slower against these bosses
@@ -302,14 +258,14 @@ belt_mp_columns=1
belt_rejuv_columns=2 belt_rejuv_columns=2
; Potion/chicken settings ; Potion/chicken settings
take_health_potion=0.95 take_health_potion=0.60
take_mana_potion=0.90 take_mana_potion=0.40
take_rejuv_potion_health=0.80 take_rejuv_potion_health=0.40
take_rejuv_potion_mana=0.55 take_rejuv_potion_mana=0.10
heal_merc=0.85 heal_merc=0.60
heal_rejuv_merc=0.55 heal_rejuv_merc=0.25
chicken=0.50 chicken=0.45
merc_chicken=0.20 merc_chicken=0.25
; Misc. ; Misc.
; helps reduce accidental pickups when enabled especially on walking characters ; helps reduce accidental pickups when enabled especially on walking characters
@@ -319,24 +275,24 @@ fill_shared_stash_first=0
; to gamble, add any/all of the following: circlet, ring, coronet, talon, amulet ; to gamble, add any/all of the following: circlet, ring, coronet, talon, amulet
gamble_items= gamble_items=
; open_chests: 1 = open clickable chests along path when possible ; open_chests: 1 = open clickable chests along path when possible
open_chests=1 open_chests=0
; pre_buff_every_run: 1 = always recast buffs at run start ; pre_buff_every_run: 1 = always recast buffs at run start
pre_buff_every_run=0 pre_buff_every_run=0
; runs_per_repair: visit repair vendor every X runs (blank/0 disables) ; runs_per_repair: visit repair vendor every X runs (blank/0 disables)
runs_per_repair=5 runs_per_repair=2000
; repair_npc: preferred repair vendor strategy. ; repair_npc: preferred repair vendor strategy.
; - a4_halbu (recommended, most stable) ; - a4_halbu (recommended, most stable)
; - a5_larzuk (faster when templates are reliable) ; - a5_larzuk (faster when templates are reliable)
repair_npc=a4_halbu repair_npc=a4_halbu
; runs_per_stash: stash/sell every X runs (blank/0 disables) ; runs_per_stash: stash/sell every X runs (blank/0 disables)
runs_per_stash=4 runs_per_stash=1
; sell_junk: 1 = vendor non-keep items automatically ; sell_junk: 1 = vendor non-keep items automatically
sell_junk=1 sell_junk=0
; protect_shields_from_sell: 1 = never vendor items with "shield" in detected name (recommended safety) ; protect_shields_from_sell: 1 = never vendor items with "shield" in detected name (recommended safety)
protect_shields_from_sell=1 protect_shields_from_sell=1
; pick_rares_for_gold: 1 = pick all yellow (rare) ground items; non-keep rares will be sold ; pick_rares_for_gold: 1 = pick all yellow (rare) ground items; non-keep rares will be sold
; Requires sell_junk=1 to convert extra pickups into gold. ; Requires sell_junk=1 to convert extra pickups into gold.
pick_rares_for_gold=0 pick_rares_for_gold=1
; pick_gold: 1 = pick up ground gold piles, 0 = ignore all ground gold ; pick_gold: 1 = pick up ground gold piles, 0 = ignore all ground gold
pick_gold=1 pick_gold=1
@@ -347,12 +303,12 @@ stash_destination=3,2,1,0
; chipped, flawed, standard, flawless ; chipped, flawed, standard, flawless
transmute=flawless transmute=flawless
;how often we want to run transmute routine(e.g. every 100 games) ;how often we want to run transmute routine(e.g. every 100 games)
transmute_every_x_game=2000 transmute_every_x_game=500
; number of stash tabs (ROTW has more than 6, adjust as needed) ; number of stash tabs (ROTW has more than 6, adjust as needed)
stash_tabs=6 stash_tabs=6
; potion transmute settings ; potion transmute settings
; convert_rejuv: 1 = convert regular Rejuv Potions to Full Rejuv via cube (3 -> 1) ; convert_rejuv: 1 = convert regular Rejuv Potions to Full Rejuv via cube (3 -> 1)
convert_rejuv=1 convert_rejuv=0
; min_rejuv_to_convert: minimum regular rejuv potions in inventory before starting conversion ; min_rejuv_to_convert: minimum regular rejuv potions in inventory before starting conversion
min_rejuv_to_convert=6 min_rejuv_to_convert=6
@@ -402,23 +358,22 @@ hydra=f1
; ==== Builds: Paladin ==== ; ==== Builds: Paladin ====
; ========================= ; =========================
[paladin] [paladin]
cleansing=f9 cleansing=f2
holy_shield=f2 holy_shield=f7
redemption=f3 redemption=
vigor=f4 vigor=f3
[fohdin] [fohdin]
; foh must be left skill, hotkey required ; foh must be left skill, hotkey required
blessed_hammer=f1 blessed_hammer=f9
concentration=f8 concentration=f1
conviction=f5 conviction=
foh=f6 foh=f6
holy_bolt=f7 holy_bolt=f7
[hammerdin] [hammerdin]
blessed_hammer=f1 blessed_hammer=f9
concentration=f8 concentration=f1
conviction=f5
; ========================= ; =========================
; ==== Builds: Warlock ==== ; ==== Builds: Warlock ====
@@ -576,13 +531,13 @@ buff_2=
; select_runs_key: open run selector UI ; select_runs_key: open run selector UI
select_runs_key=pagedown select_runs_key=pagedown
; restore_settings_from_backup_key: restore backed-up D2R settings ; restore_settings_from_backup_key: restore backed-up D2R settings
restore_settings_from_backup_key=insert restore_settings_from_backup_key=f10
; settings_backup_key: create backup of current D2R settings ; settings_backup_key: create backup of current D2R settings
settings_backup_key=pause settings_backup_key=pause
; auto_settings_key: auto-apply required D2R settings ; auto_settings_key: auto-apply required D2R settings
auto_settings_key=pageup auto_settings_key=pageup
; graphic_debugger_key: toggle on-screen debug layers ; graphic_debugger_key: toggle on-screen debug layers
graphic_debugger_key=delete graphic_debugger_key=insert
; resume_key: start/pause bot loop ; resume_key: start/pause bot loop
resume_key=f11 resume_key=f11
; exit_key: hard stop bot ; exit_key: hard stop bot
@@ -606,7 +561,7 @@ message_headers=
; ocr_during_pickit: 1 = run OCR while looting (slower, more diagnostics) ; ocr_during_pickit: 1 = run OCR while looting (slower, more diagnostics)
ocr_during_pickit=0 ocr_during_pickit=0
;use "can_teleport_natively" or "can_teleport_with_charges" if you want to force certain behavior in case autodetection isn't working properly ;use "can_teleport_natively" or "can_teleport_with_charges" if you want to force certain behavior in case autodetection isn't working properly
override_capabilities=can_teleport_natively override_capabilities=
; pathing_delay_factor: movement/click delay multiplier (1 fast .. 10 slow) ; pathing_delay_factor: movement/click delay multiplier (1 fast .. 10 slow)
pathing_delay_factor=2 pathing_delay_factor=2
; If you want to control Hyper-V window from host use 0,51 here ; If you want to control Hyper-V window from host use 0,51 here
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

+43 -71
View File
@@ -100,7 +100,7 @@ class IChar:
return False return False
else: else:
mouse.move(pos[0], pos[1]) mouse.move(pos[0], pos[1])
wait(0.1, 0.12) wait(0.04, 0.06)
mouse.click(button="left") mouse.click(button="left")
#Current logic only sets force_run if we previously teled. #Current logic only sets force_run if we previously teled.
@@ -157,8 +157,7 @@ class IChar:
while time.time() - check_success_start < 2: while time.time() - check_success_start < 2:
if success_func is None or success_func(): if success_func is None or success_func():
return True return True
elapsed = time.time() - start Logger.error(f"Wanted to select {template_type}, but could not find it")
Logger.error(f"select_by_template: could not find {template_type} after {elapsed:.1f}s (timeout={timeout}s, threshold={threshold})")
return False return False
def skill_is_charged(self, img: np.ndarray = None) -> bool: def skill_is_charged(self, img: np.ndarray = None) -> bool:
@@ -304,83 +303,56 @@ class IChar:
return False return False
def _pre_buff_cta(self): def _pre_buff_cta(self):
"""Switch to CTA weapon, cast BC + BO, switch back to main. # Check if we are already on CTA side
if skills.is_right_skill_selected(["BC", "BO"]):
Uses the BC skill-bar template to verify each weapon switch rather than
blindly toggling — prevents the character from fighting with the wrong
weapon after a chicken/death interrupted a previous buff cycle and saved
the game with the CTA slot active.
"""
bc_hotkey = Config().char.get("battle_command")
if not bc_hotkey:
# No hotkey configured → can't verify; fall back to unverified toggle
Logger.warning("_pre_buff_cta: battle_command hotkey not set — using unverified weapon switch")
keyboard.send(Config().char["weapon_switch"]) keyboard.send(Config().char["weapon_switch"])
wait(0.3, 0.35) wait(0.4, 0.45)
self._select_skill(skill="battle_command", mouse_click_type="right", delay=(0.1, 0.2))
# Save current skill img
skill_before = cut_roi(grab(), Config().ui_roi["skill_right"])
# Try to switch weapons and select bo until we find the skill on the right skill slot
start = time.time()
switch_sucess = False
while time.time() - start < 4:
keyboard.send(Config().char["weapon_switch"])
wait(0.4, 0.45)
keyboard.send(Config().char["battle_command"])
wait(0.2, 0.3)
if skills.is_right_skill_selected(["BC", "BO"]):
switch_sucess = True
break
else:
Logger.warning("Failed to find Battle Command, swapping weapons again.")
if not switch_sucess:
Logger.error("You dont have Battle Command bound, or you do not have CTA. Ending CTA buff")
return False
else:
# We switched succesfully, let's pre buff
mouse.click(button="right") mouse.click(button="right")
wait(self._cta_cast_duration, self._cta_cast_duration) wait(self._cta_cast_duration, self._cta_cast_duration)
self._select_skill(skill="battle_orders", mouse_click_type="right", delay=(0.1, 0.2)) self._select_skill(skill = "battle_orders", mouse_click_type="right", delay=(0.1, 0.2))
mouse.click(button="right") mouse.click(button="right")
wait(self._cta_cast_duration, self._cta_cast_duration) wait(self._cta_cast_duration, self._cta_cast_duration)
if Config().char["buff_with_cta"]: if Config().char["buff_with_cta"]:
self.cast_buffs(self._cta_cast_duration) self.cast_buffs(self._cta_cast_duration)
wait(0.08, 0.08) wait(0.08,0.08) #Give extra time after last cast to allow weapon switch to be available
keyboard.send(Config().char["weapon_switch"])
wait(0.3, 0.35)
return True
# --- Step 1: ensure we start on the main weapon slot --- # Make sure the switch back to the original weapon is good
# If a previous run was interrupted mid-buff (chicken / death), D2R may have start = time.time()
# saved with the CTA slot active. Detect that by pressing the BC hotkey and switch_sucess = False
# checking if BC appears on the right skill bar. while time.time() - start < 4:
keyboard.send(bc_hotkey)
wait(0.12, 0.18)
if skills.is_right_skill_selected(["BC"]):
Logger.warning("_pre_buff_cta: game started on CTA slot (leftover from interrupted buff) — correcting to main weapon")
keyboard.send(Config().char["weapon_switch"]) keyboard.send(Config().char["weapon_switch"])
wait(0.45, 0.55) wait(0.4, 0.45)
skill_after = cut_roi(grab(), Config().ui_roi["skill_right"])
# --- Step 2: switch TO CTA slot and verify --- _, max_val, _, _ = cv2.minMaxLoc(cv2.matchTemplate(skill_after, skill_before, cv2.TM_CCOEFF_NORMED))
keyboard.send(Config().char["weapon_switch"]) if max_val > 0.8:
wait(0.3, 0.35) switch_sucess = True
keyboard.send(bc_hotkey) break
wait(0.12, 0.18) else:
on_cta = skills.is_right_skill_selected(["BC"]) Logger.warning("Failed to switch weapon, try again")
if not on_cta: wait(0.5)
Logger.warning("_pre_buff_cta: switch to CTA slot failed — retrying") return switch_sucess
keyboard.send(Config().char["weapon_switch"])
wait(0.45, 0.55)
keyboard.send(bc_hotkey)
wait(0.12, 0.18)
on_cta = skills.is_right_skill_selected(["BC"])
if not on_cta:
Logger.error("_pre_buff_cta: could not confirm CTA slot after retry — casting Holy Shield from main and skipping CTA buffs")
self.cast_buffs(self._cast_duration)
return True # buffs cast from main; weapon slot is correct
# --- Step 3: cast BC (already selected above) then BO ---
mouse.click(button="right")
wait(self._cta_cast_duration, self._cta_cast_duration)
self._select_skill(skill="battle_orders", mouse_click_type="right", delay=(0.1, 0.2))
mouse.click(button="right")
wait(self._cta_cast_duration, self._cta_cast_duration)
if Config().char["buff_with_cta"]:
self.cast_buffs(self._cta_cast_duration)
wait(0.08, 0.08)
# --- Step 4: switch back to main weapon slot and verify ---
keyboard.send(Config().char["weapon_switch"])
wait(0.3, 0.35)
if skills.is_right_skill_selected(["BC"]):
Logger.warning("_pre_buff_cta: switch back to main failed — retrying")
keyboard.send(Config().char["weapon_switch"])
wait(0.45, 0.55)
if skills.is_right_skill_selected(["BC"]):
Logger.error("_pre_buff_cta: still on CTA slot after retry — CHARACTER WILL FIGHT WITH WRONG WEAPON")
# Buffs are cast; return True so pre_buff() doesn't skip Holy Shield entirely.
return True
def vec_to_monitor(self, target): def vec_to_monitor(self, target):
+1 -1
View File
@@ -14,7 +14,7 @@ class DeathManager:
def __init__(self): def __init__(self):
self._died = False self._died = False
self._do_monitor = False self._do_monitor = False
self._loop_delay = 1.0 self._loop_delay = 0.5
self._callback = None self._callback = None
self._last_death_screenshot = None self._last_death_screenshot = None
+77 -83
View File
@@ -1,20 +1,19 @@
import cv2
import time
import random
from threading import Lock
from inventory import belt from inventory import belt
from pather import Location from pather import Location
import cv2
from threading import Lock
import time
from input_layer import keyboard from input_layer import keyboard
from input_layer import mouse from input_layer import mouse
from utils.misc import wait from utils.misc import wait
from logger import Logger from logger import Logger
from utils.log_rotation import safe_imwrite from utils.log_rotation import safe_imwrite
from screen import grab from screen import grab
import time
from config import Config from config import Config
from inventory import common from inventory import common
from ui import view, meters from ui import view, meters
from ui_manager import ScreenObjects, is_visible from ui_manager import ScreenObjects, is_visible
from random import uniform
class HealthManager: class HealthManager:
@@ -92,104 +91,99 @@ class HealthManager:
Logger.info("Start health monitoring") Logger.info("Start health monitoring")
self._do_monitor = True self._do_monitor = True
self._did_chicken = False self._did_chicken = False
start = time.time()
lp_hp_potion_delay = 10.24 lp_hp_potion_delay = 10.24
lp_mp_potion_delay = 5.12 lp_mp_potion_delay = 1.0
merc_hp_potion_delay = 10.24 merc_hp_potion_delay = 10.24
while self._do_monitor: while self._do_monitor:
if self._did_chicken or self.get_pause_state(): if self._did_chicken or self.get_pause_state():
wait(1) wait(0.5, 0.7)
continue continue
fn_start = time.perf_counter()
img = grab()
if is_visible(ScreenObjects.InGame, img):
health_percentage = meters.get_health(img)
mana_percentage = meters.get_mana(img)
# check rejuv first try:
success_drink_rejuv = False fn_start = time.perf_counter()
last_drink = time.time() - self._last_rejuv img = grab()
#It seems that hit recovery can delay the use of juvs and 15 frames is max recovery time. if is_visible(ScreenObjects.InGame, img):
#To delay two juvs being used back to back, we'll need to wait the recovery time between uses. health_percentage = meters.get_health(img)
#15 frames is 0.60 seconds. mana_percentage = meters.get_mana(img)
if last_drink > 0.60:
if (health_percentage <= Config().char["take_rejuv_potion_health"]) or \
(mana_percentage <= Config().char["take_rejuv_potion_mana"]):
# Simulate human reaction time (100-300ms)
wait(0.1, 0.3)
success_drink_rejuv = belt.drink_potion("rejuv", stats=[health_percentage, mana_percentage])
#failure to drink juv (likely out of juvs). Perform chicken
if not success_drink_rejuv:
Logger.warning(f"Failed to drink rejuv. Trying to chicken, player HP {(health_percentage*100):.1f}%!")
self._do_chicken(img)
continue
self._last_rejuv = time.time()
#if we drink two juvs within 8 seconds of one another AND HP is still low, may be in bad state. Perform chicken # check rejuv first
# Only chicken on rapid double-rejuv if HP was the actual trigger (not just mana). success_drink_rejuv = False
# Hammerdins spend mana fast — mana-triggered rejuvs can legitimately fire back-to-back. last_drink = time.time() - self._last_rejuv
if last_drink < 8 and health_percentage <= Config().char["take_rejuv_potion_health"]: if last_drink > 0.60:
Logger.warning(f"Two juvs drank within {last_drink} seconds. Trying to chicken, player HP {(health_percentage*100):.1f}%!") if (health_percentage <= Config().char["take_rejuv_potion_health"]) or \
self._do_chicken(img) (mana_percentage <= Config().char["take_rejuv_potion_mana"]):
continue # Critical: Reduce delay to minimum
wait(0.02, 0.05)
success_drink_rejuv = belt.drink_potion("rejuv", stats=[health_percentage, mana_percentage])
if not success_drink_rejuv:
Logger.warning(f"Failed to drink rejuv. Trying to chicken, player HP {(health_percentage*100):.1f}%!")
self._do_chicken(img)
continue
self._last_rejuv = time.time()
# Default chicken if health is below threshold. if last_drink < 8:
if health_percentage <= Config().char["chicken"]: Logger.warning(f"Two juvs drank within {last_drink:.1f} seconds. Trying to chicken, player HP {(health_percentage*100):.1f}%!")
Logger.warning(f"Trying to chicken, player HP {(health_percentage*100):.1f}%!") self._do_chicken(img)
self._do_chicken(img) continue
continue
# in case no rejuv was used, check for chicken, health pot and mana pot usage if health_percentage <= Config().char["chicken"]:
if not success_drink_rejuv: Logger.warning(f"Trying to chicken, player HP {(health_percentage*100):.1f}%!")
# check health
last_drink = time.time() - self._last_health
if health_percentage <= Config().char["take_health_potion"] and last_drink > lp_hp_potion_delay:
wait(0.1, 0.3) # human reaction time
belt.drink_potion("health", stats=[health_percentage, mana_percentage])
self._last_health = time.time()
# check mana
last_drink = time.time() - self._last_mana
if mana_percentage <= Config().char["take_mana_potion"] and last_drink > lp_mp_potion_delay:
wait(0.1, 0.3) # human reaction time
belt.drink_potion("mana", stats=[health_percentage, mana_percentage])
self._last_mana = time.time()
# check merc health and heal/chicken as needed
if any([Config().char[x] for x in ["heal_rejuv_merc", "merc_chicken", "heal_merc"]]):
merc_health = meters.get_merc_health(img)
last_drink = time.time() - self._last_merc_heal
# chicken if merc is too low
if Config().char["merc_chicken"] and (merc_health < Config().char["merc_chicken"]):
Logger.warning(f"Trying to chicken, merc HP {(merc_health*100):.1f}%!")
self._do_chicken(img) self._do_chicken(img)
continue continue
# rejuv if merc is critically low
if Config().char["heal_rejuv_merc"] and (merc_health < Config().char["heal_rejuv_merc"]) and (last_drink > 4.0): if not success_drink_rejuv:
wait(0.1, 0.3) # human reaction time # check health
belt.drink_potion("rejuv", merc=True, stats=[merc_health]) last_drink = time.time() - self._last_health
self._last_merc_heal = time.time() if health_percentage <= Config().char["take_health_potion"] and last_drink > lp_hp_potion_delay:
elif Config().char["heal_merc"] and (merc_health < Config().char["heal_merc"]) and (last_drink > merc_hp_potion_delay): wait(0.05, 0.1)
wait(0.1, 0.3) # human reaction time if belt.drink_potion("health", stats=[health_percentage, mana_percentage]):
belt.drink_potion("health", merc=True, stats=[merc_health]) self._last_health = time.time()
self._last_merc_heal = time.time() # check mana
last_drink = time.time() - self._last_mana
if mana_percentage <= Config().char["take_mana_potion"] and last_drink > lp_mp_potion_delay:
wait(0.05, 0.1)
if belt.drink_potion("mana", stats=[health_percentage, mana_percentage]):
self._last_mana = time.time()
# check merc health
if any([Config().char[x] for x in ["heal_rejuv_merc", "merc_chicken", "heal_merc"]]):
merc_health = meters.get_merc_health(img)
last_drink = time.time() - self._last_merc_heal
if Config().char["merc_chicken"] and (merc_health < Config().char["merc_chicken"]):
Logger.warning(f"Trying to chicken, merc HP {(merc_health*100):.1f}%!")
self._do_chicken(img)
continue
if Config().char["heal_rejuv_merc"] and (merc_health < Config().char["heal_rejuv_merc"]) and (last_drink > 4.0):
wait(0.05, 0.1)
if belt.drink_potion("rejuv", merc=True, stats=[merc_health]):
self._last_merc_heal = time.time()
elif Config().char["heal_merc"] and (merc_health < Config().char["heal_merc"]) and (last_drink > merc_hp_potion_delay):
wait(0.05, 0.1)
if belt.drink_potion("health", merc=True, stats=[merc_health]):
self._last_merc_heal = time.time()
# Close any open panels that might block detection
if not self.get_panel_check_paused() and (is_visible(ScreenObjects.LeftPanel, img) or is_visible(ScreenObjects.RightPanel, img)): if not self.get_panel_check_paused() and (is_visible(ScreenObjects.LeftPanel, img) or is_visible(ScreenObjects.RightPanel, img)):
Logger.warning(f"Found an open inventory / quest / skill / stats page. Close it.")
self._count_panel_detects += 1 self._count_panel_detects += 1
if self._count_panel_detects >= 2: if self._count_panel_detects >= 2:
self._count_panel_detects = 0 self._count_panel_detects = 0
Logger.warning(f"Found an open inventory / quest / skill / stats page again. Chicken to dismiss.") Logger.warning(f"Found open panels (inv/quest/stats) twice. Chickening to be safe.")
self._do_chicken(img) self._do_chicken(img)
continue continue
Logger.debug("Found an open panel. Closing it.")
common.close() common.close()
fn_end = time.perf_counter()
# Add timing jitter to avoid perfectly regular polling pattern (anti-cheat) fn_end = time.perf_counter()
import random # Target ~15 FPS polling with anti-cheat jitter
base_wait = 3/25 - (fn_end - fn_start) base_wait = 1/15 - (fn_end - fn_start)
jitter = random.uniform(0.8, 1.2) # ±20% jitter wait_time = max(0.01, base_wait * random.uniform(0.8, 1.2))
wait_time = max(0.0, base_wait * jitter) wait(wait_time)
if wait_time > 0:
wait(wait_time) # wait ~3 frames before rechecking except Exception as e:
Logger.error(f"HealthManager error: {e}")
wait(0.5)
Logger.debug("Stop health monitoring") Logger.debug("Stop health monitoring")
+14 -8
View File
@@ -260,15 +260,21 @@ class mouse:
@staticmethod @staticmethod
def click(button): def click(button):
if button != "left" or mouse._is_clicking_safe(): if button != "left" or mouse._is_clicking_safe():
try: # DISABLED STEALTH FOR DEBUGGING
from utils.stealth import apply_click_delay # try:
apply_click_delay() # from utils.stealth import apply_click_delay
except Exception: # apply_click_delay()
from logger import Logger # except Exception:
Logger.warning("[Stealth] apply_click_delay failed, falling back to manual delay") # from logger import Logger
from utils.misc import wait as _wait # Logger.warning("[Stealth] apply_click_delay failed, falling back to manual delay")
_wait(0.05, 0.3) # from utils.misc import wait as _wait
# _wait(0.05, 0.3)
from logger import Logger
Logger.debug(f"Executing native click: {button}")
_native_click(button) _native_click(button)
else:
from logger import Logger
Logger.debug(f"Click blocked by safety check (button: {button})")
@staticmethod @staticmethod
def press(button): def press(button):
+3 -2
View File
@@ -177,7 +177,7 @@ def _make_mouse_input(flags: int, dx: int = 0, dy: int = 0, data: int = 0):
i.union.mi.dx = dx i.union.mi.dx = dx
i.union.mi.dy = dy i.union.mi.dy = dy
i.union.mi.mouseData = data i.union.mi.mouseData = data
i.union.mi.dwFlags = flags | MOUSEEVENTF_ABSOLUTE i.union.mi.dwFlags = flags
i.union.mi.dwExtraInfo = None i.union.mi.dwExtraInfo = None
return i return i
@@ -285,8 +285,9 @@ def mouse_up(button: str = "left"):
raise ValueError(f"Unknown button: {button}") raise ValueError(f"Unknown button: {button}")
def mouse_click(button: str = "left"): def mouse_click(button: str = "left"):
"""Click (press + release) a mouse button.""" """Click (press + release) a mouse button with a reliable delay."""
mouse_down(button) mouse_down(button)
time.sleep(0.1) # 100ms hold time is very reliable for games
mouse_up(button) mouse_up(button)
def mouse_wheel(delta: int): def mouse_wheel(delta: int):
+2 -2
View File
@@ -78,7 +78,7 @@ def _potion_type(img: np.ndarray) -> str:
roi = [int(w * 0.4), int(h * 0.3), int(w * 0.4), int(h * 0.7)] roi = [int(w * 0.4), int(h * 0.3), int(w * 0.4), int(h * 0.7)]
img = cut_roi(img, roi) img = cut_roi(img, roi)
avg_brightness = np.average(img) avg_brightness = np.average(img)
if avg_brightness < 47: if avg_brightness < 30:
return "empty" return "empty"
score_list = [] score_list = []
# rejuv # rejuv
@@ -92,7 +92,7 @@ def _potion_type(img: np.ndarray) -> str:
score_list.append((float(np.sum(mask)) / mask.size) * (1/255.0)) score_list.append((float(np.sum(mask)) / mask.size) * (1/255.0))
# find max score # find max score
max_val = np.max(score_list) max_val = np.max(score_list)
if max_val > 0.28: if max_val > 0.20:
idx = np.argmax(score_list) idx = np.argmax(score_list)
types = ["rejuv", "health", "mana"] types = ["rejuv", "health", "mana"]
return types[idx] return types[idx]
+2 -3
View File
@@ -120,9 +120,8 @@ class PickIt:
tele_success = True tele_success = True
Logger.info(f"Teled to pickup item {ground_item.Name} and now at distance {ground_item.Distance}") Logger.info(f"Teled to pickup item {ground_item.Name} and now at distance {ground_item.Distance}")
char.pick_up_item((ground_item.CenterMonitor['x'], ground_item.CenterMonitor['y']), item_name=ground_item.Name, distance=ground_item.Distance, force_run=True) char.pick_up_item((ground_item.CenterMonitor['x'], ground_item.CenterMonitor['y']), item_name=ground_item.Name, distance=ground_item.Distance, force_run=True)
# Wait for the pickup animation to complete before moving on # Wait briefly for the pickup animation to start/complete
# Issue #939: bot was teleporting away before item was actually grabbed wait(0.08, 0.12)
wait(0.2, 0.3)
break break
if not tele_success: if not tele_success:
Logger.warning(f"Failed to scan item '{item.Name}' after tele. Items visible: {len(items)}") Logger.warning(f"Failed to scan item '{item.Name}' after tele. Items visible: {len(items)}")
+29 -8
View File
@@ -272,25 +272,46 @@ def open_npc_menu(npc_key: Npc) -> bool:
results.append({"pos": res.center_monitor, "score": res.score, "combo": min_dist / (res.score**2)}) results.append({"pos": res.center_monitor, "score": res.score, "combo": min_dist / (res.score**2)})
# sort by composite of template match score and distance to NPC pose # sort by composite of template match score and distance to NPC pose
results = sorted(results, key=lambda r: r["combo"]) results = sorted(results, key=lambda r: r["combo"])
if not results:
Logger.debug(f"NPC {npc_key} search - No templates matched in this frame")
for result in results: for result in results:
Logger.debug(f"Hovering over NPC at monitor pos: {result['pos']}")
mouse.move(*result["pos"], randomize=3, delay_factor=[0.3, 0.5]) mouse.move(*result["pos"], randomize=3, delay_factor=[0.3, 0.5])
wait(0.2, 0.3) wait(0.2, 0.3)
img = grab() img = grab()
img = escape_dialogue(img) img = escape_dialogue(img)
_, filtered_inp_w = color_filter(img, Config().colors["white"]) _, filtered_inp_w = color_filter(img, Config().colors["white"])
_, filtered_inp_g = color_filter(img, Config().colors["gold"]) _, filtered_inp_g = color_filter(img, Config().colors["gold"])
res_w = template_finder.search(npcs[npc_key]["name_tag_white"], filtered_inp_w, 0.9, roi=roi).valid res_w = template_finder.search(npcs[npc_key]["name_tag_white"], filtered_inp_w, 0.5, roi=roi)
res_g = template_finder.search(npcs[npc_key]["name_tag_gold"], filtered_inp_g, 0.9, roi=roi).valid res_g = template_finder.search(npcs[npc_key]["name_tag_gold"], filtered_inp_g, 0.5, roi=roi)
if res_w:
# fallback to grayscale if color filter is too aggressive
if not res_w.valid:
res_w_gray = template_finder.search(npcs[npc_key]["name_tag_white"], img, 0.5, roi=roi, use_grayscale=True)
if res_w_gray.score > res_w.score:
res_w = res_w_gray
Logger.debug(f"NPC {npc_key} hover - using grayscale fallback (score: {res_w.score:.3f})")
Logger.debug(f"NPC {npc_key} hover - White score: {res_w.score:.3f}, Gold score: {res_g.score:.3f}")
if res_w.valid:
Logger.info(f"Clicking on {npc_key} at {mouse.get_position()}")
mouse.click(button="left") mouse.click(button="left")
attempts += 1 attempts += 1
wait(0.7, 1.0) # Wait for gold name tag to appear (indicates NPC menu opened or mouse stayed over NPC)
_, filtered_inp = color_filter(grab(), Config().colors["gold"]) wait(0.8, 1.2)
res = template_finder.search(npcs[npc_key]["name_tag_gold"], filtered_inp, 0.9, roi=roi).valid img_after = grab()
if res: _, filtered_inp_g2 = color_filter(img_after, Config().colors["gold"])
res_g2 = template_finder.search(npcs[npc_key]["name_tag_gold"], filtered_inp_g2, 0.5, roi=roi)
if res_g2.valid:
Logger.debug(f"NPC {npc_key} - gold name tag confirmed after click")
return True return True
elif res_g: else:
Logger.warning(f"NPC {npc_key} - clicked but gold name tag not found (score: {res_g2.score:.3f})")
elif res_g.valid:
Logger.debug(f"NPC {npc_key} - already hovered/selected (gold tag score: {res_g.score:.3f})")
return True return True
wait(0.1) # Brief pause before next frame grab if no matches were valid
return False return False
def press_npc_btn(npc_key: Npc, action_btn_key: str): def press_npc_btn(npc_key: Npc, action_btn_key: str):
+29 -23
View File
@@ -96,7 +96,7 @@ def _single_template_match(template: Template, inp_img: np.ndarray = None, roi:
# filter for desired color or make grayscale # filter for desired color or make grayscale
if color_match: if color_match:
template_img, = color_filter(template.img_bgr, color_match)[1], template_img = color_filter(template.img_bgr, color_match)[1]
img = color_filter(img, color_match)[1] img = color_filter(img, color_match)[1]
elif use_grayscale: elif use_grayscale:
template_img = template.img_gray template_img = template.img_gray
@@ -104,25 +104,32 @@ def _single_template_match(template: Template, inp_img: np.ndarray = None, roi:
else: else:
template_img = template.img_bgr template_img = template.img_bgr
if not (img.shape[0] > template_img.shape[0] and img.shape[1] > template_img.shape[1]): if not (img.shape[0] >= template_img.shape[0] and img.shape[1] >= template_img.shape[1]):
Logger.error(f"Image shape and template shape are incompatible: {template.name}. Image: {img.shape}, Template: {template_img.shape}, roi: {roi}") from logger import Logger
Logger.debug(f"Incompatible shapes for {template.name}: Image {img.shape}, Template {template_img.shape}, ROI {roi}")
else: else:
res = cv2.matchTemplate(img, template_img, cv2.TM_CCOEFF_NORMED, mask = template.alpha_mask) try:
np.nan_to_num(res, copy=False, nan=0.0, posinf=0.0, neginf=0.0) res = cv2.matchTemplate(img, template_img, cv2.TM_CCOEFF_NORMED, mask = template.alpha_mask)
_, max_val, _, max_pos = cv2.minMaxLoc(res) np.nan_to_num(res, copy=False, nan=0.0, posinf=0.0, neginf=0.0)
_, max_val, _, max_pos = cv2.minMaxLoc(res)
# save rectangle corresponding to matched region # save rectangle corresponding to matched region
rec_x = int((max_pos[0] + rx)) rec_x = int((max_pos[0] + rx))
rec_y = int((max_pos[1] + ry)) rec_y = int((max_pos[1] + ry))
rec_w = int(template_img.shape[1]) rec_w = int(template_img.shape[1])
rec_h = int(template_img.shape[0]) rec_h = int(template_img.shape[0])
template_match.region = [rec_x, rec_y, rec_w, rec_h] template_match.region = [rec_x, rec_y, rec_w, rec_h]
template_match.region_monitor = [*convert_screen_to_monitor((rec_x, rec_y)), rec_w, rec_h] template_match.region_monitor = [*convert_screen_to_monitor((rec_x, rec_y)), rec_w, rec_h]
template_match.center = roi_center(template_match.region) template_match.center = roi_center(template_match.region)
template_match.center_monitor = convert_screen_to_monitor(template_match.center) template_match.center_monitor = convert_screen_to_monitor(template_match.center)
template_match.name = template.name template_match.name = template.name
template_match.score = max_val template_match.score = max_val
template_match.valid = True template_match.valid = True
except Exception as e:
from logger import Logger
Logger.error(f"Template match failed for {template.name}: {e}")
import traceback
Logger.debug(traceback.format_exc())
return template_match return template_match
@@ -160,7 +167,8 @@ def search(
# Single template — no benefit to parallelize # Single template — no benefit to parallelize
if len(templates) == 1: if len(templates) == 1:
match = _single_template_match(templates[0], inp_img, roi, color_match, use_grayscale) match = _single_template_match(templates[0], inp_img, roi, color_match, use_grayscale)
return match if match.score >= threshold else TemplateMatch() match.valid = match.score >= threshold
return match
# Multiple templates — parallel search # Multiple templates — parallel search
matches = [] matches = []
@@ -171,10 +179,8 @@ def search(
] ]
for future in concurrent.futures.as_completed(futures): for future in concurrent.futures.as_completed(futures):
match = future.result() match = future.result()
if match.score >= threshold: match.valid = match.score >= threshold
if not best_match: matches.append(match)
return match
matches.append(match)
if matches: if matches:
matches = sorted(matches, key=lambda obj: obj.score, reverse=True) matches = sorted(matches, key=lambda obj: obj.score, reverse=True)
+1 -1
View File
@@ -20,7 +20,7 @@ def get_mana(img: np.ndarray) -> float:
return mana_percentage return mana_percentage
def get_merc_health(img: np.ndarray) -> float: def get_merc_health(img: np.ndarray) -> float:
health_rec = [Config().ui_pos["merc_health_left"], Config().ui_pos["merc_health_top"], Config().ui_pos["merc_health_width"], 1] health_rec = [Config().ui_pos["merc_health_left"], Config().ui_pos["merc_health_top"], Config().ui_pos["merc_health_width"], 3]
merc_health_img = cut_roi(img, health_rec) merc_health_img = cut_roi(img, health_rec)
merc_health_img = cv2.cvtColor(merc_health_img, cv2.COLOR_BGR2GRAY) merc_health_img = cv2.cvtColor(merc_health_img, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(merc_health_img, 5, 255, cv2.THRESH_BINARY) _, thresh = cv2.threshold(merc_health_img, 5, 255, cv2.THRESH_BINARY)
+3 -3
View File
@@ -17,7 +17,7 @@ def is_left_skill_selected(template_list: list[str]) -> bool:
""" """
skill_left_ui_roi = Config().ui_roi["skill_left"] skill_left_ui_roi = Config().ui_roi["skill_left"]
for template in template_list: for template in template_list:
if template_finder.search(template, grab(), threshold=0.84, roi=skill_left_ui_roi).valid: if template_finder.search(template, grab(), threshold=0.8, roi=skill_left_ui_roi).valid:
return True return True
return False return False
@@ -73,7 +73,7 @@ def is_right_skill_selected(template_list: list[str]) -> bool:
""" """
skill_right_ui_roi = Config().ui_roi["skill_right"] skill_right_ui_roi = Config().ui_roi["skill_right"]
for template in template_list: for template in template_list:
if template_finder.search(template, grab(), threshold=0.84, roi=skill_right_ui_roi).valid: if template_finder.search(template, grab(), threshold=0.8, roi=skill_right_ui_roi).valid:
return True return True
return False return False
@@ -82,7 +82,7 @@ def wait_until_right_skill_selected(template: str, timeout: float = 1) -> bool:
:return: Bool if skill gets selected on the right skill slot within the timeout. :return: Bool if skill gets selected on the right skill slot within the timeout.
""" """
skill_right_ui_roi = Config().ui_roi["skill_right"] skill_right_ui_roi = Config().ui_roi["skill_right"]
if template_finder.search_and_wait(template, roi=skill_right_ui_roi, timeout = timeout, threshold=0.84).valid: if template_finder.search_and_wait(template, roi=skill_right_ui_roi, timeout = timeout, threshold=0.8).valid:
return True return True
return False return False
+6 -10
View File
@@ -61,13 +61,7 @@ class ScreenObjects:
MercIcon=ScreenObject( MercIcon=ScreenObject(
ref=["MERC_A2", "MERC_A2_2", "MERC_A1", "MERC_A5", "MERC_A3"], ref=["MERC_A2", "MERC_A2_2", "MERC_A1", "MERC_A5", "MERC_A3"],
roi="merc_icon", roi="merc_icon",
threshold=0.85, threshold=0.75,
use_grayscale=True
)
MercPanelText=ScreenObject(
ref=["MERCENARY_SCREEN"],
roi="merc_panel",
threshold=0.85,
use_grayscale=True use_grayscale=True
) )
PlayBtn=ScreenObject( PlayBtn=ScreenObject(
@@ -182,7 +176,7 @@ class ScreenObjects:
YouHaveDied=ScreenObject( YouHaveDied=ScreenObject(
ref="YOU_HAVE_DIED", ref="YOU_HAVE_DIED",
roi="death", roi="death",
threshold=0.9, threshold=0.8,
color_match=Config().colors["red"], color_match=Config().colors["red"],
use_grayscale=True use_grayscale=True
) )
@@ -257,8 +251,10 @@ class ScreenObjects:
threshold=0.8, threshold=0.8,
) )
NotEnoughGold=ScreenObject( NotEnoughGold=ScreenObject(
ref="MISSING_GOLD", ref="NOT_ENOUGH_GOLD",
threshold=0.8, threshold=0.9,
color_match=Config().colors["red"],
use_grayscale=True
) )
QuestSkillBtn=ScreenObject( QuestSkillBtn=ScreenObject(
ref="QUEST_SKILL_BTN", ref="QUEST_SKILL_BTN",
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB