mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-08 15:29:48 -10:00
Use the SDL gamepad API to add full gamepad support. Adds rumble for supported gamepads and changes the FF in-game options to take immediate effect instead of requiring a restart. Additionally this makes changes to allow gamepad triggers to be used as both an axis and a button. This support is specific to gamepads since we can explicitly identify the trigger with the API and act on it accordingly. FSO control config doesn't currently support such an action directly however so we have to hack it in just a bit. Due to this the control binding will show the trigger axis name bound to a button control only while a gamepad is connected, otherwise it shows a button name associated with the trigger.
833 lines
21 KiB
C++
833 lines
21 KiB
C++
/*
|
|
* Copyright (C) Volition, Inc. 1999. All rights reserved.
|
|
*
|
|
* All source code herein is the property of Volition, Inc. You may not sell
|
|
* or otherwise commercially exploit the source or things you created based on the
|
|
* source.
|
|
*
|
|
*/
|
|
|
|
#include "globalincs/pstypes.h"
|
|
#include "io/joy.h"
|
|
#include "io/joy_ff.h"
|
|
#include "io/joy_haptic.h"
|
|
#include "math/vecmat.h"
|
|
|
|
|
|
static SDL_Haptic *haptic = nullptr;
|
|
static int joy_haptic_handling_scaler = 0;
|
|
static bool Gain_adjust = false;
|
|
static float Gain = 1.0f;
|
|
|
|
typedef struct haptic_effect_t {
|
|
SDL_HapticEffect eff;
|
|
int id;
|
|
bool loaded;
|
|
|
|
void init() {
|
|
eff = {0};
|
|
id = -1;
|
|
loaded = false;
|
|
}
|
|
|
|
haptic_effect_t() { init(); }
|
|
} haptic_effect_t;
|
|
|
|
static haptic_effect_t pHitEffect1;
|
|
static haptic_effect_t pHitEffect2;
|
|
static haptic_effect_t pAfterburn1;
|
|
static haptic_effect_t pAfterburn2;
|
|
static haptic_effect_t pShootEffect;
|
|
static haptic_effect_t pSecShootEffect;
|
|
static haptic_effect_t pSpring;
|
|
static haptic_effect_t pDock;
|
|
|
|
|
|
static bool joy_haptic_create_effects();
|
|
static void joy_haptic_start_effect(haptic_effect_t *eff, const char *name);
|
|
static void joy_haptic_update_effect(haptic_effect_t *eff, const char *name);
|
|
static void joy_haptic_stop_effect(haptic_effect_t *eff, const char *name);
|
|
|
|
static void check_and_print_haptic_feature(uint32_t flags, uint32_t check_flag, const char* description) {
|
|
auto has_flag = (flags & check_flag) == check_flag;
|
|
|
|
mprintf((" Supports %s: %s\n", description, has_flag ? "true" : "false"));
|
|
}
|
|
|
|
static void print_haptic_support() {
|
|
mprintf((" Haptic feature support:\n"));
|
|
|
|
auto supported = SDL_GetHapticFeatures(haptic);
|
|
check_and_print_haptic_feature(supported, SDL_HAPTIC_CONSTANT, "Constant effect");
|
|
check_and_print_haptic_feature(supported, SDL_HAPTIC_SINE, "Sine effect");
|
|
check_and_print_haptic_feature(supported, SDL_HAPTIC_LEFTRIGHT, "Left right effect");
|
|
check_and_print_haptic_feature(supported, SDL_HAPTIC_TRIANGLE, "Triangle effect");
|
|
check_and_print_haptic_feature(supported, SDL_HAPTIC_SAWTOOTHUP, "Sawtooth up effect");
|
|
check_and_print_haptic_feature(supported, SDL_HAPTIC_SAWTOOTHDOWN, "Sawtooth down effect");
|
|
check_and_print_haptic_feature(supported, SDL_HAPTIC_RAMP, "Ramp effect");
|
|
check_and_print_haptic_feature(supported, SDL_HAPTIC_SPRING, "Spring effect");
|
|
check_and_print_haptic_feature(supported, SDL_HAPTIC_DAMPER, "Damper effect");
|
|
check_and_print_haptic_feature(supported, SDL_HAPTIC_INERTIA, "Inertia effect");
|
|
check_and_print_haptic_feature(supported, SDL_HAPTIC_FRICTION, "Friction effect");
|
|
check_and_print_haptic_feature(supported, SDL_HAPTIC_CUSTOM, "Custom effect");
|
|
check_and_print_haptic_feature(supported, SDL_HAPTIC_GAIN, "global gain");
|
|
check_and_print_haptic_feature(supported, SDL_HAPTIC_AUTOCENTER, "Autocenter");
|
|
check_and_print_haptic_feature(supported, SDL_HAPTIC_STATUS, "Status query");
|
|
check_and_print_haptic_feature(supported, SDL_HAPTIC_PAUSE, "Pause effects");
|
|
}
|
|
|
|
bool joy_haptic_init()
|
|
{
|
|
auto Joy = io::joystick::getPlayerJoystick(CID_JOY0);
|
|
|
|
if ( !Joy || !Joy->isHaptic() ) {
|
|
return false;
|
|
}
|
|
|
|
mprintf(("\n"));
|
|
mprintf((" Initializing Haptic...\n"));
|
|
|
|
if ( !SDL_InitSubSystem(SDL_INIT_HAPTIC) ) {
|
|
mprintf((" ERROR: Could not initialize Haptic subsystem: %s\n", SDL_GetError()));
|
|
return false;
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
mprintf((" Available haptic devices:\n"));
|
|
|
|
auto haptics = SDL_GetHaptics(nullptr);
|
|
|
|
if ( !haptics ) {
|
|
mprintf((" <none>\n"));
|
|
} else {
|
|
for (int i = 0; haptics[i]; ++i) {
|
|
mprintf((" %s\n", SDL_GetHapticNameForID(haptics[i])));
|
|
}
|
|
|
|
SDL_free(haptics);
|
|
}
|
|
#endif
|
|
|
|
haptic = SDL_OpenHapticFromJoystick(Joy->getJoystick());
|
|
|
|
if (haptic == NULL) {
|
|
mprintf((" ERROR: Unable to open haptic joystick: %s\n", SDL_GetError()));
|
|
SDL_QuitSubSystem(SDL_INIT_HAPTIC);
|
|
return false;
|
|
}
|
|
|
|
if ( !joy_haptic_create_effects() ) {
|
|
mprintf((" ERROR: Unable to create effects\n"));
|
|
joy_haptic_shutdown();
|
|
return false;
|
|
}
|
|
|
|
mprintf(("\n"));
|
|
mprintf((" Number of haptic axes: %d\n", SDL_GetNumHapticAxes(haptic)));
|
|
mprintf((" Number of effects supported: %d\n", SDL_GetMaxHapticEffects(haptic)));
|
|
mprintf((" Number of simultaneous effects: %d\n", SDL_GetMaxHapticEffectsPlaying(haptic)));
|
|
print_haptic_support();
|
|
|
|
mprintf((" ... Haptic successfully initialized!\n"));
|
|
|
|
return true;
|
|
}
|
|
|
|
void joy_haptic_shutdown()
|
|
{
|
|
if ( !haptic ) {
|
|
return;
|
|
}
|
|
|
|
joy_haptic_afterburn_off();
|
|
SDL_StopHapticEffects(haptic);
|
|
|
|
SDL_CloseHaptic(haptic);
|
|
haptic = nullptr;
|
|
|
|
SDL_QuitSubSystem(SDL_INIT_HAPTIC);
|
|
}
|
|
|
|
static bool joy_haptic_can_play()
|
|
{
|
|
return (haptic != nullptr);
|
|
}
|
|
|
|
void joy_haptic_set_gain(int gain)
|
|
{
|
|
if ( !joy_haptic_can_play() ) {
|
|
return;
|
|
}
|
|
|
|
CLAMP(gain, 0, 100);
|
|
|
|
if (SDL_GetHapticFeatures(haptic) & SDL_HAPTIC_GAIN) {
|
|
SDL_SetHapticGain(haptic, gain);
|
|
} else {
|
|
Gain = gain / 100.0f;
|
|
Gain_adjust = (gain < 100);
|
|
|
|
// force update spring effect now since it only changes at mission start
|
|
joy_haptic_update_effect(&pSpring, "Spring");
|
|
}
|
|
}
|
|
|
|
static void haptic_adjust_effect_strength(SDL_HapticEffect *eff)
|
|
{
|
|
if ( !Gain_adjust ) {
|
|
return;
|
|
}
|
|
|
|
switch (eff->type) {
|
|
case SDL_HAPTIC_SINE:
|
|
case SDL_HAPTIC_SQUARE:
|
|
case SDL_HAPTIC_TRIANGLE:
|
|
case SDL_HAPTIC_SAWTOOTHUP:
|
|
case SDL_HAPTIC_SAWTOOTHDOWN:
|
|
eff->periodic.magnitude = Sint16(eff->periodic.magnitude * Gain);
|
|
break;
|
|
|
|
case SDL_HAPTIC_CONSTANT:
|
|
eff->constant.level = Sint16(eff->constant.level * Gain);
|
|
break;
|
|
|
|
// special case for spring
|
|
case SDL_HAPTIC_SPRING: {
|
|
auto val = Uint16(0x7FFF * Gain);
|
|
|
|
for (size_t i = 0; i < SDL_arraysize(eff->condition.right_sat); i++) {
|
|
eff->condition.right_sat[i] = val;
|
|
eff->condition.left_sat[i] = val;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static bool haptic_create_effect(haptic_effect_t *effect)
|
|
{
|
|
// manually adjust effect strength if necessary
|
|
haptic_adjust_effect_strength(&effect->eff);
|
|
|
|
effect->id = SDL_CreateHapticEffect(haptic, &effect->eff);
|
|
|
|
if (effect->id < 0) {
|
|
return false;
|
|
}
|
|
|
|
effect->loaded = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool joy_haptic_create_effects()
|
|
{
|
|
int num_loaded = 0;
|
|
|
|
// clear all SDL errors
|
|
SDL_ClearError();
|
|
|
|
// missing effects aren't fatal, we'll just ignore/disable them
|
|
auto supported = SDL_GetHapticFeatures(haptic);
|
|
|
|
// effects are created in priority order, most important first
|
|
// this allows a soft fail for low memory devices that can't load everything
|
|
|
|
if (supported & SDL_HAPTIC_SPRING) {
|
|
// pSpring
|
|
pSpring.init();
|
|
|
|
pSpring.eff.type = SDL_HAPTIC_SPRING;
|
|
pSpring.eff.condition.type = SDL_HAPTIC_SPRING;
|
|
pSpring.eff.condition.length = SDL_HAPTIC_INFINITY;
|
|
|
|
for (size_t i = 0; i < SDL_arraysize(pSpring.eff.condition.right_sat); i++) {
|
|
pSpring.eff.condition.right_sat[i] = 0x7FFF;
|
|
pSpring.eff.condition.left_sat[i] = 0x7FFF;
|
|
pSpring.eff.condition.right_coeff[i] = 0x147;
|
|
pSpring.eff.condition.left_coeff[i] = 0x147;
|
|
}
|
|
|
|
if ( !haptic_create_effect(&pSpring) ) {
|
|
mprintf((" Spring effect failed to load:\n %s\n", SDL_GetError()));
|
|
} else {
|
|
++num_loaded;
|
|
}
|
|
}
|
|
|
|
if (supported & SDL_HAPTIC_SAWTOOTHDOWN) {
|
|
// pShootEffect
|
|
pShootEffect.init();
|
|
|
|
pShootEffect.eff.type = SDL_HAPTIC_SAWTOOTHDOWN;
|
|
pShootEffect.eff.periodic.type = SDL_HAPTIC_SAWTOOTHDOWN;
|
|
pShootEffect.eff.periodic.direction.type = SDL_HAPTIC_POLAR;
|
|
pShootEffect.eff.periodic.direction.dir[0] = 0;
|
|
pShootEffect.eff.periodic.length = 160;
|
|
pShootEffect.eff.periodic.period = 20;
|
|
pShootEffect.eff.periodic.magnitude = 0x7FFF;
|
|
pShootEffect.eff.periodic.fade_length = 120;
|
|
|
|
if ( !haptic_create_effect(&pShootEffect) ) {
|
|
mprintf((" Fire primary effect failed to load:\n %s\n", SDL_GetError()));
|
|
} else {
|
|
++num_loaded;
|
|
}
|
|
}
|
|
|
|
if (supported & SDL_HAPTIC_CONSTANT) {
|
|
// pSecShootEffect
|
|
pSecShootEffect.init();
|
|
|
|
pSecShootEffect.eff.type = SDL_HAPTIC_CONSTANT;
|
|
pSecShootEffect.eff.constant.type = SDL_HAPTIC_CONSTANT;
|
|
pSecShootEffect.eff.constant.direction.type = SDL_HAPTIC_POLAR;
|
|
pSecShootEffect.eff.constant.direction.dir[0] = 0;
|
|
pSecShootEffect.eff.constant.length = 200;
|
|
pSecShootEffect.eff.constant.level = 0x7FFF;
|
|
pSecShootEffect.eff.constant.attack_length = 50;
|
|
pSecShootEffect.eff.constant.attack_level = 0x7FFF;
|
|
pSecShootEffect.eff.constant.fade_length = 100;
|
|
pSecShootEffect.eff.constant.fade_level = 1;
|
|
|
|
if ( !haptic_create_effect(&pSecShootEffect) ) {
|
|
mprintf((" Fire secondary effect failed to load:\n %s\n", SDL_GetError()));
|
|
} else {
|
|
++num_loaded;
|
|
}
|
|
}
|
|
|
|
if (supported & SDL_HAPTIC_SINE) {
|
|
// pAfterburn1
|
|
pAfterburn1.init();
|
|
|
|
pAfterburn1.eff.type = SDL_HAPTIC_SINE;
|
|
pAfterburn1.eff.periodic.type = SDL_HAPTIC_SINE;
|
|
pAfterburn1.eff.periodic.direction.type = SDL_HAPTIC_POLAR;
|
|
pAfterburn1.eff.periodic.direction.dir[0] = 0;
|
|
pAfterburn1.eff.periodic.length = SDL_HAPTIC_INFINITY;
|
|
pAfterburn1.eff.periodic.period = 20;
|
|
pAfterburn1.eff.periodic.magnitude = 0x3332;
|
|
|
|
if ( !haptic_create_effect(&pAfterburn1) ) {
|
|
mprintf((" Afterburn effect 1 failed to load:\n %s\n", SDL_GetError()));
|
|
} else {
|
|
++num_loaded;
|
|
}
|
|
|
|
// pAfterburn2
|
|
pAfterburn2.init();
|
|
|
|
pAfterburn2.eff.type = SDL_HAPTIC_SINE;
|
|
pAfterburn2.eff.periodic.type = SDL_HAPTIC_SINE;
|
|
pAfterburn2.eff.periodic.direction.type = SDL_HAPTIC_POLAR;
|
|
pAfterburn2.eff.periodic.direction.dir[0] = 9000;
|
|
pAfterburn2.eff.periodic.length = 125;
|
|
pAfterburn2.eff.periodic.period = 100;
|
|
pAfterburn2.eff.periodic.magnitude = 0x1999;
|
|
|
|
if ( !haptic_create_effect(&pAfterburn2) ) {
|
|
mprintf((" Afterburn effect 2 failed to load:\n %s\n", SDL_GetError()));
|
|
} else {
|
|
++num_loaded;
|
|
}
|
|
}
|
|
|
|
if (supported & SDL_HAPTIC_CONSTANT) {
|
|
// pHitEffect1
|
|
pHitEffect1.init();
|
|
|
|
pHitEffect1.eff.type = SDL_HAPTIC_CONSTANT;
|
|
pHitEffect1.eff.constant.type = SDL_HAPTIC_CONSTANT;
|
|
pHitEffect1.eff.constant.direction.type = SDL_HAPTIC_POLAR;
|
|
pHitEffect1.eff.constant.direction.dir[0] = 0;
|
|
pHitEffect1.eff.constant.length = 300;
|
|
pHitEffect1.eff.constant.level = 0x7FFF;
|
|
pHitEffect1.eff.constant.attack_length = 0;
|
|
pHitEffect1.eff.constant.attack_level = 0x7FFF;
|
|
pHitEffect1.eff.constant.fade_length = 120;
|
|
pHitEffect1.eff.constant.fade_level = 1;
|
|
|
|
if ( !haptic_create_effect(&pHitEffect1) ) {
|
|
mprintf((" Hit effect 1 failed to load:\n %s\n", SDL_GetError()));
|
|
} else {
|
|
++num_loaded;
|
|
}
|
|
}
|
|
|
|
if (supported & SDL_HAPTIC_SINE) {
|
|
// pHitEffect2
|
|
pHitEffect2.init();
|
|
|
|
pHitEffect2.eff.type = SDL_HAPTIC_SINE;
|
|
pHitEffect2.eff.periodic.type = SDL_HAPTIC_SINE;
|
|
pHitEffect2.eff.periodic.direction.type = SDL_HAPTIC_POLAR;
|
|
pHitEffect2.eff.periodic.direction.dir[0] = 9000;
|
|
pHitEffect2.eff.periodic.length = 300;
|
|
pHitEffect2.eff.periodic.period = 100;
|
|
pHitEffect2.eff.periodic.magnitude = 0x7FFF;
|
|
pHitEffect2.eff.periodic.attack_length = 100;
|
|
pHitEffect2.eff.periodic.fade_length = 100;
|
|
|
|
if ( !haptic_create_effect(&pHitEffect2) ) {
|
|
mprintf((" Hit effect 2 failed to load:\n %s\n", SDL_GetError()));
|
|
} else {
|
|
++num_loaded;
|
|
}
|
|
}
|
|
|
|
if (supported & SDL_HAPTIC_SQUARE) {
|
|
// pDock
|
|
pDock.init();
|
|
|
|
pDock.eff.type = SDL_HAPTIC_SQUARE;
|
|
pDock.eff.periodic.type = SDL_HAPTIC_SQUARE;
|
|
pDock.eff.periodic.direction.type = SDL_HAPTIC_POLAR;
|
|
pDock.eff.periodic.direction.dir[0] = 9000;
|
|
pDock.eff.periodic.length = 125;
|
|
pDock.eff.periodic.period = 100;
|
|
pDock.eff.periodic.magnitude = 0x3332;
|
|
|
|
if ( !haptic_create_effect(&pDock) ) {
|
|
mprintf((" Dock effect failed to load:\n %s\n", SDL_GetError()));
|
|
} else {
|
|
++num_loaded;
|
|
}
|
|
}
|
|
|
|
return (num_loaded > 0);
|
|
}
|
|
|
|
static void joy_haptic_start_effect(haptic_effect_t *eff, const char* /* name */)
|
|
{
|
|
if ( !eff->loaded ) {
|
|
return;
|
|
}
|
|
|
|
// nprintf(("Joystick", "FF: Starting effect %s\n", name));
|
|
|
|
if ( !SDL_RunHapticEffect(haptic, eff->id, 1) ) {
|
|
// nprintf(("Joystick", "FF: Unable to run %s:\n %s\n", name, SDL_GetError()));
|
|
}
|
|
}
|
|
|
|
static void joy_haptic_update_effect(haptic_effect_t *eff, const char* /* name */)
|
|
{
|
|
if ( !eff->loaded ) {
|
|
return;
|
|
}
|
|
|
|
// manually adjust effect strength if necessary
|
|
haptic_adjust_effect_strength(&eff->eff);
|
|
|
|
// nprintf(("Joystick", "FF: Updating effect %s\n", name));
|
|
|
|
if ( !SDL_UpdateHapticEffect(haptic, eff->id, &eff->eff) ) {
|
|
// nprintf(("Joystick", "FF: Unable to update %s:\n %s\n", name, SDL_GetError()));
|
|
}
|
|
}
|
|
|
|
static void joy_haptic_stop_effect(haptic_effect_t *eff, const char* /* name */)
|
|
{
|
|
if ( !eff->loaded ) {
|
|
return;
|
|
}
|
|
|
|
// nprintf(("Joystick", "FF: Stopping effect %s\n", name));
|
|
|
|
if ( !SDL_StopHapticEffect(haptic, eff->id) ) {
|
|
// nprintf(("Joystick", "FF: Unable to stop %s:\n %s\n", name, SDL_GetError()));
|
|
}
|
|
}
|
|
|
|
void joy_haptic_stop_effects()
|
|
{
|
|
if ( !joy_haptic_can_play() ) {
|
|
return;
|
|
}
|
|
|
|
joy_haptic_afterburn_off();
|
|
|
|
// stop everything *except* spring
|
|
joy_haptic_stop_effect(&pHitEffect1, "HitEffect1");
|
|
joy_haptic_stop_effect(&pHitEffect2, "HitEffect2");
|
|
joy_haptic_stop_effect(&pAfterburn1, "Afterburn1");
|
|
joy_haptic_stop_effect(&pAfterburn2, "Afterburn2");
|
|
joy_haptic_stop_effect(&pShootEffect, "ShootEffect");
|
|
joy_haptic_stop_effect(&pSecShootEffect, "SecShootEffect");
|
|
joy_haptic_stop_effect(&pDock, "Dock");
|
|
}
|
|
|
|
void joy_haptic_mission_init(vec3d *v)
|
|
{
|
|
if ( !joy_haptic_can_play() ) {
|
|
return;
|
|
}
|
|
|
|
v->xyz.z = 0.0f;
|
|
|
|
joy_haptic_handling_scaler = (int) ((vm_vec_mag(v) + 1.3f) * 5.0f);
|
|
|
|
joy_haptic_adjust_handling(0);
|
|
joy_haptic_start_effect(&pSpring, "Spring");
|
|
|
|
// reset afterburn effects to default values
|
|
|
|
pAfterburn1.eff.periodic.length = SDL_HAPTIC_INFINITY;
|
|
pAfterburn1.eff.periodic.period = 20;
|
|
pAfterburn1.eff.periodic.magnitude = 0x3332;
|
|
pAfterburn1.eff.periodic.attack_length = 0;
|
|
|
|
joy_haptic_update_effect(&pAfterburn1, "Afterburn1 (init)");
|
|
|
|
pAfterburn2.eff.periodic.length = SDL_HAPTIC_INFINITY;
|
|
pAfterburn2.eff.periodic.period = 100;
|
|
pAfterburn2.eff.periodic.magnitude = 0x1999;
|
|
pAfterburn2.eff.periodic.attack_length = 0;
|
|
|
|
joy_haptic_update_effect(&pAfterburn2, "Afterburn2 (init)");
|
|
|
|
// reset primary shoot effect to default values
|
|
|
|
pShootEffect.eff.periodic.direction.dir[0] = 0;
|
|
pShootEffect.eff.periodic.length = 160;
|
|
pShootEffect.eff.periodic.fade_length = 120;
|
|
|
|
joy_haptic_update_effect(&pShootEffect, "ShootEffect (init)");
|
|
}
|
|
|
|
void joy_haptic_play_vector_effect(vec3d *v, float scaler)
|
|
{
|
|
vec3d vf;
|
|
float x, y;
|
|
|
|
if ( !joy_haptic_can_play() ) {
|
|
return;
|
|
}
|
|
|
|
// nprintf(("Joystick", "FF: vec = { %f, %f, %f } s = %f\n", v->xyz.x, v->xyz.y, v->xyz.z, scaler));
|
|
vm_vec_copy_scale(&vf, v, scaler);
|
|
x = vf.xyz.x;
|
|
vf.xyz.x = 0.0f;
|
|
|
|
if (vf.xyz.y + vf.xyz.z < 0.0f) {
|
|
y = -vm_vec_mag(&vf);
|
|
} else {
|
|
y = vm_vec_mag(&vf);
|
|
}
|
|
|
|
joy_haptic_play_dir_effect(-x, -y);
|
|
}
|
|
|
|
void joy_haptic_play_dir_effect(float x, float y)
|
|
{
|
|
static UI_TIMESTAMP hit_timeout;
|
|
int idegs, imag;
|
|
float degs;
|
|
|
|
if ( !joy_haptic_can_play() ) {
|
|
return;
|
|
}
|
|
|
|
// allow for at least one of the effects to work
|
|
if ( !pHitEffect1.loaded && !pHitEffect2.loaded ) {
|
|
return;
|
|
}
|
|
|
|
if ( !ui_timestamp_elapsed(hit_timeout) ) {
|
|
nprintf(("Joystick", "FF: HitEffect already playing. Skipping\n"));
|
|
return;
|
|
}
|
|
|
|
hit_timeout = ui_timestamp(pHitEffect1.eff.condition.length);
|
|
|
|
if (joy_ff_hit_effect_enabled()) {
|
|
if (x > 8000.0f) {
|
|
x = 8000.0f;
|
|
} else if (x < -8000.0f) {
|
|
x = -8000.0f;
|
|
}
|
|
|
|
if (y > 8000.0f) {
|
|
y = 8000.0f;
|
|
} else if (y < -8000.0f) {
|
|
y = -8000.0f;
|
|
}
|
|
|
|
imag = (int) fl_sqrt(x * x + y * y);
|
|
if (imag > 10000) {
|
|
imag = 10000;
|
|
}
|
|
|
|
degs = (float)atan2(x, y);
|
|
idegs = (int) (degs * 18000.0f / PI) + 90;
|
|
while (idegs < 0) {
|
|
idegs += 36000;
|
|
}
|
|
|
|
while (idegs >= 36000) {
|
|
idegs -= 36000;
|
|
}
|
|
|
|
pHitEffect1.eff.constant.direction.dir[0] = idegs;
|
|
pHitEffect1.eff.constant.level = (Sint16)fl2i(0x7FFF * (imag / 10000.0f));
|
|
|
|
joy_haptic_update_effect(&pHitEffect1, "HitEffect1");
|
|
|
|
idegs += 9000;
|
|
if (idegs >= 36000)
|
|
idegs -= 36000;
|
|
|
|
pHitEffect2.eff.periodic.direction.dir[0] = idegs;
|
|
pHitEffect2.eff.periodic.magnitude = (Sint16)fl2i(0x7FFF * (imag / 10000.0f));
|
|
|
|
joy_haptic_update_effect(&pHitEffect2, "HitEffect2");
|
|
}
|
|
|
|
joy_haptic_start_effect(&pHitEffect1, "HitEffect1");
|
|
joy_haptic_start_effect(&pHitEffect2, "HitEffect2");
|
|
}
|
|
|
|
void joy_haptic_play_primary_shoot(int gain)
|
|
{
|
|
if ( !joy_haptic_can_play() ) {
|
|
return;
|
|
}
|
|
|
|
if ( !pShootEffect.loaded ) {
|
|
return;
|
|
}
|
|
|
|
CLAMP(gain, 1, 10000);
|
|
|
|
static int primary_ff_level = 10000;
|
|
|
|
if (gain != primary_ff_level) {
|
|
pShootEffect.eff.periodic.magnitude = (Sint16) fl2i(0x7FFF * (gain / 10000.0f));
|
|
|
|
joy_haptic_update_effect(&pShootEffect, "ShootEffect");
|
|
|
|
primary_ff_level = gain;
|
|
}
|
|
|
|
joy_haptic_start_effect(&pShootEffect, "ShootEffect");
|
|
}
|
|
|
|
void joy_haptic_play_secondary_shoot(int gain)
|
|
{
|
|
if ( !joy_haptic_can_play() ) {
|
|
return;
|
|
}
|
|
|
|
if ( !pSecShootEffect.loaded ) {
|
|
return;
|
|
}
|
|
|
|
gain = gain * 100 + 2500;
|
|
|
|
CLAMP(gain, 1, 10000);
|
|
|
|
static int secondary_ff_level = 10000;
|
|
|
|
if (gain != secondary_ff_level) {
|
|
pSecShootEffect.eff.constant.level = (Sint16) fl2i(0x7FFF * (gain / 10000.0f));
|
|
pSecShootEffect.eff.constant.length = (150000 + gain * 25) / 1000;
|
|
|
|
joy_haptic_update_effect(&pSecShootEffect, "SecShootEffect");
|
|
|
|
secondary_ff_level = gain;
|
|
// nprintf(("Joystick", "FF: Secondary force = 0x%04x\n", pSecShootEffect.eff.constant.level));
|
|
}
|
|
|
|
joy_haptic_start_effect(&pSecShootEffect, "SecShootEffect");
|
|
}
|
|
|
|
void joy_haptic_adjust_handling(int speed)
|
|
{
|
|
int v;
|
|
short coeff = 0;
|
|
|
|
if ( !joy_haptic_can_play() ) {
|
|
return;
|
|
}
|
|
|
|
if ( !pSpring.loaded ) {
|
|
return;
|
|
}
|
|
|
|
static int last_speed = -1000;
|
|
|
|
if (speed == last_speed) {
|
|
return;
|
|
}
|
|
|
|
last_speed = speed;
|
|
|
|
v = speed * joy_haptic_handling_scaler * 2 / 3;
|
|
// v += joy_haptic_handling_scaler * joy_haptic_handling_scaler * 6 / 7 + 250;
|
|
v += joy_haptic_handling_scaler * 45 - 500;
|
|
|
|
CLAMP(v, 0, 10000);
|
|
|
|
coeff = (Sint16) fl2i(0x7FFF * (v / 10000.0f));
|
|
|
|
for (size_t i = 0; i < SDL_arraysize(pSpring.eff.condition.right_coeff); i++) {
|
|
pSpring.eff.condition.right_coeff[i] = coeff;
|
|
pSpring.eff.condition.left_coeff[i] = coeff;
|
|
}
|
|
|
|
// nprintf(("Joystick", "FF: New handling force = 0x%04x\n", coeff));
|
|
|
|
joy_haptic_update_effect(&pSpring, "Spring");
|
|
}
|
|
|
|
void joy_haptic_docked()
|
|
{
|
|
if ( !joy_haptic_can_play() ) {
|
|
return;
|
|
}
|
|
|
|
if ( !pDock.loaded ) {
|
|
return;
|
|
}
|
|
|
|
pDock.eff.periodic.magnitude = 0x3332;
|
|
|
|
joy_haptic_update_effect(&pDock, "Dock");
|
|
joy_haptic_start_effect(&pDock, "Dock");
|
|
}
|
|
|
|
void joy_haptic_play_reload_effect()
|
|
{
|
|
if ( !joy_haptic_can_play() ) {
|
|
return;
|
|
}
|
|
|
|
if ( !pDock.loaded ) {
|
|
return;
|
|
}
|
|
|
|
pDock.eff.periodic.magnitude = 0x1999;
|
|
|
|
joy_haptic_update_effect(&pDock, "Dock (Reload)");
|
|
joy_haptic_start_effect(&pDock, "Dock (Reload)");
|
|
}
|
|
|
|
void joy_haptic_afterburn_on()
|
|
{
|
|
if ( !joy_haptic_can_play() ) {
|
|
return;
|
|
}
|
|
|
|
pAfterburn1.eff.periodic.length = SDL_HAPTIC_INFINITY;
|
|
pAfterburn1.eff.periodic.magnitude = 0x3332;
|
|
|
|
joy_haptic_update_effect(&pAfterburn1, "Afterburn1");
|
|
|
|
pAfterburn2.eff.periodic.length = SDL_HAPTIC_INFINITY;
|
|
pAfterburn2.eff.periodic.magnitude = 0x1999;
|
|
|
|
joy_haptic_update_effect(&pAfterburn2, "Afterburn2");
|
|
|
|
joy_haptic_start_effect(&pAfterburn1, "Afterburn1");
|
|
joy_haptic_start_effect(&pAfterburn2, "Afterburn2");
|
|
|
|
// nprintf(("Joystick", "FF: Afterburn started\n"));
|
|
}
|
|
|
|
void joy_haptic_afterburn_off()
|
|
{
|
|
if ( !joy_haptic_can_play() ) {
|
|
return;
|
|
}
|
|
|
|
joy_haptic_stop_effect(&pAfterburn1, "Afterburn1");
|
|
joy_haptic_stop_effect(&pAfterburn2, "Afterburn2");
|
|
|
|
// nprintf(("Joystick", "FF: Afterburn stopped\n"));
|
|
}
|
|
|
|
void joy_haptic_explode()
|
|
{
|
|
if ( !joy_haptic_can_play() ) {
|
|
return;
|
|
}
|
|
|
|
joy_haptic_afterburn_off();
|
|
|
|
// reuse shoot effect here to save device memory
|
|
if (pShootEffect.loaded) {
|
|
// direction changes so an effect stop is needed before udpate
|
|
joy_haptic_stop_effect(&pShootEffect, "ShootEffect (Explode)");
|
|
|
|
pShootEffect.eff.periodic.direction.dir[0] = 9000;
|
|
pShootEffect.eff.periodic.length = 500;
|
|
pShootEffect.eff.periodic.magnitude = 0x7FFF;
|
|
pShootEffect.eff.periodic.fade_length = 500;
|
|
|
|
joy_haptic_update_effect(&pShootEffect, "ShootEffect (Explode)");
|
|
|
|
joy_haptic_start_effect(&pShootEffect, "ShootEffect (Explode)");
|
|
}
|
|
}
|
|
|
|
void joy_haptic_fly_by(int mag)
|
|
{
|
|
int gain;
|
|
|
|
if ( !joy_haptic_can_play() ) {
|
|
return;
|
|
}
|
|
|
|
gain = mag * 120 + 4000;
|
|
|
|
CLAMP(gain, 1, 10000);
|
|
|
|
pAfterburn1.eff.periodic.length = (6000 * mag + 400000) / 1000;
|
|
pAfterburn1.eff.periodic.magnitude = (Sint16) fl2i(0x7FFF * (gain / 10000.0f));
|
|
|
|
joy_haptic_update_effect(&pAfterburn1, "Afterburn1 (Fly by)");
|
|
|
|
pAfterburn2.eff.periodic.length = (6000 * mag + 400000) / 1000;
|
|
pAfterburn2.eff.periodic.magnitude = (Sint16) fl2i(0x7FFF * (gain / 10000.0f));
|
|
|
|
joy_haptic_update_effect(&pAfterburn2, "Afterburn2 (Fly by)");
|
|
|
|
joy_haptic_start_effect(&pAfterburn1, "Afterburn1 (Fly by)");
|
|
joy_haptic_start_effect(&pAfterburn2, "Afterburn2 (Fly by)");
|
|
}
|
|
|
|
void joy_haptic_deathroll()
|
|
{
|
|
if ( !joy_haptic_can_play() ) {
|
|
return;
|
|
}
|
|
|
|
if ( !(pAfterburn1.loaded && pAfterburn2.loaded) ) {
|
|
return;
|
|
}
|
|
|
|
// reuse afterburn effects here to save device memory
|
|
|
|
pAfterburn1.eff.periodic.length = SDL_HAPTIC_INFINITY;
|
|
pAfterburn1.eff.periodic.period = 200;
|
|
pAfterburn1.eff.periodic.magnitude = 0x7FFF;
|
|
pAfterburn1.eff.periodic.attack_length = 200;
|
|
|
|
joy_haptic_update_effect(&pAfterburn1, "Afterburn1 (Death Roll)");
|
|
|
|
pAfterburn2.eff.periodic.length = SDL_HAPTIC_INFINITY;
|
|
pAfterburn2.eff.periodic.period = 200;
|
|
pAfterburn2.eff.periodic.magnitude = 0x7FFF;
|
|
pAfterburn2.eff.periodic.attack_length = 200;
|
|
|
|
joy_haptic_update_effect(&pAfterburn2, "Afterburn2 (Death Roll)");
|
|
|
|
joy_haptic_start_effect(&pAfterburn1, "Afterburn1 (Death Roll)");
|
|
joy_haptic_start_effect(&pAfterburn2, "Afterburn2 (Death Roll)");
|
|
}
|