-
Notifications
You must be signed in to change notification settings - Fork 3
Custom Inputs
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};
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.