1572 lines
No EOL
41 KiB
C
1572 lines
No EOL
41 KiB
C
// BLANKART
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 1998-2000 by DooM Legacy Team.
|
|
// Copyright (C) 1999-2020 by Sonic Team Junior.
|
|
//
|
|
// This program is free software distributed under the
|
|
// terms of the GNU General Public License, version 2.
|
|
// See the 'LICENSE' file for more details.
|
|
//-----------------------------------------------------------------------------
|
|
/// \file g_input.c
|
|
/// \brief handle mouse/keyboard/joystick inputs,
|
|
/// maps inputs to game controls (forward, spin, jump...)
|
|
|
|
#include "doomdef.h"
|
|
#include "doomstat.h"
|
|
#include "g_input.h"
|
|
#include "k_items.h"
|
|
#include "keys.h"
|
|
#include "hu_stuff.h" // need HUFONT start & end
|
|
#include "d_net.h"
|
|
#include "console.h"
|
|
#include "i_gamepad.h" // JOYAXISRANGE
|
|
#include "m_menu.h" // menustack
|
|
#include "i_system.h"
|
|
#include "g_game.h"
|
|
#include "v_video.h"
|
|
#include "p_local.h"
|
|
#include "k_kart.h"
|
|
#include "m_fixed.h"
|
|
#include "m_easing.h"
|
|
|
|
#define MAXMOUSESENSITIVITY 100 // sensitivity steps
|
|
|
|
static CV_PossibleValue_t mousesens_cons_t[] = {{1, "MIN"}, {MAXMOUSESENSITIVITY, "MAX"}, {0, NULL}};
|
|
static CV_PossibleValue_t onecontrolperkey_cons_t[] = {{1, "One"}, {2, "Several"}, {0, NULL}};
|
|
static CV_PossibleValue_t turnsmooth_cons_t[] = {{2, "Slow"}, {1, "Fast"}, {0, "Off"}, {0, NULL}};
|
|
|
|
// mouse values are used once
|
|
consvar_t cv_mousesens = CVAR_INIT ("mousesens", "20", CV_SAVE, mousesens_cons_t, NULL);
|
|
consvar_t cv_mousesens2 = CVAR_INIT ("mousesens2", "20", CV_SAVE, mousesens_cons_t, NULL);
|
|
consvar_t cv_mouseysens = CVAR_INIT ("mouseysens", "20", CV_SAVE, mousesens_cons_t, NULL);
|
|
consvar_t cv_mouseysens2 = CVAR_INIT ("mouseysens2", "20", CV_SAVE, mousesens_cons_t, NULL);
|
|
consvar_t cv_controlperkey = CVAR_INIT ("controlperkey", "One", CV_SAVE, onecontrolperkey_cons_t, NULL);
|
|
|
|
consvar_t cv_litesteer[MAXSPLITSCREENPLAYERS] = {
|
|
CVAR_INIT ("litesteer", "Off", CV_SAVE, CV_OnOff, NULL),
|
|
CVAR_INIT ("litesteer2", "Off", CV_SAVE, CV_OnOff, NULL),
|
|
CVAR_INIT ("litesteer3", "Off", CV_SAVE, CV_OnOff, NULL),
|
|
CVAR_INIT ("litesteer4", "Off", CV_SAVE, CV_OnOff, NULL)
|
|
};
|
|
|
|
consvar_t cv_turnsmooth[MAXSPLITSCREENPLAYERS] = {
|
|
CVAR_INIT ("turnsmoothing", "Off", CV_SAVE, turnsmooth_cons_t, NULL),
|
|
CVAR_INIT ("turnsmoothing2", "Off", CV_SAVE, turnsmooth_cons_t, NULL),
|
|
CVAR_INIT ("turnsmoothing3", "Off", CV_SAVE, turnsmooth_cons_t, NULL),
|
|
CVAR_INIT ("turnsmoothing4", "Off", CV_SAVE, turnsmooth_cons_t, NULL)
|
|
};
|
|
|
|
static void G_ResetPlayerControllerRumble(INT32 player);
|
|
static void rumble_off_handle(void);
|
|
static void rumble_off_handle2(void);
|
|
static void rumble_off_handle3(void);
|
|
static void rumble_off_handle4(void);
|
|
|
|
static void G_ResetPlayerControllerIndicatorColor(INT32 player);
|
|
static void led_off_handle(void);
|
|
static void led_off_handle2(void);
|
|
static void led_off_handle3(void);
|
|
static void led_off_handle4(void);
|
|
|
|
consvar_t cv_controllerrumble[MAXSPLITSCREENPLAYERS] = {
|
|
CVAR_INIT ("rumble", "On", CV_SAVE, CV_OnOff, rumble_off_handle),
|
|
CVAR_INIT ("rumble2", "On", CV_SAVE, CV_OnOff, rumble_off_handle2),
|
|
CVAR_INIT ("rumble3", "On", CV_SAVE, CV_OnOff, rumble_off_handle3),
|
|
CVAR_INIT ("rumble4", "On", CV_SAVE, CV_OnOff, rumble_off_handle4)
|
|
};
|
|
|
|
static CV_PossibleValue_t rumblestrength_cons_t[] = {{FRACUNIT/4, "MIN"}, {FRACUNIT*4, "MAX"}, {0, NULL}};
|
|
|
|
consvar_t cv_rumblestrength[MAXSPLITSCREENPLAYERS] = {
|
|
CVAR_INIT ("rumblestrength", "1.0", CV_SAVE|CV_FLOAT, rumblestrength_cons_t, NULL),
|
|
CVAR_INIT ("rumblestrength2", "1.0", CV_SAVE|CV_FLOAT, rumblestrength_cons_t, NULL),
|
|
CVAR_INIT ("rumblestrength3", "1.0", CV_SAVE|CV_FLOAT, rumblestrength_cons_t, NULL),
|
|
CVAR_INIT ("rumblestrength4", "1.0", CV_SAVE|CV_FLOAT, rumblestrength_cons_t, NULL)
|
|
};
|
|
|
|
static CV_PossibleValue_t controllerled_cons_t[] = {{0, "Off"}, {1, "Skincolor"}, {2, "Mobjcolor"}, {0, NULL}};
|
|
consvar_t cv_controllerled[MAXSPLITSCREENPLAYERS] = {
|
|
CVAR_INIT ("gamepadled", "Skincolor", CV_SAVE|CV_CALL|CV_NOINIT, controllerled_cons_t, led_off_handle),
|
|
CVAR_INIT ("gamepadled2", "Skincolor", CV_SAVE|CV_CALL|CV_NOINIT, controllerled_cons_t, led_off_handle2),
|
|
CVAR_INIT ("gamepadled3", "Skincolor", CV_SAVE|CV_CALL|CV_NOINIT, controllerled_cons_t, led_off_handle3),
|
|
CVAR_INIT ("gamepadled4", "Skincolor", CV_SAVE|CV_CALL|CV_NOINIT, controllerled_cons_t, led_off_handle4)
|
|
};
|
|
|
|
static CV_PossibleValue_t tiltcontrol_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Shakes Only"}, {0, NULL}};
|
|
consvar_t cv_tiltcontrol[MAXSPLITSCREENPLAYERS] = {
|
|
CVAR_INIT ("tiltcontrol", "Off", CV_SAVE, tiltcontrol_cons_t, NULL),
|
|
CVAR_INIT ("tiltcontrol2", "Off", CV_SAVE, tiltcontrol_cons_t, NULL),
|
|
CVAR_INIT ("tiltcontrol3", "Off", CV_SAVE, tiltcontrol_cons_t, NULL),
|
|
CVAR_INIT ("tiltcontrol4", "Off", CV_SAVE, tiltcontrol_cons_t, NULL)
|
|
};
|
|
|
|
static void rumble_off_handle(void)
|
|
{
|
|
if (cv_controllerrumble[0].value == 0)
|
|
G_ResetPlayerControllerRumble(0);
|
|
}
|
|
|
|
static void rumble_off_handle2(void)
|
|
{
|
|
if (cv_controllerrumble[1].value == 0)
|
|
G_ResetPlayerControllerRumble(1);
|
|
}
|
|
|
|
static void rumble_off_handle3(void)
|
|
{
|
|
if (cv_controllerrumble[2].value == 0)
|
|
G_ResetPlayerControllerRumble(2);
|
|
}
|
|
|
|
static void rumble_off_handle4(void)
|
|
{
|
|
if (cv_controllerrumble[3].value == 0)
|
|
G_ResetPlayerControllerRumble(3);
|
|
}
|
|
|
|
static void led_off_handle(void)
|
|
{
|
|
G_ResetPlayerControllerIndicatorColor(0);
|
|
}
|
|
|
|
static void led_off_handle2(void)
|
|
{
|
|
G_ResetPlayerControllerIndicatorColor(1);
|
|
}
|
|
|
|
static void led_off_handle3(void)
|
|
{
|
|
G_ResetPlayerControllerIndicatorColor(2);
|
|
}
|
|
|
|
static void led_off_handle4(void)
|
|
{
|
|
G_ResetPlayerControllerIndicatorColor(3);
|
|
}
|
|
|
|
// current state of the keys
|
|
// JOYAXISRANGE for fully pressed, 0 for not pressed
|
|
inputpoll_t gamekeydown = {};
|
|
inputpoll_t gamekeydown_msec = {};
|
|
|
|
// several key codes (or virtual key) per game control
|
|
INT32 gamecontrol[MAXSPLITSCREENPLAYERS][num_gamecontrols][MAXINPUTMAPPING] = {};
|
|
|
|
INT32 gamecontroldefault[num_gamecontrols][MAXINPUTMAPPING] = {
|
|
[gc_aimforward ] = {KEY_UPARROW, KEY_AXIS1+2 }, // Left Y-
|
|
[gc_aimbackward] = {KEY_DOWNARROW, KEY_AXIS1+3 }, // Left Y+
|
|
[gc_turnleft ] = {KEY_LEFTARROW, KEY_AXIS1+0 }, // Left X-
|
|
[gc_turnright ] = {KEY_RIGHTARROW, KEY_AXIS1+1 }, // Left X+
|
|
[gc_accelerate ] = {'a', KEY_JOY1+0 }, // East
|
|
[gc_drift ] = {'s', KEY_JOY1+10, KEY_AXIS1+9}, // RB, RT
|
|
[gc_brake ] = {'d', KEY_JOY1+1 }, // South
|
|
[gc_fire ] = {KEY_SPACE, KEY_JOY1+9, KEY_AXIS1+8}, // LB, LT
|
|
[gc_lookback ] = {KEY_LSHIFT, KEY_JOY1+2 }, // North
|
|
[gc_horncode ] = {'r', KEY_JOY1+8 }, // R-Stick Click
|
|
|
|
[gc_pause ] = {KEY_PAUSE, KEY_JOY1+4 }, // Back
|
|
[gc_systemmenu ] = { KEY_JOY1+6 }, // Start
|
|
[gc_console ] = {KEY_CONSOLE },
|
|
[gc_screenshot ] = {KEY_F8 },
|
|
[gc_recordgif ] = {KEY_F9 },
|
|
[gc_viewpoint ] = {KEY_F12, KEY_JOY1+3 }, // West
|
|
[gc_talkkey ] = {'t', KEY_HAT1+1 }, // D-Pad Down
|
|
//[gc_teamkey ] = {'y' },
|
|
[gc_scores ] = {KEY_TAB, KEY_HAT1+0 }, // D-Pad Up
|
|
[gc_spectate ] = {'\'' },
|
|
[gc_lookup ] = {KEY_PGUP, KEY_AXIS1+6}, // Right Y-
|
|
[gc_lookdown ] = {KEY_PGDN, KEY_AXIS1+7}, // Right Y+
|
|
[gc_centerview ] = {KEY_END },
|
|
[gc_camreset ] = {KEY_HOME },
|
|
[gc_camtoggle ] = {KEY_BACKSPACE },
|
|
[gc_freelook ] = {KEY_RCTRL },
|
|
};
|
|
|
|
// lists of GC codes for selective operation
|
|
/*
|
|
const INT32 gcl_accelerate[num_gcl_accelerate] = { gc_accelerate };
|
|
|
|
const INT32 gcl_brake[num_gcl_brake] = { gc_brake };
|
|
|
|
const INT32 gcl_drift[num_gcl_drift] = { gc_drift };
|
|
|
|
const INT32 gcl_movement[num_gcl_movement] = {
|
|
gc_accelerate, gc_drift, gc_brake, gc_turnleft, gc_turnright
|
|
};
|
|
|
|
const INT32 gcl_item[num_gcl_item] = {
|
|
gc_fire, gc_aimforward, gc_aimbackward
|
|
};
|
|
|
|
const INT32 gcl_full[num_gcl_full] = {
|
|
gc_accelerate, gc_drift, gc_brake, gc_turnleft, gc_turnright,
|
|
gc_fire, gc_aimforward, gc_aimbackward,
|
|
gc_lookback
|
|
};
|
|
*/
|
|
|
|
INT32 G_GetDevicePlayer(INT32 deviceID)
|
|
{
|
|
INT32 i;
|
|
|
|
for (i = 0; i < MAXSPLITSCREENPLAYERS; i++)
|
|
{
|
|
if (deviceID == I_GetControllerIDForPlayer(i) + 1)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
// converts ev_joystick axis to its corresponding key
|
|
INT32 G_AxisToKey(event_t *ev)
|
|
{
|
|
return KEY_AXIS1 + (ev->data1 >= JOYANALOGS
|
|
? ev->data1 + JOYANALOGS // triggers
|
|
: ev->data1*2 + (ev->data2 >= 0)); // analog sticks
|
|
}
|
|
|
|
//
|
|
// Remaps the inputs to game controls.
|
|
//
|
|
// A game control can be triggered by one or more keys/buttons.
|
|
//
|
|
// Each key/mousebutton/joybutton triggers ONLY ONE game control.
|
|
//
|
|
void G_MapEventsToControls(event_t *ev)
|
|
{
|
|
INT32 i;
|
|
|
|
INT32 device = ev->device;
|
|
|
|
// not a keyboard?
|
|
// try to see if its a controller in use
|
|
if (device > KEYBOARD_MOUSE_DEVICE)
|
|
device = I_GetControllerSlotfromID(device-1);
|
|
|
|
// invalid device
|
|
if (device < KEYBOARD_MOUSE_DEVICE || device >= MAXDEVICES)
|
|
return;
|
|
|
|
switch (ev->type)
|
|
{
|
|
case ev_keydown:
|
|
if (ev->data1 < NUMINPUTS)
|
|
{
|
|
gamekeydown[device][ev->data1] = JOYAXISRANGE;
|
|
}
|
|
#ifdef PARANOIA
|
|
else
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "Bad downkey input %d\n", ev->data1);
|
|
}
|
|
#endif
|
|
break;
|
|
|
|
case ev_keyup:
|
|
if (ev->data1 < NUMINPUTS)
|
|
{
|
|
gamekeydown[device][ev->data1] = 0;
|
|
}
|
|
#ifdef PARANOIA
|
|
else
|
|
{
|
|
CONS_Debug(DBG_GAMELOGIC, "Bad upkey input %d\n", ev->data1);
|
|
}
|
|
#endif
|
|
break;
|
|
|
|
case ev_mouse: // buttons are virtual keys
|
|
if (menustack[0])
|
|
{
|
|
break;
|
|
}
|
|
|
|
// X axis
|
|
if (ev->data2 < 0)
|
|
{
|
|
// Left
|
|
gamekeydown[device][KEY_MOUSEMOVE + 2] = abs(ev->data2);
|
|
gamekeydown[device][KEY_MOUSEMOVE + 3] = 0;
|
|
}
|
|
else
|
|
{
|
|
// Right
|
|
gamekeydown[device][KEY_MOUSEMOVE + 2] = 0;
|
|
gamekeydown[device][KEY_MOUSEMOVE + 3] = abs(ev->data2);
|
|
}
|
|
|
|
// Y axis
|
|
if (ev->data3 < 0)
|
|
{
|
|
// Up
|
|
gamekeydown[device][KEY_MOUSEMOVE] = abs(ev->data3);
|
|
gamekeydown[device][KEY_MOUSEMOVE + 1] = 0;
|
|
}
|
|
else
|
|
{
|
|
// Down
|
|
gamekeydown[device][KEY_MOUSEMOVE] = 0;
|
|
gamekeydown[device][KEY_MOUSEMOVE + 1] = abs(ev->data3);
|
|
}
|
|
break;
|
|
|
|
case ev_joystick: // buttons are virtual keys
|
|
if (menustack[0])
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (ev->data1 >= JOYAXISES)
|
|
{
|
|
#ifdef PARANOIA
|
|
CONS_Debug(DBG_GAMELOGIC, "Bad joystick axis event %d\n", ev->data1);
|
|
#endif
|
|
break;
|
|
}
|
|
|
|
// tf is this for?
|
|
if (G_GetDevicePlayer(ev->device) == 0)
|
|
{
|
|
if (CON_Ready() || chat_on)
|
|
break;
|
|
}
|
|
|
|
i = G_AxisToKey(ev);
|
|
if (ev->data1 < JOYANALOGS)
|
|
{
|
|
// analog stick axes have a negative and positive keydown
|
|
i -= ev->data2 >= 0;
|
|
gamekeydown[device][i++] = max(0, -ev->data2);
|
|
}
|
|
gamekeydown[device][i] = max(0, ev->data2);
|
|
|
|
break;
|
|
|
|
case ev_accelerometer:
|
|
// average out accel events recieved for this tick (favouring the newer data)
|
|
gamekeydown[device][KEY_ACCELEROMETER1+0] = (gamekeydown[device][KEY_ACCELEROMETER1+0] + (3*ev->data1))/4;
|
|
gamekeydown[device][KEY_ACCELEROMETER1+1] = (gamekeydown[device][KEY_ACCELEROMETER1+1] + (3*ev->data2))/4;
|
|
gamekeydown[device][KEY_ACCELEROMETER1+2] = (gamekeydown[device][KEY_ACCELEROMETER1+2] + (3*ev->data3))/4;
|
|
break;
|
|
|
|
case ev_gyroscope:
|
|
// average out gyro events recieved for this tick
|
|
gamekeydown[device][KEY_GYROSCOPE1+0] = (gamekeydown[device][KEY_GYROSCOPE1+0] + ev->data1)/2;
|
|
gamekeydown[device][KEY_GYROSCOPE1+1] = (gamekeydown[device][KEY_GYROSCOPE1+1] + ev->data2)/2;
|
|
gamekeydown[device][KEY_GYROSCOPE1+2] = (gamekeydown[device][KEY_GYROSCOPE1+2] + ev->data3)/2;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
INT32 keynum;
|
|
const char *name;
|
|
} keyname_t;
|
|
|
|
static keyname_t keynames[] =
|
|
{
|
|
{KEY_SPACE, "SPACE"},
|
|
{KEY_CAPSLOCK, "CAPS LOCK"},
|
|
{KEY_ENTER, "ENTER"},
|
|
{KEY_TAB, "TAB"},
|
|
{KEY_ESCAPE, "ESCAPE"},
|
|
{KEY_BACKSPACE, "BACKSPACE"},
|
|
|
|
{KEY_NUMLOCK, "NUM LOCK"},
|
|
{KEY_SCROLLLOCK, "SCROLL LOCK"},
|
|
|
|
// bill gates keys
|
|
{KEY_LEFTWIN, "LWINDOWS"},
|
|
{KEY_RIGHTWIN, "RWINDOWS"},
|
|
{KEY_MENU, "MENU"},
|
|
|
|
{KEY_LSHIFT, "LSHIFT"},
|
|
{KEY_RSHIFT, "RSHIFT"},
|
|
{KEY_LSHIFT, "SHIFT"},
|
|
{KEY_LCTRL, "LCTRL"},
|
|
{KEY_RCTRL, "RCTRL"},
|
|
{KEY_LCTRL, "CTRL"},
|
|
{KEY_LALT, "LALT"},
|
|
{KEY_RALT, "RALT"},
|
|
{KEY_LALT, "ALT"},
|
|
|
|
// keypad keys
|
|
{KEY_KPADSLASH, "KEYPAD /"},
|
|
{KEY_KEYPAD7, "KEYPAD 7"},
|
|
{KEY_KEYPAD8, "KEYPAD 8"},
|
|
{KEY_KEYPAD9, "KEYPAD 9"},
|
|
{KEY_MINUSPAD, "KEYPAD -"},
|
|
{KEY_KEYPAD4, "KEYPAD 4"},
|
|
{KEY_KEYPAD5, "KEYPAD 5"},
|
|
{KEY_KEYPAD6, "KEYPAD 6"},
|
|
{KEY_PLUSPAD, "KEYPAD +"},
|
|
{KEY_KEYPAD1, "KEYPAD 1"},
|
|
{KEY_KEYPAD2, "KEYPAD 2"},
|
|
{KEY_KEYPAD3, "KEYPAD 3"},
|
|
{KEY_KEYPAD0, "KEYPAD 0"},
|
|
{KEY_KPADDEL, "KEYPAD ."},
|
|
|
|
// extended keys (not keypad)
|
|
{KEY_HOME, "HOME"},
|
|
{KEY_UPARROW, "UP ARROW"},
|
|
{KEY_PGUP, "PAGE UP"},
|
|
{KEY_LEFTARROW, "LEFT ARROW"},
|
|
{KEY_RIGHTARROW, "RIGHT ARROW"},
|
|
{KEY_END, "END"},
|
|
{KEY_DOWNARROW, "DOWN ARROW"},
|
|
{KEY_PGDN, "PAGE DOWN"},
|
|
{KEY_INS, "INSERT"},
|
|
{KEY_DEL, "DELETE"},
|
|
|
|
// other keys
|
|
{KEY_F1, "F1"},
|
|
{KEY_F2, "F2"},
|
|
{KEY_F3, "F3"},
|
|
{KEY_F4, "F4"},
|
|
{KEY_F5, "F5"},
|
|
{KEY_F6, "F6"},
|
|
{KEY_F7, "F7"},
|
|
{KEY_F8, "F8"},
|
|
{KEY_F9, "F9"},
|
|
{KEY_F10, "F10"},
|
|
{KEY_F11, "F11"},
|
|
{KEY_F12, "F12"},
|
|
|
|
// KEY_CONSOLE has an exception in the keyname code
|
|
{'`', "TILDE"},
|
|
{KEY_PAUSE, "PAUSE/BREAK"},
|
|
|
|
// virtual keys for mouse buttons and joystick buttons
|
|
{KEY_MOUSE1+0,"MOUSE1"},
|
|
{KEY_MOUSE1+1,"MOUSE2"},
|
|
{KEY_MOUSE1+2,"MOUSE3"},
|
|
{KEY_MOUSE1+3,"MOUSE4"},
|
|
{KEY_MOUSE1+4,"MOUSE5"},
|
|
{KEY_MOUSE1+5,"MOUSE6"},
|
|
{KEY_MOUSE1+6,"MOUSE7"},
|
|
{KEY_MOUSE1+7,"MOUSE8"},
|
|
{KEY_MOUSEMOVE+0,"Mouse Up"},
|
|
{KEY_MOUSEMOVE+1,"Mouse Down"},
|
|
{KEY_MOUSEMOVE+2,"Mouse Left"},
|
|
{KEY_MOUSEMOVE+3,"Mouse Right"},
|
|
{KEY_MOUSEWHEELUP, "Wheel Up"},
|
|
{KEY_MOUSEWHEELDOWN, "Wheel Down"},
|
|
|
|
{KEY_JOY1+0, "A BUTTON"},
|
|
{KEY_JOY1+1, "B BUTTON"},
|
|
{KEY_JOY1+2, "X BUTTON"},
|
|
{KEY_JOY1+3, "Y BUTTON"},
|
|
{KEY_JOY1+4, "BACK BUTTON"},
|
|
{KEY_JOY1+5, "GUIDE BUTTON"},
|
|
{KEY_JOY1+6, "START BUTTON"},
|
|
{KEY_JOY1+7, "L-STICK CLICK"},
|
|
{KEY_JOY1+8, "R-STICK CLICK"},
|
|
{KEY_JOY1+9, "L BUMPER"},
|
|
{KEY_JOY1+10, "R BUMPER"},
|
|
{KEY_JOY1+11, "D-PAD UP"},
|
|
{KEY_JOY1+12, "D-PAD DOWN"},
|
|
{KEY_JOY1+13, "D-PAD LEFT"},
|
|
{KEY_JOY1+14, "D-PAD RIGHT"},
|
|
{KEY_JOY1+15, "MISC. BUTTON 1"},
|
|
{KEY_JOY1+16, "PADDLE BUTTON 1"},
|
|
{KEY_JOY1+17, "PADDLE BUTTON 2"},
|
|
{KEY_JOY1+18, "PADDLE BUTTON 3"},
|
|
{KEY_JOY1+19, "PADDLE BUTTON 4"},
|
|
{KEY_JOY1+20, "TOUCHPAD"},
|
|
{KEY_JOY1+21, "MISC. BUTTON 2"},
|
|
{KEY_JOY1+22, "MISC. BUTTON 3"},
|
|
{KEY_JOY1+23, "MISC. BUTTON 4"},
|
|
{KEY_JOY1+24, "MISC. BUTTON 5"},
|
|
{KEY_JOY1+25, "MISC. BUTTON 6"},
|
|
|
|
{KEY_AXIS1+0, "L-STICK LEFT"},
|
|
{KEY_AXIS1+1, "L-STICK RIGHT"},
|
|
{KEY_AXIS1+2, "L-STICK UP"},
|
|
{KEY_AXIS1+3, "L-STICK DOWN"},
|
|
{KEY_AXIS1+4, "R-STICK LEFT"},
|
|
{KEY_AXIS1+5, "R-STICK RIGHT"},
|
|
{KEY_AXIS1+6, "R-STICK UP"},
|
|
{KEY_AXIS1+7, "R-STICK DOWN"},
|
|
{KEY_AXIS1+8, "L TRIGGER"},
|
|
{KEY_AXIS1+9, "R TRIGGER"},
|
|
|
|
{KEY_ACCELEROMETER1+0, "TILT X"},
|
|
{KEY_ACCELEROMETER1+1, "TILT Y"},
|
|
{KEY_ACCELEROMETER1+2, "TILT Z"},
|
|
|
|
{KEY_GYROSCOPE1+0, "ROTATE X"},
|
|
{KEY_GYROSCOPE1+1, "ROTATE Y"},
|
|
{KEY_GYROSCOPE1+2, "ROTATE Z"},
|
|
};
|
|
|
|
static const char *gamecontrolname[num_gamecontrols] =
|
|
{
|
|
"nothing", // a key/button mapped to gc_null has no effect
|
|
"aimforward",
|
|
"aimbackward",
|
|
"turnleft",
|
|
"turnright",
|
|
"accelerate",
|
|
"drift",
|
|
"brake",
|
|
"fire",
|
|
"lookback",
|
|
"camreset",
|
|
"camtoggle",
|
|
"spectate",
|
|
"lookup",
|
|
"lookdown",
|
|
"centerview",
|
|
"talkkey",
|
|
"teamtalkkey",
|
|
"scores",
|
|
"console",
|
|
"pause",
|
|
"systemmenu",
|
|
"screenshot",
|
|
"recordgif",
|
|
"viewpoint",
|
|
"custom1",
|
|
"custom2",
|
|
"custom3",
|
|
"respawn",
|
|
"director",
|
|
"horncode",
|
|
};
|
|
|
|
#define NUMKEYNAMES (sizeof (keynames)/sizeof (keyname_t))
|
|
|
|
skincolornum_t G_GetSkinColorForController(INT32 playernum)
|
|
{
|
|
I_Assert(playernum >= 0 && playernum < MAXSPLITSCREENPLAYERS);
|
|
|
|
if (gamestate == GS_LEVEL)
|
|
{
|
|
const player_t *player = &players[displayplayers[playernum]];
|
|
|
|
if (player)
|
|
{
|
|
// make rgb rainbow vomit when invul or flash blue when grow
|
|
if ((cv_controllerled[playernum].value == 2) && player->mo && player->mo->color)
|
|
return (skincolornum_t)player->mo->color;
|
|
|
|
// take actual player skincolour when ingame
|
|
if (player->skincolor)
|
|
return (skincolornum_t)player->skincolor;
|
|
}
|
|
}
|
|
|
|
// otherwise just fallback to whatever the cvar is
|
|
return (skincolornum_t)cv_playercolor[playernum].value;
|
|
}
|
|
|
|
// Sets the Indicator LED on supported gamepads to a desired skincolor
|
|
// pass SKINCOLOR_NONE/0 to set the players skin color
|
|
void G_SetPlayerControllerIndicatorColor(INT32 playernum, UINT16 color)
|
|
{
|
|
skincolornum_t skincolor;
|
|
byteColor_t byte_color;
|
|
|
|
I_Assert(playernum >= 0 && playernum < MAXSPLITSCREENPLAYERS);
|
|
|
|
if (cv_controllerled[playernum].value == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// so we can override this
|
|
skincolor = color ? color : G_GetSkinColorForController(playernum);
|
|
byte_color = V_GetColor(skincolors[skincolor].ramp[8]).s;
|
|
|
|
I_SetControllerIndicatorColor(playernum, byte_color.red, byte_color.green, byte_color.blue);
|
|
}
|
|
|
|
static void G_ResetPlayerControllerIndicatorColor(INT32 playernum)
|
|
{
|
|
I_Assert(playernum >= 0 && playernum < MAXSPLITSCREENPLAYERS);
|
|
|
|
if (cv_controllerled[playernum].value == 0)
|
|
{
|
|
I_SetControllerIndicatorColor(playernum, 0, 0, 255);
|
|
}
|
|
else
|
|
{
|
|
G_SetPlayerControllerIndicatorColor(playernum, 0);
|
|
}
|
|
}
|
|
|
|
static skincolornum_t curcolor[MAXSPLITSCREENPLAYERS] = {};
|
|
|
|
void G_ControllerLEDTick(void)
|
|
{
|
|
UINT8 i;
|
|
static skincolornum_t newcolor = SKINCOLOR_NONE;
|
|
const INT32 numcontrollers = I_NumControllers();
|
|
|
|
if (numcontrollers == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i <= splitscreen; i++)
|
|
{
|
|
if (!cv_usecontroller[i].value || !cv_controllerled[i].value)
|
|
continue;
|
|
|
|
// Players controller does not have an addressable rbg light, bummer!
|
|
if (!I_ControllerSupportsIndicatorColor(i))
|
|
continue;
|
|
|
|
newcolor = G_GetSkinColorForController(i);
|
|
|
|
if (curcolor[i] == newcolor) // dont update if same colour
|
|
continue;
|
|
|
|
G_SetPlayerControllerIndicatorColor(i, newcolor);
|
|
curcolor[i] = newcolor;
|
|
}
|
|
}
|
|
|
|
void G_ResetControllerLED(void)
|
|
{
|
|
memset(curcolor, 0, sizeof(curcolor));
|
|
}
|
|
|
|
static void G_ResetPlayerControllerRumble(INT32 playernum)
|
|
{
|
|
I_Assert(playernum >= 0 && playernum < MAXSPLITSCREENPLAYERS);
|
|
|
|
I_ControllerRumble(playernum, 0, 0, 0);
|
|
}
|
|
|
|
void G_ResetAllControllerRumbles(void)
|
|
{
|
|
for (int i = 0; i < MAXSPLITSCREENPLAYERS; i++)
|
|
{
|
|
I_ControllerRumble(i, 0, 0, 0);
|
|
}
|
|
}
|
|
|
|
void G_PlayerControllerRumble(INT32 playernum, UINT16 low_strength, UINT16 high_strength, UINT32 duration)
|
|
{
|
|
I_Assert(playernum >= 0 && playernum < MAXSPLITSCREENPLAYERS);
|
|
|
|
if (cv_controllerrumble[playernum].value == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// doesent need to be super precise
|
|
// but ensure this is within range (0-65535)
|
|
// placed here so it applies to lua aswell
|
|
low_strength = (UINT16)min(FixedMul(low_strength, cv_rumblestrength[playernum].value), UINT16_MAX);
|
|
high_strength = (UINT16)min(FixedMul(high_strength, cv_rumblestrength[playernum].value), UINT16_MAX);
|
|
|
|
I_ControllerRumble(playernum, low_strength, high_strength, duration);
|
|
}
|
|
|
|
// rumble strengths
|
|
enum
|
|
{
|
|
RUMBLE_VERYSTRONG = FRACUNIT / 4, // 16384
|
|
RUMBLE_STRONG = FRACUNIT / 8, // 8192
|
|
RUMBLE_MODERATE = FRACUNIT / 64, // 1024
|
|
RUMBLE_WEAK = FRACUNIT / 128, // 512
|
|
RUMBLE_VERYWEAK = FRACUNIT / 256, // 256
|
|
};
|
|
|
|
// Controller rumble!
|
|
// this keeps track of a bunch of things
|
|
// and makes your controller rumble accordingly
|
|
void G_ControllerRumbleTick(void)
|
|
{
|
|
UINT8 i;
|
|
const INT32 numcontrollers = I_NumControllers();
|
|
|
|
if (dedicated || numcontrollers == 0 || gamestate != GS_LEVEL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i <= splitscreen; i++)
|
|
{
|
|
if (!cv_usecontroller[i].value || !cv_controllerrumble[i].value)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Players controller does not support rumble, bummer!
|
|
if (!I_ControllerSupportsRumble(i))
|
|
continue;
|
|
|
|
if (camera[i].freecam)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
UINT16 low = 0, high = 0;
|
|
// for how long the action should rumble, in ms
|
|
UINT16 lenght = 57; // 57ms is a short pulse, matches super well with this updating once per tic
|
|
const player_t *player = &players[g_localplayers[i]];
|
|
|
|
// allow lua to do some crap for spectators
|
|
if (player->spectator || !player->mo)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// reset the rumble if you exit or are ded lel
|
|
if (player->exiting ||
|
|
player->playerstate == PST_DEAD ||
|
|
player->respawn > 1)
|
|
{
|
|
G_PlayerControllerRumble(i, low, high, 0);
|
|
continue;
|
|
}
|
|
|
|
if (player->mo->eflags & MFE_JUSTBOUNCEDWALL)
|
|
{
|
|
high = RUMBLE_STRONG;
|
|
low = RUMBLE_MODERATE;
|
|
}
|
|
else if (player->spinouttimer)
|
|
{
|
|
//low = high = FRACUNIT / 6;
|
|
low = high = FixedMul((RUMBLE_VERYSTRONG), (FixedDiv(player->spinouttimer, (3*TICRATE / 2)))); // try do some some kinda fadeout, 3*TICRATE / 2 is the "default" spinout time
|
|
}
|
|
else if (player->flamestore && !player->offroad)
|
|
{
|
|
high = (player->flamestore * RUMBLE_MODERATE) / FLAMESTOREMAX;
|
|
lenght = 32;
|
|
}
|
|
else if (player->sneakertimer > (sneakertime-(TICRATE/2)) || player->bubbleboost > (BUBBLEBOOSTTIME-(TICRATE/2)))
|
|
{
|
|
low = high = RUMBLE_STRONG;
|
|
}
|
|
else if ((player->offroad)
|
|
&& player->speed != 0
|
|
&& P_IsObjectOnGround(player->mo))
|
|
{
|
|
// weaken this depending on if you got hyu or invinc
|
|
if (player->hyudorotimer)
|
|
{
|
|
high = RUMBLE_WEAK;
|
|
}
|
|
else if (player->flameoverheat)
|
|
{
|
|
low = RUMBLE_MODERATE;
|
|
}
|
|
else if (player->invincibilitytimer)
|
|
{
|
|
high = RUMBLE_MODERATE;
|
|
}
|
|
else if (player->smonitortimer)
|
|
{
|
|
high = RUMBLE_MODERATE;
|
|
}
|
|
else
|
|
{
|
|
low = high = RUMBLE_MODERATE;
|
|
}
|
|
}
|
|
else if ((player->bananadrag > TICRATE)
|
|
&& player->speed != 0
|
|
&& P_IsObjectOnGround(player->mo))
|
|
{
|
|
if (leveltime & 1) // this is actually funny lel
|
|
high = RUMBLE_MODERATE;
|
|
}
|
|
|
|
if (player->pflags & PF_BRAKEDRIFT)
|
|
{
|
|
high = CLAMP((high + RUMBLE_VERYWEAK), 0, UINT16_MAX);
|
|
}
|
|
|
|
// pulse when gettin new driftlevel
|
|
if (/*player->driftcharge // gets reset within K_KartDrift
|
|
&& */player->driftlevel)
|
|
{
|
|
high = CLAMP((high + RUMBLE_VERYWEAK), 0, UINT16_MAX);
|
|
|
|
// rumble during charging drifts
|
|
// when you reach new driftlevel
|
|
if (player->driftlevel < 20)
|
|
{
|
|
if (player->driftlevel == 2)
|
|
lenght = 114;
|
|
else if (player->driftlevel == 3)
|
|
lenght = 144;
|
|
else if (player->driftlevel == 4)
|
|
lenght = 174;
|
|
}
|
|
|
|
// drift release
|
|
// adjust those if you want
|
|
if (player->driftlevel >= 20)
|
|
{
|
|
// give some oompfh on release
|
|
low = CLAMP((low + RUMBLE_VERYWEAK), 0, UINT16_MAX);
|
|
|
|
if (player->driftlevel == 20)
|
|
lenght = 114;
|
|
else if (player->driftlevel == 30)
|
|
lenght = 144;
|
|
else if (player->driftlevel == 40)
|
|
lenght = 174;
|
|
}
|
|
}
|
|
|
|
// pulse when using rings
|
|
// let this come last
|
|
if ((player->cmd.buttons & BT_ATTACK)
|
|
&& (player->itemflags & IF_USERINGS)
|
|
&& (player->rings > 0
|
|
&& player->rings > player->rings-1
|
|
&& player->rings < player->rings+1)
|
|
)
|
|
{
|
|
low = CLAMP((low + RUMBLE_VERYWEAK), 0, UINT16_MAX);
|
|
}
|
|
|
|
// hack alert! i just dont want this thing constantly resetting the rumble lol
|
|
if (low == 0 && high == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
G_PlayerControllerRumble(i, low, high, lenght);
|
|
}
|
|
}
|
|
|
|
// If keybind is necessary to navigate menus, it's on this list.
|
|
boolean G_KeyBindIsNecessary(INT32 gc)
|
|
{
|
|
switch (gc)
|
|
{
|
|
case gc_accelerate:
|
|
case gc_brake:
|
|
case gc_aimforward:
|
|
case gc_aimbackward:
|
|
case gc_turnleft:
|
|
case gc_turnright:
|
|
case gc_systemmenu:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Returns false if a key is deemed unreachable for this device.
|
|
boolean G_KeyIsAvailable(INT32 key, INT32 deviceID, boolean digital)
|
|
{
|
|
// Invalid key number.
|
|
if (key <= 0 || key >= NUMINPUTS)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// analog input only
|
|
if (digital && key >= KEY_AXIS1 && key < JOYINPUTEND)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Valid keyboard-specific virtual key, but no keyboard attached for player.
|
|
if (key < NUMKEYS && deviceID > 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Valid controller-specific virtual key, but no controller attached for player.
|
|
if (key >= KEY_JOY1 && key < JOYINPUTEND && deviceID <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Valid mouse-specific virtual key, but no mouse attached for player. TODO HOW TO DETECT ACTIVE MOUSE CONNECTION
|
|
/*
|
|
if (key >= KEY_MOUSE1 && key < MOUSEINPUTEND && ????????)
|
|
{
|
|
return false;
|
|
}
|
|
*/
|
|
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// Detach any keys associated to the given game control
|
|
// - pass the pointer to the gamecontrol table for the player being edited
|
|
void G_ClearControlKeys(INT32 (*setupcontrols)[MAXINPUTMAPPING], INT32 control)
|
|
{
|
|
INT32 i;
|
|
for (i = 0; i < MAXINPUTMAPPING; i++)
|
|
{
|
|
setupcontrols[control][i] = KEY_NULL;
|
|
}
|
|
}
|
|
|
|
void G_ClearAllControlKeys(void)
|
|
{
|
|
INT32 i, j;
|
|
for (j = 0; j < MAXSPLITSCREENPLAYERS; j++)
|
|
{
|
|
for (i = 0; i < num_gamecontrols; i++)
|
|
{
|
|
G_ClearControlKeys(gamecontrol[j], i);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Returns the name of a key (or virtual key for mouse and joy)
|
|
// the input value being an keynum
|
|
//
|
|
const char *G_KeynumToString(INT32 keynum)
|
|
{
|
|
static char keynamestr[8];
|
|
|
|
UINT32 j;
|
|
|
|
// return a string with the ascii char if displayable
|
|
if (keynum > ' ' && keynum <= 'z' && keynum != KEY_CONSOLE)
|
|
{
|
|
keynamestr[0] = (char)keynum;
|
|
keynamestr[1] = '\0';
|
|
return keynamestr;
|
|
}
|
|
|
|
// find a description for special keys
|
|
for (j = 0; j < NUMKEYNAMES; j++)
|
|
if (keynames[j].keynum == keynum)
|
|
return keynames[j].name;
|
|
|
|
// create a name for unknown keys
|
|
sprintf(keynamestr, "KEY%d", keynum);
|
|
return keynamestr;
|
|
}
|
|
|
|
INT32 G_KeyStringtoNum(const char *keystr)
|
|
{
|
|
UINT32 j;
|
|
|
|
if (!keystr[0])
|
|
return 0;
|
|
|
|
if (!keystr[1] && keystr[0] > ' ' && keystr[0] <= 'z')
|
|
return keystr[0];
|
|
|
|
if (!strncmp(keystr, "KEY", 3) && keystr[3] >= '0' && keystr[3] <= '9')
|
|
{
|
|
/* what if we out of range bruh? */
|
|
j = atoi(&keystr[3]);
|
|
if (j < NUMINPUTS)
|
|
return j;
|
|
return 0;
|
|
}
|
|
|
|
for (j = 0; j < NUMKEYNAMES; j++)
|
|
if (fasticmp(keynames[j].name, keystr))
|
|
return keynames[j].keynum;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void G_ResetControls(UINT8 p)
|
|
{
|
|
INT32 i, j, k;
|
|
|
|
for (i = 0; i < num_gamecontrols; i++)
|
|
{
|
|
for (j = k = 0; j < MAXINPUTMAPPING; j++)
|
|
{
|
|
INT32 key = gamecontroldefault[i][j];
|
|
gamecontrol[p][i][j] = 0;
|
|
if (p == 0 || key >= NUMKEYS) // keyboard is for P1 only
|
|
gamecontrol[p][i][k++] = key;
|
|
}
|
|
}
|
|
}
|
|
|
|
void G_SaveKeySetting(FILE *f, INT32 (*fromcontrolsa)[MAXINPUTMAPPING], INT32 (*fromcontrolsb)[MAXINPUTMAPPING], INT32 (*fromcontrolsc)[MAXINPUTMAPPING], INT32 (*fromcontrolsd)[MAXINPUTMAPPING])
|
|
{
|
|
INT32 i, j;
|
|
|
|
// TODO: would be nice to get rid of this code duplication
|
|
for (i = 1; i < num_gamecontrols; i++)
|
|
{
|
|
fprintf(f, "setcontrol \"%s\" \"%s\"", gamecontrolname[i], G_KeynumToString(fromcontrolsa[i][0]));
|
|
|
|
for (j = 1; j < MAXINPUTMAPPING+1; j++)
|
|
{
|
|
if (j < MAXINPUTMAPPING && fromcontrolsa[i][j])
|
|
{
|
|
fprintf(f, " \"%s\"", G_KeynumToString(fromcontrolsa[i][j]));
|
|
}
|
|
else
|
|
{
|
|
fprintf(f, "\n");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 1; i < num_gamecontrols; i++)
|
|
{
|
|
fprintf(f, "setcontrol2 \"%s\" \"%s\"", gamecontrolname[i],
|
|
G_KeynumToString(fromcontrolsb[i][0]));
|
|
|
|
for (j = 1; j < MAXINPUTMAPPING+1; j++)
|
|
{
|
|
if (j < MAXINPUTMAPPING && fromcontrolsb[i][j])
|
|
{
|
|
fprintf(f, " \"%s\"", G_KeynumToString(fromcontrolsb[i][j]));
|
|
}
|
|
else
|
|
{
|
|
fprintf(f, "\n");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 1; i < num_gamecontrols; i++)
|
|
{
|
|
fprintf(f, "setcontrol3 \"%s\" \"%s\"", gamecontrolname[i],
|
|
G_KeynumToString(fromcontrolsc[i][0]));
|
|
|
|
for (j = 1; j < MAXINPUTMAPPING+1; j++)
|
|
{
|
|
if (j < MAXINPUTMAPPING && fromcontrolsc[i][j])
|
|
{
|
|
fprintf(f, " \"%s\"", G_KeynumToString(fromcontrolsc[i][j]));
|
|
}
|
|
else
|
|
{
|
|
fprintf(f, "\n");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 1; i < num_gamecontrols; i++)
|
|
{
|
|
fprintf(f, "setcontrol4 \"%s\" \"%s\"", gamecontrolname[i],
|
|
G_KeynumToString(fromcontrolsd[i][0]));
|
|
|
|
for (j = 1; j < MAXINPUTMAPPING+1; j++)
|
|
{
|
|
if (j < MAXINPUTMAPPING && fromcontrolsd[i][j])
|
|
{
|
|
fprintf(f, " \"%s\"", G_KeynumToString(fromcontrolsd[i][j]));
|
|
}
|
|
else
|
|
{
|
|
fprintf(f, "\n");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
INT32 G_CheckDoubleUsage(INT32 keynum, INT32 playernum, boolean modify)
|
|
{
|
|
INT32 result = gc_null;
|
|
|
|
if (cv_controlperkey.value == 1)
|
|
{
|
|
INT32 i, j;
|
|
for (i = 0; i < num_gamecontrols; i++)
|
|
{
|
|
for (j = 0; j < MAXINPUTMAPPING; j++)
|
|
{
|
|
if (gamecontrol[playernum][i][j] == keynum)
|
|
{
|
|
result = i;
|
|
if (modify)
|
|
{
|
|
gamecontrol[playernum][i][j] = KEY_NULL;
|
|
}
|
|
}
|
|
|
|
if (result && !modify)
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static void setcontrol(UINT8 player)
|
|
{
|
|
INT32 numctrl;
|
|
const char *namectrl;
|
|
INT32 keynum;
|
|
INT32 inputMap = 0;
|
|
INT32 i;
|
|
|
|
namectrl = COM_Argv(1);
|
|
|
|
for (numctrl = 0;
|
|
numctrl < num_gamecontrols && gamecontrolname[numctrl] && !fasticmp(namectrl, gamecontrolname[numctrl]);
|
|
numctrl++)
|
|
{ ; }
|
|
|
|
if (numctrl == num_gamecontrols)
|
|
{
|
|
CONS_Printf(M_GetText("Control '%s' unknown\n"), namectrl);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < MAXINPUTMAPPING; i++)
|
|
{
|
|
keynum = G_KeyStringtoNum(COM_Argv(inputMap + 2));
|
|
|
|
if (keynum >= 0)
|
|
{
|
|
(void)G_CheckDoubleUsage(keynum, player, true);
|
|
|
|
// if keynum was rejected, try it again with the next key.
|
|
while (keynum == 0)
|
|
{
|
|
inputMap++;
|
|
if (inputMap >= MAXINPUTMAPPING)
|
|
{
|
|
break;
|
|
}
|
|
|
|
keynum = G_KeyStringtoNum(COM_Argv(inputMap + 2));
|
|
|
|
if (keynum >= 0)
|
|
{
|
|
(void)G_CheckDoubleUsage(keynum, player, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (keynum >= 0)
|
|
{
|
|
gamecontrol[player][numctrl][i] = keynum;
|
|
}
|
|
|
|
inputMap++;
|
|
if (inputMap >= MAXINPUTMAPPING)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// clear out binds that weren't set
|
|
while (++i < MAXINPUTMAPPING)
|
|
gamecontrol[player][numctrl][i] = 0;
|
|
}
|
|
|
|
void Command_Setcontrol_f(void)
|
|
{
|
|
INT32 na;
|
|
|
|
na = (INT32)COM_Argc();
|
|
|
|
if (na < 3 || na > MAXINPUTMAPPING+2)
|
|
{
|
|
CONS_Printf(M_GetText("setcontrol <controlname> <keyname> [<keyname>] [<keyname>] [<keyname>]: set controls for player 1\n"));
|
|
return;
|
|
}
|
|
|
|
setcontrol(0);
|
|
}
|
|
|
|
void Command_Setcontrol2_f(void)
|
|
{
|
|
INT32 na;
|
|
|
|
na = (INT32)COM_Argc();
|
|
|
|
if (na < 3 || na > MAXINPUTMAPPING+2)
|
|
{
|
|
CONS_Printf(M_GetText("setcontrol2 <controlname> <keyname> [<keyname>] [<keyname>] [<keyname>]: set controls for player 2\n"));
|
|
return;
|
|
}
|
|
|
|
setcontrol(1);
|
|
}
|
|
|
|
void Command_Setcontrol3_f(void)
|
|
{
|
|
INT32 na;
|
|
|
|
na = (INT32)COM_Argc();
|
|
|
|
if (na < 3 || na > MAXINPUTMAPPING+2)
|
|
{
|
|
CONS_Printf(M_GetText("setcontrol3 <controlname> <keyname> [<keyname>] [<keyname>] [<keyname>]: set controls for player 3\n"));
|
|
return;
|
|
}
|
|
|
|
setcontrol(2);
|
|
}
|
|
|
|
void Command_Setcontrol4_f(void)
|
|
{
|
|
INT32 na;
|
|
|
|
na = (INT32)COM_Argc();
|
|
|
|
if (na < 3 || na > MAXINPUTMAPPING+2)
|
|
{
|
|
CONS_Printf(M_GetText("setcontrol4 <controlname> <keyname> [<keyname>] [<keyname>] [<keyname>]: set controls for player 4\n"));
|
|
return;
|
|
}
|
|
|
|
setcontrol(3);
|
|
}
|
|
|
|
// accelerometer and gyro stuff
|
|
|
|
// when holding the controller still (shaking and turning included), correct this quickly to resolve error
|
|
#define GyroCalibrationRollingAvgSamples (TICRATE/2)
|
|
#define GyroCalibrationStart (TICRATE/2)
|
|
#define GyroCalibrationTrust (1*FRACUNIT/100)
|
|
|
|
boolean localgyrocalibrating[MAXSPLITSCREENPLAYERS];
|
|
vector3_t localgyrovectors[MAXSPLITSCREENPLAYERS];
|
|
|
|
tic_t localgyrocalibrationsamples[MAXSPLITSCREENPLAYERS];
|
|
vector3_t localgyrocalibrationlastoffset[MAXSPLITSCREENPLAYERS];
|
|
vector3_t localgyrocalibrationoffset[MAXSPLITSCREENPLAYERS];
|
|
|
|
// assume the accelerometer doesn't need calibration, use this to determine if gyro can be calibrated
|
|
vector3_t localaccelcalibrationoffset[MAXSPLITSCREENPLAYERS];
|
|
|
|
fixed_t localshakinessfac[MAXSPLITSCREENPLAYERS];
|
|
vector3_t localsmoothedaccel[MAXSPLITSCREENPLAYERS];
|
|
vector3_t localgravityvectors[MAXSPLITSCREENPLAYERS];
|
|
vector4_t localquaternions[MAXSPLITSCREENPLAYERS];
|
|
|
|
// copy/pasted from the lua version of these routines
|
|
inline static vector3_t *QuaternionMulVec3(vector3_t *out, vector3_t *a, vector4_t *b)
|
|
{
|
|
fixed_t ax = a->x, ay = a->y, az = a->z, aw = 0;
|
|
fixed_t bx = b->x, by = b->y, bz = b->z, bw = b->a;
|
|
|
|
FV3_NormalizeEx(out, FV3_Load(out,
|
|
FixedMul(aw, bx) + FixedMul(ax, bw) + FixedMul(ay, bz) - FixedMul(az, by),
|
|
FixedMul(aw, by) - FixedMul(ax, bz) + FixedMul(ay, bw) + FixedMul(az, bx),
|
|
FixedMul(aw, bz) + FixedMul(ax, by) - FixedMul(ay, bx) + FixedMul(az, bw)
|
|
));
|
|
|
|
return out;
|
|
}
|
|
|
|
inline static fixed_t FV3_LengthSquared(const vector3_t *vec)
|
|
{
|
|
return FixedMul(vec->x, vec->x) + FixedMul(vec->y, vec->y) + FixedMul(vec->z, vec->z);
|
|
}
|
|
|
|
inline static vector4_t AngleAxis(fixed_t angle, fixed_t x, fixed_t y, fixed_t z)
|
|
{
|
|
fixed_t sinangle = FINESINE(FixedAngle(angle/2) >> ANGLETOFINESHIFT);
|
|
fixed_t cosangle = FINECOSINE(FixedAngle(angle/2) >> ANGLETOFINESHIFT);
|
|
vector3_t axis = {x, y, z};
|
|
vector4_t result;
|
|
|
|
FV3_Normalize(&axis);
|
|
|
|
FV4_Load(&result,
|
|
FixedMul(axis.x, sinangle),
|
|
FixedMul(axis.y, sinangle),
|
|
FixedMul(axis.z, sinangle),
|
|
cosangle
|
|
);
|
|
|
|
return result;
|
|
}
|
|
|
|
vector3_t G_GetCalibratedGyroOffset(INT32 p)
|
|
{
|
|
return localgyrocalibrationoffset[p];
|
|
}
|
|
|
|
void G_UpdateGamepadAutoCalibration(INT32 p, vector3_t accel, vector3_t gyro, boolean allowautocalibration)
|
|
{
|
|
fixed_t trust = FV3_Distance(&localaccelcalibrationoffset[p], &accel);
|
|
FV3_Load(
|
|
&localaccelcalibrationoffset[p],
|
|
(localaccelcalibrationoffset[p].x + accel.x)/2,
|
|
(localaccelcalibrationoffset[p].y + accel.y)/2,
|
|
(localaccelcalibrationoffset[p].z + accel.z)/2
|
|
);
|
|
|
|
CONS_Debug(DBG_IMU, "== Gyro Update ==\n");
|
|
CONS_Debug(DBG_IMU, "Gyro calibration safety: %4.3f\n", FixedToFloat(trust));
|
|
if (allowautocalibration && (trust < GyroCalibrationTrust))
|
|
{
|
|
if (!localgyrocalibrating[p])
|
|
{
|
|
localgyrocalibrationsamples[p] = 0;
|
|
FV3_Copy(&localgyrocalibrationlastoffset[p], &localgyrocalibrationoffset[p]);
|
|
FV3_Load(
|
|
&localgyrocalibrationoffset[p],
|
|
0, 0, 0
|
|
);
|
|
localgyrocalibrating[p] = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// incomplete calibration
|
|
if (localgyrocalibrating[p] && localgyrocalibrationsamples[p] <= (GyroCalibrationRollingAvgSamples + GyroCalibrationStart))
|
|
{
|
|
FV3_Copy(&localgyrocalibrationoffset[p], &localgyrocalibrationlastoffset[p]);
|
|
localgyrocalibrationsamples[p] = 0;
|
|
}
|
|
localgyrocalibrating[p] = false;
|
|
}
|
|
}
|
|
|
|
void G_UpdateGamepadGyro(INT32 p, vector3_t gyro)
|
|
{
|
|
vector3_t offset = {0};
|
|
|
|
if (p >= MAXSPLITSCREENPLAYERS)
|
|
{
|
|
#ifdef PARANOIA
|
|
CONS_Debug(DBG_GAMELOGIC, "G_UpdateGamepadGyro: Invalid player ID %d\n", p);
|
|
#endif
|
|
return;
|
|
}
|
|
if (!I_ControllerSupportsSensorGyro(p)) return;
|
|
|
|
if (localgyrocalibrating[p])
|
|
{
|
|
localgyrocalibrationsamples[p]++;
|
|
if (localgyrocalibrationsamples[p] > GyroCalibrationStart)
|
|
{
|
|
FV3_Load(
|
|
&localgyrocalibrationoffset[p],
|
|
(((GyroCalibrationRollingAvgSamples-1)*localgyrocalibrationoffset[p].x) + gyro.x)/GyroCalibrationRollingAvgSamples,
|
|
(((GyroCalibrationRollingAvgSamples-1)*localgyrocalibrationoffset[p].y) + gyro.y)/GyroCalibrationRollingAvgSamples,
|
|
(((GyroCalibrationRollingAvgSamples-1)*localgyrocalibrationoffset[p].z) + gyro.z)/GyroCalibrationRollingAvgSamples
|
|
);
|
|
}
|
|
}
|
|
CONS_Debug(DBG_IMU, "Gyro calibration samples: %d\n", localgyrocalibrationsamples[p]);
|
|
|
|
offset = G_GetCalibratedGyroOffset(p);
|
|
FV3_SubEx(&gyro, &offset, &localgyrovectors[p]);
|
|
}
|
|
|
|
// math sourced from this article
|
|
// http://gyrowiki.jibbsmart.com/blog:finding-gravity-with-sensor-fusion
|
|
|
|
// the time it takes in our acceleration smoothing for 'A' to get halfway to 'B'
|
|
#define SmoothingHalfTime (0.25)
|
|
// thresholds of trust for accel shakiness. less shakiness = more trust
|
|
#define ShakinessMaxThreshold (40*FRACUNIT/100)
|
|
#define ShakinessMinThreshold (1*FRACUNIT/100)
|
|
// when we trust the accel a lot (the controller is "still"), how quickly do we correct our gravity vector?
|
|
#define CorrectionStillRate (FRACUNIT)
|
|
// when we don't trust the accel (the controller is "shaky"), how quickly do we correct our gravity vector?
|
|
#define CorrectionShakyRate (1*FRACUNIT/100)
|
|
// if our old gravity vector is close enough to our new one, limit further corrections to this proportion of the rotation speed
|
|
#define CorrectionGyroFactor (5*FRACUNIT/100)
|
|
// thresholds for what's considered "close enough"
|
|
#define CorrectionGyroMinThreshold (5*FRACUNIT/100)
|
|
#define CorrectionGyroMaxThreshold (FRACUNIT/4)
|
|
// no matter what, always apply a minimum of this much correction to our gravity vector
|
|
#define CorrectionMinimumSpeed (1*FRACUNIT/100)
|
|
|
|
void G_UpdateGamepadGravity(INT32 p, vector3_t gyro, vector3_t accel)
|
|
{
|
|
// convert gyro input to reverse rotation
|
|
vector3_t invAccel = {-accel.x, -accel.y, -accel.z};
|
|
fixed_t correctionRate = 0;
|
|
// scaling is reversed, smaller time scales = larger steps in this code
|
|
// (1/timescale)/dt
|
|
fixed_t deltaseconds = FixedDiv(FRACUNIT, max(cv_timescale.value, FRACUNIT/20))/TICRATE;
|
|
// we don't have exp2 and we actually need it here to take timescale into account :(
|
|
fixed_t smoothFactor = FloatToFixed(exp2(-FixedToFloat(deltaseconds) / SmoothingHalfTime));
|
|
fixed_t angleRate = FixedMul(FV3_Magnitude(&gyro), M_PI_FIXED)/180;
|
|
fixed_t correctionLimit;
|
|
vector4_t invRotation = {0};
|
|
vector3_t gravityDelta = {0};
|
|
vector3_t gravityDeltaDirection = {0};
|
|
vector3_t correction = {0};
|
|
|
|
if (!G_GetGamepadCanUseTilt(p)) return;
|
|
CONS_Debug(DBG_IMU, "= Update Gravity Delta Time: %4.3f =\n", FixedToFloat(deltaseconds));
|
|
|
|
invRotation = AngleAxis(
|
|
FixedMul(FixedMul(FV3_Magnitude(&gyro), deltaseconds), 190*FRACUNIT/100),
|
|
-gyro.x,
|
|
-gyro.y,
|
|
-gyro.z
|
|
);
|
|
|
|
// rotate gravity vector
|
|
QuaternionMulVec3(&localgravityvectors[p], &localgravityvectors[p], &invRotation);
|
|
QuaternionMulVec3(&localsmoothedaccel[p], &localsmoothedaccel[p], &invRotation);
|
|
localshakinessfac[p] = FixedMul(localshakinessfac[p], smoothFactor),
|
|
FV3_SubEx(&accel, &localsmoothedaccel[p], &correction);
|
|
localshakinessfac[p] = max(localshakinessfac[p], FV3_Magnitude(&correction));
|
|
FV3_Load(&localsmoothedaccel[p],
|
|
Easing_Linear(smoothFactor, accel.x, localsmoothedaccel[p].x),
|
|
Easing_Linear(smoothFactor, accel.y, localsmoothedaccel[p].y),
|
|
Easing_Linear(smoothFactor, accel.z, localsmoothedaccel[p].z)
|
|
);
|
|
|
|
CONS_Debug(DBG_IMU, "Shakiness: %4.2f\n", FixedToFloat(localshakinessfac[p]));
|
|
|
|
FV3_SubEx(&invAccel, &localgravityvectors[p], &gravityDelta);
|
|
if (FV3_Magnitude(&gravityDelta) > 0)
|
|
{
|
|
FV3_NormalizeEx(&gravityDelta, &gravityDeltaDirection);
|
|
}
|
|
|
|
CONS_Debug(DBG_IMU, "Gravity Delta Magnitude: %4.3f\n", FixedToFloat(FV3_Magnitude(&gravityDelta)));
|
|
|
|
if (ShakinessMaxThreshold > ShakinessMinThreshold)
|
|
{
|
|
fixed_t stillness = CLAMP(FixedDiv((localshakinessfac[p] - ShakinessMinThreshold), (ShakinessMaxThreshold - ShakinessMinThreshold)), 0, FRACUNIT);
|
|
correctionRate = CorrectionStillRate + FixedMul((CorrectionShakyRate - CorrectionStillRate), stillness);
|
|
}
|
|
else if (localshakinessfac[p] > ShakinessMaxThreshold)
|
|
{
|
|
correctionRate = CorrectionShakyRate;
|
|
}
|
|
else
|
|
{
|
|
correctionRate = CorrectionStillRate;
|
|
}
|
|
|
|
// limit in proportion to rotation rate
|
|
correctionLimit = FixedMul(angleRate, CorrectionGyroFactor);
|
|
|
|
CONS_Debug(DBG_IMU, "Angle Rate: %4.3f\n", FixedToFloat(angleRate));
|
|
CONS_Debug(DBG_IMU, "Correction Limit: %4.3f\n", FixedToFloat(correctionLimit));
|
|
|
|
if (correctionRate > correctionLimit) {
|
|
fixed_t closeEnoughFactor;
|
|
if (CorrectionGyroMaxThreshold > CorrectionGyroMinThreshold)
|
|
{
|
|
closeEnoughFactor = CLAMP(FixedDiv((FV3_Magnitude(&gravityDelta) - CorrectionGyroMinThreshold), (CorrectionGyroMaxThreshold - CorrectionGyroMinThreshold)), 0, FRACUNIT);
|
|
}
|
|
else if (FV3_Magnitude(&gravityDelta) > CorrectionGyroMaxThreshold)
|
|
{
|
|
closeEnoughFactor = FRACUNIT;
|
|
}
|
|
else
|
|
{
|
|
closeEnoughFactor = 0;
|
|
}
|
|
|
|
CONS_Debug(DBG_IMU, "'Close Enough' Fac: %4.3f\n", FixedToFloat(closeEnoughFactor));
|
|
correctionRate = correctionLimit + FixedMul((correctionRate - correctionLimit), closeEnoughFactor);
|
|
}
|
|
|
|
correctionRate = max(correctionRate, CorrectionMinimumSpeed);
|
|
|
|
CONS_Debug(DBG_IMU, "Correction Rate: %4.2f\n", FixedToFloat(correctionRate));
|
|
|
|
FV3_Load(&correction,
|
|
FixedMul(gravityDeltaDirection.x, FixedMul(deltaseconds, correctionRate)),
|
|
FixedMul(gravityDeltaDirection.y, FixedMul(deltaseconds, correctionRate)),
|
|
FixedMul(gravityDeltaDirection.z, FixedMul(deltaseconds, correctionRate))
|
|
);
|
|
if ((FV3_LengthSquared(&correction) < FV3_LengthSquared(&gravityDelta)))
|
|
{
|
|
FV3_Add(&localgravityvectors[p], &correction);
|
|
}
|
|
else
|
|
{
|
|
FV3_Add(&localgravityvectors[p], &gravityDelta);
|
|
}
|
|
}
|
|
|
|
INT32 G_GetGamepadTilt(INT32 p)
|
|
{
|
|
fixed_t tilt;
|
|
fixed_t curve;
|
|
if (!G_GetGamepadCanUseTilt(p)) return 0;
|
|
tilt = CLAMP(FixedDiv(G_GetGamepadGravity(p).x + MAXGAMEPADTILT, 2*MAXGAMEPADTILT), 0, FRACUNIT);
|
|
CONS_Debug(DBG_IMU, "Tilt: %4.2f\n", FixedToFloat(tilt));
|
|
|
|
curve = FINESINE(FixedAngle(180*(tilt-FRACUNIT/2))>>ANGLETOFINESHIFT);
|
|
CONS_Debug(DBG_IMU, "Pinched Tilt: %4.2f\n", FixedToFloat(curve));
|
|
|
|
return (JOYAXISRANGE * curve)/FRACUNIT;
|
|
}
|
|
|
|
vector3_t G_GetGamepadGravity(INT32 p)
|
|
{
|
|
const vector3_t zero = {0, -ACCELEROMETERGRAVITY, 0};
|
|
if (!G_GetGamepadCanUseTilt(p)) return zero;
|
|
return localgravityvectors[p];
|
|
}
|
|
|
|
vector3_t G_GetGamepadShake(INT32 p)
|
|
{
|
|
vector3_t accel = {0};
|
|
if (!G_GetGamepadCanUseTilt(p)) return accel;
|
|
accel = G_PlayerInputSensor(p, ACCELEROMETER);
|
|
FV3_Add(&accel, &localgravityvectors[p]);
|
|
|
|
return accel;
|
|
}
|
|
|
|
fixed_t G_GetGamepadShakinessFactor(INT32 p)
|
|
{
|
|
if (!G_GetGamepadCanUseTilt(p)) return 0;
|
|
return localshakinessfac[p];
|
|
}
|
|
|
|
vector3_t G_GetGamepadCalibratedGyro(INT32 p)
|
|
{
|
|
vector3_t zero = {0};
|
|
if (p >= MAXSPLITSCREENPLAYERS)
|
|
{
|
|
#ifdef PARANOIA
|
|
CONS_Debug(DBG_GAMELOGIC, "G_GetGamepadGyro: Invalid player ID %d\n", p);
|
|
#endif
|
|
return zero;
|
|
}
|
|
|
|
if (!I_ControllerSupportsSensorGyro(p)) return zero;
|
|
|
|
return localgyrovectors[p];
|
|
}
|
|
|
|
boolean G_GetGamepadCanUseTilt(INT32 p)
|
|
{
|
|
if (p >= MAXSPLITSCREENPLAYERS)
|
|
{
|
|
#ifdef PARANOIA
|
|
CONS_Debug(DBG_GAMELOGIC, "G_GetGamepadCanUseTilt: Invalid player ID %d\n", p);
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
return (I_ControllerSupportsSensorAccelerometer(p) && I_ControllerSupportsSensorGyro(p));
|
|
}
|
|
#undef ShakinessMaxThreshold
|
|
#undef ShakinessMinThreshold
|
|
#undef CorrectionStillRate
|
|
#undef CorrectionShakyRate
|
|
#undef CorrectionGyroFactor
|
|
#undef CorrectionGyroMinThreshold
|
|
#undef CorrectionGyroMaxThreshold
|
|
#undef CorrectionMinimumSpeed
|
|
#undef SmoothingHalfTime
|
|
|
|
#undef GyroCalibrationTrust
|
|
#undef GyroCalibrationStart
|
|
#undef GyroCalibrationRollingAvgSamples |