Skip to content

Commit

Permalink
Update README for 0.4 (#13)
Browse files Browse the repository at this point in the history
* Document new installation method and update info

* Increment version and enable update checker

* Format files and remove .vscode dir

* Remove demo gif in favor of future video

* Don't treat AutoIt license as code

* Exclude html files from GH Linguist stats
  • Loading branch information
ElectricityMachine authored Nov 11, 2023
1 parent 3835e02 commit 69a8935
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 61 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.html linguist-vendored
3 changes: 0 additions & 3 deletions .vscode/settings.json

This file was deleted.

68 changes: 36 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,27 @@
<h1 align="center">SCR: SG+</h1>

<h1 align="center">

![macro-beechley_1_2](https://user-images.githubusercontent.com/47489506/165611305-da7b72a5-1492-4db0-b37a-3ce81f1aad22.gif)

</h1>


### Description
SG+ is a macro script written in Python that streamlines your signalling experience by allowing you to change a signal aspect, announce your signalling presence, or open a camera view with only one button press.
SG+ is a macro script written in Python that streamlines your signalling experience by allowing you to change a signal aspect, open a camera view, toggle rollback, or announce your signalling presence.

### Will this get me demoted? Am I safe to use this?
**There is nothing to indicate you will be demoted or banned for using this script.** The onus is still on the signaller to signal trains in a safe and proper manner. It is not an exploit and does not automate signalling entirely. It is just to aid the signaller in their task of efficiently running their zone and keeping time. If you have had trouble with a member of staff as a result from using this script, **contact me immediately (scroll down) or open an issue.**
### Will this get me demoted?
**You will not be banned or demoted just from using this script.** The responsibility is still on the signaller to signal trains in a safe and proper manner. It is not an exploit and does not automate signalling entirely, rather, it aims to speed up commonly performed actions. If you've encountered trouble with a member of staff while using this script, please [contact me as soon as possible](#contact-information), or open a new issue.

### Usage:
- Hover over a signal and press 1, 2, or 3, corresponding to the aspect you wish to change the signal to.
- Hover over a signal and press C to enter camera view. Once in camera view, press C again to exit.
- Hover over a signal and:
- press 1, 2, or 3, corresponding to the desired signal aspect.
- press C to enter camera view. Once in camera view, press C again to exit.
- press R to toggle rollback.
- Press F1 to enable or disable the script. You only need to do this if you are chatting or typing things to prevent false activations.
- Use numpad 1-7 to copy zone opening messages to your clipboard
- Use numpad 1-7 (1 is A, 2 is B, etc.) to copy zone opening messages to your clipboard.

### Features
- Only one button press is needed to change a signal aspect or copy zone opening messages to your clipboard
- Decreases setup and train clearing time significantly
- Allows the signaller to focus on more important things rather than just clicking to change aspects
- Only one button press is needed to change a signal aspect
- Quickly get in and out of a camera view (signals only for now)
- Toggle rollback on or off
- Easily copy zone opening messages to your clipboard with the numpad
- Audible warning if you try to chat or run commands with the macro enabled

### Planned features:
- TRTS Audio cue
- Station cameras
- Auto terminus station setup

### Limitations:
- Windows only
- Primary monitor only
Expand All @@ -52,20 +42,34 @@ SG+ is a macro script written in Python that streamlines your signalling experie

### **Installation**

## Windows
1. Download and install the latest Python3 installer from [Python Downloads Page](https://www.python.org/downloads/)
- IMPORTANT: Make sure to check the box during installation which adds Python to PATH. Labeled something like **Add Python 3.X to PATH**
2. Download the latest release in the [Releases section of this repo](https://github.com/ElectricityMachine/SCR-SGPlus/releases/). Click on "Source Code (zip)"
#### Binary (.exe file)
1. Download the sgplus.zip from the [latest releases page](https://github.com/ElectricityMachine/SCR-SGPlus/releases/latest)
2. Extract the zip to your desired location (right click > Extract All).
3. Run **sgplus.exe**.
**Note:** Some antiviruses may detect the program as malware. This is a false-positive, as such you may have to add an exclusion to your antivirus software for the program to run. Any binary attached to a release is the same as the one built automatically [here](https://github.com/ElectricityMachine/SCR-SGPlus/actions/workflows/build.yml)

#### Source (running script directly)
Note: This method is for those who wish to contribute to the project, for those who don't like the idea of running an exe file, or for those who like manual labour.
1. Download and install the latest Python 3 installer from the [Python Downloads Page](https://www.python.org/downloads/)
- Make sure to check the box during installation which adds Python to PATH. Labeled something like **Add Python 3.X to PATH**
2. Download the latest release in the [Releases section of this repo](https://github.com/ElectricityMachine/SCR-SGPlus/releases/latest). Click on "Source Code (zip)"
- Note: If you want the development release with all the latest changes, press the green "Code" button on the main page of this repo and press "Download ZIP". The following instructions still apply.
4. Extract the folder to your desired location. You can do this natively in Windows by opening the .zip folder and pressing "Extract all" at the top.
3. Extract the folder to your desired location. You can do this natively in Windows by opening the .zip folder and pressing "Extract all" at the top.
- Note: If you are updating from an older version, please delete the old version of the script before extracting the new version. Failure to do so may cause conflicts and bugs.
5. Enter the folder where the script and images are stored, then in the Windows File Explorer address bar, type "cmd". A Command Prompt window should pop up. See the gif below for an example of how to do this. ![explorer_UNTq76MoQy](https://user-images.githubusercontent.com/47489506/181626707-6f58a2b6-e9e4-423e-9cb8-15d2add19cc7.gif)
4. Run ``install.bat`` and answer the prompts.
5. After the installation has completed, run ``start.bat`` to start the script. A console window will appear, a beep will sound, and the macro is now active.

### I've found an issue!
Great! Please [open an issue](https://github.com/ElectricityMachine/SCR-SGPlus/issues/new) or contact me below.

6. In that CMD window, run ``pip install -r requirements.txt``. This will download all the requirements for the script to function.
7. After the installation has completed, run the ``start.bat`` batch file to start the script. A console window will appear, a beep will sound, and the macro is now active.
Some issues might be (but not limited to):
- Script fails to run or install
- Certain signals do not respond to the script when trying to change aspects or enter/exit camera view
- Entering/exiting camera view or toggling rollback does not work

### License
By using this script, you MUST adhere to the license terms in the LICENSE file.
##### License
By using this script, you must adhere to the license terms in the LICENSE file.

### Contact information
If you have issues or need to contact me, please reach me on Discord at ElectricityMachine (hashtag) One Seven Five Three. The Discord username is spelt this way to avoid scrapers and bots.
##### Contact information
Discord: @electricity.machine
ROBLOX: Electricity_Machine
81 changes: 60 additions & 21 deletions script.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# ElectricityMachine
# Version: 0.3.1-alpha
# Made by ElectricityMachine
# Version: 0.4.0
# Major changes: Python 12 support, refactor
# Description: A script to automate tasks when signalling for SCR
# Keybinds: 1 2 3 for Danger, Caution, and Proceed signal settings. C for Camera. R for Rollback Toggle.
Expand All @@ -21,17 +21,24 @@
import win32gui
import threading
from numpy import array as np_array, allclose as np_allclose
from settings import AVG_FPS, VERSION, AVG_PING, DEBUG_ENABLED, UPDATE_CHECK_ENABLED, Colors
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
from mss import mss
from PIL.Image import frombytes, Image
from requests import get as requests_get

logging.basicConfig(stream=sys.stdout, level=logging.DEBUG if DEBUG_ENABLED else logging.INFO)
VERSION = "v0.4.0"
enabled = True
signal_mouse_coords: tuple = () # Mouse coordinates used to return cursor to signal when exiting camera/rollback
signal_mouse_coords: (
tuple
) = () # Mouse coordinates used to return cursor to signal when exiting camera/rollback
one_frame_time = round((1000 / AVG_FPS) * 10**-3, 4)
logging.basicConfig(
stream=sys.stdout,
level=logging.DEBUG if DEBUG_ENABLED else logging.INFO,
format="%(levelname)s: %(message)s",
)


def update_check() -> None:
Expand Down Expand Up @@ -133,7 +140,9 @@ def click_rollback() -> None:
logging.debug("click_rollback: scan_for_dialog(exitcamera) returned true")
return
elif scan_for_dialog("signal", mousex, mousey):
logging.debug("click_rollback: scan_for_dialog(signal) returned true, pressing enter")
logging.debug(
"click_rollback: scan_for_dialog(signal) returned true, pressing enter"
)
press_and_release("enter")
else:
logging.debug("return path in click_rollback")
Expand All @@ -147,11 +156,15 @@ def click_rollback() -> None:
_y = bbox[1]
window_width = bbox[2]
window_height = bbox[3]
zone_screen_height, zone_screen_width, zone_screen_x = calculate_zone_screen(window_width, window_height)
zone_screen_height, zone_screen_width, zone_screen_x = calculate_zone_screen(
window_width, window_height
)

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)))
rollback_position = win32gui.ClientToScreen(
window, (int(rollback_x), int(rollback_y))
)

move_mouse(x=rollback_position[0], y=rollback_position[1], speed=1)
mouse_click("left")
Expand All @@ -166,7 +179,9 @@ def click_camera() -> None:
logging.debug("click_camera: called")
global signal_mouse_coords
if scan_for_dialog("exitcamera"):
logging.debug("click_camera: scan_for_dialog(exitcamera) returned true, pressing backspace twice")
logging.debug(
"click_camera: scan_for_dialog(exitcamera) returned true, pressing backspace twice"
)
for _ in range(2):
press_and_release("backspace")
if signal_mouse_coords:
Expand All @@ -186,7 +201,9 @@ def click_camera() -> None:
# sleep_frames(2)
camera_y = 0.80137 if scan_for_dialog("viewcamera") == 0 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}")
logging.debug(
f"click_camera: uncontrolled scan_for_dialog true in click_camera with x-value of {x}"
)
else:
logging.debug("click_camera: return none path in click_camera")
return
Expand Down Expand Up @@ -225,7 +242,9 @@ def toggle_disable() -> None:
global enabled
logging.debug(f"toggle_disable called: enabled is {enabled}")
enabled = not enabled
beep = threading.Thread(target=lambda: winsound.Beep(500, 100) if enabled else winsound.Beep(400, 100))
beep = threading.Thread(
target=lambda: winsound.Beep(500, 100) if enabled else winsound.Beep(400, 100)
)
beep.start()


Expand Down Expand Up @@ -260,7 +279,9 @@ def find_uncontrolled_sig_dialog(h: int, mousex: int, mousey: int) -> bool:
dialogbox_x = math.floor(mousex - dialogbox_width / 2)
dialogbox_y = math.floor(mousey - dialogbox_height)

capture = screen_grab(dialogbox_x, dialogbox_y, dialogbox_width, dialogbox_height * 2).convert("RGB")
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))
Expand All @@ -275,7 +296,9 @@ def find_uncontrolled_sig_dialog(h: int, mousex: int, mousey: int) -> bool:
for image in imagesToProcess:
logging.debug("find_uncontrolled_sig_dialog: iterating images")
if check_color_percentage_single(image, Colors.COLOR_DIALOG_WHITE):
logging.debug("find_uncontrolled_sig_dialog: image loop: numpy white pixels returned success")
logging.debug(
"find_uncontrolled_sig_dialog: image loop: numpy white pixels returned success"
)
return True
logging.debug("find_uncontrolled_sig_dialog: return false path")
return False
Expand All @@ -288,7 +311,9 @@ def find_controlled_sig_dialog(h: int, mousex: int, mousey: int) -> bool:
dialogbox_x = math.floor(mousex - dialogbox_width / 2)
dialogbox_y = math.floor(mousey - dialogbox_height)

capture = screen_grab(dialogbox_x, dialogbox_y, dialogbox_width, dialogbox_height * 2).convert("RGB")
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))
Expand Down Expand Up @@ -316,7 +341,9 @@ def find_camera_buttons(h: int, w: int, windowID: int):
camerabutton_x = zone_screen_width * 0.79760 + zone_screen_x
camerabutton_y = h * 0.80629

screen_cords = win32gui.ClientToScreen(windowID, (int(camerabutton_x), int(camerabutton_y)))
screen_cords = win32gui.ClientToScreen(
windowID, (int(camerabutton_x), int(camerabutton_y))
)
capture = screen_grab(
screen_cords[0],
screen_cords[1],
Expand Down Expand Up @@ -381,13 +408,17 @@ def check_color_multiple(image: Image, colors: list, threshold=7) -> bool:
col_to_compare = arr[i, j]
for color in colors:
if color_approx_eq_np(col_to_compare, color, threshold):
logging.debug("check_colored_pixels_np: colors similar, return True")
logging.debug(
"check_colored_pixels_np: colors similar, return True"
)
return True
logging.debug("check_colored_pixels_np: no similar colors found, returning False")
return False


def check_color_percentage_single(image: Image, color: tuple, compareThreshold=7, threshold=0.05) -> bool:
def check_color_percentage_single(
image: Image, color: tuple, compareThreshold=7, threshold=0.05
) -> bool:
logging.debug("check_color_percentage_single: called")
matching_pixels = 0
arr = np_array(image)
Expand All @@ -401,7 +432,9 @@ def check_color_percentage_single(image: Image, color: tuple, compareThreshold=7
if color_approx_eq_np(col_to_compare, color, compareThreshold):
matching_pixels += 1
if matching_pixels / arr.size >= threshold:
logging.debug(f"check_color_percentage_single: matching pixels > {threshold * 10 ** 2}% found")
logging.debug(
f"check_color_percentage_single: matching pixels > {threshold * 10 ** 2}% found"
)
return True
logging.debug(
f"check_color_percentage_single: not enough white pixels found for array size. numpixels: {matching_pixels/arr.size}"
Expand All @@ -420,7 +453,9 @@ def find_exit_cam_button(w: int, bbox: tuple[int, int, int, int], window):
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)))
screen_cords = win32gui.ClientToScreen(
window, (int(exit_camera_button_x), int(exit_camera_button_y))
)
capture = screen_grab(
screen_cords[0],
screen_cords[1],
Expand All @@ -431,7 +466,9 @@ def find_exit_cam_button(w: int, bbox: tuple[int, int, int, int], window):
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)
return all(
check_color_single(image, Colors.COLOR_CAMERA_EXIT) for image in imagesToProcess
)


@check_able_to_run
Expand Down Expand Up @@ -470,7 +507,9 @@ def enabled_warning():
add_hotkey(46, lambda: click_camera()) # C
add_hotkey(59, lambda: toggle_disable()) # F1
add_hotkey("R", lambda: click_rollback()) # R
add_hotkey("/", lambda: enabled_warning()) # / warning when opening chat while enabled
add_hotkey(
"/", lambda: enabled_warning()
) # / warning when opening chat while enabled
add_hotkey("`", lambda: enabled_warning()) # Command bar
add_hotkey("'", lambda: enabled_warning()) # Command bar
add_hotkey(79, lambda: send_zone_message("A")) # Num 1
Expand Down
9 changes: 4 additions & 5 deletions settings.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
VERSION = "v0.3.1-alpha"
AVG_FPS = 60 # Set your average FPS here
AVG_PING = 0 # Set your average ping here
DEBUG_ENABLED = True # If true, will enable debug prints if they're specified
UPDATE_CHECK_ENABLED = False # If true, will run check_for_update() on startup
AVG_FPS = 45 # Set your average FPS here (default 45)
AVG_PING = 0 # Set your average ping here (default 0)
DEBUG_ENABLED = False # If true, will enable debug logging
UPDATE_CHECK_ENABLED = True # If true, will check for updates on startup


class Colors:
Expand Down

0 comments on commit 69a8935

Please sign in to comment.