mirror of
https://github.com/astromaddie/lambda1-vr.git
synced 2026-10-19 16:00:15 +02:00
192 lines
9.4 KiB
C
192 lines
9.4 KiB
C
// Host test for the Steam Frame controller map (VrFrameMap.c). It builds the effective table, physical
|
|
// input to game action, for both hands and every context (playing, the hands behind the head, a
|
|
// menu, multiplayer), and fails on:
|
|
// - one physical input doing two different things in the same context;
|
|
// - an action with two inputs that isn't a documented alias;
|
|
// - an action that nothing can reach;
|
|
// - a grip or trigger doing something that isn't a hand's job;
|
|
// - the right stick's up or down doing anything in the game (it only turns, and scrolls in a menu);
|
|
// - a button for the flat screen view (that's the vr_flat_screen setting) or the D-pad crowbar.
|
|
// With --markdown it prints the table for the docs.
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "VrFrameMap.h"
|
|
|
|
static int failures = 0;
|
|
#define FAIL(...) do { printf("FAIL: " __VA_ARGS__); printf("\n"); failures++; } while (0)
|
|
|
|
// actions that may have more than one input in a context, because they are meant to
|
|
static bool aliasAllowed(int action)
|
|
{
|
|
switch (action)
|
|
{
|
|
case ACT_USE: // X and the weapon hand's grip
|
|
case ACT_PREV_WEAPON: // left bumper and D-pad left
|
|
case ACT_NEXT_WEAPON: // right bumper and D-pad right
|
|
case ACT_FLASHLIGHT: // right stick click and View
|
|
case ACT_QUICKSAVE: // hold Menu; with the hand behind the head the right stick click
|
|
case ACT_MENU_BACK: // B, Menu, View
|
|
case ACT_MENU_UP: case ACT_MENU_DOWN: case ACT_MENU_LEFT: case ACT_MENU_RIGHT: // D-pad and left stick
|
|
case ACT_MOVE: case ACT_TURN: // four stick directions
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool isHandInput(int input)
|
|
{
|
|
return input == FI_L_GRIP || input == FI_R_GRIP;
|
|
}
|
|
|
|
static bool handAction(int action)
|
|
{
|
|
return action == ACT_BACKPACK || action == ACT_USE || action == ACT_USE_OFF || action == ACT_STEADY || action == ACT_NONE;
|
|
}
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
int rowCount;
|
|
const frameMapRow_t* rows = VrFrameMap_Rows(&rowCount);
|
|
|
|
bool reached[ACT_COUNT] = {false};
|
|
|
|
for (int hand = 0; hand < 2; hand++)
|
|
{
|
|
const bool left = hand == 1;
|
|
for (int context = 0; context < 32; context++)
|
|
{
|
|
// a menu has no hands behind the head
|
|
if ((context & CTX_MENU) && (context & (CTX_DOM_ZONE | CTX_OFF_ZONE)))
|
|
continue;
|
|
// quick save and load aren't offered in multiplayer
|
|
if ((context & CTX_MULTI) && (context & CTX_OFF_ZONE))
|
|
continue;
|
|
|
|
int inputsOfAction[ACT_COUNT] = {0};
|
|
for (int input = 0; input < FI_COUNT; input++)
|
|
{
|
|
// overlapping rows: how many match this input in this context
|
|
int matches = 0, firstAction = ACT_NONE;
|
|
for (int r = 0; r < rowCount; r++)
|
|
{
|
|
const int rowInput = rows[r].role ? VrFrameMap_RoleInput(rows[r].role, left) : rows[r].input;
|
|
if (rowInput != input) continue;
|
|
if ((context & rows[r].require) != rows[r].require || (context & rows[r].forbid) != 0) continue;
|
|
if (matches++ == 0) firstAction = rows[r].action;
|
|
else FAIL("%s: %s does %s and %s (context %d, %s)", left ? "left-handed" : "right-handed",
|
|
VrFrameMap_InputName(input), VrFrameMap_ActionName(firstAction), VrFrameMap_ActionName(rows[r].action),
|
|
context, "rows overlap");
|
|
}
|
|
const int action = VrFrameMap_Action(input, context, left);
|
|
if (action != firstAction) FAIL("Action() and the rows disagree for %s", VrFrameMap_InputName(input));
|
|
if (action == ACT_NONE) continue;
|
|
|
|
inputsOfAction[action]++;
|
|
reached[action] = true;
|
|
if (isHandInput(input) && !handAction(action))
|
|
FAIL("%s does %s, which isn't a hand's job", VrFrameMap_InputName(input), VrFrameMap_ActionName(action));
|
|
}
|
|
for (int action = 1; action < ACT_COUNT; action++)
|
|
if (inputsOfAction[action] > 1 && !aliasAllowed(action))
|
|
FAIL("%s: %s has %d inputs in context %d and isn't a listed alias", left ? "left-handed" : "right-handed",
|
|
VrFrameMap_ActionName(action), inputsOfAction[action], context);
|
|
}
|
|
}
|
|
|
|
// everything must be reachable somewhere
|
|
for (int action = 1; action < ACT_COUNT; action++)
|
|
if (!reached[action]) FAIL("nothing does %s", VrFrameMap_ActionName(action));
|
|
|
|
// the hands: face buttons, bumpers, D-pad, Menu, View and sticks don't change with the weapon hand;
|
|
// the weapon hand's trigger fires and its grip is the interaction / backpack one
|
|
for (int context = 0; context < 32; context++)
|
|
{
|
|
for (int input = 0; input < FI_COUNT; input++)
|
|
{
|
|
if (input == FI_L_TRIGGER || input == FI_R_TRIGGER || input == FI_L_GRIP || input == FI_R_GRIP) continue;
|
|
if (VrFrameMap_Action(input, context, false) != VrFrameMap_Action(input, context, true))
|
|
FAIL("%s changes with the hand in context %d", VrFrameMap_InputName(input), context);
|
|
}
|
|
if (!(context & CTX_MENU))
|
|
{
|
|
if (VrFrameMap_Action(FI_R_TRIGGER, context, false) != ACT_FIRE || VrFrameMap_Action(FI_L_TRIGGER, context, false) != ACT_ALTFIRE)
|
|
FAIL("right-handed triggers");
|
|
if (VrFrameMap_Action(FI_L_TRIGGER, context, true) != ACT_FIRE || VrFrameMap_Action(FI_R_TRIGGER, context, true) != ACT_ALTFIRE)
|
|
FAIL("left-handed triggers");
|
|
}
|
|
else if (VrFrameMap_Action(FI_R_TRIGGER, context, false) != ACT_MENU_CLICK || VrFrameMap_Action(FI_L_TRIGGER, context, true) != ACT_MENU_CLICK)
|
|
FAIL("the laser's trigger in a menu");
|
|
}
|
|
|
|
// the things the user asked for by name
|
|
struct { int input; int context; int action; } expect[] = {
|
|
{ FI_A, 0, ACT_JUMP }, { FI_B, 0, ACT_CROUCH_HOLD }, { FI_X, 0, ACT_USE }, { FI_Y, 0, ACT_RELOAD },
|
|
{ FI_L_STICK_CLICK, 0, ACT_CROUCH_TOGGLE }, { FI_R_STICK_CLICK, 0, ACT_FLASHLIGHT },
|
|
{ FI_L_BUMPER, 0, ACT_PREV_WEAPON }, { FI_R_BUMPER, 0, ACT_NEXT_WEAPON },
|
|
{ FI_DPAD_LEFT, 0, ACT_PREV_WEAPON }, { FI_DPAD_RIGHT, 0, ACT_NEXT_WEAPON },
|
|
{ FI_DPAD_UP, 0, ACT_LAST_WEAPON }, { FI_DPAD_DOWN, 0, ACT_LASER },
|
|
{ FI_VIEW_SHORT, CTX_OFF_ZONE, ACT_QUICKLOAD }, { FI_R_STICK_CLICK, CTX_OFF_ZONE, ACT_QUICKSAVE },
|
|
{ FI_VIEW_SHORT, CTX_MULTI, ACT_SCOREBOARD },
|
|
{ FI_MENU_SHORT, 0, ACT_PAUSE }, { FI_MENU_HOLD, 0, ACT_QUICKSAVE },
|
|
{ FI_VIEW_SHORT, 0, ACT_FLASHLIGHT }, { FI_VIEW_HOLD_1S, 0, ACT_RECENTRE }, { FI_VIEW_HOLD_3S, 0, ACT_RECENTRE_HEIGHT },
|
|
{ FI_R_GRIP, 0, ACT_USE }, { FI_R_GRIP, CTX_DOM_ZONE, ACT_BACKPACK },
|
|
{ FI_L_GRIP, 0, ACT_STEADY }, { FI_L_GRIP, CTX_OFF_GRIP_AWAY, ACT_USE_OFF },
|
|
{ FI_A, CTX_MENU, ACT_MENU_CONFIRM }, { FI_B, CTX_MENU, ACT_MENU_BACK },
|
|
{ FI_DPAD_UP, CTX_MENU, ACT_MENU_UP }, { FI_L_STICK_UP, CTX_MENU, ACT_MENU_UP },
|
|
};
|
|
for (size_t i = 0; i < sizeof(expect) / sizeof(expect[0]); i++)
|
|
if (VrFrameMap_Action(expect[i].input, expect[i].context, false) != expect[i].action)
|
|
FAIL("%s should be %s (context %d)", VrFrameMap_InputName(expect[i].input), VrFrameMap_ActionName(expect[i].action), expect[i].context);
|
|
|
|
// the right stick's up and down are not bound in the game, in any context outside a menu (and the
|
|
// flat screen view has no action to reach, so no input can do it)
|
|
for (int hand = 0; hand < 2; hand++)
|
|
for (int context = 0; context < 32; context++)
|
|
{
|
|
if (context & CTX_MENU) continue;
|
|
if (VrFrameMap_Action(FI_R_STICK_UP, context, hand == 1) != ACT_NONE)
|
|
FAIL("right stick up does %s in the game (context %d)", VrFrameMap_ActionName(VrFrameMap_Action(FI_R_STICK_UP, context, hand == 1)), context);
|
|
if (VrFrameMap_Action(FI_R_STICK_DOWN, context, hand == 1) != ACT_NONE)
|
|
FAIL("right stick down does %s in the game (context %d)", VrFrameMap_ActionName(VrFrameMap_Action(FI_R_STICK_DOWN, context, hand == 1)), context);
|
|
}
|
|
for (int r = 0; r < rowCount; r++)
|
|
{
|
|
if (rows[r].require & CTX_MENU) continue;
|
|
if (rows[r].input == FI_R_STICK_UP || rows[r].input == FI_R_STICK_DOWN)
|
|
FAIL("a game row for the right stick's %s", VrFrameMap_InputName(rows[r].input));
|
|
}
|
|
for (int action = 1; action < ACT_COUNT; action++)
|
|
if (strstr(VrFrameMap_ActionName(action), "screen view") || strcmp(VrFrameMap_ActionName(action), "crowbar") == 0)
|
|
FAIL("%s is an action again", VrFrameMap_ActionName(action));
|
|
// right stick up and down still scroll in a menu
|
|
if (VrFrameMap_Action(FI_R_STICK_UP, CTX_MENU, false) != ACT_MENU_SCROLL_UP || VrFrameMap_Action(FI_R_STICK_DOWN, CTX_MENU, false) != ACT_MENU_SCROLL_DOWN)
|
|
FAIL("the right stick should still scroll in a menu");
|
|
// the crowbar is the grip behind the head, as it was
|
|
if (VrFrameMap_Action(FI_R_GRIP, CTX_DOM_ZONE, false) != ACT_BACKPACK || VrFrameMap_Action(FI_L_GRIP, CTX_DOM_ZONE, true) != ACT_BACKPACK)
|
|
FAIL("the grip behind the head should be the backpack weapon");
|
|
|
|
if (argc > 1 && strcmp(argv[1], "--markdown") == 0)
|
|
{
|
|
printf("| Frame input | Playing | Notes |\n| --- | --- | --- |\n");
|
|
for (int r = 0; r < rowCount; r++)
|
|
{
|
|
if (rows[r].require & CTX_MENU) continue;
|
|
const char* name = rows[r].role == ROLE_TRIGGER_WEAPON ? "weapon hand trigger" : rows[r].role == ROLE_TRIGGER_OTHER ? "other hand trigger"
|
|
: rows[r].role == ROLE_GRIP_WEAPON ? "weapon hand grip" : rows[r].role == ROLE_GRIP_OTHER ? "other hand grip" : VrFrameMap_InputName(rows[r].input);
|
|
printf("| %s | %s | %s |\n", name, VrFrameMap_ActionName(rows[r].action), rows[r].note);
|
|
}
|
|
printf("\n| Frame input | In a menu | Notes |\n| --- | --- | --- |\n");
|
|
for (int r = 0; r < rowCount; r++)
|
|
{
|
|
if (!(rows[r].require & CTX_MENU)) continue;
|
|
const char* name = rows[r].role == ROLE_TRIGGER_WEAPON ? "weapon hand trigger" : VrFrameMap_InputName(rows[r].input);
|
|
printf("| %s | %s | %s |\n", name, VrFrameMap_ActionName(rows[r].action), rows[r].note);
|
|
}
|
|
}
|
|
|
|
printf(failures ? "frame map: FAILED (%d)\n" : "frame map: ok\n", failures);
|
|
return failures ? 1 : 0;
|
|
}
|