18
18
import webbrowser
19
19
20
20
from ctypes import windll
21
+
22
+ import win32clipboard
21
23
from pynput import keyboard
22
24
from pynput .keyboard import KeyCode
23
25
24
26
import win32process
27
+ from winrt .windows .media .ocr import OcrResult
25
28
26
- from subot import models , ocr
29
+ from subot import models
30
+ from subot .ui_areas .CodexGeneric import CodexGeneric
27
31
from subot .ui_areas .CreatureReorderSelectFirst import OCRCreatureRecorderSelectFirst , OCRCreatureRecorderSwapWith
28
32
from subot .ui_areas .OCRGodForgeSelect import OCRGodForgeSelectSystem
29
33
from subot .ui_areas .OcrUnknownArea import OcrUnknownArea
34
+ from subot .ui_areas .PerkScreen import PerkScreen
30
35
from subot .ui_areas .creatures_display import OCRCreaturesDisplaySystem
31
36
from subot .ui_areas .realm_select import OCRRealmSelect , SelectStep
32
37
from subot .ui_areas .summoning import OcrSummoningSystem
33
- from subot .trait_info import TraitData , Creature
38
+ from subot .trait_info import TraitData
34
39
from subot .hang_monitor import HangMonitorWorker , HangMonitorChan , HangAnnotation , HangMonitorAlert , Shutdown
35
40
36
41
import cv2
37
42
import numpy as np
38
43
import mss
39
44
from subot .settings import Session , GameControl
40
45
import subot .settings as settings
41
- from subot .ocr import detect_green_text , detect_dialog_text , english_installed , detect_title , OCR
46
+ from subot .ocr import detect_title , OCR
42
47
from subot .ui_areas .ui_ocr_types import OCR_UI_SYSTEMS
43
48
from subot .ui_areas .base import OCRMode
44
49
import win32gui
69
74
70
75
user32 = windll .user32
71
76
72
-
73
77
def set_dpi_aware ():
74
78
# makes functions return real pixel numbers instead of scaled values
75
79
user32 .SetProcessDPIAware ()
@@ -242,6 +246,14 @@ class ActionType(enum.Enum):
242
246
READ_ALL_INFO = auto ()
243
247
COPY_ALL_INFO = auto ()
244
248
HELP = auto ()
249
+ SCREENSHOT = auto ()
250
+ SILENCE = auto ()
251
+ OPEN_CONFIG_LOCATION = auto ()
252
+ FORCE_OCR = auto ()
253
+
254
+
255
+ def open_config_file ():
256
+ os .startfile (settings .config_file_path (), 'edit' )
245
257
246
258
247
259
class Bot :
@@ -259,6 +271,12 @@ def on_release(self, key):
259
271
self .action_queue .put_nowait (ActionType .COPY_ALL_INFO )
260
272
elif key == KeyCode .from_char ('?' ):
261
273
self .action_queue .put_nowait (ActionType .HELP )
274
+ elif key == KeyCode .from_char ("P" ):
275
+ self .action_queue .put_nowait (ActionType .SCREENSHOT )
276
+ elif key == KeyCode .from_char (self .config .open_config_key ):
277
+ self .action_queue .put_nowait (ActionType .OPEN_CONFIG_LOCATION )
278
+ elif key == KeyCode .from_char ("O" ):
279
+ self .action_queue .put_nowait (ActionType .FORCE_OCR )
262
280
263
281
def on_press (self , key ):
264
282
pass
@@ -732,6 +750,32 @@ def run(self):
732
750
self .whole_window_thandle .copy_all_info ()
733
751
elif msg is ActionType .HELP :
734
752
root .debug ("got help request" )
753
+ self .whole_window_thandle .speak_help ()
754
+ elif msg is ActionType .SILENCE :
755
+ self .audio_system .silence ()
756
+ elif msg is ActionType .OPEN_CONFIG_LOCATION :
757
+ open_config_file ()
758
+ elif msg is ActionType .FORCE_OCR :
759
+ self .whole_window_thandle .force_ocr ()
760
+ elif msg is ActionType .SCREENSHOT :
761
+
762
+
763
+ def send_to_clipboard (clip_type , data ):
764
+ """copy image to clipboard. Found at https://stackoverflow.com/a/62007792/17323787"""
765
+ import win32clipboard
766
+ win32clipboard .OpenClipboard ()
767
+ win32clipboard .EmptyClipboard ()
768
+ win32clipboard .SetClipboardData (clip_type , data )
769
+ win32clipboard .CloseClipboard ()
770
+
771
+ bgr_frame = self .whole_window_thandle .frame
772
+ is_success , buffer = cv2 .imencode (".bmp" , bgr_frame )
773
+ BMP_HEADER_LEN = 14
774
+ bmp_data = buffer [BMP_HEADER_LEN :].tobytes ()
775
+ send_to_clipboard (win32clipboard .CF_DIB , bmp_data )
776
+
777
+
778
+ root .info ("copied whole frame bytes to clipboard" )
735
779
except queue .Empty :
736
780
pass
737
781
@@ -889,9 +933,9 @@ def __init__(self, incoming_frame_queue: Queue, queue_child_comm_send: queue.Que
889
933
self ._hang_monitor = hang_monitor
890
934
self .hang_activity_sender : Optional [HangMonitorChan ] = None
891
935
892
- self .frame : np .typing .ArrayLike = np .zeros (
936
+ self .frame : np .typing .NDArray = np .zeros (
893
937
shape = (self .parent .su_client_rect .h , self .parent .su_client_rect .w , 3 ), dtype = "uint8" )
894
- self .gray_frame : np .typing .ArrayLike = np .zeros (
938
+ self .gray_frame : np .typing .NDArray = np .zeros (
895
939
shape = (self .parent .su_client_rect .h , self .parent .su_client_rect .w ),
896
940
dtype = "uint8" )
897
941
self .ocr_engine : OCR = OCR ()
@@ -901,40 +945,74 @@ def __init__(self, incoming_frame_queue: Queue, queue_child_comm_send: queue.Que
901
945
self .frames_since_last_scan : int = 0
902
946
self .got_first_frame : bool = False
903
947
904
- def ocr_title (self ):
905
- mask = detect_title (self .frame )
906
- resize_factor = 2
907
- mask = cv2 .resize (mask , (mask .shape [1 ] * resize_factor , mask .shape [0 ] * resize_factor ),
908
- interpolation = cv2 .INTER_LINEAR )
909
- ocr_result = self .ocr_engine .recognize_cv2_image (mask )
910
- detected_system = OcrUnknownArea (audio_system = self .parent .audio_system , config = self .config ,
948
+ def determine_ocr_system (self , ocr_result : OcrResult ) -> OCR_UI_SYSTEMS :
949
+ unknown_system = OcrUnknownArea (audio_system = self .parent .audio_system , config = self .config ,
911
950
ocr_engine = self .ocr_engine )
912
951
try :
913
952
title = ocr_result .merged_text
914
- root .debug (f"title: { title } " )
915
- if title .startswith ("Select a creature to summon" ):
916
- detected_system = OcrSummoningSystem (self .creature_data , self .parent .audio_system , self .config ,
953
+ root .debug (f"{ title = } " )
954
+ lower_title = title .lower ()
955
+ first_word = ocr_result .lines [0 ].words [0 ]
956
+ if lower_title .startswith ("select a creature to summon" ):
957
+ return OcrSummoningSystem (self .creature_data , self .parent .audio_system , self .config ,
917
958
self .ocr_engine )
918
959
919
- elif title .startswith ("Creatures" ):
920
- detected_system = OCRCreaturesDisplaySystem (self .creature_data , self .parent .audio_system ,
921
- self .config , self .ocr_engine )
922
- elif title .startswith ("Choose the Avatar" ):
923
- detected_system = OCRGodForgeSelectSystem (audio_system = self .parent .audio_system , config = self .config ,
960
+ elif lower_title .startswith ("creatures" ) and first_word .bounding_rect .x / self .frame .shape [1 ] > 0.13 :
961
+ return OCRCreaturesDisplaySystem (self .parent .audio_system , self .config , self .ocr_engine )
962
+ elif lower_title .startswith ("choose the avatar" ):
963
+ return OCRGodForgeSelectSystem (audio_system = self .parent .audio_system , config = self .config ,
924
964
ocr_engine = self .ocr_engine )
925
965
926
- elif title .startswith ("Choose the creature whose position" ):
927
- detected_system = OCRCreatureRecorderSelectFirst (audio_system = self .parent .audio_system , config = self .config , ocr_engine = self .ocr_engine )
966
+ elif lower_title .startswith ("choose the creature whose position" ):
967
+ return OCRCreatureRecorderSelectFirst (audio_system = self .parent .audio_system , config = self .config , ocr_engine = self .ocr_engine )
928
968
929
- elif title .startswith ("Choose a creature to swap" ):
930
- detected_system = OCRCreatureRecorderSwapWith (audio_system = self .parent .audio_system ,
969
+ elif lower_title .startswith ("choose a creature to swap" ):
970
+ return OCRCreatureRecorderSwapWith (audio_system = self .parent .audio_system ,
931
971
config = self .config , ocr_engine = self .ocr_engine )
932
972
elif step_type := _realm_select_step (title ):
933
973
root .debug (f"realm step - { step_type } { title } " )
934
- detected_system = OCRRealmSelect (audio_system = self .parent .audio_system , config = self .config ,
974
+ return OCRRealmSelect (audio_system = self .parent .audio_system , config = self .config ,
935
975
ocr_engine = self .ocr_engine , step = step_type )
976
+ elif lower_title .startswith ("choose an item to purchase" ):
977
+ pass
978
+ # Equip / Items -> Artifacts screen
979
+ elif lower_title .startswith ("artifacts (" ):
980
+ pass
981
+ # Spell gems in inventory screen
982
+ elif lower_title .startswith ("spell gems (" ):
983
+ pass
984
+ elif lower_title .startswith ("choose a perk to rank" ):
985
+ return PerkScreen (self .parent .audio_system , self .config , self .ocr_engine )
986
+
987
+ # codex section
988
+ elif lower_title .startswith (("artifact properties" , "realm properties" , "status effects" , "spell gem properties" , "traits" , "skins" , "gate of the gods" , "gods" , "guilds and false gods" , "rodian creature masters" , "macros" , "nether bosses" )):
989
+ return CodexGeneric (audio_system = self .parent .audio_system , ocr_engine = self .ocr_engine , config = self .config , title = title )
990
+ # todo:-Problematic codex entries "Castle", "Character", "Events", "Gods", "Items", "Realms", "Relics", "Spell Gems"
991
+ elif lower_title .startswith ("spells" ):
992
+ # todo: proper spell gem screen
993
+ return CodexGeneric (audio_system = self .parent .audio_system , ocr_engine = self .ocr_engine , config = self .config , title = title )
994
+ elif lower_title .startswith ("skins" ):
995
+ pass
996
+ elif lower_title .startswith ("traits" ):
997
+ pass
998
+ # codex artifact info
999
+ elif lower_title .startswith ("artifacts" ) and first_word .bounding_rect .x / self .frame .shape [1 ] < 0.1 :
1000
+ print ("art screen" )
1001
+ elif lower_title .startswith ("nether stones (" ):
1002
+ print ("nether stone item screen" )
1003
+
936
1004
except IndexError :
937
- pass
1005
+ return unknown_system
1006
+ return unknown_system
1007
+
1008
+ def ocr_title (self ):
1009
+ mask = detect_title (self .frame )
1010
+ resize_factor = 2
1011
+ mask = cv2 .resize (mask , (mask .shape [1 ] * resize_factor , mask .shape [0 ] * resize_factor ),
1012
+ interpolation = cv2 .INTER_LINEAR )
1013
+
1014
+ ocr_result = self .ocr_engine .recognize_cv2_image (mask )
1015
+ detected_system = self .determine_ocr_system (ocr_result )
938
1016
939
1017
if detected_system .mode != self .ocr_ui_system .mode or self .ocr_ui_system .step != detected_system .step :
940
1018
root .debug (f"new ocr system: { detected_system .mode } , { self .ocr_ui_system .mode } " )
@@ -949,12 +1027,18 @@ def ocr_title(self):
949
1027
def speak_interaction_info (self ):
950
1028
if not self .ocr_ui_system :
951
1029
return
952
- elif isinstance (self .ocr_ui_system , OcrSummoningSystem ):
953
- self .ocr_ui_system .speak_interaction ()
954
- elif isinstance (self .ocr_ui_system , OcrUnknownArea ):
955
- self .ocr_ui_system .speak_interaction ()
956
- elif isinstance (self .ocr_ui_system , OCRRealmSelect ):
1030
+ try :
957
1031
self .ocr_ui_system .speak_interaction ()
1032
+ except AttributeError :
1033
+ pass
1034
+
1035
+ def speak_help (self ):
1036
+ if not self .ocr_ui_system :
1037
+ return
1038
+ try :
1039
+ self .ocr_ui_system .speak_help ()
1040
+ except AttributeError :
1041
+ root .warning (f"no help implemented for { self .ocr_ui_system .__name__ } " )
958
1042
959
1043
def ocr_screen (self ):
960
1044
self .ocr_title ()
@@ -1027,14 +1111,22 @@ def run(self):
1027
1111
root .info ("WindowAnalyzer thread shutting down" )
1028
1112
1029
1113
def speak_all_info (self ):
1030
- if isinstance (self .ocr_ui_system , OcrSummoningSystem ):
1031
- self .ocr_ui_system .speak_detailed ()
1032
- elif isinstance (self .ocr_ui_system , OCRRealmSelect ):
1114
+ try :
1033
1115
self .ocr_ui_system .speak_all_info ()
1116
+ except AttributeError :
1117
+ pass
1034
1118
1035
1119
def copy_all_info (self ):
1036
- if isinstance ( self . ocr_ui_system , OcrSummoningSystem ) :
1120
+ try :
1037
1121
self .ocr_ui_system .copy_detailed_text ()
1122
+ except AttributeError :
1123
+ pass
1124
+
1125
+ def force_ocr (self ):
1126
+ try :
1127
+ self .ocr_ui_system .force_ocr_content (self .gray_frame )
1128
+ except AttributeError :
1129
+ pass
1038
1130
1039
1131
1040
1132
class NearbyFrameGrabber (multiprocessing .Process ):
@@ -1486,11 +1578,11 @@ def version_check(config, audio_system):
1486
1578
def init_bot () -> Bot :
1487
1579
config = settings .load_config ()
1488
1580
audio_system = AudioSystem (config )
1489
- if not english_installed ():
1490
- audio_system .speak_blocking (ocr .ENGLISH_NOT_INSTALLED_EXCEPTION .args [0 ])
1491
- root .error (ocr .ENGLISH_NOT_INSTALLED_EXCEPTION .args [0 ])
1492
- audio_system .speak_blocking ("Shutting down" )
1493
- sys .exit (1 )
1581
+ # if not english_installed():
1582
+ # audio_system.speak_blocking(ocr.ENGLISH_NOT_INSTALLED_EXCEPTION.args[0])
1583
+ # root.error(ocr.ENGLISH_NOT_INSTALLED_EXCEPTION.args[0])
1584
+ # audio_system.speak_blocking("Shutting down")
1585
+ # sys.exit(1)
1494
1586
version_check (config , audio_system )
1495
1587
is_minimized = True
1496
1588
while is_minimized :
0 commit comments