Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve speed by not using numpy, handle edge case #15

Merged
merged 5 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions profile.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
173 changes: 109 additions & 64 deletions script.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import math
import time
from typing import Literal

from collections.abc import Callable
import winsound
import logging
Expand All @@ -20,7 +19,7 @@
import pyperclip
import win32gui
import threading
from numpy import array as np_array, allclose as np_allclose
from numpy import array as np_array
from settings import AVG_FPS, AVG_PING, DEBUG_ENABLED, UPDATE_CHECK_ENABLED, Colors
from keyboard import add_hotkey, press_and_release
from keyboard import wait as keyboard_wait
Expand Down Expand Up @@ -95,7 +94,7 @@ def move_mouse(x: int, y: int, speed=1):


def sleep_frames(frames: int, minwait=0) -> None:
logging.debug(f"Sleeping for {frames} frame(s)")
logging.debug(f"sleep_frames: Sleeping for {frames} frame(s)")
time.sleep(max((frames * one_frame_time), minwait))


Expand Down Expand Up @@ -161,11 +160,11 @@ def click_rollback() -> None:
rollback_x = zone_screen_width * 0.89955 + zone_screen_x
rollback_y = 0.69518 * window_height
rollback_position = win32gui.ClientToScreen(window, (int(rollback_x), int(rollback_y)))

move_mouse(x=rollback_position[0], y=rollback_position[1], speed=1)
move_mouse(x=rollback_position[0], y=rollback_position[1], speed=2)
mouse_click("left")
sleep_frames(3)
press_and_release("backspace, backspace")
sleep_frames(1)
press_and_release("backspace")
press_and_release("backspace")
move_mouse(mousex, mousey, speed=0)
return

Expand All @@ -191,9 +190,22 @@ def click_camera() -> None:
camera_y = 0.92133
elif scan_for_dialog("uncontrolled"):
logging.debug("click_camera: uncontrolled signal found in click_camera")
window = win32gui.GetForegroundWindow()
rect = win32gui.GetClientRect(window)

bbox = (rect[0], rect[1], rect[2] - rect[0], rect[3] - rect[1])
_x = bbox[0]
_y = bbox[1]
w = bbox[2]
h = bbox[3]
press_and_release("enter")
# sleep_frames(2)
camera_y = 0.80137 if scan_for_dialog("viewcamera") == 0 else 0.92133
sleep_frames(2)
result = find_camera_buttons(h, w, window)
logging.debug(f"click_camera: result is {result}")
if result is False:
logging.debug("click_camera: returning because result is False, no dialog found")
return
camera_y = 0.80137 if result == 1 else 0.92133
x = "lower number" if camera_y == 0.80137 else "upper number"
logging.debug(f"click_camera: uncontrolled scan_for_dialog true in click_camera with x-value of {x}")
else:
Expand Down Expand Up @@ -238,9 +250,12 @@ def toggle_disable() -> None:
beep.start()


# TODO: Swap variables "h, w" for "w, h" for readability


def scan_for_dialog(type: str, mousex=0, mousey=0) -> bool | int | bool:
logging.debug("scan_for_dialog: called")
if mousex is mousey and mousex == 0:
if mousex == mousey and mousex == 0:
mousex, mousey = mouse.get_position()
window = win32gui.GetForegroundWindow()
rect = win32gui.GetClientRect(window)
Expand All @@ -253,34 +268,45 @@ def scan_for_dialog(type: str, mousex=0, mousey=0) -> bool | int | bool:
if type == "exitcamera":
return find_exit_cam_button(w, bbox, window)
elif type == "signal":
return find_controlled_sig_dialog(h, mousex, mousey)
return find_controlled_sig_dialog(w, h, mousex, mousey)
elif type == "uncontrolled":
return find_uncontrolled_sig_dialog(h, mousex, mousey)
return find_uncontrolled_sig_dialog(h, w, mousex, mousey)
elif type == "viewcamera":
return find_camera_buttons(h, w, window)

return False


def find_uncontrolled_sig_dialog(h: int, mousex: int, mousey: int) -> bool:
# TODO: Fix SG+ not working at different resolutions


def find_uncontrolled_sig_dialog(h: int, w: int, mousex: int, mousey: int) -> bool:
logging.debug("find_uncontrolled_sig_dialog: called")
dialogbox_height = math.ceil(h * 0.125)
dialogbox_width = math.ceil(dialogbox_height * 2)
dialogbox_x = math.floor(mousex - dialogbox_width / 2)
dialogbox_y = math.floor(mousey - dialogbox_height)

dialogbox_left_bound = dialogbox_x
dialogbox_right_bound = dialogbox_x + dialogbox_width

zone_screen_height, zone_screen_width, zone_screen_x = calculate_zone_screen(w, h)
zone_screen_right_bound = zone_screen_x + zone_screen_width
zone_screen_left_bound = zone_screen_x
dialogbox_left_bound = dialogbox_x
if zone_screen_right_bound < dialogbox_right_bound:
dialogbox_x -= abs(zone_screen_right_bound - dialogbox_right_bound)
elif zone_screen_left_bound > dialogbox_left_bound:
dialogbox_x += abs(zone_screen_left_bound - dialogbox_left_bound)
capture = screen_grab(dialogbox_x, dialogbox_y, dialogbox_width, dialogbox_height * 2).convert("RGB")
w, h = capture.size
upper = capture.crop((0, 0, w, h / 2))
lower = capture.crop((0, h / 2, w, h))
upperw, upperh = upper.size
width, height = lower.size
lowershelf = lower.crop((0, height * 0.33, width / 2, height * 1 / 3 + 2))
upper = capture.crop((0, 0, w, h / 2))
upperw, upperh = upper.size
uppershelf = upper.crop((0, upperh * 0.1, upperw / 2, upperh * 0.1 + 2))
imagesToProcess = [lowershelf, uppershelf]

capture_width, capture_height = capture.size
upper_y = capture_height * 0.05
upper = capture.crop((0, upper_y, 0 + capture_width, upper_y + 4))
lower_y = capture_height * 0.65
lower = capture.crop((0, lower_y, 0 + w, lower_y + 4))

imagesToProcess = [lower, upper]
for image in imagesToProcess:
logging.debug("find_uncontrolled_sig_dialog: iterating images")
if check_color_percentage_single(image, Colors.COLOR_DIALOG_WHITE):
Expand All @@ -290,13 +316,25 @@ def find_uncontrolled_sig_dialog(h: int, mousex: int, mousey: int) -> bool:
return False


def find_controlled_sig_dialog(h: int, mousex: int, mousey: int) -> bool:
def find_controlled_sig_dialog(w: int, h: int, mousex: int, mousey: int) -> bool:
logging.debug("find_controlled_sig: called")
dialogbox_height = math.ceil(h * 0.125)
dialogbox_width = math.ceil(dialogbox_height * 2)
dialogbox_x = math.floor(mousex - dialogbox_width / 2)
dialogbox_y = math.floor(mousey - dialogbox_height)

dialogbox_left_bound = dialogbox_x
dialogbox_right_bound = dialogbox_x + dialogbox_width

zone_screen_height, zone_screen_width, zone_screen_x = calculate_zone_screen(w, h)
zone_screen_right_bound = zone_screen_x + zone_screen_width
zone_screen_left_bound = zone_screen_x
dialogbox_left_bound = dialogbox_x
if zone_screen_right_bound < dialogbox_right_bound:
dialogbox_x -= abs(zone_screen_right_bound - dialogbox_right_bound)
elif zone_screen_left_bound > dialogbox_left_bound:
dialogbox_x += abs(zone_screen_left_bound - dialogbox_left_bound)

capture = screen_grab(dialogbox_x, dialogbox_y, dialogbox_width, dialogbox_height * 2).convert("RGB")
w, h = capture.size
upper = capture.crop((0, 0, w, h / 2))
Expand All @@ -317,13 +355,13 @@ def find_controlled_sig_dialog(h: int, mousex: int, mousey: int) -> bool:


def find_camera_buttons(h: int, w: int, windowID: int):
logging.debug("find_camera_button: called")
logging.debug("find_camera_buttons: called")
zone_screen_height, zone_screen_width, zone_screen_x = calculate_zone_screen(w, h)

camerabutton_height = math.ceil(h * 0.125 * 0.375)
camerabutton_width = math.ceil(camerabutton_height * 2 / 0.375)
camerabutton_x = zone_screen_width * 0.79760 + zone_screen_x
camerabutton_y = h * 0.80629
camerabutton_x = zone_screen_width * 0.8 + zone_screen_x
camerabutton_y = h * 0.82

screen_cords = win32gui.ClientToScreen(windowID, (int(camerabutton_x), int(camerabutton_y)))
capture = screen_grab(
Expand All @@ -333,36 +371,66 @@ def find_camera_buttons(h: int, w: int, windowID: int):
camerabutton_height * 2,
).convert("RGB")
width, height = capture.size
uppershelf = capture.crop((0, 0, width, 3))
lowershelf = capture.crop((0, height * 0.94, width, height))
uppershelf = capture.crop((0, 0, width * 0.1, height / 4))
lowershelf = capture.crop((0, height / 2.5, width * 0.1, height))

imagesToProcess = [uppershelf, lowershelf]
t1 = time.perf_counter()
imagesToProcess = [lowershelf, uppershelf]
for image in imagesToProcess:
if check_color_single(image, Colors.COLOR_VIEWCAMERA):
if check_color_multiple(image, Colors.COLOR_VIEWCAMERA):
logging.debug(
f"View camera button found. We got {0 if image==imagesToProcess[0] else 1} (0=upper, 1=lower)"
f"find_camera_buttons: View camera button found. We got {0 if image==imagesToProcess[0] else 1} (0=upper, 1=lower)"
)
return 0 if image == imagesToProcess[0] else 1
logging.debug("find_camera_buttons: none found")
return False


def color_approx_eq_np(color1: tuple, color2: tuple, threshold=10) -> bool:
def find_exit_cam_button(w: int, bbox: tuple[int, int, int, int], window):
logging.debug("find_exit_cam_button")
camera_controls_width = 283
camera_controls_x = math.ceil(w / 2 - camera_controls_width / 2)

y = bbox[1]
exit_camera_button_y = 85 + y
exit_camera_button_x = 0.91166 * camera_controls_width + camera_controls_x - 5
exit_camera_button_width = 50
exit_camera_button_height = exit_camera_button_width

screen_cords = win32gui.ClientToScreen(window, (int(exit_camera_button_x), int(exit_camera_button_y)))
capture = screen_grab(
screen_cords[0],
screen_cords[1],
exit_camera_button_width,
exit_camera_button_height,
).convert("RGB")
width, height = capture.size
lowershelf = capture.crop((0, height / 2, width, height / 2 + 2))
imagesToProcess = [lowershelf]

return all(check_color_single(image, Colors.COLOR_CAMERA_EXIT) for image in imagesToProcess)


def color_approx_eq_np(inputColor: tuple, colorToCompare: tuple, threshold=5) -> bool:
"""Check if a color is equal to another color within a given value

Args:
color1 (tuple): First RGB color to check against
color2 (tuple): Second RGB color to check against
tolerance (int, optional): How many units of R, G, or B to tolerate. Defaults to 10.
inputColor (tuple): First RGB color to check against
colorToCompare (tuple): Second RGB color to check against
threshold (int, optional): How many units of R, G, or B to tolerate. Defaults to 5.

Returns:
bool: Whether or not the colors are approximately equal to eachother
bool: Whether or not the colors are approximately equal to each other
"""
# Get the absolute value of the difference between the arrays
return np_allclose(color1, color2, atol=threshold)
# Calculate the absolute difference between each color component
diff = [abs(c1 - c2) for c1, c2 in zip(inputColor, colorToCompare)]

# Check if the maximum difference is within the threshold
return max(diff) <= threshold


def check_color_single(image: Image, color, threshold=7) -> bool:
start_time = time.perf_counter()
logging.debug("check_color_single: called")
arr = np_array(image)

Expand All @@ -371,8 +439,10 @@ def check_color_single(image: Image, color, threshold=7) -> bool:
# Iterate over the x-axis
for j in range(arr.shape[1]):
col_to_compare = arr[i, j]
logging.debug(f"check_color_single: col_to_compare is {col_to_compare}")
if color_approx_eq_np(col_to_compare, color, threshold):
logging.debug("check_color_single: colors similar, return True")
logging.debug(f"check_color_single: Time taken was {time.perf_counter() - start_time}")
return True
logging.debug("check_color_single: no similar colors found, returning False")
return False
Expand Down Expand Up @@ -418,31 +488,6 @@ def check_color_percentage_single(image: Image, color: tuple, compareThreshold=7
return False


def find_exit_cam_button(w: int, bbox: tuple[int, int, int, int], window):
logging.debug("find_exit_cam_button")
camera_controls_width = 283
camera_controls_x = math.ceil(w / 2 - camera_controls_width / 2)

y = bbox[1]
exit_camera_button_y = 85 + y
exit_camera_button_x = 0.91166 * camera_controls_width + camera_controls_x - 5
exit_camera_button_width = 50
exit_camera_button_height = exit_camera_button_width

screen_cords = win32gui.ClientToScreen(window, (int(exit_camera_button_x), int(exit_camera_button_y)))
capture = screen_grab(
screen_cords[0],
screen_cords[1],
exit_camera_button_width,
exit_camera_button_height,
).convert("RGB")
width, height = capture.size
lowershelf = capture.crop((0, height / 2, width, height / 2 + 2))
imagesToProcess = [lowershelf]

return all(check_color_single(image, Colors.COLOR_CAMERA_EXIT) for image in imagesToProcess)


@check_able_to_run
def send_zone_message(zone: str) -> None:
"""Copy a Zone opening message to the user's clipboard and sound an audible tone
Expand Down
5 changes: 4 additions & 1 deletion settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ class Colors:
COLOR_MENU = (194, 186, 189) # Side menu main color
COLOR_CAMERA_EXIT = (255, 255, 255) # White X on the close button in camera view
COLOR_DIALOG_WHITE = (227, 218, 218) # White elements in the signal dialog
COLOR_VIEWCAMERA = (147, 0, 207) # Purple View Camera button
COLOR_VIEWCAMERA = [
(147, 0, 207), # Non-hovered
(89, 0, 152), # Hovered
]