Added new builtins: gp_rumble(), gp_rumbletriggers(), gp_setledcolor(),

gp_settriggerfx(). More improvements to touchpad sensor code, analog
stick tweaks... mostly beneficial to the SDL2 backend


git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5994 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
Eukara 2021-07-25 16:12:24 +00:00
parent 0ed2c90da9
commit 1f75d2bc9e
6 changed files with 297 additions and 24 deletions

View file

@ -77,7 +77,7 @@ static cvar_t joy_advaxis[6] =
{
#define ADVAXISDESC (const char *)"Provides a way to remap each joystick/controller axis.\nShould be set to one of: moveforward, moveback, lookup, lookdown, turnleft, turnright, moveleft, moveright, moveup, movedown, rollleft, rollright"
CVARCD("joyadvaxisx", "moveright", joyaxiscallback, ADVAXISDESC), //left rightwards axis
CVARCD("joyadvaxisy", "moveforward", joyaxiscallback, ADVAXISDESC), //left downwards axis
CVARCD("joyadvaxisy", "moveback", joyaxiscallback, ADVAXISDESC), //left downwards axis
CVARCD("joyadvaxisz", "", joyaxiscallback, ADVAXISDESC), //typically left trigger (use it as a button)
CVARCD("joyadvaxisr", "turnright", joyaxiscallback, ADVAXISDESC), //right rightwards axis
CVARCD("joyadvaxisu", "lookup", joyaxiscallback, ADVAXISDESC), //right downwards axis
@ -990,7 +990,7 @@ void IN_MoveJoystick(struct joy_s *joy, float *movements, int pnum, float framet
//angle changes
cl.playerview[pnum].viewanglechange[PITCH] += joy_anglesens[0].value * jlook[0];
cl.playerview[pnum].viewanglechange[YAW] += joy_anglesens[1].value * jlook[1];
cl.playerview[pnum].viewanglechange[YAW] -= joy_anglesens[1].value * jlook[1];
cl.playerview[pnum].viewanglechange[ROLL] += joy_anglesens[2].value * jlook[2];
if (in_mlook.state[pnum] & 1)

View file

@ -58,19 +58,20 @@ void IN_DeactivateMouse(void)
static struct sdlfinger_s
{
qboolean active;
SDL_JoystickID jid;
SDL_TouchID tid;
SDL_FingerID fid;
} sdlfinger[MAX_FINGERS];
//sanitizes sdl fingers into touch events that our engine can eat.
//we don't really deal with different devices, we just munge the lot into a single thing (allowing fingers to be tracked, at least if splitscreen isn't active).
static uint32_t SDL_GiveFinger(SDL_TouchID tid, SDL_FingerID fid, qboolean fingerraised)
static uint32_t SDL_GiveFinger(SDL_JoystickID jid, SDL_TouchID tid, SDL_FingerID fid, qboolean fingerraised)
{
uint32_t f;
for (f = 0; f < countof(sdlfinger); f++)
{
if (sdlfinger[f].active)
{
if (sdlfinger[f].tid == tid && sdlfinger[f].fid == fid)
if (sdlfinger[f].jid == jid && sdlfinger[f].tid == tid && sdlfinger[f].fid == fid)
{
sdlfinger[f].active = !fingerraised;
return f;
@ -82,6 +83,7 @@ static uint32_t SDL_GiveFinger(SDL_TouchID tid, SDL_FingerID fid, qboolean finge
if (!sdlfinger[f].active)
{
sdlfinger[f].active = !fingerraised;
sdlfinger[f].jid = jid;
sdlfinger[f].tid = tid;
sdlfinger[f].fid = fid;
return f;
@ -102,9 +104,12 @@ static struct sdljoy_s
SDL_GameController *controller;
SDL_JoystickID id;
unsigned int qdevid;
unsigned int axistobuttonp;
unsigned int axistobuttonn;
} sdljoy[MAX_JOYSTICKS];
//the enumid is the value for the open function rather than the working id.
static unsigned int J_AllocateDevID(void)
static void J_AllocateDevID(struct sdljoy_s *joy)
{
unsigned int id = 0, j;
for (j = 0; j < MAX_JOYSTICKS;)
@ -116,7 +121,16 @@ static unsigned int J_AllocateDevID(void)
}
}
return id;
joy->qdevid = id;
if (joy->controller)
{
//enable some sensors if they're there. because we can.
if (SDL_GameControllerHasSensor(joy->controller, SDL_SENSOR_ACCEL))
SDL_GameControllerSetSensorEnabled(joy->controller, SDL_SENSOR_ACCEL, SDL_TRUE);
if (SDL_GameControllerHasSensor(joy->controller, SDL_SENSOR_GYRO))
SDL_GameControllerSetSensorEnabled(joy->controller, SDL_SENSOR_GYRO, SDL_TRUE);
}
}
static void J_ControllerAdded(int enumid)
{
@ -172,21 +186,46 @@ static struct sdljoy_s *J_DevId(SDL_JoystickID jid)
}
static void J_ControllerAxis(SDL_JoystickID jid, int axis, int value)
{
static int axismap[] = {
// SDL_CONTROLLER_AXIS_LEFTX, SDL_CONTROLLER_AXIS_LEFTY, SDL_CONTROLLER_AXIS_RIGHTX,
GPAXIS_LT_RIGHT, GPAXIS_LT_DOWN, GPAXIS_RT_RIGHT,
// SDL_CONTROLLER_AXIS_RIGHTY, SDL_CONTROLLER_AXIS_TRIGGERLEFT,SDL_CONTROLLER_AXIS_TRIGGERRIGHT,
GPAXIS_RT_DOWN, GPAXIS_LT_TRIGGER, GPAXIS_RT_TRIGGER};
static struct
{
int qaxis;
keynum_t pos, neg;
} axismap[] =
{
{/*SDL_CONTROLLER_AXIS_LEFTX*/ GPAXIS_LT_RIGHT, K_GP_LEFT_THUMB_RIGHT, K_GP_LEFT_THUMB_LEFT},
{/*SDL_CONTROLLER_AXIS_LEFTY*/ GPAXIS_LT_DOWN, K_GP_LEFT_THUMB_DOWN, K_GP_LEFT_THUMB_UP},
{/*SDL_CONTROLLER_AXIS_RIGHTX*/ GPAXIS_RT_RIGHT, K_GP_RIGHT_THUMB_RIGHT, K_GP_RIGHT_THUMB_LEFT},
{/*SDL_CONTROLLER_AXIS_RIGHTY*/ GPAXIS_RT_DOWN, K_GP_RIGHT_THUMB_DOWN, K_GP_RIGHT_THUMB_UP},
{/*SDL_CONTROLLER_AXIS_TRIGGERLEFT*/ GPAXIS_LT_TRIGGER, K_GP_LEFT_TRIGGER, 0},
{/*SDL_CONTROLLER_AXIS_TRIGGERRIGHT*/ GPAXIS_RT_TRIGGER, K_GP_RIGHT_TRIGGER, 0},
};
struct sdljoy_s *joy = J_DevId(jid);
if (joy && axis < sizeof(axismap)/sizeof(axismap[0]) && joy->qdevid != DEVID_UNSET) {
/* hack to allow for RTRIGGER and LTRIGGER */
if (axis == 4)
IN_KeyEvent(joy->qdevid, (value > 16382) ? 1 : 0, K_GP_LEFT_TRIGGER, 0);
else if (axis == 5)
IN_KeyEvent(joy->qdevid, (value > 16382) ? 1 : 0, K_GP_RIGHT_TRIGGER, 0);
else
IN_JoystickAxisEvent(joy->qdevid, axismap[axis], value / 32767.0);
if (joy && axis < sizeof(axismap)/sizeof(axismap[0]) && joy->qdevid != DEVID_UNSET)
{
if (value > 0x4000 && axismap[axis].pos)
{
IN_KeyEvent(joy->qdevid, true, axismap[axis].pos, 0);
joy->axistobuttonp |= 1u<<axis;
}
else if (joy->axistobuttonp & (1u<<axis))
{
IN_KeyEvent(joy->qdevid, false, axismap[axis].pos, 0);
joy->axistobuttonp &= ~(1u<<axis);
}
if (value < -0x4000 && axismap[axis].neg)
{
IN_KeyEvent(joy->qdevid, true, axismap[axis].neg, 0);
joy->axistobuttonn |= 1u<<axis;
}
else if (joy->axistobuttonn & (1u<<axis))
{
IN_KeyEvent(joy->qdevid, false, axismap[axis].neg, 0);
joy->axistobuttonn &= ~(1u<<axis);
}
IN_JoystickAxisEvent(joy->qdevid, axismap[axis].qaxis, value / 32767.0);
}
}
static void J_JoystickAxis(SDL_JoystickID jid, int axis, int value)
@ -234,7 +273,7 @@ static void J_ControllerButton(SDL_JoystickID jid, int button, qboolean pressed)
{
if (!pressed)
return;
joy->qdevid = J_AllocateDevID();
J_AllocateDevID(joy);
}
IN_KeyEvent(joy->qdevid, pressed, buttonmap[button], 0);
}
@ -300,11 +339,140 @@ static void J_JoystickButton(SDL_JoystickID jid, int button, qboolean pressed)
{
if (!pressed)
return;
joy->qdevid = J_AllocateDevID();
J_AllocateDevID(joy);
}
IN_KeyEvent(joy->qdevid, pressed, buttonmap[button], 0);
}
}
void J_Rumble(int id, uint16_t amp_low, uint16_t amp_high, int duration)
{
#if SDL_VERSION_ATLEAST(2,0,9)
if (duration > 10000)
duration = 10000;
for (int i = 0; i < MAX_JOYSTICKS; i++)
{
if (sdljoy[i].qdevid == id)
{
SDL_GameControllerRumble(SDL_GameControllerFromInstanceID(sdljoy[i].id), amp_low, amp_high, duration);
return;
}
}
#else
Con_DPrintf(CON_WARNING "Rumble is requires at least SDL 2.0.9\n");
#endif
}
void J_RumbleTriggers(int id, uint16_t left, uint16_t right, uint32_t duration)
{
#if SDL_VERSION_ATLEAST(2,0,14)
if (duration > 10000)
duration = 10000;
for (int i = 0; i < MAX_JOYSTICKS; i++)
{
if (sdljoy[i].qdevid == id)
{
SDL_GameControllerRumbleTriggers(SDL_GameControllerFromInstanceID(sdljoy[i].id), left, right, duration);
return;
}
}
#else
Con_DPrintf(CON_WARNING "Trigger rumble is requires at least SDL 2.0.14\n");
#endif
}
void J_SetLEDColor(int id, vec3_t color)
{
#if SDL_VERSION_ATLEAST(2,0,14)
printf("got color %f %f %f\n", color[0], color[1], color[2]);
/* maybe we'll eventually get sRGB LEDs */
color[0] *= 255.0f;
color[1] *= 255.0f;
color[2] *= 255.0f;
for (int i = 0; i < MAX_JOYSTICKS; i++)
{
if (sdljoy[i].qdevid == id)
{
SDL_GameControllerSetLED(SDL_GameControllerFromInstanceID(sdljoy[i].id), (uint8_t)color[0], (uint8_t)color[1], (uint8_t)color[2]);
return;
}
}
#else
Con_DPrintf(CON_WARNING "Trigger rumble is requires at least SDL 2.0.14\n");
#endif
}
void J_SetTriggerFX(int id, const void *data, int size)
{
#if SDL_VERSION_ATLEAST(2,0,15)
for (int i = 0; i < MAX_JOYSTICKS; i++)
{
if (sdljoy[i].qdevid == id)
{
SDL_GameControllerSendEffect(SDL_GameControllerFromInstanceID(sdljoy[i].id), &data, size);
return;
}
}
#else
Con_DPrintf(CON_WARNING "Trigger FX is requires at least SDL 2.0.15\n");
#endif
}
#if SDL_VERSION_ATLEAST(2,0,14)
static void J_ControllerTouchPad(SDL_JoystickID jid, int pad, int finger, int fingerstate, float x, float y, float pressure)
{
#if 1
//FIXME: forgets which seat its meant to be controlling.
uint32_t thefinger = SDL_GiveFinger(jid, pad, finger, fingerstate<0);
#else
//FIXME: conflicts with regular mice (very problematic when using absolute coords)
uint32_t thefinger;
struct sdljoy_s *joy = J_DevId(jid);
if (!joy)
return;
if (joy->qdevid == DEVID_UNSET)
{
if (fingerstate!=1)
return;
J_AllocateDevID(joy);
}
thefinger = joy->qdevid;
#endif
x *= vid.pixelwidth;
y *= vid.pixelheight;
IN_MouseMove(thefinger, true, x, y, 0, pressure);
if (finger)
IN_KeyEvent(thefinger, finger>0, K_MOUSE1, 0);
}
static void J_ControllerSensor(SDL_JoystickID jid, SDL_SensorType sensor, float *data)
{
struct sdljoy_s *joy = J_DevId(jid);
if (!joy)
return;
//don't assign an id here. wait for a button.
if (joy->qdevid == DEVID_UNSET)
return;
safeswitch(sensor)
{
case SDL_SENSOR_ACCEL:
IN_Accelerometer(joy->qdevid, data[0], data[1], data[2]);
break;
case SDL_SENSOR_GYRO:
IN_Gyroscope(joy->qdevid, data[0], data[1], data[2]);
break;
case SDL_SENSOR_INVALID:
case SDL_SENSOR_UNKNOWN:
safedefault:
break;
}
}
#endif
static void J_Kill(SDL_JoystickID jid, qboolean verbose)
{
int i;
@ -432,7 +600,7 @@ unsigned int MySDL_MapKey(SDL_Keycode sdlkey)
case SDLK_x:
case SDLK_y:
case SDLK_z:
return sdlkey;
return sdlkey;
case SDLK_CAPSLOCK: return K_CAPSLOCK;
case SDLK_F1: return K_F1;
case SDLK_F2: return K_F2;
@ -915,14 +1083,14 @@ void Sys_SendKeyEvents(void)
case SDL_FINGERDOWN:
case SDL_FINGERUP:
{
uint32_t thefinger = SDL_GiveFinger(event.tfinger.touchId, event.tfinger.fingerId, event.type==SDL_FINGERUP);
uint32_t thefinger = SDL_GiveFinger(-1, event.tfinger.touchId, event.tfinger.fingerId, event.type==SDL_FINGERUP);
IN_MouseMove(thefinger, true, event.tfinger.x * vid.pixelwidth, event.tfinger.y * vid.pixelheight, 0, event.tfinger.pressure);
IN_KeyEvent(thefinger, event.type==SDL_FINGERDOWN, K_MOUSE1, 0);
}
break;
case SDL_FINGERMOTION:
{
uint32_t thefinger = SDL_GiveFinger(event.tfinger.touchId, event.tfinger.fingerId, false);
uint32_t thefinger = SDL_GiveFinger(-1, event.tfinger.touchId, event.tfinger.fingerId, false);
IN_MouseMove(thefinger, true, event.tfinger.x * vid.pixelwidth, event.tfinger.y * vid.pixelheight, 0, event.tfinger.pressure);
}
break;
@ -1026,6 +1194,20 @@ void Sys_SendKeyEvents(void)
break;
// case SDL_CONTROLLERDEVICEREMAPPED:
// break;
#if SDL_VERSION_ATLEAST(2,0,14)
case SDL_CONTROLLERTOUCHPADDOWN:
J_ControllerTouchPad(event.cdevice.which, event.ctouchpad.touchpad, event.ctouchpad.finger, 1, event.ctouchpad.x, event.ctouchpad.y, event.ctouchpad.pressure);
break;
case SDL_CONTROLLERTOUCHPADMOTION:
J_ControllerTouchPad(event.cdevice.which, event.ctouchpad.touchpad, event.ctouchpad.finger, 0, event.ctouchpad.x, event.ctouchpad.y, event.ctouchpad.pressure);
break;
case SDL_CONTROLLERTOUCHPADUP:
J_ControllerTouchPad(event.cdevice.which, event.ctouchpad.touchpad, event.ctouchpad.finger, -1, event.ctouchpad.x, event.ctouchpad.y, event.ctouchpad.pressure);
break;
case SDL_CONTROLLERSENSORUPDATE:
J_ControllerSensor(event.csensor.which, event.csensor.sensor, event.csensor.data);
break;
#endif
#endif
}
}

View file

@ -463,6 +463,27 @@ static int Joy_AllocateDevID(void)
}
}
}
void J_Rumble(int id, uint16_t amp_low, uint16_t amp_high, int duration)
{
//Con_DPrintf(CON_WARNING "Rumble is unavailable on this platform\n");
}
void J_RumbleTriggers(int id, uint16_t left, uint16_t right, uint32_t duration)
{
//Con_DPrintf(CON_WARNING "Trigger rumble is unavailable on this platform\n");
}
void J_SetLEDColor(int id, vec3_t color)
{
//Con_DPrintf(CON_WARNING "Game-Pad LED colors are unavailable on this platform\n");
}
void J_SetTriggerFX(int id, const void *data, int size)
{
//Con_DPrintf(CON_WARNING "Trigger FX are unavailable on this platform\n");
}
#ifdef USINGRAWINPUT
static int Mouse_AllocateDevID(void)
{

View file

@ -6731,6 +6731,47 @@ static void QCBUILTIN PF_resourcestatus(pubprogfuncs_t *prinst, struct globalvar
}
}
void J_Rumble(int joy, uint16_t amp_low, uint16_t amp_high, uint32_t duration);
void J_RumbleTriggers(int joy, uint16_t left, uint16_t right, uint32_t duration);
void J_SetLEDColor(int id, vec3_t color);
void J_SetTriggerFX(int id, const void *data, int size);
static void QCBUILTIN PF_cl_gp_rumble(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
int device = G_FLOAT(OFS_PARM0);
uint16_t amp_low = G_FLOAT(OFS_PARM1);
uint16_t amp_high = G_FLOAT(OFS_PARM2);
uint32_t duration = G_FLOAT(OFS_PARM3);
J_Rumble(device, amp_low, amp_high, duration);
}
static void QCBUILTIN PF_cl_gp_rumbletriggers(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
int device = G_FLOAT(OFS_PARM0);
uint16_t left = G_FLOAT(OFS_PARM1);
uint16_t right = G_FLOAT(OFS_PARM2);
uint32_t duration = G_FLOAT(OFS_PARM3);
J_RumbleTriggers(device, left, right, duration);
}
static void QCBUILTIN PF_cl_gp_setledcolor(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
int device = G_FLOAT(OFS_PARM0);
J_SetLEDColor(device, G_VECTOR(OFS_PARM1));
}
static void QCBUILTIN PF_cl_gp_settriggerfx(pubprogfuncs_t *prinst, struct globalvars_s *pr_globals)
{
int device = G_FLOAT(OFS_PARM0);
int size = G_INT(OFS_PARM2);
void *fxptr = PR_GetReadQCPtr(prinst, G_INT(OFS_PARM1), size);
if (!fxptr)
PR_BIError(prinst, "PF_cl_gp_settriggerfx: invalid pointer/size\n");
else
J_SetTriggerFX(device, fxptr, size);
}
/*static void PF_cs_clipboard_got(void *ctx, const char *utf8)
{
void *pr_globals;
@ -7523,6 +7564,10 @@ static struct {
{"fremove", PF_fremove, 652},
{"fexists", PF_fexists, 653},
{"rmtree", PF_rmtree, 654},
{"gp_rumble", PF_cl_gp_rumble, 0}, // #0 void(float devid, float amp_low, float amp_high, float duration) gp_rumble
{"gp_rumbletriggers", PF_cl_gp_rumbletriggers, 0}, // #0 void(float devid, float left, float right, float duration) gp_rumbletriggers
{"gp_setledcolor", PF_cl_gp_setledcolor, 0}, // #0 void(float devid, float red, float green, float blue) gp_setledcolor
{"gp_settriggerfx", PF_cl_gp_settriggerfx, 0}, // #0 void(float devid, const void *data, int size) gp_settriggerfx
{NULL}
};

View file

@ -5003,3 +5003,23 @@ void INS_EnumerateDevices(void *ctx, void(*callback)(void *ctx, const char *type
#endif
}
/* doubt this will ever happen to begin with */
void J_Rumble(int id, uint16_t amp_low, uint16_t amp_high, int duration)
{
//Con_DPrintf(CON_WARNING "Rumble is unavailable on this platform\n");
}
void J_RumbleTriggers(int id, uint16_t left, uint16_t right, uint32_t duration)
{
//Con_DPrintf(CON_WARNING "Trigger rumble is unavailable on this platform\n");
}
void J_SetLEDColor(int id, vec3_t color)
{
//Con_DPrintf(CON_WARNING "Game-Pad LED colors are unavailable on this platform\n");
}
void J_SetTriggerFX(int id, const void *data, int size)
{
//Con_DPrintf(CON_WARNING "Trigger FX are unavailable on this platform\n");
}

View file

@ -11532,6 +11532,11 @@ static BuiltinList_t BuiltinList[] = { //nq qw h2 ebfs
{"loadcustomskin", PF_Fixme, 0, 0, 0, 377, D("float(string skinfilename, optional string skindata)", "Creates a new skin object and returns it. These are custom per-entity surface->shader lookups. The skinfilename/data should be in .skin format:\nsurfacename,shadername - makes the named surface use the named shader (legacy format for compat with q3)\nreplace \"surfacename\" \"shadername\" - non-legacy equivalent.\nqwskin \"foo\" - use an unmodified quakeworld player skin (including crop+repalette rules)\nq1lower 0xff0000 - specify an override for the entity's lower colour, in this case to red\nq1upper 0x0000ff - specify an override for the entity's lower colour, in this case to blue\ncompose \"surfacename\" \"shader\" \"imagename@x,y:w,h$s,t,s2,t2?r,g,b,a\" - compose a skin texture from multiple images.\n The texture is determined to be sufficient to hold the first named image, additional images can be named as extra tokens on the same line.\n Use a + at the end of the line to continue reading image tokens from the next line also, the named shader must use 'map $diffuse' to read the composed texture (compatible with the defaultskin shader). Must be matched with a releasecustomskin call later, and is pointless without applycustomskin.")},
{"applycustomskin", PF_Fixme, 0, 0, 0, 378, D("void(entity e, float skinobj)", "Updates the entity's custom skin (refcounted).")},
{"releasecustomskin",PF_Fixme, 0, 0, 0, 379, D("void(float skinobj)", "Lets the engine know that the skin will no longer be needed. Thanks to refcounting any ents with the skin already applied will retain their skin until later changed. It is valid to destroy a skin just after applying it to an ent in the same function that it was created in, as the skin will only be destroyed once its refcount rops to 0.")},
{"gp_rumble", PF_Fixme, 0, 0, 0, 0, D("void(float devid, float amp_low, float amp_high, float duration)", "Sends a single rumble event to the game-pad specified in devid. Every time you call this, the previous effect is cancelled out.")},
{"gp_rumbletriggers", PF_Fixme, 0, 0, 0, 0, D("void(float devid, float left, float right, float duration)", "Makes the analog triggers rumble of the specified game-pad, like gp_rumble() one call cancels out the previous one on the device.")},
{"gp_setledcolor", PF_Fixme, 0, 0, 0, 0, D("void(float devid, vector color)", "Updates the game-pad LED color.")},
{"gp_settriggerfx", PF_Fixme, 0, 0, 0, 0, D("void(float devid, const void *data, int size)", "Sends a specific effect packet to the controller. On the PlayStation 5's DualSense that can adjust the tension on the analog triggers.")},
//END EXT_CSQC
{"memalloc", PF_memalloc, 0, 0, 0, 384, D("__variant*(int size)", "Allocate an arbitary block of memory")},