Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bc34a7b2f | ||
|
|
2dc1356cf7 | ||
|
|
ae0ab54efe | ||
|
|
bfb367ee62 | ||
|
|
8d925509fa | ||
|
|
811d656022 | ||
|
|
c7c0e9e1a3 | ||
|
|
9bc085af3a | ||
|
|
a5d7fdaffa | ||
|
|
16bf599d88 | ||
|
|
59ba4409b5 | ||
|
|
f510965d05 | ||
|
|
d43441358d | ||
|
|
1ece05f7a7 | ||
|
|
ca93f3aa5d | ||
|
|
5c6e2211d6 | ||
|
|
f469d98f74 | ||
|
|
699fcac7af | ||
|
|
c22275c1bb | ||
|
|
470f42b1b9 | ||
|
|
4b5b8385f8 | ||
|
|
42655d5209 | ||
|
|
09a33ccf3d | ||
|
|
fd93910941 | ||
|
|
51de6f9f00 | ||
|
|
d615f11608 | ||
|
|
c6d2f3f068 | ||
|
|
0606adb5da | ||
|
|
82576282ab | ||
|
|
e6fe7ef609 | ||
|
|
45e7a5ca9a | ||
|
|
7d3710d1e8 | ||
|
|
e354ed8074 | ||
|
|
c4a5c6fe34 | ||
|
|
2079b81df4 | ||
|
|
e64b312562 | ||
|
|
cceb7905c7 | ||
|
|
4007087ceb | ||
|
|
e64f1151f3 | ||
|
|
46202e5a69 | ||
|
|
782001d338 | ||
|
|
4eeadb934f | ||
|
|
1f16c13300 | ||
|
|
ee479fb526 | ||
|
|
d05ff50f76 | ||
|
|
927ade6108 | ||
|
|
5eda34ded0 | ||
|
|
d9b9d66e3b | ||
|
|
1adeb3a32a | ||
|
|
1abf57eae1 | ||
|
|
65797eba8f | ||
|
|
03a6e8b05c | ||
|
|
2c09459bde | ||
|
|
7564fea4b0 | ||
|
|
95b27f099f | ||
|
|
e96c042853 | ||
|
|
57bf7a0f7d | ||
|
|
da892d9043 | ||
|
|
7fad5e9aff | ||
|
|
840ffcdf0c | ||
|
|
1078563f2d | ||
|
|
c1f2cad291 | ||
|
|
285e635b0d | ||
|
|
54203ff036 | ||
|
|
6b378a4606 | ||
|
|
9707fc4563 | ||
|
|
1a4e6c9533 | ||
|
|
fb22b86d11 | ||
|
|
3c3f95142d | ||
|
|
029b8dcdc6 | ||
|
|
400d43c49c |
@@ -50,8 +50,3 @@ jobs:
|
||||
run: |
|
||||
C:\Miniconda\condabin\conda.bat activate botty
|
||||
coverage xml
|
||||
- name: Upload Coverage to Codecov
|
||||
uses: codecov/codecov-action@v2
|
||||
with:
|
||||
verbose: true
|
||||
files: ./coverage.xml
|
||||
|
||||
@@ -59,7 +59,6 @@ run_shenk=0
|
||||
| difficulty | Set to `normal` `nightmare` or `hell` for game difficulty. |
|
||||
| message_api_type | Which api to use to send botty messages. Supports "generic_api" (basic discord), or "discord" (discord embeds with images). |
|
||||
| discord_status_count | Number of games between discord status messages being sent. Leave empty for no status reports.
|
||||
| discord_status_condensed | Toggles condensed view of Discord status messages. 0 Full text, 1 Condensed text.
|
||||
| info_screenshots | If `1`, the bot takes a screenshot with timestamp on every stuck / chicken / timeout / inventory full event. This is 1 by Default, so remember to clean up the folder every once in a while. |
|
||||
| loot_screenshots | If `1`, the bot takes a screenshot with timestamp everytime he presses `show_items` button and saves it to `loot_screenshots` folder. Remember to clear them once in a while... |
|
||||
| saved_games_folder | [Optional] Defaults to `~\Saved Games\Diablo II Resurrected`. Used to store configuration settings for `f9` / auto settings. |
|
||||
@@ -97,7 +96,7 @@ run_shenk=0
|
||||
| battle_order | Hotkey for battle orders from cta (only needed if cta_available=1) |
|
||||
| battle_command | Hotkey for battle command from cta (only needed if cta_available=1) |
|
||||
| stash_gold | Bool value to stash gold each time when stashing items |
|
||||
| gold_trav_only | Hacky config that will restrict gold pickup to trav only. misc_gold must be set to 1 for this to have any effect |
|
||||
| min_gold_to_pick | Minimum quantity of gold to pickup (also must set misc_gold=1 in pickit config) |
|
||||
| use_merc | Set to 1 for using merc. Set to 0 for not using merc (will not revive merc when dead), default = 1 |
|
||||
| atk_len_arc | Attack length for hdin/sorc fighting arcane |
|
||||
| atk_len_trav | Attack length for hdin fighting trav (note this atk length will be applied in 4 different spots each) |
|
||||
|
||||
@@ -45,5 +45,6 @@
|
||||
"Always Run": 1,
|
||||
"Quick Cast Enabled": 0,
|
||||
"Display Active Skill Bindings": 0,
|
||||
"Lobby Wide Item Drop Enabled": 1
|
||||
"Lobby Wide Item Drop Enabled": 1,
|
||||
"Item Tooltip Hotkey Appender": 1
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
|
||||
"name": "botty",
|
||||
|
||||
"savepath": "../"
|
||||
|
||||
}
|
||||
|
After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
@@ -1 +0,0 @@
|
||||
comment: false
|
||||
@@ -22,6 +22,7 @@ mana_globe=117,120,20,121,255,255
|
||||
blue_slot=102,194,18,138,230,54
|
||||
green_slot=33,181,18,87,258,69
|
||||
red_slot=161,204,28,197,240,64
|
||||
tab_text=0,0,125,180,255,255
|
||||
|
||||
[ui_pos]
|
||||
screen_width=1280
|
||||
@@ -85,7 +86,7 @@ chat_line_1=12,537,391,25
|
||||
save_and_exit=500,233,273,173
|
||||
play_btn=426,616,320,71
|
||||
difficulty_select=536,236,210,320
|
||||
gold_btn=927,491,267,100
|
||||
gold_btn=997,521,20,18
|
||||
inventory_gold=980,510,150,40
|
||||
gold_btn_stash=150,480,40,65
|
||||
vendor_gold_digits=186,509,97,16
|
||||
@@ -129,6 +130,11 @@ left_panel_header=0,0,450,54
|
||||
right_panel_header=830,0,450,54
|
||||
npc_dialogue=458,4,21,140
|
||||
bind_skill=516,619,251,29
|
||||
quest_skill_btn=284,455,710,121
|
||||
left_inventory_tabs=31,62,385,28
|
||||
tab_indicator=31,82,385,14
|
||||
deposit_btn=454,365,186,43
|
||||
equipped_inventory_area=861,59,387,287
|
||||
|
||||
[path]
|
||||
; static pathes in format: x0,y0, x1,y1, x2,y2, ...
|
||||
|
||||
@@ -5,28 +5,30 @@ message_api_type=discord
|
||||
custom_message_hook=
|
||||
max_game_length_s=380
|
||||
max_consecutive_fails=0
|
||||
max_runtime_before_break_m=0
|
||||
break_length_m=0
|
||||
randomize_runs=0
|
||||
difficulty=hell
|
||||
info_screenshots=1
|
||||
loot_screenshots=0
|
||||
discord_status_count=20
|
||||
discord_status_condensed=1
|
||||
saved_games_folder=
|
||||
d2r_path=C:\Program Files (x86)\Diablo II Resurrected\D2R.exe
|
||||
d2r_path=C:\Program Files (x86)\Diablo II Resurrected
|
||||
; if you set this field to 1, botty will save the top-most visible character template just after
|
||||
; the online/offline tab and upon restart will attempt to find the same template to select the char
|
||||
restart_d2r_when_stuck=0
|
||||
|
||||
|
||||
[routes]
|
||||
run_trav=0
|
||||
run_pindle=1
|
||||
run_eldritch=1
|
||||
run_shenk=0
|
||||
run_nihlathak=0
|
||||
run_arcane=0
|
||||
run_diablo=0
|
||||
; Note: You can not split up or change order of eldritch and shenk as botty treats it as a single run
|
||||
order=run_pindle, run_eldritch, run_shenk, run_nihlathak, run_trav, run_arcane, run_diablo
|
||||
; Add these possible routes to "order" to run them:
|
||||
; run_trav
|
||||
; run_pindle
|
||||
; run_eldritch
|
||||
; run_eldritch_shenk
|
||||
; run_nihlathak
|
||||
; run_arcane
|
||||
; run_diablo
|
||||
order=run_pindle, run_eldritch_shenk
|
||||
|
||||
[char]
|
||||
; ==========================
|
||||
@@ -38,6 +40,7 @@ casting_frames=10
|
||||
show_items=alt
|
||||
inventory_screen=i
|
||||
tp=f9
|
||||
;Note: loot columns are counted from left to right. Items on the left side of inventory will be considered lootable.
|
||||
num_loot_columns=5
|
||||
force_move=e
|
||||
;Note: stand_still can not be the default "shift" as it would interfere with merc healing
|
||||
@@ -57,7 +60,7 @@ battle_command=f8
|
||||
; ==== Optional configs ====
|
||||
; ==========================
|
||||
stash_gold=1
|
||||
gold_trav_only=0
|
||||
min_gold_to_pick=1000
|
||||
use_merc=1
|
||||
; Attack length for barbarians should be as high as 8-10 and even 10-12 for trav/shenk
|
||||
atk_len_arc=2.5
|
||||
@@ -202,6 +205,23 @@ corpse_explosion=
|
||||
clear_pindle_pack=
|
||||
heart_of_wolverine=
|
||||
|
||||
[poison_necro]
|
||||
teleport=
|
||||
poison_nova=
|
||||
# Not Required
|
||||
skill_left=
|
||||
bone_armor=
|
||||
# Can use any golem
|
||||
clay_golem=
|
||||
raise_skeleton=
|
||||
raise_mage=
|
||||
raise_revive=
|
||||
# Can Use any curse
|
||||
lower_res=
|
||||
corpse_explosion=
|
||||
clear_pindle_pack=
|
||||
heart_of_wolverine=
|
||||
|
||||
; ===========================
|
||||
; ==== Builds: Basic ====
|
||||
; ===========================
|
||||
@@ -259,3 +279,4 @@ hwnd_window_process=D2R\.exe
|
||||
;If you want to control Hyper-V window from host use 0,51 here
|
||||
window_client_area_offset=0,0
|
||||
ocr_during_pickit=0
|
||||
launch_options=
|
||||
|
||||
@@ -37,15 +37,31 @@
|
||||
|
||||
;examples
|
||||
; keeps small charms with resi, 7_mf or 3 max_AD and AR
|
||||
; magic_small_charm=1, (and(resi, 7_mf), and(3_maximum_damage, attack_rating)
|
||||
; magic_small_charm=1, (all_resist, 7_magicfind, and(3_maximum_damage, attack_rating))
|
||||
|
||||
; keeps all Skiller or (Life, max_AD, Life) GC
|
||||
; magic_grand_charm=1, (Amazonskiller, Assasinskiller, Barbarianskiller, Druidskiller, Necromancerskiller, Paladinskiller, Sorceressskiller, (Life, maximum_damage, attack_rating))
|
||||
|
||||
; keeps greater talons with (2 Assa Skills + 3 lightning sentry) or (+Traps skills and + lightning sentry)
|
||||
; magic_greater_talons=1, ((2_assasin_skills, 3_lightning_sentry), (traps, lightning_sentry))
|
||||
|
||||
|
||||
; you can also create and use variables for filters you use often
|
||||
; make sure it's under the [variables] section
|
||||
|
||||
;examples
|
||||
; creates a variable called has_skiller
|
||||
; has_skiller=Amazonskiller, Assasinskiller, Barbarianskiller, Druidskiller, Necromancerskiller, Paladinskiller, Sorceressskiller
|
||||
; use the has_skiller variable
|
||||
; magic_grand_charm=1, (has_skiller, (Life, maximum_damage, attack_rating))
|
||||
|
||||
[variables]
|
||||
;==================================================================;
|
||||
;<<<<<<<<<<<<<<<<<<<<<<<<<<<< Variables >>>>>>>>>>>>>>>>>>>>>>>>>>>>;
|
||||
;==================================================================;
|
||||
|
||||
has_skiller=Amazonskiller, Assasinskiller, Barbarianskiller, Druidskiller, Necromancerskiller, Paladinskiller, Sorceressskiller
|
||||
lightning_fire_cold_res=and(lightning_resist, fire_resist, cold_resist)
|
||||
|
||||
[items]
|
||||
;==================================================================;
|
||||
;<<<<<<<<<<<<<<<<<<<<<<<<<<<< MISC >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>;
|
||||
@@ -152,7 +168,7 @@ rune_33_zod=2
|
||||
;==================================================================;
|
||||
|
||||
;magic charms
|
||||
magic_grand_charm=1, (Amazonskiller, Assasinskiller, Barbarianskiller, Druidskiller, Necromancerskiller, Paladinskiller, Sorceressskiller, (Life, maximum_damage, attack_rating))
|
||||
magic_grand_charm=1, (has_skiller, (Life, maximum_damage, attack_rating))
|
||||
magic_large_charm=0
|
||||
magic_small_charm=1, (all_resist, 7_magicfind, 20_life, and(3_maximum_damage, attack_rating))
|
||||
|
||||
@@ -345,25 +361,25 @@ rare_colossus_girdle=0,, ethereal
|
||||
;---------------------------------------;
|
||||
|
||||
;normal rare boots
|
||||
rare_greaves=1, and(lightning_resist, fire_resist, cold_resist), ethereal
|
||||
rare_boots=1, and(lightning_resist, fire_resist, cold_resist), ethereal
|
||||
rare_chain_boots=1, and(lightning_resist, fire_resist, cold_resist), ethereal
|
||||
rare_light_plated_boots=1, and(lightning_resist, fire_resist, cold_resist), ethereal
|
||||
rare_heavy_boots=1, and(lightning_resist, fire_resist, cold_resist), ethereal
|
||||
rare_greaves=1, lightning_fire_cold_res, ethereal
|
||||
rare_boots=1, lightning_fire_cold_res, ethereal
|
||||
rare_chain_boots=1, lightning_fire_cold_res, ethereal
|
||||
rare_light_plated_boots=1, lightning_fire_cold_res, ethereal
|
||||
rare_heavy_boots=1, lightning_fire_cold_res, ethereal
|
||||
|
||||
;exceptional rare boots
|
||||
rare_war_boots=1, and(lightning_resist, fire_resist, cold_resist), ethereal
|
||||
rare_battle_boots=1, and(lightning_resist, fire_resist, cold_resist), ethereal
|
||||
rare_mesh_boots=1, and(lightning_resist, fire_resist, cold_resist), ethereal
|
||||
rare_sharkskin_boots=1, and(lightning_resist, fire_resist, cold_resist), ethereal
|
||||
rare_demonhide_boots=1, and(lightning_resist, fire_resist, cold_resist), ethereal
|
||||
rare_war_boots=1, lightning_fire_cold_res, ethereal
|
||||
rare_battle_boots=1, lightning_fire_cold_res, ethereal
|
||||
rare_mesh_boots=1, lightning_fire_cold_res, ethereal
|
||||
rare_sharkskin_boots=1, lightning_fire_cold_res, ethereal
|
||||
rare_demonhide_boots=1, lightning_fire_cold_res, ethereal
|
||||
|
||||
;elite rare boots
|
||||
rare_myrmidon_greaves=1, and(lightning_resist, fire_resist, cold_resist), ethereal
|
||||
rare_mirrored_boots=1, and(lightning_resist, fire_resist, cold_resist), ethereal
|
||||
rare_boneweave_boots=1, and(lightning_resist, fire_resist, cold_resist), ethereal
|
||||
rare_scarabshell_boots=1, and(lightning_resist, fire_resist, cold_resist), ethereal
|
||||
rare_wyrmhide_boots=1, and(lightning_resist, fire_resist, cold_resist), ethereal
|
||||
rare_myrmidon_greaves=1, lightning_fire_cold_res, ethereal
|
||||
rare_mirrored_boots=1, lightning_fire_cold_res, ethereal
|
||||
rare_boneweave_boots=1, lightning_fire_cold_res, ethereal
|
||||
rare_scarabshell_boots=1, lightning_fire_cold_res, ethereal
|
||||
rare_wyrmhide_boots=1, lightning_fire_cold_res, ethereal
|
||||
|
||||
;---------------------------------------;
|
||||
;============= RARE GLOVES =============;
|
||||
@@ -709,14 +725,14 @@ white_superior_phase_blade=0
|
||||
;-----------------------------------------;
|
||||
|
||||
;eth/non eth/socketed weapons
|
||||
gray_weapon_berserker_axe=0, (socketed_1, socketed_2, socketed_3, socketed_4)
|
||||
gray_weapon_berserker_axe=0,, (socketed_1, socketed_2, socketed_3, socketed_4)
|
||||
gray_weapon_crystal_sword=0,, (ethereal, socketed_1, socketed_2, socketed_3)
|
||||
gray_weapon_flail=0,, (ethereal, socketed_1, socketed_2, socketed_3)
|
||||
gray_weapon_phase_blade=0,, (ethereal, socketed_1, socketed_2, socketed_3, socketed_4, socketed_6)
|
||||
gray_weapon_war_scepter=1,, (ethereal, socketed_1, socketed_2, socketed_3, socketed_4)
|
||||
gray_divine_scepter=1,, (ethereal, socketed_1, socketed_2, socketed_3, socketed_4)
|
||||
gray_caduceus=1,, (ethereal, socketed_1, socketed_2, socketed_3, socketed_4)
|
||||
gray_weapon_war_pike=0, (socketed_1, socketed_2, socketed_3, socketed_4, socketed_5)
|
||||
gray_weapon_war_pike=0,, (socketed_1, socketed_2, socketed_3, socketed_4, socketed_5)
|
||||
|
||||
;superior ethereal
|
||||
gray_superior_berserker_axe=1, enhanced_damage, (socketed_1, socketed_2, socketed_3, socketed_4)
|
||||
@@ -1454,4 +1470,4 @@ rare_thunder_maul=0, ethereal
|
||||
rare_truncheon=0, ethereal
|
||||
rare_tyrant_club=0, ethereal
|
||||
rare_war_club=0, ethereal
|
||||
rare_war_hammer=0, ethereal
|
||||
rare_war_hammer=0, ethereal
|
||||
@@ -31,3 +31,5 @@ dependencies:
|
||||
- parse
|
||||
- dependencies/tesserocr-2.5.2-cp39-cp39-win_amd64.whl
|
||||
- typing_extensions
|
||||
- rapidfuzz
|
||||
- colorama
|
||||
@@ -5,12 +5,14 @@ import time
|
||||
import os
|
||||
import random
|
||||
import cv2
|
||||
import math
|
||||
from copy import copy
|
||||
from typing import Union
|
||||
from collections import OrderedDict
|
||||
from health_manager import set_pause_state
|
||||
from transmute import Transmute
|
||||
from utils.misc import wait, hms
|
||||
|
||||
from utils.misc import wait
|
||||
from game_stats import GameStats
|
||||
from logger import Logger
|
||||
from config import Config
|
||||
@@ -25,9 +27,10 @@ from char.trapsin import Trapsin
|
||||
from char.hammerdin import Hammerdin
|
||||
from char.barbarian import Barbarian
|
||||
from char.necro import Necro
|
||||
from char.poison_necro import Poison_Necro
|
||||
from char.basic import Basic
|
||||
from char.basic_ranged import Basic_Ranged
|
||||
from ui_manager import wait_for_screen_object, detect_screen_object, ScreenObjects
|
||||
from ui_manager import wait_until_hidden, wait_until_visible, ScreenObjects, is_visible
|
||||
from ui import meters, skills, view, character_select, main_menu
|
||||
from inventory import personal, vendor, belt, common, consumables
|
||||
|
||||
@@ -61,6 +64,8 @@ class Bot:
|
||||
self._char: IChar = Trapsin(Config().trapsin, self._pather)
|
||||
elif Config().char["type"] == "barbarian":
|
||||
self._char: IChar = Barbarian(Config().barbarian, self._pather)
|
||||
elif Config().char["type"] == "poison_necro":
|
||||
self._char: IChar = Poison_Necro(Config().poison_necro, self._pather)
|
||||
elif Config().char["type"] == "necro":
|
||||
self._char: IChar = Necro(Config().necro, self._pather)
|
||||
elif Config().char["type"] == "basic":
|
||||
@@ -80,16 +85,13 @@ class Bot:
|
||||
self._town_manager = TownManager(a1, a2, a3, a4, a5)
|
||||
|
||||
# Create runs
|
||||
if Config().routes["run_shenk"] and not Config().routes["run_eldritch"]:
|
||||
Logger.error("Running shenk without eldtritch is not supported. Either run none or both")
|
||||
os._exit(1)
|
||||
self._do_runs = {
|
||||
"run_trav": Config().routes["run_trav"],
|
||||
"run_pindle": Config().routes["run_pindle"],
|
||||
"run_shenk": Config().routes["run_shenk"] or Config().routes["run_eldritch"],
|
||||
"run_nihlathak": Config().routes["run_nihlathak"],
|
||||
"run_arcane": Config().routes["run_arcane"],
|
||||
"run_diablo": Config().routes["run_diablo"],
|
||||
"run_trav": Config().routes.get("run_trav"),
|
||||
"run_pindle": Config().routes.get("run_pindle"),
|
||||
"run_shenk": Config().routes.get("run_eldritch") or Config().routes.get("run_eldritch_shenk"),
|
||||
"run_nihlathak": Config().routes.get("run_nihlathak"),
|
||||
"run_arcane": Config().routes.get("run_arcane"),
|
||||
"run_diablo": Config().routes.get("run_diablo"),
|
||||
}
|
||||
# Adapt order to the config
|
||||
self._do_runs = OrderedDict((k, self._do_runs[k]) for k in Config().routes_order if k in self._do_runs and self._do_runs[k])
|
||||
@@ -105,7 +107,6 @@ class Bot:
|
||||
self._diablo = Diablo(self._pather, self._town_manager, self._char, self._pickit)
|
||||
|
||||
# Create member variables
|
||||
self._pick_corpse = False
|
||||
self._picked_up_items = False
|
||||
self._curr_loc: Union[bool, Location] = None
|
||||
self._use_id_tome = True
|
||||
@@ -116,6 +117,7 @@ class Bot:
|
||||
self._current_threads = []
|
||||
self._ran_no_pickup = False
|
||||
self._previous_run_failed = False
|
||||
self._timer = time.time()
|
||||
|
||||
# Create State Machine
|
||||
self._states=['initialization','hero_selection', 'town', 'pindle', 'shenk', 'trav', 'nihlathak', 'arcane', 'diablo']
|
||||
@@ -218,8 +220,7 @@ class Bot:
|
||||
|
||||
def on_create_game(self):
|
||||
# Start a game from hero selection
|
||||
m = wait_for_screen_object(ScreenObjects.MainMenu)
|
||||
if m.valid:
|
||||
if (m := wait_until_visible(ScreenObjects.MainMenu)).valid:
|
||||
if "DARK" in m.name:
|
||||
keyboard.send("esc")
|
||||
main_menu.start_game()
|
||||
@@ -229,6 +230,19 @@ class Bot:
|
||||
|
||||
def on_start_from_town(self):
|
||||
self._curr_loc = self._town_manager.wait_for_town_spawn()
|
||||
|
||||
# Handle picking up corpse in case of death
|
||||
if (corpse_present := is_visible(ScreenObjects.Corpse)):
|
||||
self._previous_run_failed = True
|
||||
view.pickup_corpse()
|
||||
wait_until_hidden(ScreenObjects.Corpse)
|
||||
belt.fill_up_belt_from_inventory(Config().char["num_loot_columns"])
|
||||
self._char.discover_capabilities()
|
||||
if corpse_present and self._char.capabilities.can_teleport_with_charges and not self._char.select_tp():
|
||||
keybind = self._char._skill_hotkeys["teleport"]
|
||||
Logger.info(f"Teleport keybind is lost upon death. Rebinding teleport to '{keybind}'")
|
||||
self._char.remap_right_skill_hotkey("TELE_ACTIVE", self._char._skill_hotkeys["teleport"])
|
||||
|
||||
# Check for the current game ip and pause if we are able to obtain the hot ip
|
||||
if Config().dclone["region_ips"] != "" and Config().dclone["dclone_hotip"] != "":
|
||||
cur_game_ip = get_d2r_game_ip()
|
||||
@@ -250,23 +264,18 @@ class Bot:
|
||||
Logger.info("Activated /nopickup")
|
||||
else:
|
||||
Logger.error("Failed to detect if /nopickup command was applied or not")
|
||||
|
||||
self._game_stats.log_exp()
|
||||
|
||||
self.trigger_or_stop("maintenance")
|
||||
|
||||
def on_maintenance(self):
|
||||
# Handle picking up corpse in case of death
|
||||
self._pick_corpse = detect_screen_object(ScreenObjects.Corpse).valid
|
||||
if self._pick_corpse:
|
||||
self._previous_run_failed = True
|
||||
time.sleep(1.6)
|
||||
view.pickup_corpse()
|
||||
wait(1.2, 1.5)
|
||||
belt.fill_up_belt_from_inventory(Config().char["num_loot_columns"])
|
||||
wait(0.5)
|
||||
self._char.discover_capabilities()
|
||||
if self._pick_corpse and self._char.capabilities.can_teleport_with_charges and not self._char.select_tp():
|
||||
keybind = self._char._skill_hotkeys["teleport"]
|
||||
Logger.info(f"Teleport keybind is lost upon death. Rebinding teleport to '{keybind}'")
|
||||
self._char.remap_right_skill_hotkey("TELE_ACTIVE", self._char._skill_hotkeys["teleport"])
|
||||
# Pause health manager if not already paused
|
||||
set_pause_state(True)
|
||||
|
||||
# Dismiss skill/quest/help/stats icon if they are on screen
|
||||
if not view.dismiss_skills_icon():
|
||||
view.return_to_play()
|
||||
|
||||
# Look at belt to figure out how many pots need to be picked up
|
||||
belt.update_pot_needs()
|
||||
@@ -281,7 +290,7 @@ class Bot:
|
||||
# Update TP, ID, key needs
|
||||
if self._game_stats._game_counter == 1:
|
||||
self._use_id_tome = common.tome_state(img, 'id')[0] is not None
|
||||
self._use_keys = detect_screen_object(ScreenObjects.Key, img).valid
|
||||
self._use_keys = is_visible(ScreenObjects.Key, img)
|
||||
if (self._game_stats._run_counter - 1) % 4 == 0 or self._previous_run_failed:
|
||||
consumables.update_tome_key_needs(img, item_type = 'tp')
|
||||
if self._use_id_tome:
|
||||
@@ -323,7 +332,6 @@ class Bot:
|
||||
items = result_items
|
||||
sell_items = any([item.sell for item in items]) if items else None
|
||||
Logger.debug(f"Needs: {consumables.get_needs()}")
|
||||
wait(0.5, 0.8)
|
||||
elif meters.get_health(img) < 0.6 or meters.get_mana(img) < 0.2:
|
||||
Logger.info("Healing at next possible Vendor")
|
||||
self._curr_loc = self._town_manager.heal(self._curr_loc)
|
||||
@@ -331,7 +339,7 @@ class Bot:
|
||||
return self.trigger_or_stop("end_game", failed=True)
|
||||
|
||||
# Stash stuff
|
||||
if keep_items:
|
||||
if keep_items or personal.get_inventory_gold_full():
|
||||
Logger.info("Stashing items")
|
||||
self._curr_loc, result_items = self._town_manager.stash(self._curr_loc, items=items)
|
||||
Logger.info("Running transmutes")
|
||||
@@ -340,10 +348,9 @@ class Bot:
|
||||
if not self._curr_loc:
|
||||
return self.trigger_or_stop("end_game", failed=True)
|
||||
self._picked_up_items = False
|
||||
wait(1.0)
|
||||
|
||||
# Check if we are out of tps or need repairing
|
||||
need_repair = detect_screen_object(ScreenObjects.NeedRepair).valid
|
||||
need_repair = is_visible(ScreenObjects.NeedRepair)
|
||||
need_routine_repair = False if not Config().char["runs_per_repair"] else self._game_stats._run_counter % Config().char["runs_per_repair"] == 0
|
||||
need_refill_teleport = self._char.capabilities.can_teleport_with_charges and (not self._char.select_tp() or self._char.is_low_on_teleport_charges())
|
||||
if need_repair or need_routine_repair or need_refill_teleport or sell_items:
|
||||
@@ -360,11 +367,9 @@ class Bot:
|
||||
items = result_items
|
||||
if not self._curr_loc:
|
||||
return self.trigger_or_stop("end_game", failed=True)
|
||||
wait(1.0)
|
||||
|
||||
# Check if merc needs to be revived
|
||||
match = detect_screen_object(ScreenObjects.MercIcon)
|
||||
if not match.valid and Config().char["use_merc"]:
|
||||
if not is_visible(ScreenObjects.MercIcon) and Config().char["use_merc"]:
|
||||
Logger.info("Resurrect merc")
|
||||
self._game_stats.log_merc_death()
|
||||
self._curr_loc = self._town_manager.resurrect(self._curr_loc)
|
||||
@@ -399,11 +404,34 @@ class Bot:
|
||||
self._curr_loc = False
|
||||
self._pre_buffed = False
|
||||
view.save_and_exit()
|
||||
set_pause_state(True)
|
||||
self._game_stats.log_end_game(failed=failed)
|
||||
|
||||
if Config().general["max_runtime_before_break_m"] and Config().general["break_length_m"]:
|
||||
elapsed_time = time.time() - self._timer
|
||||
Logger.debug(f'Session length = {math.ceil(elapsed_time/60)} minutes, max_runtime_before_break_m {Config().general["max_runtime_before_break_m"]}.')
|
||||
|
||||
if elapsed_time > (Config().general["max_runtime_before_break_m"]*60):
|
||||
break_msg = f'Ran for {hms(elapsed_time)}, taking a break for {hms(Config().general["break_length_m"]*60)}.'
|
||||
Logger.info(break_msg)
|
||||
self._messenger.send_message(break_msg)
|
||||
if not self._pausing:
|
||||
self.toggle_pause()
|
||||
|
||||
wait(Config().general["break_length_m"]*60)
|
||||
|
||||
break_msg = f'Break over, will now run for {hms(Config().general["max_runtime_before_break_m"]*60)}.'
|
||||
Logger.info(break_msg)
|
||||
self._messenger.send_message(break_msg)
|
||||
if self._pausing:
|
||||
self.toggle_pause()
|
||||
|
||||
self._timer = time.time()
|
||||
|
||||
|
||||
self._do_runs = copy(self._do_runs_reset)
|
||||
if Config().general["randomize_runs"]:
|
||||
self.shuffle_runs()
|
||||
wait(0.2, 0.5)
|
||||
self.trigger_or_stop("init")
|
||||
|
||||
def on_end_run(self):
|
||||
@@ -413,15 +441,18 @@ class Bot:
|
||||
if success:
|
||||
self._curr_loc = self._town_manager.wait_for_tp(self._curr_loc)
|
||||
if self._curr_loc:
|
||||
set_pause_state(True)
|
||||
return self.trigger_or_stop("maintenance")
|
||||
if not skills.has_tps():
|
||||
consumables.set_needs("tp", 20)
|
||||
set_pause_state(True)
|
||||
self.trigger_or_stop("end_game", failed=True)
|
||||
|
||||
# All the runs go here
|
||||
# ==================================
|
||||
def _ending_run_helper(self, res: Union[bool, tuple[Location, bool]]):
|
||||
self._game_stats._run_counter += 1
|
||||
self._game_stats.log_exp()
|
||||
# either fill member variables with result data or mark run as failed
|
||||
failed_run = True
|
||||
if res:
|
||||
@@ -438,9 +469,10 @@ class Bot:
|
||||
def on_run_pindle(self):
|
||||
res = False
|
||||
self._do_runs["run_pindle"] = False
|
||||
self._game_stats.update_location("Pin" if Config().general['discord_status_condensed'] else "Pindle")
|
||||
self._game_stats.update_location("Pin")
|
||||
self._curr_loc = self._pindle.approach(self._curr_loc)
|
||||
if self._curr_loc:
|
||||
set_pause_state(False)
|
||||
res = self._pindle.battle(not self._pre_buffed)
|
||||
self._ending_run_helper(res)
|
||||
|
||||
@@ -449,41 +481,46 @@ class Bot:
|
||||
self._do_runs["run_shenk"] = False
|
||||
self._curr_loc = self._shenk.approach(self._curr_loc)
|
||||
if self._curr_loc:
|
||||
res = self._shenk.battle(Config().routes["run_shenk"], not self._pre_buffed, self._game_stats)
|
||||
set_pause_state(False)
|
||||
res = self._shenk.battle(Config().routes.get("run_eldritch_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("Trav" if Config().general['discord_status_condensed'] else "Travincal")
|
||||
self._game_stats.update_location("Trav")
|
||||
self._curr_loc = self._trav.approach(self._curr_loc)
|
||||
if self._curr_loc:
|
||||
set_pause_state(False)
|
||||
res = self._trav.battle(not self._pre_buffed)
|
||||
self._ending_run_helper(res)
|
||||
|
||||
def on_run_nihlathak(self):
|
||||
res = False
|
||||
self._do_runs["run_nihlathak"] = False
|
||||
self._game_stats.update_location("Nihl" if Config().general['discord_status_condensed'] else "Nihlathak")
|
||||
self._game_stats.update_location("Nihl")
|
||||
self._curr_loc = self._nihlathak.approach(self._curr_loc)
|
||||
if self._curr_loc:
|
||||
set_pause_state(False)
|
||||
res = self._nihlathak.battle(not self._pre_buffed)
|
||||
self._ending_run_helper(res)
|
||||
|
||||
def on_run_arcane(self):
|
||||
res = False
|
||||
self._do_runs["run_arcane"] = False
|
||||
self._game_stats.update_location("Arc" if Config().general['discord_status_condensed'] else "Arcane")
|
||||
self._game_stats.update_location("Arc")
|
||||
self._curr_loc = self._arcane.approach(self._curr_loc)
|
||||
if self._curr_loc:
|
||||
set_pause_state(False)
|
||||
res = self._arcane.battle(not self._pre_buffed)
|
||||
self._ending_run_helper(res)
|
||||
|
||||
def on_run_diablo(self):
|
||||
res = False
|
||||
self._do_runs["run_diablo"] = False
|
||||
self._game_stats.update_location("Dia" if Config().general['discord_status_condensed'] else "Diablo")
|
||||
self._game_stats.update_location("Dia")
|
||||
self._curr_loc = self._diablo.approach(self._curr_loc)
|
||||
if self._curr_loc:
|
||||
set_pause_state(False)
|
||||
res = self._diablo.battle(not self._pre_buffed)
|
||||
self._ending_run_helper(res)
|
||||
|
||||
@@ -93,8 +93,8 @@ class Barbarian(IChar):
|
||||
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._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, timeout=1.0, do_pre_move=self._do_pre_move)
|
||||
self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, timeout=0.1)
|
||||
self._cast_war_cry(Config().char["atk_len_pindle"])
|
||||
wait(0.1, 0.15)
|
||||
self._do_hork(4)
|
||||
@@ -104,7 +104,7 @@ class Barbarian(IChar):
|
||||
if self.capabilities.can_teleport_natively:
|
||||
self._pather.traverse_nodes_fixed("eldritch_end", self)
|
||||
else:
|
||||
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)
|
||||
self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, timeout=1.0, do_pre_move=self._do_pre_move)
|
||||
wait(0.05, 0.1)
|
||||
self._cast_war_cry(Config().char["atk_len_eldritch"])
|
||||
wait(0.1, 0.15)
|
||||
@@ -112,7 +112,7 @@ class Barbarian(IChar):
|
||||
return True
|
||||
|
||||
def kill_shenk(self):
|
||||
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)
|
||||
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=1.0, do_pre_move=self._do_pre_move)
|
||||
wait(0.05, 0.1)
|
||||
self._cast_war_cry(Config().char["atk_len_shenk"])
|
||||
wait(0.1, 0.15)
|
||||
@@ -123,14 +123,14 @@ class Barbarian(IChar):
|
||||
# Check out the node screenshot in assets/templates/trav/nodes to see where each node is at
|
||||
atk_len = Config().char["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._pather.traverse_nodes([228, 229], self, timeout=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.capabilities.can_teleport_natively:
|
||||
# Back to center stairs and more war cry
|
||||
self._pather.traverse_nodes([226], self, time_out=2.5, force_tp=True)
|
||||
self._pather.traverse_nodes([226], self, timeout=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)
|
||||
@@ -143,7 +143,7 @@ class Barbarian(IChar):
|
||||
|
||||
def kill_nihlathak(self, end_nodes: list[int]) -> bool:
|
||||
# Move close to nihlathak
|
||||
self._pather.traverse_nodes(end_nodes, self, time_out=0.8, do_pre_move=False)
|
||||
self._pather.traverse_nodes(end_nodes, self, timeout=0.8, do_pre_move=False)
|
||||
# move mouse to center (leftover from hammerdin)
|
||||
pos_m = convert_abs_to_monitor((0, 0))
|
||||
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
|
||||
|
||||
@@ -75,8 +75,8 @@ class Basic(IChar):
|
||||
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._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, timeout=1.0, do_pre_move=self._do_pre_move)
|
||||
self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, timeout=0.1)
|
||||
self._cast_attack_pattern(Config().char["atk_len_pindle"])
|
||||
wait(0.1, 0.15)
|
||||
return True
|
||||
@@ -88,7 +88,7 @@ class Basic(IChar):
|
||||
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)
|
||||
self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, timeout=1.0, do_pre_move=self._do_pre_move)
|
||||
wait(0.05, 0.1)
|
||||
self._cast_attack_pattern(Config().char["atk_len_eldritch"])
|
||||
return True
|
||||
@@ -97,7 +97,7 @@ class Basic(IChar):
|
||||
# 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)
|
||||
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=1.0, do_pre_move=self._do_pre_move)
|
||||
wait(0.05, 0.1)
|
||||
self._cast_attack_pattern(Config().char["atk_len_shenk"])
|
||||
wait(0.1, 0.15)
|
||||
@@ -107,14 +107,14 @@ class Basic(IChar):
|
||||
# Check out the node screenshot in assets/templates/trav/nodes to see where each node is at
|
||||
atk_len = Config().char["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._pather.traverse_nodes([228, 229], self, timeout=2.5, force_tp=True)
|
||||
self._cast_attack_pattern(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.capabilities.can_teleport_natively:
|
||||
# Back to center stairs and more war cry
|
||||
self._pather.traverse_nodes([226], self, time_out=2.5, force_tp=True)
|
||||
self._pather.traverse_nodes([226], self, timeout=2.5, force_tp=True)
|
||||
self._cast_attack_pattern(atk_len)
|
||||
# move a bit to the top
|
||||
self._move_and_attack((65, -30), atk_len)
|
||||
@@ -126,7 +126,7 @@ class Basic(IChar):
|
||||
|
||||
def kill_nihlathak(self, end_nodes: list[int]) -> bool:
|
||||
# Move close to nihlathak
|
||||
self._pather.traverse_nodes(end_nodes, self, time_out=0.8, do_pre_move=False)
|
||||
self._pather.traverse_nodes(end_nodes, self, timeout=0.8, do_pre_move=False)
|
||||
# move mouse to center (leftover from hammerdin)
|
||||
pos_m = convert_abs_to_monitor((0, 0))
|
||||
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
|
||||
|
||||
@@ -72,7 +72,7 @@ class Basic_Ranged(IChar):
|
||||
keyboard.send(Config().char["stand_still"], do_press=False)
|
||||
wait(self._cast_duration, self._cast_duration + 0.2)
|
||||
# Move to items
|
||||
self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, time_out=1.4, force_tp=True)
|
||||
self._pather.traverse_nodes((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), self, timeout=1.4, force_tp=True)
|
||||
return True
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ class Basic_Ranged(IChar):
|
||||
keyboard.send(Config().char["stand_still"], do_press=False)
|
||||
wait(self._cast_duration, self._cast_duration + 0.2)
|
||||
# Move to items
|
||||
self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, time_out=1.4, force_tp=True)
|
||||
self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, timeout=1.4, force_tp=True)
|
||||
return True
|
||||
|
||||
def kill_shenk(self) -> bool:
|
||||
@@ -111,7 +111,7 @@ class Basic_Ranged(IChar):
|
||||
keyboard.send(Config().char["stand_still"], do_press=False)
|
||||
wait(self._cast_duration, self._cast_duration + 0.2)
|
||||
# Move to items
|
||||
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, time_out=1.4, force_tp=True)
|
||||
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=1.4, force_tp=True)
|
||||
return True
|
||||
|
||||
def kill_council(self) -> bool:
|
||||
@@ -119,7 +119,7 @@ class Basic_Ranged(IChar):
|
||||
# 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.offset_node(229, [250, 130])
|
||||
self._pather.traverse_nodes([228, 229], self, time_out=2.5, force_tp=True)
|
||||
self._pather.traverse_nodes([228, 229], self, timeout=2.5, force_tp=True)
|
||||
self._pather.offset_node(229, [-250, -130])
|
||||
atk_pos_abs = self._pather.find_abs_node_pos(230, grab())
|
||||
if atk_pos_abs is None:
|
||||
@@ -149,7 +149,7 @@ class Basic_Ranged(IChar):
|
||||
self._left_attack(cast_pos_abs, spray=11)
|
||||
# Move outside
|
||||
# Move a bit back and another round
|
||||
self._pather.traverse_nodes([226], self, time_out=2.5, force_tp=True)
|
||||
self._pather.traverse_nodes([226], self, timeout=2.5, force_tp=True)
|
||||
cast_pos_abs = np.array([-300, -100])
|
||||
for _ in range(atk_len_trav_2):
|
||||
if skills.is_right_skill_active():
|
||||
@@ -183,7 +183,7 @@ class Basic_Ranged(IChar):
|
||||
self._left_attack(cast_pos_abs, spray=11)
|
||||
# Move to items
|
||||
wait(self._cast_duration, self._cast_duration + 0.2)
|
||||
self._pather.traverse_nodes(end_nodes, self, time_out=0.8)
|
||||
self._pather.traverse_nodes(end_nodes, self, timeout=0.8)
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ class Hammerdin(IChar):
|
||||
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, timeout=1.0, do_pre_move=self._do_pre_move)
|
||||
self._cast_hammers(Config().char["atk_len_pindle"])
|
||||
wait(0.1, 0.15)
|
||||
self._cast_hammers(1.6, "redemption")
|
||||
@@ -95,7 +95,7 @@ class Hammerdin(IChar):
|
||||
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, force_tp=True, use_tp_charge=True)
|
||||
self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, timeout=1.0, do_pre_move=self._do_pre_move, force_tp=True, use_tp_charge=True)
|
||||
wait(0.05, 0.1)
|
||||
self._cast_hammers(Config().char["atk_len_eldritch"])
|
||||
wait(0.1, 0.15)
|
||||
@@ -106,7 +106,7 @@ class Hammerdin(IChar):
|
||||
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, force_tp=True, use_tp_charge=True)
|
||||
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=1.0, do_pre_move=self._do_pre_move, force_tp=True, use_tp_charge=True)
|
||||
wait(0.05, 0.1)
|
||||
self._cast_hammers(Config().char["atk_len_shenk"])
|
||||
wait(0.1, 0.15)
|
||||
@@ -120,14 +120,14 @@ class Hammerdin(IChar):
|
||||
# Check out the node screenshot in assets/templates/trav/nodes to see where each node is at
|
||||
atk_len = Config().char["atk_len_trav"]
|
||||
# Go inside and hammer a bit
|
||||
self._pather.traverse_nodes([228, 229], self, time_out=2.5, force_tp=True, use_tp_charge=True)
|
||||
self._pather.traverse_nodes([228, 229], self, timeout=2.5, force_tp=True, use_tp_charge=True)
|
||||
self._cast_hammers(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.capabilities.can_teleport_natively or self.capabilities.can_teleport_with_charges:
|
||||
# Back to center stairs and more hammers
|
||||
self._pather.traverse_nodes([226], self, time_out=2.5, force_tp=True, use_tp_charge=True)
|
||||
self._pather.traverse_nodes([226], self, timeout=2.5, force_tp=True, use_tp_charge=True)
|
||||
self._cast_hammers(atk_len)
|
||||
# move a bit to the top
|
||||
self._move_and_attack((65, -30), atk_len)
|
||||
@@ -140,7 +140,7 @@ class Hammerdin(IChar):
|
||||
|
||||
def kill_nihlathak(self, end_nodes: list[int]) -> bool:
|
||||
# Move close to nihlathak
|
||||
self._pather.traverse_nodes(end_nodes, self, time_out=0.8, do_pre_move=False)
|
||||
self._pather.traverse_nodes(end_nodes, self, timeout=0.8, do_pre_move=False)
|
||||
# move mouse to center, otherwise hammers sometimes dont fly, not sure why
|
||||
pos_m = convert_abs_to_monitor((0, 0))
|
||||
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
|
||||
@@ -200,7 +200,7 @@ class Hammerdin(IChar):
|
||||
|
||||
elif location == "rof_01": #node 603 - outside CS in ROF
|
||||
### APPROACH ###
|
||||
if not self._pather.traverse_nodes([603], self, time_out=3): return False #calibrate after static path
|
||||
if not self._pather.traverse_nodes([603], self, timeout=3): return False #calibrate after static path
|
||||
pos_m = convert_abs_to_monitor((0, 0))
|
||||
### ATTACK ###
|
||||
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
|
||||
@@ -221,7 +221,7 @@ class Hammerdin(IChar):
|
||||
|
||||
elif location == "rof_02": #node 604 - inside ROF
|
||||
### APPROACH ###
|
||||
if not self._pather.traverse_nodes([604], self, time_out=3): return False #threshold=0.8 (ex 601)
|
||||
if not self._pather.traverse_nodes([604], self, timeout=3): return False #threshold=0.8 (ex 601)
|
||||
### ATTACK ###
|
||||
pos_m = convert_abs_to_monitor((0, 0))
|
||||
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
|
||||
@@ -298,7 +298,7 @@ class Hammerdin(IChar):
|
||||
wait(0.3, 0.6)
|
||||
### LOOT ###
|
||||
self._picked_up_items |= self._pickit.pick_up_items(self)
|
||||
if not self._pather.traverse_nodes([673], self): return False # , time_out=3): # Re-adjust itself and continues to attack
|
||||
if not self._pather.traverse_nodes([673], self): return False # , timeout=3): # Re-adjust itself and continues to attack
|
||||
|
||||
elif location == "entrance1_02": #node 673
|
||||
### APPROACH ###
|
||||
@@ -317,7 +317,7 @@ class Hammerdin(IChar):
|
||||
### LOOT ###
|
||||
self._picked_up_items |= self._pickit.pick_up_items(self)
|
||||
self._pather.traverse_nodes_fixed("diablo_entrance_1_1", self) # Moves char to postion close to node 674 continues to attack
|
||||
if not self._pather.traverse_nodes([674], self): return False#, time_out=3)
|
||||
if not self._pather.traverse_nodes([674], self): return False#, timeout=3)
|
||||
|
||||
elif location == "entrance1_03": #node 674
|
||||
### APPROACH ###
|
||||
@@ -336,9 +336,9 @@ class Hammerdin(IChar):
|
||||
### LOOT ###
|
||||
self._picked_up_items |= self._pickit.pick_up_items(self)
|
||||
self._picked_up_items |= self._pickit.pick_up_items(self)
|
||||
if not self._pather.traverse_nodes([675], self): return False#, time_out=3) # Re-adjust itself
|
||||
if not self._pather.traverse_nodes([675], self): return False#, timeout=3) # Re-adjust itself
|
||||
self._pather.traverse_nodes_fixed("diablo_entrance_1_1", self) #static path to get to be able to spot 676
|
||||
if not self._pather.traverse_nodes([676], self): return False#, time_out=3)
|
||||
if not self._pather.traverse_nodes([676], self): return False#, timeout=3)
|
||||
|
||||
elif location == "entrance1_04": #node 676- Hall3
|
||||
### APPROACH ###
|
||||
@@ -379,10 +379,10 @@ class Hammerdin(IChar):
|
||||
|
||||
elif location == "entrance2_02": #node 682
|
||||
### APPROACH ###
|
||||
#if not self._pather.traverse_nodes([682], self): return False # , time_out=3):
|
||||
#if not self._pather.traverse_nodes([682], self): return False # , timeout=3):
|
||||
self._pather.traverse_nodes_fixed("diablo_trash_b_hall2_605_right", self) #pull mobs from the right
|
||||
wait (0.2, 0.5)
|
||||
if not self._pather.traverse_nodes([605], self): return False#, time_out=3)
|
||||
if not self._pather.traverse_nodes([605], self): return False#, timeout=3)
|
||||
### ATTACK ###
|
||||
pos_m = convert_abs_to_monitor((0, 0))
|
||||
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
|
||||
@@ -400,13 +400,13 @@ class Hammerdin(IChar):
|
||||
|
||||
elif location == "entrance2_03": #node 683
|
||||
### APPROACH ###
|
||||
#if not self._pather.traverse_nodes([682], self): return False # , time_out=3):
|
||||
#if not self._pather.traverse_nodes([682], self): return False # , timeout=3):
|
||||
#self._pather.traverse_nodes_fixed("diablo_entrance2_1", self)
|
||||
#if not self._pather.traverse_nodes([683], self): return False # , time_out=3):
|
||||
#if not self._pather.traverse_nodes([683], self): return False # , timeout=3):
|
||||
self._pather.traverse_nodes_fixed("diablo_trash_b_hall2_605_top1", self) #pull mobs from top
|
||||
wait (0.2, 0.5)
|
||||
self._pather.traverse_nodes_fixed("diablo_trash_b_hall2_605_top2", self) #pull mobs from top
|
||||
if not self._pather.traverse_nodes([605], self): return False#, time_out=3)
|
||||
if not self._pather.traverse_nodes([605], self): return False#, timeout=3)
|
||||
### ATTACK ###
|
||||
pos_m = convert_abs_to_monitor((0, 0))
|
||||
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
|
||||
@@ -424,14 +424,14 @@ class Hammerdin(IChar):
|
||||
|
||||
elif location == "entrance2_04": #node 686 - Hall3
|
||||
### APPROACH ###
|
||||
if not self._pather.traverse_nodes([605], self): return False#, time_out=3)
|
||||
#if not self._pather.traverse_nodes([683,684], self): return False#, time_out=3)
|
||||
if not self._pather.traverse_nodes([605], self): return False#, timeout=3)
|
||||
#if not self._pather.traverse_nodes([683,684], self): return False#, timeout=3)
|
||||
#self._pather.traverse_nodes_fixed("diablo_entrance2_2", self)
|
||||
#if not self._pather.traverse_nodes([685,686], self): return False#, time_out=3)
|
||||
#if not self._pather.traverse_nodes([685,686], self): return False#, timeout=3)
|
||||
self._pather.traverse_nodes_fixed("diablo_trash_b_hall2_605_hall3", self)
|
||||
if not self._pather.traverse_nodes([609], self): return False#, time_out=3)
|
||||
if not self._pather.traverse_nodes([609], self): return False#, timeout=3)
|
||||
self._pather.traverse_nodes_fixed("diablo_trash_b_hall3_pull_609", self)
|
||||
if not self._pather.traverse_nodes([609], self): return False#, time_out=3)
|
||||
if not self._pather.traverse_nodes([609], self): return False#, timeout=3)
|
||||
### ATTACK ###
|
||||
pos_m = convert_abs_to_monitor((0, 0))
|
||||
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
|
||||
@@ -450,9 +450,9 @@ class Hammerdin(IChar):
|
||||
### LOOT ###
|
||||
self._picked_up_items |= self._pickit.pick_up_items(self)
|
||||
self._picked_up_items |= self._pickit.pick_up_items(self)
|
||||
if not self._pather.traverse_nodes([609], self): return False#, time_out=3)
|
||||
if not self._pather.traverse_nodes([609], self): return False#, timeout=3)
|
||||
self._picked_up_items |= self._pickit.pick_up_items(self)
|
||||
if not self._pather.traverse_nodes([609], self): return False#, time_out=3)
|
||||
if not self._pather.traverse_nodes([609], self): return False#, timeout=3)
|
||||
|
||||
####################
|
||||
# PENT TRASH TO SEAL
|
||||
@@ -605,7 +605,7 @@ class Hammerdin(IChar):
|
||||
|
||||
elif location == "A1-L_01": #node 611 seal layout A1-L: safe_dist
|
||||
### APPROACH ###
|
||||
if not self._pather.traverse_nodes([611], self): return False # , time_out=3):
|
||||
if not self._pather.traverse_nodes([611], self): return False # , timeout=3):
|
||||
### ATTACK ###
|
||||
pos_m = convert_abs_to_monitor((0, 0))
|
||||
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
|
||||
@@ -623,7 +623,7 @@ class Hammerdin(IChar):
|
||||
|
||||
elif location == "A1-L_02": #node 612 seal layout A1-L: center
|
||||
### APPROACH ###
|
||||
if not self._pather.traverse_nodes([612], self): return False # , time_out=3):
|
||||
if not self._pather.traverse_nodes([612], self): return False # , timeout=3):
|
||||
### ATTACK ###
|
||||
if self._skill_hotkeys["redemption"]:
|
||||
keyboard.send(self._skill_hotkeys["redemption"])
|
||||
@@ -646,7 +646,7 @@ class Hammerdin(IChar):
|
||||
|
||||
elif location == "A1-L_03": #node 613 seal layout A1-L: fake_seal
|
||||
### APPROACH ###
|
||||
if not self._pather.traverse_nodes([613], self): return False # , time_out=3):
|
||||
if not self._pather.traverse_nodes([613], self): return False # , timeout=3):
|
||||
### ATTACK ###
|
||||
pos_m = convert_abs_to_monitor((0, 0))
|
||||
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
|
||||
@@ -677,7 +677,7 @@ class Hammerdin(IChar):
|
||||
|
||||
elif location == "A1-L_seal2": #node 614 seal layout A1-L: boss_seal
|
||||
### APPROACH ###
|
||||
if not self._pather.traverse_nodes([613, 615], self): return False # , time_out=3):
|
||||
if not self._pather.traverse_nodes([613, 615], self): return False # , timeout=3):
|
||||
### ATTACK ###
|
||||
if self._skill_hotkeys["redemption"]:
|
||||
keyboard.send(self._skill_hotkeys["redemption"])
|
||||
@@ -693,7 +693,7 @@ class Hammerdin(IChar):
|
||||
### APPROACH ###
|
||||
if not self._pather.traverse_nodes_fixed("dia_a2y_hop_622", self): return False
|
||||
Logger.info("A2-Y: Hop!")
|
||||
#if not self._pather.traverse_nodes([622], self): return False # , time_out=3):
|
||||
#if not self._pather.traverse_nodes([622], self): return False # , timeout=3):
|
||||
if not self._pather.traverse_nodes([622], self): return False
|
||||
if self._skill_hotkeys["redemption"]:
|
||||
keyboard.send(self._skill_hotkeys["redemption"])
|
||||
@@ -743,7 +743,7 @@ class Hammerdin(IChar):
|
||||
### ATTACK ###
|
||||
### LOOT ###
|
||||
# we loot at boss
|
||||
if not self._pather.traverse_nodes([625], self): return False # , time_out=3):
|
||||
if not self._pather.traverse_nodes([625], self): return False # , timeout=3):
|
||||
if self._skill_hotkeys["redemption"]:
|
||||
keyboard.send(self._skill_hotkeys["redemption"])
|
||||
wait(0.3, 0.6)
|
||||
@@ -785,7 +785,7 @@ class Hammerdin(IChar):
|
||||
|
||||
elif location == "B1-S_seal2": #B only has 1 seal, which is the boss seal = seal2
|
||||
### APPROACH ###
|
||||
if not self._pather.traverse_nodes([634], self): return False # , time_out=3):
|
||||
if not self._pather.traverse_nodes([634], self): return False # , timeout=3):
|
||||
### ATTACK ###
|
||||
if self._skill_hotkeys["redemption"]:
|
||||
keyboard.send(self._skill_hotkeys["redemption"])
|
||||
@@ -821,7 +821,7 @@ class Hammerdin(IChar):
|
||||
elif location == "B2-U_seal2": #B only has 1 seal, which is the boss seal = seal2
|
||||
### APPROACH ###
|
||||
self._pather.traverse_nodes_fixed("dia_b2u_bold_seal", self)
|
||||
if not self._pather.traverse_nodes([644], self): return False # , time_out=3):
|
||||
if not self._pather.traverse_nodes([644], self): return False # , timeout=3):
|
||||
### ATTACK ###
|
||||
### LOOT ###
|
||||
# we loot at boss
|
||||
@@ -860,7 +860,7 @@ class Hammerdin(IChar):
|
||||
wait(0.1,0.3)
|
||||
self._pather.traverse_nodes_fixed("dia_c1f_hop_fakeseal", self)
|
||||
wait(0.1,0.3)
|
||||
if not self._pather.traverse_nodes([655], self): return False # , time_out=3):
|
||||
if not self._pather.traverse_nodes([655], self): return False # , timeout=3):
|
||||
### ATTACK ###
|
||||
pos_m = convert_abs_to_monitor((0, 0))
|
||||
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
|
||||
@@ -875,7 +875,7 @@ class Hammerdin(IChar):
|
||||
wait(0.3, 0.6)
|
||||
### LOOT ###
|
||||
self._picked_up_items |= self._pickit.pick_up_items(self)
|
||||
if not self._pather.traverse_nodes([655], self): return False # , time_out=3):
|
||||
if not self._pather.traverse_nodes([655], self): return False # , timeout=3):
|
||||
if self._skill_hotkeys["redemption"]:
|
||||
keyboard.send(self._skill_hotkeys["redemption"])
|
||||
wait(0.3, 0.6)
|
||||
@@ -883,7 +883,7 @@ class Hammerdin(IChar):
|
||||
elif location == "C1-F_seal2":
|
||||
### APPROACH ###
|
||||
self._pather.traverse_nodes_fixed("dia_c1f_654_651", self)
|
||||
if not self._pather.traverse_nodes([652], self): return False # , time_out=3):
|
||||
if not self._pather.traverse_nodes([652], self): return False # , timeout=3):
|
||||
### ATTACK ###
|
||||
pos_m = convert_abs_to_monitor((0, 0))
|
||||
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
|
||||
@@ -898,7 +898,7 @@ class Hammerdin(IChar):
|
||||
wait(0.3, 0.6)
|
||||
### LOOT ###
|
||||
self._picked_up_items |= self._pickit.pick_up_items(self)
|
||||
if not self._pather.traverse_nodes([652], self): return False # , time_out=3):
|
||||
if not self._pather.traverse_nodes([652], self): return False # , timeout=3):
|
||||
if self._skill_hotkeys["redemption"]:
|
||||
keyboard.send(self._skill_hotkeys["redemption"])
|
||||
wait(0.3, 0.6)
|
||||
@@ -930,7 +930,7 @@ class Hammerdin(IChar):
|
||||
|
||||
elif location == "C2-G_seal1":
|
||||
### APPROACH ###
|
||||
#if not self._pather.traverse_nodes([663, 662], self): return False # , time_out=3): #caused 7% failed runs, replaced by static path.
|
||||
#if not self._pather.traverse_nodes([663, 662], self): return False # , timeout=3): #caused 7% failed runs, replaced by static path.
|
||||
self._pather.traverse_nodes_fixed("dia_c2g_lc_661", self)
|
||||
### ATTACK ###
|
||||
### LOOT ###
|
||||
@@ -976,7 +976,7 @@ class Hammerdin(IChar):
|
||||
if self._skill_hotkeys["redemption"]:
|
||||
keyboard.send(self._skill_hotkeys["redemption"])
|
||||
wait(0.3, 0.6)
|
||||
if not self._pather.traverse_nodes([664, 665], self): return False # , time_out=3):
|
||||
if not self._pather.traverse_nodes([664, 665], self): return False # , timeout=3):
|
||||
|
||||
else:
|
||||
### APPROACH ###
|
||||
@@ -1002,7 +1002,7 @@ class Hammerdin(IChar):
|
||||
def kill_vizier(self, seal_layout:str) -> bool:
|
||||
if seal_layout == "A1-L":
|
||||
### APPROACH ###
|
||||
if not self._pather.traverse_nodes([612], self): return False # , time_out=3):
|
||||
if not self._pather.traverse_nodes([612], self): return False # , timeout=3):
|
||||
### ATTACK ###
|
||||
Logger.debug(seal_layout + ": Attacking Vizier at position 1/2")
|
||||
pos_m = convert_abs_to_monitor((0, 0))
|
||||
@@ -1011,7 +1011,7 @@ class Hammerdin(IChar):
|
||||
self._move_and_attack((-30, -15), Config().char["atk_len_diablo_vizier"] * 0.5)
|
||||
self._cast_hammers(1, "redemption")
|
||||
Logger.debug(seal_layout + ": Attacking Vizier at position 2/2")
|
||||
self._pather.traverse_nodes([611], self, time_out=3)
|
||||
self._pather.traverse_nodes([611], self, timeout=3)
|
||||
self._move_and_attack((30, 15), Config().char["atk_len_diablo_vizier"] * 0.5)
|
||||
self._move_and_attack((-30, -15), Config().char["atk_len_diablo_vizier"]) # no factor, so merc is not reset by teleport and he his some time to move & kill stray bosses
|
||||
self._cast_hammers(1, "redemption")
|
||||
@@ -1024,16 +1024,16 @@ class Hammerdin(IChar):
|
||||
wait(0.3, 1.2)
|
||||
### LOOT ###
|
||||
self._picked_up_items |= self._pickit.pick_up_items(self)
|
||||
if not self._pather.traverse_nodes([612], self): return False # , time_out=3):
|
||||
if not self._pather.traverse_nodes([612], self): return False # , timeout=3):
|
||||
if self._skill_hotkeys["redemption"]:
|
||||
keyboard.send(self._skill_hotkeys["redemption"])
|
||||
wait(0.3, 0.6)
|
||||
self._picked_up_items |= self._pickit.pick_up_items(self)
|
||||
if not self._pather.traverse_nodes([612], self): return False # , time_out=3): # recalibrate after loot
|
||||
if not self._pather.traverse_nodes([612], self): return False # , timeout=3): # recalibrate after loot
|
||||
|
||||
elif seal_layout == "A2-Y":
|
||||
### APPROACH ###
|
||||
if not self._pather.traverse_nodes([627, 622], self): return False # , time_out=3):
|
||||
if not self._pather.traverse_nodes([627, 622], self): return False # , timeout=3):
|
||||
### ATTACK ###
|
||||
Logger.debug(seal_layout + ": Attacking Vizier at position 1/2")
|
||||
pos_m = convert_abs_to_monitor((0, 0))
|
||||
@@ -1042,7 +1042,7 @@ class Hammerdin(IChar):
|
||||
self._move_and_attack((-30, -15), Config().char["atk_len_diablo_vizier"] * 0.5)
|
||||
self._cast_hammers(1, "redemption")
|
||||
Logger.debug(seal_layout + ": Attacking Vizier at position 2/2")
|
||||
self._pather.traverse_nodes([623], self, time_out=3)
|
||||
self._pather.traverse_nodes([623], self, timeout=3)
|
||||
self._move_and_attack((30, 15), Config().char["atk_len_diablo_vizier"] * 0.5)
|
||||
self._move_and_attack((-30, -15), Config().char["atk_len_diablo_vizier"] * 0.5)
|
||||
self._cast_hammers(1, "redemption")
|
||||
@@ -1064,12 +1064,12 @@ class Hammerdin(IChar):
|
||||
if self._skill_hotkeys["redemption"]:
|
||||
keyboard.send(self._skill_hotkeys["redemption"])
|
||||
wait(0.3, 0.6)
|
||||
if not self._pather.traverse_nodes([622], self): return False #, time_out=3):
|
||||
if not self._pather.traverse_nodes([622], self): return False #, timeout=3):
|
||||
if self._skill_hotkeys["redemption"]:
|
||||
keyboard.send(self._skill_hotkeys["redemption"])
|
||||
wait(0.3, 0.6)
|
||||
self._picked_up_items |= self._pickit.pick_up_items(self)
|
||||
if not self._pather.traverse_nodes([622], self): return False # , time_out=3): #recalibrate after loot
|
||||
if not self._pather.traverse_nodes([622], self): return False # , timeout=3): #recalibrate after loot
|
||||
|
||||
else:
|
||||
Logger.debug(seal_layout + ": Invalid location for kill_deseis("+ seal_layout +"), should not happen.")
|
||||
@@ -1093,16 +1093,16 @@ class Hammerdin(IChar):
|
||||
self._move_and_attack((-30, -15), Config().char["atk_len_diablo_deseis"] * 0.2)
|
||||
self._cast_hammers(1, "redemption")
|
||||
Logger.debug(seal_layout + ": Attacking De Seis at position 2/4")
|
||||
self._pather.traverse_nodes(nodes1, self, time_out=3)
|
||||
self._pather.traverse_nodes(nodes1, self, timeout=3)
|
||||
self._move_and_attack((30, 15), Config().char["atk_len_diablo_deseis"] * 0.2)
|
||||
self._move_and_attack((-30, -15), Config().char["atk_len_diablo_deseis"] * 0.2)
|
||||
self._cast_hammers(1, "redemption")
|
||||
Logger.debug(seal_layout + ": Attacking De Seis at position 3/4")
|
||||
self._pather.traverse_nodes(nodes2, self, time_out=3)
|
||||
self._pather.traverse_nodes(nodes2, self, timeout=3)
|
||||
self._move_and_attack((0, 0), Config().char["atk_len_diablo_deseis"] * 0.5)
|
||||
self._cast_hammers(1, "redemption")
|
||||
Logger.debug(seal_layout + ": Attacking De Seis at position 4/4")
|
||||
self._pather.traverse_nodes(nodes3, self, time_out=3)
|
||||
self._pather.traverse_nodes(nodes3, self, timeout=3)
|
||||
self._move_and_attack((0, 0), Config().char["atk_len_diablo_deseis"]) # no factor, so merc is not reset by teleport and he his some time to move & kill stray bosses
|
||||
wait(0.1, 0.2)
|
||||
self._cast_hammers(2, "redemption")
|
||||
@@ -1130,16 +1130,16 @@ class Hammerdin(IChar):
|
||||
self._move_and_attack((-30, -15), Config().char["atk_len_diablo_deseis"] * 0.2)
|
||||
self._cast_hammers(1, "redemption")
|
||||
Logger.debug(seal_layout + ": Attacking De Seis at position 2/4")
|
||||
self._pather.traverse_nodes(nodes1, self, time_out=3)
|
||||
self._pather.traverse_nodes(nodes1, self, timeout=3)
|
||||
self._move_and_attack((30, 15), Config().char["atk_len_diablo_deseis"] * 0.2)
|
||||
self._move_and_attack((-30, -15), Config().char["atk_len_diablo_deseis"] * 0.2)
|
||||
self._cast_hammers(1, "redemption")
|
||||
Logger.debug(seal_layout + ": Attacking De Seis at position 3/4")
|
||||
self._pather.traverse_nodes(nodes2, self, time_out=3)
|
||||
self._pather.traverse_nodes(nodes2, self, timeout=3)
|
||||
self._move_and_attack((0, 0), Config().char["atk_len_diablo_deseis"] * 0.5)
|
||||
self._cast_hammers(1, "redemption")
|
||||
Logger.debug(seal_layout + ": Attacking De Seis at position 4/4")
|
||||
self._pather.traverse_nodes(nodes3, self, time_out=3)
|
||||
self._pather.traverse_nodes(nodes3, self, timeout=3)
|
||||
self._move_and_attack((0, 0), Config().char["atk_len_diablo_deseis"]) # no factor, so merc is not reset by teleport and he his some time to move & kill stray bosses
|
||||
wait(0.1, 0.2)
|
||||
self._cast_hammers(2, "redemption")
|
||||
@@ -1150,11 +1150,11 @@ class Hammerdin(IChar):
|
||||
#if Config().general["info_screenshots"]: cv2.imwrite(f"./info_screenshots/info_check_deseis_dead" + seal_layout + "_" + time.strftime("%Y%m%d_%H%M%S") + ".png", grab())
|
||||
### LOOT ###
|
||||
self._picked_up_items |= self._pickit.pick_up_items(self)
|
||||
if not self._pather.traverse_nodes([641], self): return False # , time_out=3):
|
||||
if not self._pather.traverse_nodes([646], self): return False # , time_out=3):
|
||||
if not self._pather.traverse_nodes([641], self): return False # , timeout=3):
|
||||
if not self._pather.traverse_nodes([646], self): return False # , timeout=3):
|
||||
self._picked_up_items |= self._pickit.pick_up_items(self)
|
||||
if not self._pather.traverse_nodes([646], self): return False # , time_out=3):
|
||||
if not self._pather.traverse_nodes([640], self): return False # , time_out=3):
|
||||
if not self._pather.traverse_nodes([646], self): return False # , timeout=3):
|
||||
if not self._pather.traverse_nodes([640], self): return False # , timeout=3):
|
||||
self._picked_up_items |= self._pickit.pick_up_items(self)
|
||||
|
||||
else:
|
||||
|
||||
@@ -7,7 +7,7 @@ from inventory import consumables
|
||||
import keyboard
|
||||
import numpy as np
|
||||
from char.capabilities import CharacterCapabilities
|
||||
from ui_manager import wait_for_screen_object
|
||||
from ui_manager import is_visible, wait_until_visible
|
||||
from ui import skills
|
||||
from utils.custom_mouse import mouse
|
||||
from utils.misc import wait, cut_roi, is_in_roi, color_filter
|
||||
@@ -30,7 +30,7 @@ class IChar:
|
||||
self.capabilities = None
|
||||
|
||||
def _discover_capabilities(self) -> CharacterCapabilities:
|
||||
override = Config().advanced_options["override_capabilities"];
|
||||
override = Config().advanced_options["override_capabilities"]
|
||||
if override is None:
|
||||
if self._skill_hotkeys["teleport"]:
|
||||
if self.select_tp():
|
||||
@@ -69,7 +69,7 @@ class IChar:
|
||||
self,
|
||||
template_type: Union[str, List[str]],
|
||||
success_func: Callable = None,
|
||||
time_out: float = 8,
|
||||
timeout: float = 8,
|
||||
threshold: float = 0.68,
|
||||
telekinesis: bool = False
|
||||
) -> bool:
|
||||
@@ -77,17 +77,16 @@ class IChar:
|
||||
Finds any template from the template finder and interacts with it
|
||||
:param template_type: Strings or list of strings of the templates that should be searched for
|
||||
:param success_func: Function that will return True if the interaction is successful e.g. return True when loading screen is reached, defaults to None
|
||||
:param time_out: Timeout for the whole template selection, defaults to None
|
||||
:param timeout: Timeout for the whole template selection, defaults to None
|
||||
:param threshold: Threshold which determines if a template is found or not. None will use default form .ini files
|
||||
:return: True if success. False otherwise
|
||||
"""
|
||||
if type(template_type) == list and "A5_STASH" in template_type:
|
||||
# sometimes waypoint is opened and stash not found because of that, check for that
|
||||
match = detect_screen_object(ScreenObjects.WaypointLabel)
|
||||
if match.valid:
|
||||
if is_visible(ScreenObjects.WaypointLabel):
|
||||
keyboard.send("esc")
|
||||
start = time.time()
|
||||
while time_out is None or (time.time() - start) < time_out:
|
||||
while timeout is None or (time.time() - start) < timeout:
|
||||
template_match = TemplateFinder().search(template_type, grab(), threshold=threshold, normalize_monitor=True)
|
||||
if template_match.valid:
|
||||
Logger.debug(f"Select {template_match.name} ({template_match.score*100:.1f}% confidence)")
|
||||
@@ -175,6 +174,24 @@ class IChar:
|
||||
keyboard.send(Config().char["force_move"])
|
||||
else:
|
||||
mouse.click(button="left")
|
||||
|
||||
def walk(self, pos_monitor: Tuple[float, float], force_tp: bool = False, force_move: bool = False):
|
||||
factor = Config().advanced_options["pathing_delay_factor"]
|
||||
# in case we want to walk we actually want to move a bit before the point cause d2r will always "overwalk"
|
||||
pos_screen = convert_monitor_to_screen(pos_monitor)
|
||||
pos_abs = convert_screen_to_abs(pos_screen)
|
||||
dist = math.dist(pos_abs, (0, 0))
|
||||
min_wd = max(10, Config().ui_pos["min_walk_dist"])
|
||||
max_wd = random.randint(int(Config().ui_pos["max_walk_dist"] * 0.65), Config().ui_pos["max_walk_dist"])
|
||||
adjust_factor = max(max_wd, min(min_wd, dist - 50)) / max(min_wd, dist)
|
||||
pos_abs = [int(pos_abs[0] * adjust_factor), int(pos_abs[1] * adjust_factor)]
|
||||
x, y = convert_abs_to_monitor(pos_abs)
|
||||
mouse.move(x, y, randomize=5, delay_factor=[factor*0.1, factor*0.14])
|
||||
wait(0.012, 0.02)
|
||||
if force_move:
|
||||
keyboard.send(Config().char["force_move"])
|
||||
else:
|
||||
mouse.click(button="left")
|
||||
|
||||
def tp_town(self):
|
||||
# will check if tp is available and select the skill
|
||||
@@ -203,16 +220,14 @@ class IChar:
|
||||
mouse.click(button="right")
|
||||
consumables.increment_need("tp", 1)
|
||||
wait(0.8, 1.3) # takes quite a while for tp to be visible
|
||||
template_match = detect_screen_object(ScreenObjects.TownPortal)
|
||||
if template_match.valid:
|
||||
if (template_match := detect_screen_object(ScreenObjects.TownPortal)).valid:
|
||||
pos = template_match.center
|
||||
pos = (pos[0], pos[1] + 30)
|
||||
# Note: Template is top of portal, thus move the y-position a bit to the bottom
|
||||
mouse.move(*pos, randomize=6, delay_factor=[0.9, 1.1])
|
||||
wait(0.08, 0.15)
|
||||
mouse.click(button="left")
|
||||
match = wait_for_screen_object(ScreenObjects.Loading, 2)
|
||||
if match.valid:
|
||||
if wait_until_visible(ScreenObjects.Loading, 2).valid:
|
||||
return True
|
||||
# move mouse away to not overlay with the town portal if mouse is in center
|
||||
pos_screen = convert_monitor_to_screen(mouse.get_position())
|
||||
|
||||
@@ -13,7 +13,7 @@ from pather import Location, Pather
|
||||
import numpy as np
|
||||
import time
|
||||
import os
|
||||
from ui_manager import wait_for_screen_object, ScreenObjects
|
||||
from ui_manager import wait_until_visible, ScreenObjects
|
||||
|
||||
class Necro(IChar):
|
||||
def __init__(self, skill_hotkeys: dict, pather: Pather):
|
||||
@@ -491,7 +491,7 @@ class Necro(IChar):
|
||||
if self.capabilities.can_teleport_natively:
|
||||
self._pather.traverse_nodes_fixed("eldritch_end", self)
|
||||
else:
|
||||
self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, time_out=0.6, force_tp=True)
|
||||
self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, timeout=0.6, force_tp=True)
|
||||
self.bone_armor()
|
||||
|
||||
#get some more summons out for elite packs
|
||||
@@ -532,7 +532,7 @@ class Necro(IChar):
|
||||
|
||||
self._summon_stat()
|
||||
#continue to shenk fight
|
||||
self._pather.traverse_nodes(([ 146, 147, 148]), self, time_out=1.4, force_tp=True)
|
||||
self._pather.traverse_nodes(([ 146, 147, 148]), self, timeout=1.4, force_tp=True)
|
||||
|
||||
shenk_pos_abs = self._pather.find_abs_node_pos(149, grab())
|
||||
if shenk_pos_abs is None:
|
||||
@@ -566,7 +566,7 @@ class Necro(IChar):
|
||||
|
||||
# Move to items
|
||||
#wait(self._cast_duration, self._cast_duration + 0.2)
|
||||
self._pather.traverse_nodes(([148,149]), self, time_out=3.4, force_tp=True)
|
||||
self._pather.traverse_nodes(([148,149]), self, timeout=3.4, force_tp=True)
|
||||
for _ in range(30):
|
||||
self._summon_count()
|
||||
if self._skeletons_count < 10:
|
||||
@@ -678,11 +678,8 @@ class Necro(IChar):
|
||||
wait(0.08, 0.15)
|
||||
mouse.click(button="left")
|
||||
Logger.debug("enter durance lv 1")
|
||||
match = wait_for_screen_object(ScreenObjects.Loading, 2)
|
||||
if match.valid:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return wait_until_visible(ScreenObjects.Loading, 2).valid
|
||||
|
||||
else:
|
||||
Logger.debug("cant find durance!, trying fixed loc")
|
||||
return False
|
||||
@@ -719,8 +716,7 @@ class Necro(IChar):
|
||||
wait(0.08, 0.15)
|
||||
mouse.click(button="left")
|
||||
Logger.debug("entering trav...")
|
||||
match = wait_for_screen_object(ScreenObjects.Loading, 2)
|
||||
if match.valid:
|
||||
if wait_until_visible(ScreenObjects.Loading, 2).valid:
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -728,7 +724,7 @@ class Necro(IChar):
|
||||
def kill_council(self) -> bool:
|
||||
''' kill the council '''
|
||||
|
||||
result = self._pather.traverse_nodes((901,902,903,904,905,906,226,228,300), self, force_move=True,time_out = 2.5,threshold=.55)
|
||||
result = self._pather.traverse_nodes((901,902,903,904,905,906,226,228,300), self, force_move=True,timeout = 2.5,threshold=.55)
|
||||
|
||||
#this is gross but the skeletons I think cause pathing issues
|
||||
while result is False:
|
||||
@@ -745,22 +741,22 @@ class Necro(IChar):
|
||||
|
||||
if self._pather.find_abs_node_pos(904, grab()):
|
||||
#try again
|
||||
result = self._pather.traverse_nodes((904,905,906,226,228,300), self, force_move=True,time_out = 2.5)
|
||||
result = self._pather.traverse_nodes((904,905,906,226,228,300), self, force_move=True,timeout = 2.5)
|
||||
elif self._pather.find_abs_node_pos(905, grab()):
|
||||
#try again
|
||||
result = self._pather.traverse_nodes((905,906,226,228,300), self, force_move=True,time_out = 2.5)
|
||||
result = self._pather.traverse_nodes((905,906,226,228,300), self, force_move=True,timeout = 2.5)
|
||||
elif self._pather.find_abs_node_pos(906, grab()):
|
||||
#try again
|
||||
result = self._pather.traverse_nodes((906,226,228,300), self, force_move=True,time_out = 2.5)
|
||||
result = self._pather.traverse_nodes((906,226,228,300), self, force_move=True,timeout = 2.5)
|
||||
elif self._pather.find_abs_node_pos(226, grab()):
|
||||
#try again
|
||||
result = self._pather.traverse_nodes((226,228,300), self, force_move=True,time_out = 2.5)
|
||||
result = self._pather.traverse_nodes((226,228,300), self, force_move=True,timeout = 2.5)
|
||||
elif self._pather.find_abs_node_pos(901, grab()):
|
||||
#try again
|
||||
result = self._pather.traverse_nodes((901,902,903,904,905,906,226,228,300), self, force_move=True,time_out = 2.5)
|
||||
result = self._pather.traverse_nodes((901,902,903,904,905,906,226,228,300), self, force_move=True,timeout = 2.5)
|
||||
elif self._pather.find_abs_node_pos(902, grab()):
|
||||
#try again
|
||||
result = self._pather.traverse_nodes((902,903,904,905,906,226,228,300), self, force_move=True,time_out = 2.5)
|
||||
result = self._pather.traverse_nodes((902,903,904,905,906,226,228,300), self, force_move=True,timeout = 2.5)
|
||||
|
||||
self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=4,cast_v_div=3,cast_spell='amp_dmg',delay=3.0)
|
||||
|
||||
@@ -864,7 +860,7 @@ class Necro(IChar):
|
||||
self._corpse_explosion(corpse_exp_pos, 240, cast_count=18)
|
||||
|
||||
|
||||
#self._pather.traverse_nodes([230, 229 ,228], self, time_out=2.5, force_tp=True)
|
||||
#self._pather.traverse_nodes([230, 229 ,228], self, timeout=2.5, force_tp=True)
|
||||
Logger.info('\033[92m'+"atk cycle end"+'\033[0m')
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,557 @@
|
||||
import keyboard
|
||||
from utils.custom_mouse import mouse
|
||||
from char import IChar
|
||||
from template_finder import TemplateFinder
|
||||
from pather import Pather
|
||||
from logger import Logger
|
||||
from screen import grab, convert_abs_to_monitor, convert_screen_to_abs
|
||||
from config import Config
|
||||
from utils.misc import wait, rotate_vec, unit_vector
|
||||
import random
|
||||
from typing import Tuple
|
||||
from pather import Location, Pather
|
||||
import screen as screen
|
||||
import numpy as np
|
||||
import time
|
||||
import os
|
||||
from ui_manager import ScreenObjects
|
||||
|
||||
class Poison_Necro(IChar):
|
||||
def __init__(self, skill_hotkeys: dict, pather: Pather):
|
||||
os.system('color')
|
||||
Logger.info("\033[94m<<Setting up Necro>>\033[0m")
|
||||
super().__init__(skill_hotkeys)
|
||||
self._pather = pather
|
||||
#custom necro pathing for pindle
|
||||
self._pather.adapt_path((Location.A5_PINDLE_START, Location.A5_PINDLE_SAFE_DIST), [100,101])
|
||||
self._pather.adapt_path((Location.A5_PINDLE_SAFE_DIST, Location.A5_PINDLE_END), [104])
|
||||
#minor offsets to pindle fight locations
|
||||
self._pather.offset_node(102, [15, 0])
|
||||
self._pather.offset_node(103, [15, 0])
|
||||
self._pather.offset_node(101, [100,-5])
|
||||
|
||||
#Diablo
|
||||
self._pather.offset_node(644, [150, -70])
|
||||
self._pather.offset_node(610620, [50, 50])
|
||||
self._pather.offset_node(631, [-50, 50])
|
||||
self._pather.offset_node(656, [-70, -50])
|
||||
|
||||
self._shenk_dead = 0
|
||||
self._skeletons_count=0
|
||||
self._mages_count=0
|
||||
self._golem_count="none"
|
||||
self._revive_count=0
|
||||
|
||||
|
||||
def _check_shenk_death(self):
|
||||
''' make sure shenk is dead checking for fireballs so we can exit combat sooner '''
|
||||
|
||||
roi = [640,0,640,720]
|
||||
img = grab()
|
||||
|
||||
template_match = TemplateFinder().search(
|
||||
['SHENK_DEATH_1','SHENK_DEATH_2','SHENK_DEATH_3','SHENK_DEATH_4'],
|
||||
img,
|
||||
threshold=0.6,
|
||||
roi=roi,
|
||||
use_grayscale = False
|
||||
)
|
||||
if template_match.valid:
|
||||
self._shenk_dead=1
|
||||
Logger.info('\33[31m'+"Shenks Dead, looting..."+'\033[0m')
|
||||
else:
|
||||
return True
|
||||
|
||||
def _count_revives(self):
|
||||
roi = [15,14,400,45]
|
||||
img = grab()
|
||||
max_rev = 13
|
||||
|
||||
template_match = TemplateFinder().search(
|
||||
['REV_BASE'],
|
||||
img,
|
||||
threshold=0.6,
|
||||
roi=roi
|
||||
)
|
||||
if template_match.valid:
|
||||
self._revive_count=max_rev
|
||||
else:
|
||||
self._revive_count=0
|
||||
return True
|
||||
|
||||
for count in range(1,max_rev):
|
||||
rev_num = "REV_"+str(count)
|
||||
template_match = TemplateFinder().search(
|
||||
[rev_num],
|
||||
img,
|
||||
threshold=0.66,
|
||||
roi=roi,
|
||||
use_grayscale = False
|
||||
)
|
||||
if template_match.valid:
|
||||
self._revive_count=count
|
||||
|
||||
|
||||
def poison_nova(self, time_in_s: float):
|
||||
if not self._skill_hotkeys["poison_nova"]:
|
||||
raise ValueError("You did not set poison nova hotkey!")
|
||||
keyboard.send(self._skill_hotkeys["poison_nova"])
|
||||
wait(0.05, 0.1)
|
||||
start = time.time()
|
||||
while (time.time() - start) < time_in_s:
|
||||
wait(0.03, 0.04)
|
||||
mouse.press(button="right")
|
||||
wait(0.12, 0.2)
|
||||
mouse.release(button="right")
|
||||
|
||||
def _count_skeletons(self):
|
||||
roi = [15,14,400,45]
|
||||
img = grab()
|
||||
max_skeles = 13
|
||||
|
||||
template_match = TemplateFinder().search(
|
||||
['SKELE_BASE'],
|
||||
img,
|
||||
threshold=0.6,
|
||||
roi=roi
|
||||
)
|
||||
if template_match.valid:
|
||||
self._skeletons_count=max_skeles
|
||||
else:
|
||||
self._skeletons_count=0
|
||||
return True
|
||||
|
||||
for count in range(1,max_skeles):
|
||||
skele_num = "SKELE_"+str(count)
|
||||
template_match = TemplateFinder().search(
|
||||
[skele_num],
|
||||
img,
|
||||
threshold=0.66,
|
||||
roi=roi,
|
||||
use_grayscale = False
|
||||
)
|
||||
if template_match.valid:
|
||||
self._skeletons_count=count
|
||||
|
||||
def _count_gol(self):
|
||||
roi = [15,14,400,45]
|
||||
img = grab()
|
||||
|
||||
template_match = TemplateFinder().search(
|
||||
['CLAY'],
|
||||
img,
|
||||
threshold=0.6,
|
||||
roi=roi
|
||||
)
|
||||
if template_match.valid:
|
||||
self._golem_count="clay gol"
|
||||
else:
|
||||
self._golem_count="none"
|
||||
return True
|
||||
|
||||
def _summon_count(self):
|
||||
''' see how many summons and which golem are out '''
|
||||
|
||||
self._count_skeletons()
|
||||
self._count_revives()
|
||||
self._count_gol()
|
||||
def _summon_stat(self):
|
||||
''' print counts for summons '''
|
||||
Logger.info('\33[31m'+"Summon status | "+str(self._skeletons_count)+"skele | "+str(self._revive_count)+" rev | "+self._golem_count+" |"+'\033[0m')
|
||||
|
||||
def _revive(self, cast_pos_abs: Tuple[float, float], spray: int = 10, cast_count: int=12):
|
||||
Logger.info('\033[94m'+"raise revive"+'\033[0m')
|
||||
keyboard.send(Config().char["stand_still"], do_release=False)
|
||||
for _ in range(cast_count):
|
||||
if self._skill_hotkeys["raise_revive"]:
|
||||
keyboard.send(self._skill_hotkeys["raise_revive"])
|
||||
#Logger.info("revive -> cast")
|
||||
x = cast_pos_abs[0] + (random.random() * 2*spray - spray)
|
||||
y = cast_pos_abs[1] + (random.random() * 2*spray - spray)
|
||||
cast_pos_monitor = screen.convert_abs_to_monitor((x, y))
|
||||
|
||||
nx = cast_pos_monitor[0]
|
||||
ny = cast_pos_monitor[1]
|
||||
if(nx>1280):
|
||||
nx=1275
|
||||
if(ny>720):
|
||||
ny=715
|
||||
if(nx<0):
|
||||
nx=0
|
||||
if(ny<0):
|
||||
ny=0
|
||||
clamp = [nx,ny]
|
||||
|
||||
mouse.move(*clamp)
|
||||
mouse.press(button="right")
|
||||
wait(0.075, 0.1)
|
||||
mouse.release(button="right")
|
||||
keyboard.send(Config().char["stand_still"], do_press=False)
|
||||
|
||||
def _raise_skeleton(self, cast_pos_abs: Tuple[float, float], spray: int = 10, cast_count: int=16):
|
||||
Logger.info('\033[94m'+"raise skeleton"+'\033[0m')
|
||||
keyboard.send(Config().char["stand_still"], do_release=False)
|
||||
for _ in range(cast_count):
|
||||
if self._skill_hotkeys["raise_skeleton"]:
|
||||
keyboard.send(self._skill_hotkeys["raise_skeleton"])
|
||||
#Logger.info("raise skeleton -> cast")
|
||||
x = cast_pos_abs[0] + (random.random() * 2*spray - spray)
|
||||
y = cast_pos_abs[1] + (random.random() * 2*spray - spray)
|
||||
cast_pos_monitor = screen.convert_abs_to_monitor((x, y))
|
||||
|
||||
nx = cast_pos_monitor[0]
|
||||
ny = cast_pos_monitor[1]
|
||||
if(nx>1280):
|
||||
nx=1279
|
||||
if(ny>720):
|
||||
ny=719
|
||||
if(nx<0):
|
||||
nx=0
|
||||
if(ny<0):
|
||||
ny=0
|
||||
clamp = [nx,ny]
|
||||
|
||||
mouse.move(*clamp)
|
||||
mouse.press(button="right")
|
||||
wait(0.02, 0.05)
|
||||
mouse.release(button="right")
|
||||
keyboard.send(Config().char["stand_still"], do_press=False)
|
||||
|
||||
def _raise_mage(self, cast_pos_abs: Tuple[float, float], spray: int = 10, cast_count: int=16):
|
||||
Logger.info('\033[94m'+"raise mage"+'\033[0m')
|
||||
keyboard.send(Config().char["stand_still"], do_release=False)
|
||||
for _ in range(cast_count):
|
||||
if self._skill_hotkeys["raise_mage"]:
|
||||
keyboard.send(self._skill_hotkeys["raise_mage"])
|
||||
#Logger.info("raise skeleton -> cast")
|
||||
x = cast_pos_abs[0] + (random.random() * 2*spray - spray)
|
||||
y = cast_pos_abs[1] + (random.random() * 2*spray - spray)
|
||||
cast_pos_monitor = screen.convert_abs_to_monitor((x, y))
|
||||
|
||||
nx = cast_pos_monitor[0]
|
||||
ny = cast_pos_monitor[1]
|
||||
if(nx>1280):
|
||||
nx=1279
|
||||
if(ny>720):
|
||||
ny=719
|
||||
if(nx<0):
|
||||
nx=0
|
||||
if(ny<0):
|
||||
ny=0
|
||||
clamp = [nx,ny]
|
||||
|
||||
mouse.move(*clamp)
|
||||
mouse.press(button="right")
|
||||
wait(0.02, 0.05)
|
||||
mouse.release(button="right")
|
||||
keyboard.send(Config().char["stand_still"], do_press=False)
|
||||
|
||||
|
||||
def pre_buff(self):
|
||||
#only CTA if pre trav
|
||||
if Config().char["cta_available"]:
|
||||
self._pre_buff_cta()
|
||||
if self._shenk_dead==1:
|
||||
Logger.info("trav buff?")
|
||||
#self._heart_of_wolverine()
|
||||
Logger.info("prebuff/cta")
|
||||
|
||||
|
||||
def _heart_of_wolverine(self):
|
||||
Logger.info('\033[94m'+"buff ~> heart_of_wolverine"+'\033[0m')
|
||||
keyboard.send(self._skill_hotkeys["heart_of_wolverine"])
|
||||
wait(0.05, 0.2)
|
||||
mouse.click(button="right")
|
||||
wait(self._cast_duration)
|
||||
|
||||
def _clay_golem(self):
|
||||
Logger.info('\033[94m'+"cast ~> clay golem"+'\033[0m')
|
||||
keyboard.send(self._skill_hotkeys["clay_golem"])
|
||||
wait(0.05, 0.2)
|
||||
mouse.click(button="right")
|
||||
wait(self._cast_duration)
|
||||
|
||||
|
||||
def bone_armor(self):
|
||||
if self._skill_hotkeys["bone_armor"]:
|
||||
keyboard.send(self._skill_hotkeys["bone_armor"])
|
||||
wait(0.04, 0.1)
|
||||
mouse.click(button="right")
|
||||
wait(self._cast_duration)
|
||||
if self._skill_hotkeys["clay_golem"]:
|
||||
keyboard.send(self._skill_hotkeys["clay_golem"])
|
||||
wait(0.04, 0.1)
|
||||
mouse.click(button="right")
|
||||
wait(self._cast_duration)
|
||||
|
||||
def _bone_armor(self):
|
||||
if self._skill_hotkeys["bone_armor"]:
|
||||
keyboard.send(self._skill_hotkeys["bone_armor"])
|
||||
wait(0.04, 0.1)
|
||||
mouse.click(button="right")
|
||||
wait(self._cast_duration)
|
||||
|
||||
|
||||
|
||||
def _left_attack(self, cast_pos_abs: Tuple[float, float], spray: int = 10):
|
||||
keyboard.send(Config().char["stand_still"], do_release=False)
|
||||
if self._skill_hotkeys["skill_left"]:
|
||||
keyboard.send(self._skill_hotkeys["skill_left"])
|
||||
for _ in range(10):
|
||||
x = cast_pos_abs[0] + (random.random() * 2*spray - spray)
|
||||
y = cast_pos_abs[1] + (random.random() * 2*spray - spray)
|
||||
cast_pos_monitor = screen.convert_abs_to_monitor((x, y))
|
||||
mouse.move(*cast_pos_monitor)
|
||||
mouse.press(button="left")
|
||||
wait(0.25, 0.3)
|
||||
mouse.release(button="left")
|
||||
|
||||
keyboard.send(Config().char["stand_still"], do_press=False)
|
||||
|
||||
def _left_attack_single(self, cast_pos_abs: Tuple[float, float], spray: int = 10, cast_count: int=6):
|
||||
keyboard.send(Config().char["stand_still"], do_release=False)
|
||||
if self._skill_hotkeys["skill_left"]:
|
||||
keyboard.send(self._skill_hotkeys["skill_left"])
|
||||
for _ in range(cast_count):
|
||||
x = cast_pos_abs[0] + (random.random() * 2*spray - spray)
|
||||
y = cast_pos_abs[1] + (random.random() * 2*spray - spray)
|
||||
cast_pos_monitor = screen.convert_abs_to_monitor((x, y))
|
||||
mouse.move(*cast_pos_monitor)
|
||||
mouse.press(button="left")
|
||||
wait(0.25, 0.3)
|
||||
mouse.release(button="left")
|
||||
|
||||
keyboard.send(Config().char["stand_still"], do_press=False)
|
||||
|
||||
def _amp_dmg(self, cast_pos_abs: Tuple[float, float], spray: float = 10):
|
||||
if self._skill_hotkeys["amp_dmg"]:
|
||||
keyboard.send(self._skill_hotkeys["amp_dmg"])
|
||||
|
||||
x = cast_pos_abs[0] + (random.random() * 2*spray - spray)
|
||||
y = cast_pos_abs[1] + (random.random() * 2*spray - spray)
|
||||
cast_pos_monitor = screen.convert_abs_to_monitor((x, y))
|
||||
mouse.move(*cast_pos_monitor)
|
||||
mouse.press(button="right")
|
||||
wait(0.25, 0.35)
|
||||
mouse.release(button="right")
|
||||
|
||||
def _lower_res(self, cast_pos_abs: Tuple[float, float], spray: float = 10):
|
||||
if self._skill_hotkeys["lower_res"]:
|
||||
keyboard.send(self._skill_hotkeys["lower_res"])
|
||||
|
||||
x = cast_pos_abs[0] + (random.random() * 2*spray - spray)
|
||||
y = cast_pos_abs[1] + (random.random() * 2*spray - spray)
|
||||
cast_pos_monitor = screen.convert_abs_to_monitor((x, y))
|
||||
mouse.move(*cast_pos_monitor)
|
||||
mouse.press(button="right")
|
||||
wait(0.25, 0.35)
|
||||
mouse.release(button="right")
|
||||
|
||||
def _corpse_explosion(self, cast_pos_abs: Tuple[float, float], spray: int = 10,cast_count: int = 8):
|
||||
keyboard.send(Config().char["stand_still"], do_release=False)
|
||||
Logger.info('\033[93m'+"corpse explosion~> random cast"+'\033[0m')
|
||||
for _ in range(cast_count):
|
||||
if self._skill_hotkeys["corpse_explosion"]:
|
||||
keyboard.send(self._skill_hotkeys["corpse_explosion"])
|
||||
x = cast_pos_abs[0] + (random.random() * 2*spray - spray)
|
||||
y = cast_pos_abs[1] + (random.random() * 2*spray - spray)
|
||||
cast_pos_monitor = screen.convert_abs_to_monitor((x, y))
|
||||
mouse.move(*cast_pos_monitor)
|
||||
mouse.press(button="right")
|
||||
wait(0.075, 0.1)
|
||||
mouse.release(button="right")
|
||||
keyboard.send(Config().char["stand_still"], do_press=False)
|
||||
|
||||
|
||||
def _lerp(self,a: float,b: float, f:float):
|
||||
return a + f * (b - a)
|
||||
|
||||
def _cast_circle(self, cast_dir: Tuple[float,float],cast_start_angle: float=0.0, cast_end_angle: float=90.0,cast_div: int = 10,cast_v_div: int=4,cast_spell: str='raise_skeleton',delay: float=1.0,offset: float=1.0):
|
||||
Logger.info('\033[93m'+"circle cast ~>"+cast_spell+'\033[0m')
|
||||
keyboard.send(Config().char["stand_still"], do_release=False)
|
||||
keyboard.send(self._skill_hotkeys[cast_spell])
|
||||
mouse.press(button="right")
|
||||
|
||||
for i in range(cast_div):
|
||||
angle = self._lerp(cast_start_angle,cast_end_angle,float(i)/cast_div)
|
||||
target = unit_vector(rotate_vec(cast_dir, angle))
|
||||
#Logger.info("current angle ~> "+str(angle))
|
||||
for j in range(cast_v_div):
|
||||
circle_pos_screen = self._pather._adjust_abs_range_to_screen((target*120.0*float(j+1.0))*offset)
|
||||
circle_pos_monitor = screen.convert_abs_to_monitor(circle_pos_screen)
|
||||
mouse.move(*circle_pos_monitor,delay_factor=[0.3*delay, .6*delay])
|
||||
|
||||
|
||||
#Logger.info("circle move")
|
||||
mouse.release(button="right")
|
||||
keyboard.send(Config().char["stand_still"], do_press=False)
|
||||
|
||||
|
||||
def kill_pindle(self) -> bool:
|
||||
pos_m = screen.convert_abs_to_monitor((0, 30))
|
||||
self.walk(pos_m, force_move=True)
|
||||
self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=4,cast_v_div=3,cast_spell='lower_res',delay=1.0)
|
||||
self.poison_nova(3.0)
|
||||
pos_m = screen.convert_abs_to_monitor((0, -50))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
pos_m = screen.convert_abs_to_monitor((50, 0))
|
||||
self.walk(pos_m, force_move=True)
|
||||
self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=120,cast_div=5,cast_v_div=2,cast_spell='corpse_explosion',delay=1.1,offset=1.8)
|
||||
self.poison_nova(3.0)
|
||||
return True
|
||||
|
||||
def kill_eldritch(self) -> bool:
|
||||
pos_m = screen.convert_abs_to_monitor((0, -100))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
self.bone_armor()
|
||||
self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=4,cast_v_div=3,cast_spell='lower_res',delay=1.0)
|
||||
self.poison_nova(2.0)
|
||||
self._summon_stat()
|
||||
# move a bit back
|
||||
pos_m = screen.convert_abs_to_monitor((0, 50))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
self.poison_nova(2.0)
|
||||
self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, timeout=0.6, force_tp=True)
|
||||
pos_m = screen.convert_abs_to_monitor((0, 170))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
#self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=8,cast_v_div=4,cast_spell='raise_revive',delay=1.2,offset=.8)
|
||||
self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=720,cast_div=8,cast_v_div=4,cast_spell='raise_skeleton',delay=1.1,offset=.8)
|
||||
pos_m = screen.convert_abs_to_monitor((0, -50))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=720,cast_div=8,cast_v_div=4,cast_spell='raise_mage',delay=1.1,offset=1.0)
|
||||
pos_m = screen.convert_abs_to_monitor((-75, 0))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=720,cast_div=8,cast_v_div=4,cast_spell='raise_skeleton',delay=1.1,offset=.5)
|
||||
self._summon_count()
|
||||
self._summon_stat()
|
||||
|
||||
self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, timeout=0.6, force_tp=True)
|
||||
return True
|
||||
|
||||
|
||||
def kill_shenk(self) -> bool:
|
||||
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=1.0)
|
||||
#pos_m = self._screen.convert_abs_to_monitor((50, 0))
|
||||
#self.walk(pos_m, force_move=True)
|
||||
self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=4,cast_v_div=3,cast_spell='lower_res',delay=1.0)
|
||||
self.poison_nova(3.0)
|
||||
pos_m = screen.convert_abs_to_monitor((0, -50))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=720,cast_div=10,cast_v_div=4,cast_spell='raise_mage',delay=1.1,offset=.8)
|
||||
pos_m = screen.convert_abs_to_monitor((50, 0))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=720,cast_div=10,cast_v_div=4,cast_spell='raise_revive',delay=1.1,offset=.8)
|
||||
pos_m = screen.convert_abs_to_monitor((-20, -20))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=10,cast_v_div=4,cast_spell='raise_skeleton',delay=1.1,offset=.8)
|
||||
self._summon_count()
|
||||
#self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=2,cast_v_div=1,cast_spell='corpse_explosion',delay=3.0,offset=1.8)
|
||||
return True
|
||||
|
||||
|
||||
def kill_council(self) -> bool:
|
||||
pos_m = screen.convert_abs_to_monitor((0, -200))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
self._pather.traverse_nodes([229], self, timeout=2.5, force_tp=True, use_tp_charge=True)
|
||||
pos_m = screen.convert_abs_to_monitor((50, 0))
|
||||
self.walk(pos_m, force_move=True)
|
||||
#self._lower_res((-50, 0), spray=10)
|
||||
self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=4,cast_v_div=3,cast_spell='lower_res',delay=1.0)
|
||||
self.poison_nova(2.0)
|
||||
#self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=9,cast_v_div=3,cast_spell='raise_skeleton',delay=1.2,offset=.8)
|
||||
pos_m = screen.convert_abs_to_monitor((200, 50))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
pos_m = screen.convert_abs_to_monitor((30, -50))
|
||||
self.walk(pos_m, force_move=True)
|
||||
self.poison_nova(2.0)
|
||||
#self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=120,cast_div=2,cast_v_div=1,cast_spell='corpse_explosion',delay=3.0,offset=1.8)
|
||||
#wait(self._cast_duration, self._cast_duration +.2)
|
||||
pos_m = screen.convert_abs_to_monitor((-200, 200))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
pos_m = screen.convert_abs_to_monitor((-100, 200))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
self._pather.traverse_nodes([226], self, timeout=2.5, force_tp=True, use_tp_charge=True)
|
||||
pos_m = screen.convert_abs_to_monitor((0, 30))
|
||||
self.walk(pos_m, force_move=True)
|
||||
self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=4,cast_v_div=3,cast_spell='lower_res',delay=1.0)
|
||||
wait(0.5)
|
||||
self.poison_nova(4.0)
|
||||
#self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=120,cast_div=2,cast_v_div=1,cast_spell='corpse_explosion',delay=3.0,offset=1.8)
|
||||
#wait(self._cast_duration, self._cast_duration +.2)
|
||||
#self.poison_nova(2.0)
|
||||
pos_m = screen.convert_abs_to_monitor((50, 0))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=120,cast_div=5,cast_v_div=2,cast_spell='corpse_explosion',delay=0.5,offset=1.8)
|
||||
#self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=9,cast_v_div=3,cast_spell='raise_skeleton',delay=1.2,offset=.8)
|
||||
#self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=9,cast_v_div=3,cast_spell='raise_mage',delay=1.2,offset=.8)
|
||||
pos_m = screen.convert_abs_to_monitor((-200, -200))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
self._pather.traverse_nodes([229], self, timeout=2.5, force_tp=True, use_tp_charge=True)
|
||||
pos_m = screen.convert_abs_to_monitor((20, -50))
|
||||
self.walk(pos_m, force_move=True)
|
||||
self.poison_nova(2.0)
|
||||
pos_m = screen.convert_abs_to_monitor((50, 0))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=120,cast_div=5,cast_v_div=2,cast_spell='corpse_explosion',delay=3.0,offset=1.8)
|
||||
pos_m = screen.convert_abs_to_monitor((-30, -20))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=10,cast_v_div=4,cast_spell='raise_skeleton',delay=1.2,offset=.8)
|
||||
self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=10,cast_v_div=4,cast_spell='raise_mage',delay=1.2,offset=.8)
|
||||
return True
|
||||
|
||||
def kill_nihlathak(self, end_nodes: list[int]) -> bool:
|
||||
# Move close to nihlathak
|
||||
self._pather.traverse_nodes(end_nodes, self, timeout=0.8, do_pre_move=True)
|
||||
pos_m = screen.convert_abs_to_monitor((20, 20))
|
||||
self.walk(pos_m, force_move=True)
|
||||
self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=4,cast_v_div=3,cast_spell='lower_res',delay=1.0)
|
||||
self.poison_nova(3.0)
|
||||
pos_m = screen.convert_abs_to_monitor((50, 0))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=7200,cast_div=2,cast_v_div=2,cast_spell='corpse_explosion',delay=3.0,offset=1.8)
|
||||
wait(self._cast_duration, self._cast_duration +.2)
|
||||
self.poison_nova(3.0)
|
||||
return True
|
||||
|
||||
def kill_summoner(self) -> bool:
|
||||
# Attack
|
||||
pos_m = screen.convert_abs_to_monitor((0, 30))
|
||||
self.walk(pos_m, force_move=True)
|
||||
self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=4,cast_v_div=3,cast_spell='lower_res',delay=1.0)
|
||||
wait(0.5)
|
||||
self.poison_nova(3.0)
|
||||
pos_m = screen.convert_abs_to_monitor((50, 0))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
wait(self._cast_duration, self._cast_duration + 0.2)
|
||||
self._cast_circle(cast_dir=[-1,1],cast_start_angle=0,cast_end_angle=360,cast_div=10,cast_v_div=4,cast_spell='raise_mage',delay=1.2,offset=.8)
|
||||
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 char import Necro
|
||||
pather = Pather()
|
||||
char = Necro(Config().necro, Config().char, pather)
|
||||
@@ -73,26 +73,33 @@ class BlizzSorc(Sorceress):
|
||||
self.move(pos_m, force_move=True)
|
||||
self._blizzard((-50, -50), spray=10)
|
||||
self._cast_static()
|
||||
wait(0.75)
|
||||
#move down
|
||||
pos_m = convert_abs_to_monitor((0, 85))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
wait(0.70)
|
||||
self._blizzard((-170, -350), spray=10)
|
||||
self._cast_static()
|
||||
#move down
|
||||
wait(0.75)
|
||||
pos_m = convert_abs_to_monitor((0, 75))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
self._blizzard((100, -300), spray=10)
|
||||
self._cast_static()
|
||||
wait(0.75)
|
||||
pos_m = convert_abs_to_monitor((0, 55))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
wait(1.0)
|
||||
self._blizzard((-50, -130), spray=10)
|
||||
self._cast_static()
|
||||
wait(3.0)
|
||||
pos_m = convert_abs_to_monitor((0, -100))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
self._blizzard((-50, -130), spray=10)
|
||||
self._cast_static()
|
||||
wait(1.75)
|
||||
self._pather.traverse_nodes_fixed("eldritch_end", self)
|
||||
return True
|
||||
|
||||
@@ -101,7 +108,7 @@ class BlizzSorc(Sorceress):
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
#lower left posistion
|
||||
self._pather.traverse_nodes([151], self, time_out=2.5, force_tp=False)
|
||||
self._pather.traverse_nodes([151], self, timeout=2.5, force_tp=False)
|
||||
self._cast_static()
|
||||
self._blizzard((-250, 100), spray=10)
|
||||
self._ice_blast((60, 70), spray=60)
|
||||
@@ -130,14 +137,14 @@ class BlizzSorc(Sorceress):
|
||||
self._cast_static()
|
||||
self._blizzard((100, -50), spray=10)
|
||||
# Move to items
|
||||
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, time_out=1.4, force_tp=True)
|
||||
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=1.4, force_tp=True)
|
||||
return True
|
||||
|
||||
def kill_council(self) -> bool:
|
||||
# Move inside to the right
|
||||
self._pather.traverse_nodes_fixed([(1110, 120)], self)
|
||||
self._pather.offset_node(300, (80, -110))
|
||||
self._pather.traverse_nodes([300], self, time_out=5.5, force_tp=True)
|
||||
self._pather.traverse_nodes([300], self, timeout=5.5, force_tp=True)
|
||||
self._pather.offset_node(300, (-80, 110))
|
||||
# Attack to the left
|
||||
self._blizzard((-150, 10), spray=80)
|
||||
@@ -154,7 +161,7 @@ class BlizzSorc(Sorceress):
|
||||
wait(0.5)
|
||||
# Move to far left
|
||||
self._pather.offset_node(301, (-80, -50))
|
||||
self._pather.traverse_nodes([301], self, time_out=2.5, force_tp=True)
|
||||
self._pather.traverse_nodes([301], self, timeout=2.5, force_tp=True)
|
||||
self._pather.offset_node(301, (80, 50))
|
||||
# Attack to RIGHT
|
||||
self._blizzard((100, 150), spray=80)
|
||||
@@ -167,7 +174,7 @@ class BlizzSorc(Sorceress):
|
||||
for p in [(450, 100), (-190, 200)]:
|
||||
pos_m = convert_abs_to_monitor(p)
|
||||
self.move(pos_m, force_move=True)
|
||||
self._pather.traverse_nodes([304], self, time_out=2.5, force_tp=True)
|
||||
self._pather.traverse_nodes([304], self, timeout=2.5, force_tp=True)
|
||||
# Attack to center of stairs
|
||||
self._blizzard((-175, -200), spray=30)
|
||||
self._ice_blast((30, -60), spray=30)
|
||||
@@ -176,7 +183,7 @@ class BlizzSorc(Sorceress):
|
||||
wait(1.0)
|
||||
# Move back inside
|
||||
self._pather.traverse_nodes_fixed([(1110, 15)], self)
|
||||
self._pather.traverse_nodes([300], self, time_out=2.5, force_tp=False)
|
||||
self._pather.traverse_nodes([300], self, timeout=2.5, force_tp=False)
|
||||
# Attack to center
|
||||
self._blizzard((-100, 0), spray=10)
|
||||
self._cast_static()
|
||||
@@ -208,7 +215,7 @@ class BlizzSorc(Sorceress):
|
||||
self._cast_static()
|
||||
self._ice_blast((-30, 50), spray=10)
|
||||
# Move outside since the trav.py expects to start searching for items there if char can teleport
|
||||
self._pather.traverse_nodes([226], self, time_out=2.5, force_tp=True)
|
||||
self._pather.traverse_nodes([226], self, timeout=2.5, force_tp=True)
|
||||
return True
|
||||
|
||||
def kill_nihlathak(self, end_nodes: list[int]) -> bool:
|
||||
@@ -233,7 +240,7 @@ class BlizzSorc(Sorceress):
|
||||
self._blizzard(cast_pos_abs, spray=15)
|
||||
# Move to items
|
||||
wait(1.3)
|
||||
self._pather.traverse_nodes(end_nodes, self, time_out=0.8)
|
||||
self._pather.traverse_nodes(end_nodes, self, timeout=0.8)
|
||||
return True
|
||||
|
||||
def kill_summoner(self) -> bool:
|
||||
|
||||
@@ -57,9 +57,9 @@ class LightSorc(Sorceress):
|
||||
def kill_pindle(self) -> bool:
|
||||
pindle_pos_abs = convert_screen_to_abs(Config().path["pindle_end"][0])
|
||||
cast_pos_abs = [pindle_pos_abs[0] * 0.9, pindle_pos_abs[1] * 0.9]
|
||||
self._lightning(cast_pos_abs, spray=11)
|
||||
for _ in range(int(Config().char["atk_len_pindle"])):
|
||||
self._chain_lightning(cast_pos_abs, spray=11)
|
||||
self._lightning(cast_pos_abs, spray=11)
|
||||
wait(self._cast_duration, self._cast_duration + 0.2)
|
||||
# Move to items
|
||||
self._pather.traverse_nodes_fixed("pindle_end", self)
|
||||
@@ -68,9 +68,9 @@ class LightSorc(Sorceress):
|
||||
def kill_eldritch(self) -> bool:
|
||||
eld_pos_abs = convert_screen_to_abs(Config().path["eldritch_end"][0])
|
||||
cast_pos_abs = [eld_pos_abs[0] * 0.9, eld_pos_abs[1] * 0.9]
|
||||
self._lightning(cast_pos_abs, spray=50)
|
||||
for _ in range(int(Config().char["atk_len_eldritch"])):
|
||||
self._chain_lightning(cast_pos_abs, spray=90)
|
||||
self._lightning(cast_pos_abs, spray=50)
|
||||
# Move to items
|
||||
wait(self._cast_duration, self._cast_duration + 0.2)
|
||||
self._pather.traverse_nodes_fixed("eldritch_end", self)
|
||||
@@ -81,36 +81,37 @@ class LightSorc(Sorceress):
|
||||
if shenk_pos_abs is None:
|
||||
shenk_pos_abs = convert_screen_to_abs(Config().path["shenk_end"][0])
|
||||
cast_pos_abs = [shenk_pos_abs[0] * 0.9, shenk_pos_abs[1] * 0.9]
|
||||
for _ in range(int(Config().char["atk_len_shenk"] * 0.5)):
|
||||
self._chain_lightning(cast_pos_abs, spray=90)
|
||||
pos_m = convert_abs_to_monitor((150, 50))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
shenk_pos_abs = convert_screen_to_abs(Config().path["shenk_end"][0])
|
||||
cast_pos_abs = [shenk_pos_abs[0] * 0.9, shenk_pos_abs[1] * 0.9]
|
||||
for _ in range(int(Config().char["atk_len_shenk"] * 0.5)):
|
||||
self._chain_lightning(cast_pos_abs, spray=90)
|
||||
self._lightning(cast_pos_abs, spray=60)
|
||||
for _ in range(int(Config().char["atk_len_shenk"] * 0.5)):
|
||||
self._chain_lightning(cast_pos_abs, spray=90)
|
||||
pos_m = convert_abs_to_monitor((150, 50))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
shenk_pos_abs = convert_screen_to_abs(Config().path["shenk_end"][0])
|
||||
cast_pos_abs = [shenk_pos_abs[0] * 0.9, shenk_pos_abs[1] * 0.9]
|
||||
self._lightning(cast_pos_abs, spray=60)
|
||||
for _ in range(int(Config().char["atk_len_shenk"] * 0.5)):
|
||||
self._chain_lightning(cast_pos_abs, spray=90)
|
||||
pos_m = convert_abs_to_monitor((150, 50))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
shenk_pos_abs = convert_screen_to_abs(Config().path["shenk_end"][0])
|
||||
cast_pos_abs = [shenk_pos_abs[0] * 0.9, shenk_pos_abs[1] * 0.9]
|
||||
self._lightning(cast_pos_abs, spray=60)
|
||||
for _ in range(int(Config().char["atk_len_shenk"])):
|
||||
self._chain_lightning(cast_pos_abs, spray=90)
|
||||
self._lightning(cast_pos_abs, spray=60)
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
# Move to items
|
||||
wait(self._cast_duration, self._cast_duration + 0.2)
|
||||
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, time_out=1.4, force_tp=True)
|
||||
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=1.4, force_tp=True)
|
||||
return True
|
||||
|
||||
def kill_council(self) -> bool:
|
||||
# Move inside to the right
|
||||
self._pather.traverse_nodes_fixed([(1110, 120)], self)
|
||||
self._pather.offset_node(300, (80, -110))
|
||||
self._pather.traverse_nodes([300], self, time_out=1.0, force_tp=True)
|
||||
self._pather.traverse_nodes([300], self, timeout=1.0, force_tp=True)
|
||||
self._pather.offset_node(300, (-80, 110))
|
||||
wait(0.5)
|
||||
self._frozen_orb((-150, -10), spray=10)
|
||||
@@ -126,7 +127,7 @@ class LightSorc(Sorceress):
|
||||
self.move(pos_m, force_move=True)
|
||||
wait(0.5)
|
||||
self._pather.offset_node(226, (-80, 60))
|
||||
self._pather.traverse_nodes([226], self, time_out=1.0, force_tp=True)
|
||||
self._pather.traverse_nodes([226], self, timeout=1.0, force_tp=True)
|
||||
self._pather.offset_node(226, (80, -60))
|
||||
wait(0.5)
|
||||
self._frozen_orb((-150, -130), spray=10)
|
||||
@@ -134,7 +135,7 @@ class LightSorc(Sorceress):
|
||||
self._chain_lightning((-170, -150), spray=20)
|
||||
wait(0.5)
|
||||
self._pather.traverse_nodes_fixed([(1110, 15)], self)
|
||||
self._pather.traverse_nodes([300], self, time_out=1.0, force_tp=True)
|
||||
self._pather.traverse_nodes([300], self, timeout=1.0, force_tp=True)
|
||||
pos_m = convert_abs_to_monitor((300, 150))
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
@@ -149,7 +150,7 @@ class LightSorc(Sorceress):
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
self._pather.offset_node(304, (0, -80))
|
||||
self._pather.traverse_nodes([304], self, time_out=1.0, force_tp=True)
|
||||
self._pather.traverse_nodes([304], self, timeout=1.0, force_tp=True)
|
||||
self._pather.offset_node(304, (0, 80))
|
||||
wait(0.5)
|
||||
self._frozen_orb((175, -170), spray=40)
|
||||
@@ -184,7 +185,7 @@ class LightSorc(Sorceress):
|
||||
self.pre_move()
|
||||
self.move(pos_m, force_move=True)
|
||||
# Move outside since the trav.py expects to start searching for items there if char can teleport
|
||||
self._pather.traverse_nodes([226], self, time_out=2.5, force_tp=True)
|
||||
self._pather.traverse_nodes([226], self, timeout=2.5, force_tp=True)
|
||||
return True
|
||||
|
||||
def kill_nihlathak(self, end_nodes: list[int]) -> bool:
|
||||
@@ -204,6 +205,7 @@ class LightSorc(Sorceress):
|
||||
|
||||
if nihlathak_pos_abs is not None:
|
||||
cast_pos_abs = np.array([nihlathak_pos_abs[0] * 0.9, nihlathak_pos_abs[1] * 0.9])
|
||||
self._lightning(cast_pos_abs, spray=60)
|
||||
self._chain_lightning(cast_pos_abs, delay, 90)
|
||||
# Do some tele "dancing" after each sequence
|
||||
if i < atk_len - 1:
|
||||
@@ -220,7 +222,7 @@ class LightSorc(Sorceress):
|
||||
|
||||
# Move to items
|
||||
wait(self._cast_duration, self._cast_duration + 0.2)
|
||||
self._pather.traverse_nodes(end_nodes, self, time_out=0.8)
|
||||
self._pather.traverse_nodes(end_nodes, self, timeout=0.8)
|
||||
return True
|
||||
|
||||
def kill_summoner(self) -> bool:
|
||||
@@ -229,8 +231,8 @@ class LightSorc(Sorceress):
|
||||
pos_m = convert_abs_to_monitor((-20, 20))
|
||||
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
|
||||
for _ in range(int(Config().char["atk_len_arc"])):
|
||||
self._chain_lightning(cast_pos_abs, spray=11)
|
||||
self._lightning(cast_pos_abs, spray=11)
|
||||
self._chain_lightning(cast_pos_abs, spray=11)
|
||||
wait(self._cast_duration, self._cast_duration + 0.2)
|
||||
return True
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ class NovaSorc(Sorceress):
|
||||
return True
|
||||
|
||||
def kill_shenk(self) -> bool:
|
||||
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, time_out=1.0)
|
||||
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=1.0)
|
||||
self._cast_static(0.6)
|
||||
self._nova(Config().char["atk_len_shenk"])
|
||||
return True
|
||||
@@ -61,13 +61,13 @@ class NovaSorc(Sorceress):
|
||||
self._pather.offset_node(229, offset_229)
|
||||
def clear_inside():
|
||||
self._pather.traverse_nodes_fixed([(1110, 120)], self)
|
||||
self._pather.traverse_nodes([229], self, time_out=0.8, force_tp=True)
|
||||
self._pather.traverse_nodes([229], self, timeout=0.8, force_tp=True)
|
||||
self._nova(atk_len)
|
||||
self._move_and_attack((-40, -20), atk_len)
|
||||
self._move_and_attack((40, 20), atk_len)
|
||||
self._move_and_attack((40, 20), atk_len)
|
||||
def clear_outside():
|
||||
self._pather.traverse_nodes([226], self, time_out=0.8, force_tp=True)
|
||||
self._pather.traverse_nodes([226], self, timeout=0.8, force_tp=True)
|
||||
self._nova(atk_len)
|
||||
self._move_and_attack((45, -20), atk_len)
|
||||
self._move_and_attack((-45, 20), atk_len)
|
||||
@@ -82,7 +82,7 @@ class NovaSorc(Sorceress):
|
||||
def kill_nihlathak(self, end_nodes: list[int]) -> bool:
|
||||
atk_len = Config().char["atk_len_nihlathak"] * 0.3
|
||||
# Move close to nihlathak
|
||||
self._pather.traverse_nodes(end_nodes, self, time_out=0.8, do_pre_move=False)
|
||||
self._pather.traverse_nodes(end_nodes, self, timeout=0.8, do_pre_move=False)
|
||||
# move mouse to center
|
||||
pos_m = convert_abs_to_monitor((0, 0))
|
||||
mouse.move(*pos_m, randomize=80, delay_factor=[0.5, 0.7])
|
||||
|
||||
@@ -10,7 +10,7 @@ import time
|
||||
from typing import Tuple
|
||||
from pather import Pather
|
||||
from config import Config
|
||||
from ui_manager import detect_screen_object, ScreenObjects
|
||||
from ui_manager import ScreenObjects, is_visible
|
||||
|
||||
class Sorceress(IChar):
|
||||
def __init__(self, skill_hotkeys: dict, pather: Pather):
|
||||
@@ -38,20 +38,19 @@ class Sorceress(IChar):
|
||||
self,
|
||||
template_type: Union[str, List[str]],
|
||||
success_func: Callable = None,
|
||||
time_out: float = 8,
|
||||
timeout: float = 8,
|
||||
threshold: float = 0.68,
|
||||
telekinesis: bool = False
|
||||
) -> bool:
|
||||
# In case telekinesis is False or hotkey is not set, just call the base implementation
|
||||
if not self._skill_hotkeys["telekinesis"] or not telekinesis:
|
||||
return super().select_by_template(template_type, success_func, time_out, threshold)
|
||||
return super().select_by_template(template_type, success_func, timeout, threshold)
|
||||
if type(template_type) == list and "A5_STASH" in template_type:
|
||||
# sometimes waypoint is opened and stash not found because of that, check for that
|
||||
match = detect_screen_object(ScreenObjects.WaypointLabel)
|
||||
if match.valid:
|
||||
if is_visible(ScreenObjects.WaypointLabel):
|
||||
keyboard.send("esc")
|
||||
start = time.time()
|
||||
while time_out is None or (time.time() - start) < time_out:
|
||||
while timeout is None or (time.time() - start) < timeout:
|
||||
template_match = TemplateFinder().search(template_type, grab(), threshold=threshold, normalize_monitor=True)
|
||||
if template_match.valid:
|
||||
keyboard.send(self._skill_hotkeys["telekinesis"])
|
||||
@@ -65,7 +64,7 @@ class Sorceress(IChar):
|
||||
if success_func is None or success_func():
|
||||
return True
|
||||
# In case telekinesis fails, try again with the base implementation
|
||||
return super().select_by_template(template_type, success_func, time_out, threshold)
|
||||
return super().select_by_template(template_type, success_func, timeout, threshold)
|
||||
|
||||
def pre_buff(self):
|
||||
if Config().char["cta_available"]:
|
||||
|
||||
@@ -96,7 +96,7 @@ class Trapsin(IChar):
|
||||
if self.capabilities.can_teleport_natively:
|
||||
self._pather.traverse_nodes_fixed("eldritch_end", self)
|
||||
else:
|
||||
self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, time_out=0.6, force_tp=True)
|
||||
self._pather.traverse_nodes((Location.A5_ELDRITCH_SAFE_DIST, Location.A5_ELDRITCH_END), self, timeout=0.6, force_tp=True)
|
||||
return True
|
||||
|
||||
def kill_shenk(self) -> bool:
|
||||
@@ -110,7 +110,7 @@ class Trapsin(IChar):
|
||||
self._left_attack(cast_pos_abs, 90)
|
||||
# Move to items
|
||||
wait(self._cast_duration, self._cast_duration + 0.2)
|
||||
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, time_out=1.4, force_tp=True)
|
||||
self._pather.traverse_nodes((Location.A5_SHENK_SAFE_DIST, Location.A5_SHENK_END), self, timeout=1.4, force_tp=True)
|
||||
return True
|
||||
|
||||
def kill_nihlathak(self, end_nodes: list[int]) -> bool:
|
||||
@@ -132,7 +132,7 @@ class Trapsin(IChar):
|
||||
self.move(pos_m)
|
||||
# Move to items
|
||||
wait(self._cast_duration, self._cast_duration + 0.2)
|
||||
self._pather.traverse_nodes(end_nodes, self, time_out=0.8)
|
||||
self._pather.traverse_nodes(end_nodes, self, timeout=0.8)
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -23,12 +23,12 @@ class Chest:
|
||||
chest = filename[:-4].upper()
|
||||
self._templates.append(chest)
|
||||
|
||||
def open_up_chests(self, time_out: float = 8.0, threshold: float = 0.73) -> bool:
|
||||
def open_up_chests(self, timeout: float = 8.0, threshold: float = 0.73) -> bool:
|
||||
Logger.debug("Open chests")
|
||||
templates = self._templates
|
||||
found_chest = True
|
||||
start = time.time()
|
||||
while time.time() - start < time_out:
|
||||
while time.time() - start < timeout:
|
||||
template_match = TemplateFinder().search(templates, grab(), roi=Config().ui_roi["reduce_to_center"], threshold=threshold, use_grayscale=True, best_match=True, normalize_monitor=True)
|
||||
# search for at least 1.5 second, if no chest found, break
|
||||
if not template_match.valid:
|
||||
|
||||
@@ -3,10 +3,8 @@ import string
|
||||
import threading
|
||||
import numpy as np
|
||||
import os
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from logger import Logger
|
||||
|
||||
config_lock = threading.Lock()
|
||||
|
||||
|
||||
@@ -23,13 +21,9 @@ class ItemProps:
|
||||
|
||||
class Config:
|
||||
data_loaded = False
|
||||
# all configparser objects
|
||||
_config = None
|
||||
_game_config = None
|
||||
_pickit_config = None
|
||||
_shop_config = None
|
||||
_transmute_config = None
|
||||
_custom = None
|
||||
|
||||
configs = {}
|
||||
|
||||
# config data
|
||||
general = {}
|
||||
advanced_options = {}
|
||||
@@ -50,6 +44,7 @@ class Config:
|
||||
hammerdin = {}
|
||||
trapsin = {}
|
||||
barbarian = {}
|
||||
poison_necro = {}
|
||||
necro = {}
|
||||
basic = {}
|
||||
basic_ranged = {}
|
||||
@@ -72,16 +67,28 @@ class Config:
|
||||
return default
|
||||
|
||||
def _select_val(self, section: str, key: str = None):
|
||||
if section in self._custom and key in self._custom[section]:
|
||||
return self._custom[section][key]
|
||||
elif section in self._config:
|
||||
return self._config[section][key]
|
||||
elif section in self._pickit_config:
|
||||
return self._pickit_config[section][key]
|
||||
elif section in self._shop_config:
|
||||
return self._shop_config[section][key]
|
||||
found_in = ""
|
||||
if section in self.configs["custom"]["parser"] and key in self.configs["custom"]["parser"][section]:
|
||||
found_val = self.configs["custom"]["parser"][section][key]
|
||||
found_in = "custom"
|
||||
elif section in self.configs["config"]["parser"]:
|
||||
found_val = self.configs["config"]["parser"][section][key]
|
||||
found_in = "config"
|
||||
elif section in self.configs["pickit"]["parser"]:
|
||||
found_val = self.configs["pickit"]["parser"][section][key]
|
||||
found_in = "pickit"
|
||||
elif section in self.configs["shop"]["parser"]:
|
||||
found_val = self.configs["shop"]["parser"][section][key]
|
||||
found_in = "shop"
|
||||
else:
|
||||
return self._game_config[section][key]
|
||||
found_val = self.configs["game"]["parser"][section][key]
|
||||
found_in = "game"
|
||||
|
||||
for var_name in self.configs[found_in]["vars"]: # set variable.
|
||||
if var_name in found_val:
|
||||
var_val = self.configs[found_in]["vars"][var_name]
|
||||
found_val = found_val.replace(var_name, var_val)
|
||||
return found_val
|
||||
|
||||
def parse_item_config_string(self, key: str = None) -> ItemProps:
|
||||
string = self._select_val("items", key).upper()
|
||||
@@ -170,33 +177,52 @@ class Config:
|
||||
self.items["misc_gold"].pickit_type = 1
|
||||
|
||||
def load_data(self):
|
||||
Logger.info("Loading Config Data")
|
||||
self._config = configparser.ConfigParser()
|
||||
self._config.read('config/params.ini')
|
||||
self._game_config = configparser.ConfigParser()
|
||||
self._game_config.read('config/game.ini')
|
||||
self._pickit_config = configparser.ConfigParser()
|
||||
self._pickit_config.read('config/pickit.ini')
|
||||
self._shop_config = configparser.ConfigParser()
|
||||
self._shop_config.read('config/shop.ini')
|
||||
self._custom = configparser.ConfigParser()
|
||||
Logger.debug("Loading config")
|
||||
self.configs = {
|
||||
"config": {"parser": configparser.ConfigParser(), "vars": {}},
|
||||
"game": {"parser": configparser.ConfigParser(), "vars": {}},
|
||||
"pickit": {"parser": configparser.ConfigParser(), "vars": {}},
|
||||
"shop": {"parser": configparser.ConfigParser(), "vars": {}},
|
||||
"transmute": {"parser": configparser.ConfigParser(), "vars": {}},
|
||||
"custom": {"parser": configparser.ConfigParser(), "vars": {}},
|
||||
}
|
||||
self.configs["config"]["parser"].read('config/params.ini')
|
||||
self.configs["game"]["parser"].read('config/game.ini')
|
||||
self.configs["pickit"]["parser"].read('config/pickit.ini')
|
||||
self.configs["shop"]["parser"].read('config/shop.ini')
|
||||
self.configs["transmute"]["parser"].read('config/transmute.ini')
|
||||
|
||||
if os.environ.get('RUN_ENV') != "test" and os.path.exists('config/custom.ini'):
|
||||
self._custom.read('config/custom.ini')
|
||||
try:
|
||||
self.configs["custom"]["parser"].read('config/custom.ini')
|
||||
except configparser.MissingSectionHeaderError:
|
||||
Logger.error("custom.ini missing section header, defaulting to params.ini")
|
||||
|
||||
for config_name in self.configs:
|
||||
config = self.configs[config_name]
|
||||
try:
|
||||
for var in config["parser"]["variables"]:
|
||||
config["vars"][var] = config["parser"]["variables"][var] # set var name to var value
|
||||
except KeyError: # "" header section was not found for this config file.
|
||||
pass
|
||||
|
||||
|
||||
|
||||
self.general = {
|
||||
"saved_games_folder": self._select_val("general", "saved_games_folder"),
|
||||
"name": self._select_val("general", "name"),
|
||||
"max_game_length_s": float(self._select_val("general", "max_game_length_s")),
|
||||
"max_consecutive_fails": int(self._select_val("general", "max_consecutive_fails")),
|
||||
"max_runtime_before_break_m": float(_default_iff(self._select_val("general", "max_runtime_before_break_m"), '', 0)),
|
||||
"break_length_m": float(_default_iff(self._select_val("general", "break_length_m"), '', 0)),
|
||||
"randomize_runs": bool(int(self._select_val("general", "randomize_runs"))),
|
||||
"difficulty": self._select_val("general", "difficulty"),
|
||||
"message_api_type": self._select_val("general", "message_api_type"),
|
||||
"custom_message_hook": self._select_val("general", "custom_message_hook"),
|
||||
"discord_status_count": False if not self._select_val("general", "discord_status_count") else int(self._select_val("general", "discord_status_count")),
|
||||
"discord_status_condensed": bool(int(self._select_val("general", "discord_status_condensed"))),
|
||||
"info_screenshots": bool(int(self._select_val("general", "info_screenshots"))),
|
||||
"loot_screenshots": bool(int(self._select_val("general", "loot_screenshots"))),
|
||||
"d2r_path": self._select_val("general", "d2r_path"),
|
||||
"d2r_path": _default_iff(self._select_val("general", "d2r_path"), "", "C:\Program Files (x86)\Diablo II Resurrected"),
|
||||
"restart_d2r_when_stuck": bool(int(self._select_val("general", "restart_d2r_when_stuck"))),
|
||||
}
|
||||
|
||||
@@ -207,11 +233,13 @@ class Config:
|
||||
}
|
||||
|
||||
self.routes = {}
|
||||
order_str = self._select_val("routes", "order").replace("run_eldritch", "run_shenk")
|
||||
order_str = self._select_val("routes", "order")
|
||||
self.routes_order = [x.strip() for x in order_str.split(",")]
|
||||
del self._config["routes"]["order"]
|
||||
for key in self._config["routes"]:
|
||||
self.routes[key] = bool(int(self._select_val("routes", key)))
|
||||
del self.configs["config"]["parser"]["routes"]["order"]
|
||||
for key in self.routes_order:
|
||||
self.routes[key] = True
|
||||
# Botty only knows "run_shenk" but in orders we split run_eldritch and run_eldritch_shenk
|
||||
self.routes_order = ["run_shenk" if x in ["run_eldritch", "run_eldritch_shenk"] else x for x in self.routes_order]
|
||||
|
||||
self.char = {
|
||||
"type": self._select_val("char", "type"),
|
||||
@@ -239,7 +267,7 @@ class Config:
|
||||
"belt_hp_columns": int(self._select_val("char", "belt_hp_columns")),
|
||||
"belt_mp_columns": int(self._select_val("char", "belt_mp_columns")),
|
||||
"stash_gold": bool(int(self._select_val("char", "stash_gold"))),
|
||||
"gold_trav_only": bool(int(self._select_val("char", "gold_trav_only"))),
|
||||
"min_gold_to_pick": int(_default_iff(self._select_val("char", "min_gold_to_pick"), '', 0)),
|
||||
"use_merc": bool(int(self._select_val("char", "use_merc"))),
|
||||
"id_items": bool(int(self._select_val("char", "id_items"))),
|
||||
"open_chests": bool(int(self._select_val("char", "open_chests"))),
|
||||
@@ -269,56 +297,60 @@ class Config:
|
||||
"sell_junk": bool(int(self._select_val("char", "sell_junk"))),
|
||||
}
|
||||
# Sorc base config
|
||||
sorc_base_cfg = dict(self._config["sorceress"])
|
||||
if "sorceress" in self._custom:
|
||||
sorc_base_cfg.update(dict(self._custom["sorceress"]))
|
||||
sorc_base_cfg = dict(self.configs["config"]["parser"]["sorceress"])
|
||||
if "sorceress" in self.configs["custom"]["parser"]:
|
||||
sorc_base_cfg.update(dict(self.configs["custom"]["parser"]["sorceress"]))
|
||||
# blizz sorc
|
||||
self.blizz_sorc = dict(self._config["blizz_sorc"])
|
||||
if "blizz_sorc" in self._custom:
|
||||
self.blizz_sorc.update(dict(self._custom["blizz_sorc"]))
|
||||
self.blizz_sorc = dict(self.configs["config"]["parser"]["blizz_sorc"])
|
||||
if "blizz_sorc" in self.configs["custom"]["parser"]:
|
||||
self.blizz_sorc.update(dict(self.configs["custom"]["parser"]["blizz_sorc"]))
|
||||
self.blizz_sorc.update(sorc_base_cfg)
|
||||
# light sorc
|
||||
self.light_sorc = dict(self._config["light_sorc"])
|
||||
if "light_sorc" in self._custom:
|
||||
self.light_sorc.update(dict(self._custom["light_sorc"]))
|
||||
self.light_sorc = dict(self.configs["config"]["parser"]["light_sorc"])
|
||||
if "light_sorc" in self.configs["custom"]["parser"]:
|
||||
self.light_sorc.update(dict(self.configs["custom"]["parser"]["light_sorc"]))
|
||||
self.light_sorc.update(sorc_base_cfg)
|
||||
# nova sorc
|
||||
self.nova_sorc = dict(self._config["nova_sorc"])
|
||||
if "nova_sorc" in self._custom:
|
||||
self.nova_sorc.update(dict(self._custom["nova_sorc"]))
|
||||
self.nova_sorc = dict(self.configs["config"]["parser"]["nova_sorc"])
|
||||
if "nova_sorc" in self.configs["custom"]["parser"]:
|
||||
self.nova_sorc.update(dict(self.configs["custom"]["parser"]["nova_sorc"]))
|
||||
self.nova_sorc.update(sorc_base_cfg)
|
||||
|
||||
# Palandin config
|
||||
self.hammerdin = self._config["hammerdin"]
|
||||
if "hammerdin" in self._custom:
|
||||
self.hammerdin.update(self._custom["hammerdin"])
|
||||
self.hammerdin = self.configs["config"]["parser"]["hammerdin"]
|
||||
if "hammerdin" in self.configs["custom"]["parser"]:
|
||||
self.hammerdin.update(self.configs["custom"]["parser"]["hammerdin"])
|
||||
|
||||
# Assasin config
|
||||
self.trapsin = self._config["trapsin"]
|
||||
if "trapsin" in self._custom:
|
||||
self.trapsin.update(self._custom["trapsin"])
|
||||
self.trapsin = self.configs["config"]["parser"]["trapsin"]
|
||||
if "trapsin" in self.configs["custom"]["parser"]:
|
||||
self.trapsin.update(self.configs["custom"]["parser"]["trapsin"])
|
||||
|
||||
# Barbarian config
|
||||
self.barbarian = self._config["barbarian"]
|
||||
if "barbarian" in self._custom:
|
||||
self.barbarian.update(self._custom["barbarian"])
|
||||
self.barbarian = self.configs["config"]["parser"]["barbarian"]
|
||||
if "barbarian" in self.configs["custom"]["parser"]:
|
||||
self.barbarian.update(self.configs["custom"]["parser"]["barbarian"])
|
||||
self.barbarian = dict(self.barbarian)
|
||||
self.barbarian["cry_frequency"] = float(self.barbarian["cry_frequency"])
|
||||
|
||||
# Basic config
|
||||
self.basic = self._config["basic"]
|
||||
if "basic" in self._custom:
|
||||
self.basic.update(self._custom["basic"])
|
||||
self.basic = self.configs["config"]["parser"]["basic"]
|
||||
if "basic" in self.configs["custom"]["parser"]:
|
||||
self.basic.update(self.configs["custom"]["parser"]["basic"])
|
||||
|
||||
# Basic Ranged config
|
||||
self.basic_ranged = self._config["basic_ranged"]
|
||||
if "basic_ranged" in self._custom:
|
||||
self.basic_ranged.update(self._custom["basic_ranged"])
|
||||
self.basic_ranged = self.configs["config"]["parser"]["basic_ranged"]
|
||||
if "basic_ranged" in self.configs["custom"]["parser"]:
|
||||
self.basic_ranged.update(self.configs["custom"]["parser"]["basic_ranged"])
|
||||
|
||||
# Necro config
|
||||
self.necro = self._config["necro"]
|
||||
if "necro" in self._custom:
|
||||
self.necro.update(self._custom["necro"])
|
||||
self.necro = self.configs["config"]["parser"]["necro"]
|
||||
if "necro" in self.configs["custom"]["parser"]:
|
||||
self.necro.update(self.configs["custom"]["parser"]["necro"])
|
||||
|
||||
self.poison_necro = self.configs["config"]["parser"]["poison_necro"]
|
||||
if "poison_necro" in self.configs["custom"]["parser"]:
|
||||
self.poison_necro.update(self.configs["custom"]["parser"]["poison_necro"])
|
||||
|
||||
self.advanced_options = {
|
||||
"pathing_delay_factor": min(max(int(self._select_val("advanced_options", "pathing_delay_factor")), 1), 10),
|
||||
@@ -336,32 +368,41 @@ class Config:
|
||||
"hwnd_window_process": _default_iff(Config()._select_val("advanced_options", "hwnd_window_process"), ''),
|
||||
"window_client_area_offset": tuple(map(int, Config()._select_val("advanced_options", "window_client_area_offset").split(","))),
|
||||
"ocr_during_pickit": bool(int(self._select_val("advanced_options", "ocr_during_pickit"))),
|
||||
"launch_options": self._select_val("advanced_options", "launch_options"),
|
||||
"override_capabilities": _default_iff(Config()._select_optional("advanced_options", "override_capabilities"), ""),
|
||||
}
|
||||
|
||||
self.items = {}
|
||||
for key in self._pickit_config["items"]:
|
||||
for key in self.configs["pickit"]["parser"]["items"]:
|
||||
try:
|
||||
self.items[key] = self.parse_item_config_string(key)
|
||||
if self.items[key].pickit_type and not os.path.exists(f"./assets/items/{key}.png"):
|
||||
print(f"Warning: You activated {key} in pickit, but there is no img available in assets/items")
|
||||
Logger.warning(f"You activated {key} in pickit, but there is no img available in assets/items")
|
||||
except ValueError as e:
|
||||
print(f"Error with pickit config: {key} ({e})")
|
||||
Logger.error(f"Error with pickit config: {key} ({e})")
|
||||
|
||||
self.colors = {}
|
||||
for key in self._game_config["colors"]:
|
||||
for key in self.configs["game"]["parser"]["colors"]:
|
||||
self.colors[key] = np.split(np.array([int(x) for x in self._select_val("colors", key).split(",")]), 2)
|
||||
|
||||
self.ui_pos = {}
|
||||
for key in self._game_config["ui_pos"]:
|
||||
for key in self.configs["game"]["parser"]["ui_pos"]:
|
||||
self.ui_pos[key] = int(self._select_val("ui_pos", key))
|
||||
|
||||
self.ui_roi = {}
|
||||
for key in self._game_config["ui_roi"]:
|
||||
for key in self.configs["game"]["parser"]["ui_roi"]:
|
||||
self.ui_roi[key] = np.array([int(x) for x in self._select_val("ui_roi", key).split(",")])
|
||||
open_width = int(self.ui_pos["slot_width"] * self.char["num_loot_columns"])
|
||||
# calc roi for restricted inventory area
|
||||
self.ui_roi["restricted_inventory_area"] = self.ui_roi["right_inventory"].copy()
|
||||
self.ui_roi["restricted_inventory_area"][0] += open_width # left
|
||||
self.ui_roi["restricted_inventory_area"][2] -= open_width # width
|
||||
# calc roi for open inventory area
|
||||
self.ui_roi["open_inventory_area"] = self.ui_roi["right_inventory"].copy()
|
||||
self.ui_roi["open_inventory_area"][2] = open_width # width
|
||||
|
||||
self.path = {}
|
||||
for key in self._game_config["path"]:
|
||||
for key in self.configs["game"]["parser"]["path"]:
|
||||
self.path[key] = np.reshape(np.array([int(x) for x in self._select_val("path", key).split(",")]), (-1, 2))
|
||||
|
||||
self.shop = {
|
||||
@@ -376,19 +417,19 @@ class Config:
|
||||
"apply_pather_adjustment": bool(int(self._select_val("scepters", "apply_pather_adjustment"))),
|
||||
}
|
||||
stash_destination_str = self._select_val("transmute","stash_destination")
|
||||
self._transmute_config = {
|
||||
self.configs["transmute"]["parser"] = {
|
||||
"stash_destination": [int(x.strip()) for x in stash_destination_str.split(",")],
|
||||
"transmute_every_x_game": self._select_val("transmute","transmute_every_x_game"),
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
from copy import deepcopy
|
||||
config = self()
|
||||
config = Config()
|
||||
|
||||
# Check if any added items miss templates
|
||||
for k in config.items:
|
||||
if not os.path.exists(f"./assets/items/{k}.png"):
|
||||
print(f"Template not found: {k}")
|
||||
Logger.warning(f"Template not found: {k}")
|
||||
|
||||
# Check if any item templates miss a config
|
||||
for filename in os.listdir(f'assets/items'):
|
||||
@@ -397,4 +438,4 @@ if __name__ == "__main__":
|
||||
item_name = filename[:-4]
|
||||
blacklist_item = item_name.startswith("bl__")
|
||||
if item_name not in config.items and not blacklist_item:
|
||||
print(f"Config not found for: " + filename)
|
||||
Logger.warning(f"Config not found for: " + filename)
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
from utils.misc import color_filter, cut_roi, wait
|
||||
from template_finder import TemplateFinder
|
||||
from screen import convert_screen_to_monitor, grab
|
||||
from utils.misc import wait
|
||||
from screen import grab
|
||||
from config import Config
|
||||
from utils.custom_mouse import mouse
|
||||
import keyboard
|
||||
import cv2
|
||||
from logger import Logger
|
||||
import time
|
||||
from ui import view
|
||||
from ui_manager import ScreenObjects, detect_screen_object
|
||||
from ui_manager import ScreenObjects, is_visible
|
||||
|
||||
|
||||
class DeathManager:
|
||||
@@ -36,8 +34,7 @@ class DeathManager:
|
||||
|
||||
def handle_death_screen(self):
|
||||
img = grab()
|
||||
template_match = detect_screen_object(ScreenObjects.YouHaveDied, img)
|
||||
if template_match.valid:
|
||||
if is_visible(ScreenObjects.YouHaveDied, img):
|
||||
Logger.warning("You have died!")
|
||||
if Config().general["info_screenshots"]:
|
||||
self._last_death_screenshot = "./info_screenshots/info_debug_death_" + time.strftime("%Y%m%d_%H%M%S") + ".png"
|
||||
@@ -55,7 +52,7 @@ class DeathManager:
|
||||
wait(0.1, 0.2)
|
||||
mouse.release(button="left")
|
||||
time.sleep(1)
|
||||
if detect_screen_object(ScreenObjects.MainMenu).valid:
|
||||
if is_visible(ScreenObjects.MainMenu):
|
||||
# in this case chicken executed and left the game, but we were still dead.
|
||||
return True
|
||||
keyboard.send("esc")
|
||||
|
||||
@@ -12,7 +12,7 @@ from game_stats import GameStats
|
||||
from health_manager import HealthManager
|
||||
from logger import Logger
|
||||
from messages import Messenger
|
||||
from screen import grab, found_offsets
|
||||
from screen import grab, get_offset_state
|
||||
from utils.restart import restart_game, kill_game
|
||||
from utils.misc import kill_thread, set_d2r_always_on_top, restore_d2r_window_visibility
|
||||
|
||||
@@ -42,7 +42,6 @@ class GameController:
|
||||
do_restart = False
|
||||
messenger = Messenger()
|
||||
while 1:
|
||||
self.health_manager.update_location(self.bot.get_curr_location())
|
||||
max_game_length_reached = self.game_stats.get_current_game_length() > Config().general["max_game_length_s"]
|
||||
max_consecutive_fails_reached = False if not Config().general["max_consecutive_fails"] else self.game_stats.get_consecutive_runs_failed() >= Config().general["max_consecutive_fails"]
|
||||
if max_game_length_reached or max_consecutive_fails_reached or self.death_manager.died() or self.health_manager.did_chicken():
|
||||
@@ -121,7 +120,7 @@ class GameController:
|
||||
self.is_running = False
|
||||
|
||||
def setup_screen(self):
|
||||
if found_offsets:
|
||||
if get_offset_state():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
from screen import grab
|
||||
from config import Config
|
||||
from death_manager import DeathManager
|
||||
import time
|
||||
import keyboard
|
||||
from ui_manager import detect_screen_object, ScreenObjects
|
||||
from ui_manager import ScreenObjects, is_visible
|
||||
from ui import view, loading
|
||||
from utils.misc import set_d2r_always_on_top
|
||||
|
||||
@@ -23,22 +22,22 @@ class GameRecovery:
|
||||
# make sure we are not on loading screen
|
||||
is_loading = loading.check_for_black_screen()
|
||||
while is_loading:
|
||||
is_loading = detect_screen_object(ScreenObjects.Loading).valid
|
||||
is_loading = is_visible(ScreenObjects.Loading)
|
||||
is_loading |= loading.check_for_black_screen()
|
||||
time.sleep(0.5)
|
||||
# lets just see if you might already be at hero selection
|
||||
if detect_screen_object(ScreenObjects.MainMenu).valid:
|
||||
if is_visible(ScreenObjects.MainMenu):
|
||||
return True
|
||||
# would have been too easy, maybe we have died?
|
||||
if self._death_manager.handle_death_screen():
|
||||
time.sleep(1)
|
||||
continue
|
||||
# check for save/exit button
|
||||
if detect_screen_object(ScreenObjects.SaveAndExit).valid:
|
||||
if is_visible(ScreenObjects.SaveAndExit):
|
||||
view.save_and_exit()
|
||||
continue
|
||||
# maybe we are in-game in stash/inventory, press escape
|
||||
elif detect_screen_object(ScreenObjects.InGame).valid:
|
||||
elif is_visible(ScreenObjects.InGame):
|
||||
keyboard.send("esc")
|
||||
time.sleep(1)
|
||||
return False
|
||||
|
||||