Skip to content

Commit 3c7e9f3

Browse files
authored
Merge pull request #65 from gurgalex/ui_refactor
UI refactor + crash fixes + teleportation UI
2 parents 2bbf3b9 + 5de8c8a commit 3c7e9f3

14 files changed

+633
-341
lines changed

CHANGELOG.md

+10
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
# v0.11.2
2+
## fix
3+
- Crash when Creatures screen would pop up a dialog box
4+
- Crash on summoning screen if no existing trait or creature name was found
5+
- Fix for mod crashing with low-end computers taking long to startup new processes
6+
7+
## features
8+
- Add more dedicated UI screens
9+
- Teleportation Shrine Realm Select UI
10+
111
# v0.11.1
212
## fix
313
- fix rapid repeat audio of realm object detection

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ A program to aid in making [Siralim Ultimate](https://store.steampowered.com/app
2525
- Creatures screen (know which creature's traits, artifact, spell gems are being configured)
2626
- Reorder creature screen
2727
- GodForge avatar select
28+
- Teleportation Shrine realm select
2829

2930
## Item sounds
3031
[video of sounds playing](https://youtu.be/2vVCJtCocbA)

VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.11.1
1+
0.11.2

subot/main.py

+118-303
Large diffs are not rendered by default.

subot/ocr.py

+63-16
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,22 @@
55
from dataclasses import dataclass
66
from typing import Optional
77

8+
import time
9+
import math
10+
811
import cv2
912
import numpy as np
1013
import numpy.typing
14+
from numpy.typing import NDArray
1115
from winrt.windows.media.ocr import OcrEngine
1216
from winrt.windows.globalization import Language
1317
from winrt.windows.graphics.imaging import *
1418
from winrt.windows.security.cryptography import CryptographicBuffer
1519

16-
from enum import Enum, auto
20+
from logging import getLogger
1721

22+
root = getLogger()
1823

19-
class OCRMode(Enum):
20-
SUMMON = auto()
21-
UNKNOWN = auto()
22-
INSPECT = auto()
23-
CREATURES_DISPLAY = auto()
24-
SELECT_GODFORGE_AVATAR = auto()
25-
CREATURE_REORDER_SELECT = auto()
26-
CREATURE_REORDER_WITH = auto()
2724

2825
# Modified from https://gist.github.com/dantmnf/23f060278585d6243ffd9b0c538beab2
2926

@@ -192,7 +189,7 @@ def english_installed() -> bool:
192189

193190

194191
def detect_green_text(image: np.typing.ArrayLike, x_start: float = 0.0, x_end: float = 1.0, y_start: float = 0.0,
195-
y_end: float = 1.0) -> np.typing.ArrayLike:
192+
y_end: float = 1.0) -> NDArray:
196193
"""Using a source image of RGB color, extract highlighted menu items which are a green color"""
197194
lower_green = np.array([60, 50, 100])
198195
upper_green = np.array([60, 255, 255])
@@ -243,16 +240,66 @@ def detect_title(frame: np.typing.ArrayLike) -> np.typing.ArrayLike:
243240
return mask
244241

245242

246-
def detect_dialog_text(frame: np.typing.ArrayLike) -> np.typing.ArrayLike:
247-
y_start = int(frame.shape[0] * 0.70)
248-
y_end = int(frame.shape[0] * 0.95)
249-
x_start = int(frame.shape[1] * 0.01)
250-
x_end = int(frame.shape[1] * 0.995)
243+
def timeit(func):
244+
def wrap_timer(*args, **kwargs):
245+
t1 = time.time()
246+
value = func(*args, **kwargs)
247+
t2 = time.time()
248+
took = t2 - t1
249+
print(f"{func.__name__!r} took {math.ceil(took * 1000)}ms")
250+
return value
251+
252+
return wrap_timer
253+
254+
255+
def detect_dialog_text(frame: NDArray, gray_frame: NDArray, ocr_engine: OCR) -> Optional[str]:
256+
"""detect dialog text from frame
257+
258+
:param gray_frame BGR whole window frame
259+
:param ocr_engine: engine that can perform OCR on image
260+
"""
261+
y_start = int(gray_frame.shape[0] * 0.70)
262+
y_end = int(gray_frame.shape[0] * 0.95)
263+
x_start = int(gray_frame.shape[1] * 0.01)
264+
x_end = int(gray_frame.shape[1] * 0.995)
251265
dialog_area = frame[y_start:y_end, x_start:x_end]
266+
# output = np.ones(dialog_area.shape, dtype='uint8')
267+
# output[dialog_area > 180] = 255
268+
# mask = output
252269

253270
img = cv2.cvtColor(dialog_area, cv2.COLOR_BGR2HLS)
254271
sensitivity = 30
255272
lower_white = np.array([0, 255 - sensitivity, 0])
256273
upper_white = np.array([0, 255, 0])
257274
mask = cv2.inRange(img, lower_white, upper_white)
258-
return mask
275+
276+
# resize_factor = 2
277+
# mask = cv2.resize(mask, (mask.shape[1] * resize_factor, mask.shape[0] * resize_factor),
278+
# interpolation=cv2.INTER_LINEAR)
279+
ocr_result = ocr_engine.recognize_cv2_image(mask)
280+
try:
281+
first_line = ocr_result.lines[0]
282+
first_word = first_line.words[0]
283+
bbox = first_word.bounding_rect
284+
root.debug(f"dialog box: {ocr_result.text}")
285+
286+
# health bar text - rect(69, 87, 73, 16), rect(282, 87, 71, 16)
287+
# dialog box text - rect(14, 23, 75, 16)
288+
289+
offset_x = mask.shape[0] * 0.40
290+
root.debug(f"{offset_x=}")
291+
is_not_dialog_box = bbox.x > offset_x
292+
root.debug(f"{is_not_dialog_box=}")
293+
if is_not_dialog_box:
294+
return None
295+
return ocr_result.merged_text
296+
# if is_not_dialog_box and not self.has_menu_entry_text:
297+
# return
298+
except IndexError:
299+
root.debug("no dialog text")
300+
return None
301+
# no text was found
302+
if not self.has_menu_entry_text and not self.has_dialog_text and not self.quest_text:
303+
root.info("Pause, menu system. both not present")
304+
self.audio_system.silence()
305+
return

subot/ui_areas/CreatureReorderSelectFirst.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Optional
22

3-
from .base import SpeakAuto
3+
from .base import SpeakAuto, FrameInfo, OCRMode
44
from .shared import detect_creature_party_selection
55
from subot.audio import AudioSystem
66
from subot.ocr import OCR
@@ -9,6 +9,8 @@
99

1010

1111
class OCRCreatureRecorderSelectFirst(SpeakAuto):
12+
mode = OCRMode.CREATURE_REORDER_SELECT
13+
1214
def __init__(self, audio_system: AudioSystem, config: Config, ocr_engine: OCR):
1315
self.auto_text: str = ""
1416
self.audio_system = audio_system
@@ -17,9 +19,9 @@ def __init__(self, audio_system: AudioSystem, config: Config, ocr_engine: OCR):
1719
self.prev_creature_pos: Optional[int] = None
1820
self.creature_pos: Optional[int] = None
1921

20-
def ocr(self, frame: numpy.typing.ArrayLike):
22+
def ocr(self, parent: FrameInfo):
2123
self.prev_creature_pos = self.creature_pos
22-
self.creature_pos = detect_creature_party_selection(frame)
24+
self.creature_pos = detect_creature_party_selection(parent.frame)
2325

2426
def speak_auto(self):
2527
if self.creature_pos != self.prev_creature_pos:
@@ -28,6 +30,8 @@ def speak_auto(self):
2830

2931

3032
class OCRCreatureRecorderSwapWith(SpeakAuto):
33+
mode = OCRMode.CREATURE_REORDER_WITH
34+
3135
def __init__(self, audio_system: AudioSystem, config: Config, ocr_engine: OCR):
3236
self.auto_text: str = ""
3337
self.audio_system = audio_system
@@ -36,9 +40,9 @@ def __init__(self, audio_system: AudioSystem, config: Config, ocr_engine: OCR):
3640
self.prev_creature_pos: Optional[int] = None
3741
self.creature_pos: Optional[int] = None
3842

39-
def ocr(self, frame: numpy.typing.ArrayLike):
43+
def ocr(self, parent: FrameInfo):
4044
self.prev_creature_pos = self.creature_pos
41-
self.creature_pos = detect_creature_party_selection(frame)
45+
self.creature_pos = detect_creature_party_selection(parent.frame)
4246

4347
def speak_auto(self):
4448
if self.creature_pos != self.prev_creature_pos:

subot/ui_areas/OCRGodForgeSelect.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Optional
22

3-
from .base import SpeakAuto
3+
from .base import SpeakAuto, FrameInfo, OCRMode
44
from .shared import detect_creature_party_selection
55
from subot.audio import AudioSystem
66
from subot.ocr import OCR
@@ -9,6 +9,8 @@
99

1010

1111
class OCRGodForgeSelectSystem(SpeakAuto):
12+
mode = OCRMode.SELECT_GODFORGE_AVATAR
13+
1214
def __init__(self, audio_system: AudioSystem, config: Config, ocr_engine: OCR):
1315
self.auto_text: str = ""
1416
self.audio_system = audio_system
@@ -17,12 +19,11 @@ def __init__(self, audio_system: AudioSystem, config: Config, ocr_engine: OCR):
1719
self.prev_creature_pos: Optional[int] = None
1820
self.creature_pos: Optional[int] = None
1921

20-
def ocr(self, frame: numpy.typing.ArrayLike):
22+
def ocr(self, parent: FrameInfo):
2123
self.prev_creature_pos = self.creature_pos
22-
self.creature_pos = detect_creature_party_selection(frame)
24+
self.creature_pos = detect_creature_party_selection(parent.frame)
2325

2426
def speak_auto(self):
2527
if self.creature_pos != self.prev_creature_pos:
2628
text = f"Creature {self.creature_pos}, selected to GodForge"
2729
self.audio_system.speak_nonblocking(text)
28-

0 commit comments

Comments
 (0)