Documentation

Script Template

Below is the basic template of a python script that can be loaded by Copycat.

from ScriptCore import Template, PrintColors

class Script(Template):
    def __init__(self, controller, report):
        super().__init__(controller, report)
        
    def run(self, frame):
        self.print_log("Hello World!", PrintColors.COLOR_RED)
        return frame

You must call your class Script, and it must extend the Template class from ScriptCore. Take notice of the import at the top of the script.

The init function is where you can initialize any variables you may need. The run function is where you will do your processing on the frame.

Main Loop

Copycat continuously runs the run method, passing in the frame from the capture card / desktop.

def run(self, frame):
    self.print_log("Hello World!", PrintColors.COLOR_RED)
    return frame

The frame from the capture method enters the run function as a parameter, and must always be returned back.

The frame is a NumPy array, and can be manipulated using OpenCV. You can find the documentation for OpenCV here.

Printing

You can print messages to the output log in Copycat by using the self.print function. Alternatively you can print to the terminal using the standard print function.

# Make sure to import the PrintColors enum
from ScriptCore import PrintColors

# Prints to output log, with new line.
self.print_log("I'm black!", PrintColors.COLOR_BLACK)
self.print_log("I'm red!", PrintColors.COLOR_RED)
self.print_log("I'm green!", PrintColors.COLOR_GREEN)
self.print_log("I'm blue!", PrintColors.COLOR_BLUE)

# Prints to output log as bold text, with new line.
self.print_log("I'm black and bold!", PrintColors.COLOR_BOLD_BLACK)
self.print_log("I'm red and bold!", PrintColors.COLOR_BOLD_RED)
self.print_log("I'm green and bold!", PrintColors.COLOR_BOLD_GREEN)
self.print_log("I'm blue and bold!", PrintColors.COLOR_BOLD_BLUE)

# Prints to output log without a new line.
self.print_log("[Error] ", PrintColors.COLOR_BOLD_RED, false)
self.print_log("Some content!", PrintColors.COLOR_BLACK)

# Prints to terminal instead
print("The terminal is boring!")

Buttons

There are 2 methods to get button/axis states. You can utilise the available get_actual_* and get_emulated_* functions. They achieve essentially the same thing, but source from different controller reports.

You will want to import the following module to use buttons, place this at the top of your script:

from ControllerMapping import Buttons

The following buttons are available:

BUTTON NAME Playstation Xbox
BTN_SOUTH X A
BTN_EAST O B
BTN_WEST X
BTN_NORTH Y
BTN_LEFT_TRIGGER L2 LT
BTN_RIGHT_TRIGGER R2 RT
BTN_LEFT_SHOULDER L1 LB
BTN_RIGHT_SHOULDER R1 RB
BTN_BACK Share View
BTN_OPTIONS Options Menu
BTN_LEFT_THUMB L3 LS
BTN_RIGHT_THUMB R3 RS
BTN_DPAD_UP DPAD UP DPAD UP
BTN_DPAD_DOWN DPAD DOWN DPAD DOWN
BTN_DPAD_LEFT DPAD LEFT DPAD LEFT
BTN_DPAD_RIGHT DPAD RIGHT DPAD RIGHT
BTN_GUIDE PS XBOX

Actual Values

The get_actual_* functions will return the actual state of the button/axis, as reported by the controller. Here is a list of the available functions:

  • bool : self.is_actual_button_pressed(button) *
  • float : self.get_actual_left_stick_x()
  • float : self.get_actual_left_stick_y()
  • float : self.get_actual_right_stick_x()
  • float : self.get_actual_right_stick_y()
  • float : self.get_actual_left_trigger() *
  • float : self.get_actual_right_trigger() *

Usage example:

if (self.is_actual_button_pressed(Buttons.BTN_SOUTH)):
    self.print_log("A/X pressed")
    
leftStickX = self.get_actual_left_stick_x()

Functions listed with the red star (*), will be effected by remapping. Currently, d-pad remapping is not supported.

Emulated Values

The get_emulated_* functions will return the state of the button/axis, as reported by the emulated controller. Here is a list of the available functions:

  • bool : self.is_emulated_button_pressed(button)
  • float : self.get_emulated_left_stick_x()
  • float : self.get_emulated_left_stick_y()
  • float : self.get_emulated_right_stick_x()
  • float : self.get_emulated_right_stick_y()
  • float : self.get_emulated_left_trigger()
  • float : self.get_emulated_right_trigger()

Usage example:

if (self.is_emulated_button_pressed(Buttons.BTN_SOUTH)):
    self.print_log("A/X pressed")
    
leftStickX = self.get_emulated_left_stick_x()    

Set Emulated Values

You can set the controller's states by using any of the follow methods:

  • self.press_button(button) *
  • self.release_button(button) *
  • self.press_dpad_button(button)
  • self.release_dpad_button(button)
  • self.left_trigger_float(value) *
  • self.right_trigger_float(value) *
  • self.left_joystick_float(x, y)
  • self.right_joystick_float(x, y)

Usage example:

if (self.is_actual_button_pressed(Buttons.BTN_SOUTH)):
    self.release_button(Buttons.BTN_SOUTH)

In the above example, when the south button (A/X) is pressed, we make it release. This will block the output from sending the south button.

Functions listed with the red star (*), will be effected by remapping. Currently, d-pad remapping is not supported.

Remap Buttons

You can easily remap any buttons by using the following functions:

  • self.remap_button(button_from, button_to)
  • self.unmap_button(button_from)

Usage example:

if (self.get_setting('flipped_triggers')):
    self.remap_button(Buttons.BTN_LEFT_TRIGGER, Buttons.BTN_LEFT_SHOULDER)
    self.remap_button(Buttons.BTN_LEFT_SHOULDER, Buttons.BTN_LEFT_TRIGGER)
    self.remap_button(Buttons.BTN_RIGHT_TRIGGER, Buttons.BTN_RIGHT_SHOULDER)
    self.remap_button(Buttons.BTN_RIGHT_SHOULDER, Buttons.BTN_RIGHT_TRIGGER)
else:
    self.unmap_button(Buttons.BTN_LEFT_TRIGGER)
    self.unmap_button(Buttons.BTN_LEFT_SHOULDER)
    self.unmap_button(Buttons.BTN_RIGHT_TRIGGER)
    self.unmap_button(Buttons.BTN_RIGHT_SHOULDER)

In the above example, when the setting "flipped_triggers" is enabled, we remap the left and right trigger, with the left and right shoulder button. When the "flipped_triggers" setting is disabled, we undo our mapping.

Please note: d-pad, right and left stick remapping is not supported.


Macros

You can easily create macros, to perform a series of time based actions. Macros must be declared in the init method, to be later run inside the run method.

To use macros you must first import the module:

from Macro import Macro

Here is an example of a simple bunny hop macro:

self.bunny_hop = Macro(controller, [
    [self.release_button, Buttons.BTN_SOUTH],
    ["wait", 50],
    [self.press_button, Buttons.BTN_SOUTH],
    ["wait", 50],
    [self.release_button, Buttons.BTN_SOUTH]
])

The above macro will release the jump button, wait 50 milliseconds, press the jump button again, wait another 50 milliseconds, then finally release the jump button.

You can include any number of actions inside a macro. The macro accepts a list of actions, which are also arrays. The first element of the array is the action to perform, and the second element is the value to use.

Remember you must initiate combos inside the init method and not the run method, otherwise you will overwrite your macro every loop.

Once initialised, the macro can be used in your run method, like so:

def run(self, frame):
    # Start Bunny hop macro when the X/A button is pressed
    if (self.is_actual_button_pressed(Buttons.BTN_SOUTH)):
        self.print_log("Bunny Hop Macro running", PrintColors.COLOR_GREEN)
        self.bunny_hop.run()

    # Cycle the macro, this iterate through he macro actions loop at a time
    self.bunny_hop.cycle()

    return frame

It's important that the cycle function runs at the end of the run method. If you have multiple combos, which trigger off the same buttons, the order of which cycle is run for each combo will make a difference to the output.

You can also randomise your wait times by using "wait_random", like this:

self.bunny_hop = Macro(controller, [
    [self.release_button, Buttons.BTN_SOUTH],
    ["wait_random", 50, 100],
    [self.press_button, Buttons.BTN_SOUTH],
    ["wait_random", 50, 100],
    [self.release_button, Buttons.BTN_SOUTH]
])

Speak

You can make the PC "speak" any text that you pass through the following function:

self.say("Hello, I am your PC speaking!")

Screen Reader

You can utilise the EasyOCR screen reader, by doing the following:

class Script(Template):
    def __init__(self, controller, report):
        super().__init__(controller, report)

        # Setup a OCR using GPU
        self.init_ocr(['en'], True)
        
    def run(self, frame):
        
        # Read text from the frame
        result = self.read_text(frame, 100, 100, 200, 100)

        # Print the result
        if (len(result) > 0):
            self.print_log("Text Found: " + str(result[0]), PrintColors.COLOR_GREEN)

        return frame

The above code will read the screen, and return a list of all the words it found. Remember to first utilise init_ocr in your init method. The first parameter of this function determines the language pack to use, and the second parameter determines if it will use GPU or CPU (True = GPU, False = CPU).

You can find the supported language list here: https://www.jaided.ai/easyocr/.



Settings Window

When creating your script you may want to provide toggles to specific features. You can utilise the settings window to accomplish this.

To create a setting window, create a file inside your script folder called settings.ini, here is an example of the available field types:

[bunny_hop]
type = onoff
label = Bunny Hop
default = False
value = False

[slider_1]
type = slider
label = Cool Slider
default = 100
value = 50
min = 0
max = 100
step = 1

[dropdown_1]
type = dropdown
label = Cool Dropdown
default = Option 1
options = Option 1, Option 2, Option 3
value = 50

[toggle_label]
type = label
label = Hello world!
color = red

[weights]
type = filelister
label = Weights
default = GENERIC
value = GENERIC
path = /weights
ext = .weights

You can receive any of the values by calling the following function:

self.get_setting('ads_strafe')

You can set any of the values by calling the following function:

self.set_setting('ads_strafe', False)

Importing Modules

You may want to import your own modules. Since you need to load in the ScriptCore template, by default the directory path is inside Copycat's root directory.

To import your own modules, that exist inside your scripts folder, you can use the following code:

from ControllerMapping import Buttons
from Macro import Macro
from ScriptCore import Template, PrintColors

# Import any class from within this script's folder
import sys
sys.path.append('scripts/ImportExample')
from MyClass import MyClass

class Script(Template):
    def __init__(self, controller, report):
        super().__init__(controller, report)

        self.myClass = MyClass()
        
    def run(self, frame):
        print(self.myClass.message)

        return frame

Its important to notice that the import of MyClass is below the other imports. Since those modules exist inside Copycat's root directory, and the MyClass exists inside the script directory. We can swap directories using the sys module.

You can access the script functions inside your additional module, by importing the Copycat module. Like so:

import Copycat

class MyClass:
    def get_left_trigger_example(self):
        return Copycat.script.get_actual_left_trigger()

Example - Bunny Hop

Here is an example of a bunny hop macro:

from ScriptCore import Template, PrintColors
from ControllerMapping import Buttons
from Macro import Macro

# This is an example script that will be run by the main program.
class Script(Template):
    def __init__(self, controller, report):
        super().__init__(controller, report)

        # Setup a macro
        self.bunny_hop = Macro(controller, [
            [self.release_button, Buttons.BTN_SOUTH],
            ["wait", 50],
            [self.press_button, Buttons.BTN_SOUTH],
            ["wait", 50],
            [self.release_button, Buttons.BTN_SOUTH]
        ])
        
    def run(self, frame):
        # Start Bunny hop macro when the X/A button is pressed
        if (self.is_actual_button_pressed(Buttons.BTN_SOUTH)):
            self.print_log("Bunny Hop Macro running", PrintColors.COLOR_GREEN)
            self.bunny_hop.run()

        # Cycle the macro, this iterate through he macro actions loop at a time
        self.bunny_hop.cycle()

        return frame

Licence - Creation or Update

If you have a sellers account, you can create licences using your api key on the fly, via a HTTP request. To do this in python it would look something like this:

import requests

response = requests.post('https://copycat.stickassist.com/licence-api/create-or-update', {
    'api_key': 'YOUR_API_KEY',
    'info[script_name]' : 'YOUR_SCRIPT_NAME',
    'info[expire_date]' : '2024-01-01', # Optional (Year)-(month)-(day)
    'info[active]' : true, # Optional
    'info[hwid]' : 'USER_HARDWARE_ID', # Optional
    'info[licence]' : 'USER_LICENCE_KEY' # Optional
})

print(response.json())

If you provide the licence parameter, it will update an existing licence.

Licence - Validation

If you have a sellers account, you can validate a licence using your api key on the fly, via a HTTP request. To do this in python it would look something like this:

import requests

response = requests.post('https://copycat.stickassist.com/licence-api/validate', {
    'api_key': 'YOUR_API_KEY',
    'script_name' : 'YOUR_SCRIPT_NAME',
    'hwid' : 'USER_HARDWARE_ID',
    'licence' : 'USER_LICENCE_KEY'
})

print(response.json())

Decrypt File

If you need decrypt a file on the fly, you can use the following function:

self.decrypt_file('example.weights', 'password123')

Encrypt File

If you need encrypt a file on the fly, you can use the following function:

self.encrypt_file('example.weights', 'password123')

Get HWID

If you need to get the users HWID for some kind of verification, or other use you can use the following function:

self.get_hwid()

Map Range

If you need to map a number from one range to another, you can use the following function:

self.map_range(input_value, from_min, from_max, to_min, to_max)