Skip to content

Custom Inputs

Tom Sherman edited this page Mar 7, 2017 · 12 revisions

Basics

If you want to be able to use moves that are not included in moves.h, you need to understand the input system that moves.h uses. It was designed to be the most efficient way to represent a series of input. There are two structs that are at the core of the input system:

typedef struct
{
    u16 controller;
    s8 frameOffset;
    u8 flags;

} RawInput;

typedef struct
{
    RawInput* inputs;
    u32 size;

} Move;

A Move is simply an array of timed inputs. The only reason it is it's own struct is for the size field. Using sizeof(inputs) / sizeof(inputs[0]) isn't reliable.

RawInput is essentially just a frame and a controller state. 16 bits is enough to represent the controller state - see controller.h for details. There are helpful macros to create a controller state coming from both controller.h and moves.h. There are 3 components to the state: button, stick, and angle. Here the options for each:

button = A_BUTTON, B_BUTTON, X_BUTTON, Z_BUTTON, L_BUTTON, START_BUTTON, DPAD_UP, DPAD_DOWN, DPAD_LEFT, DPAD_RIGHT, CSTICK_UP, CSTICK_DOWN, CSTICK_LEFT, CSTICK_RIGHT

stick = TILT_STICK, FULL_STICK

for angle you can use STICK_ANGLE(x) where x is in [0, 360].

So if you want to input side-b: B_BUTTON | FULL_STICK | STICK_ANGLE(180.f) would be the controller state.

Note that two different buttons cannot be pushed simultaneously.

The other two components of a RawInput are related to which frame this controller state should be inputted. frameOffset tells the AI how many frames to wait until inputting. flags is used for character specific modifications to the frameOffset. For instance, flags = JUMPSQUAT will result in frameOffset += character jumpsquat when this moves gets processed by the AI. These flags are defined in moves.h and cannot be user defined.

Let's use an example to put this all together. Here is what a short hop laser would look like:

RawInput raw_shNeutralB[4] = 
{
    {X_BUTTON, 0, NO_FLAGS},
    {RELEASE, -1, JUMPSQUAT},
    {B_BUTTON, 11, JUMPSQUAT},
    {RELEASE, 0, JUMPSQUAT | SH_LENGTH}
};
Move mv_shLaser = {.inputs = raw_shNeutralB, .size = 4};

Run-Time Modification of Moves

Some moves require an input to be decided at run-time. An example in the library is _mv_upB. The user must set the direction of the up-b with SET_UP_B_DIR(x). This macro just overwrites the controller state for one of the inputs used in up-b. There is a macro called OVERWRITE in moves.h that evaluates to 0, but it useful for showing that a certain input is designed to be overwritten. Since the AI makes a full copy of the move, there is no problem overwriting a move many different times - as long as the overwrite happens just before calling addMove.

Let's say we want to make a move for wavedashing. We will use OVERWRITE instead of making a direction for the wavedash.

RawInput raw_wavedash[3] = 
{
    {X_BUTTON, 0, NO_FLAGS},
    {OVERWRITE, -1, JUMPSQUAT},
    {RELEASE, 1, JUMPSQUAT}
};
Move mv_wavedash = {.inputs = raw_wavedash, .size = 3};
#define SET_WAVEDASH_DIR(x) raw_wavedash[1].controller = \
    L_BUTTON | FULL_STICK | STICK_ANGLE(x)

This move will immediately press X, then press L and a direction one frame before leaving the ground and release L and the stick 2 frames later.

Both the RawInput array and Move struct used for wavedash take up a total of 20 bytes. This is equivalent to 5 lines of ASM code. This showcases the efficiency of describing moves in this way.