Files
fs2open.github.com/code/io/joy_ff.cpp
T

760 lines
18 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 "math/vecmat.h"
#include "io/sw_force.h"
#include "osapi/osregistry.h"
#include "io/joy_ff.h"
#include "osapi/osapi.h"
typedef struct {
DIEFFECT effect;
DIPERIODIC periodic_struct;
DIENVELOPE envelope_struct;
DWORD axes[2];
LONG direction[2];
GUID guid;
} di_periodic_effect_struct;
int joy_ff_handling_scaler;
int Joy_ff_enabled = 0;
int Joy_ff_directional_hit_effect_enabled = 1;
LPDIRECTINPUT pDi;
LPDIRECTINPUTDEVICE2 pDiDevice;
int joy_ff_create_effects();
void joy_ff_stop_effects();
LPDIRECTINPUTEFFECT pHitEffect1;
LPDIRECTINPUTEFFECT pHitEffect2;
LPDIRECTINPUTEFFECT pAfterburn1;
LPDIRECTINPUTEFFECT pAfterburn2;
LPDIRECTINPUTEFFECT pShootEffect;
LPDIRECTINPUTEFFECT pSecShootEffect;
LPDIRECTINPUTEFFECT pSpring;
LPDIRECTINPUTEFFECT pDock;
LPDIRECTINPUTEFFECT pDeathroll1;
LPDIRECTINPUTEFFECT pDeathroll2;
LPDIRECTINPUTEFFECT pExplode;
di_periodic_effect_struct Struct_deathroll1;
di_periodic_effect_struct Struct_deathroll2;
di_periodic_effect_struct Struct_explode;
di_periodic_effect_struct Struct_afterburn1;
di_periodic_effect_struct Struct_afterburn2;
di_periodic_effect_struct Struct_dock;
di_condition_effect_struct Spring_cond_effect;
void joy_ff_afterburn_off();
void init_periodic_effect_struct(di_periodic_effect_struct *effect, int type, int dur, int per, int ang = 0, int mag = 10000, int att = 0, int fade = 0);
int joy_ff_init()
{
int ff_enabled;
Joy_ff_enabled = 0; // Assume no force feedback
ff_enabled = os_config_read_uint(NULL, "EnableJoystickFF", 0);
if (ff_enabled) {
HRESULT hr;
TCHAR g_szOutput[256];
TCHAR szCodeString[256];
Joy_ff_directional_hit_effect_enabled = os_config_read_uint(NULL, "EnableHitEffect", 1);
hr = SWFF_OpenDefaultFFJoystick((HWND) os_get_window(), &pDi, &pDiDevice);
if (FAILED(hr)) {
nprintf(("Sandeep", "No FF On Joystick, not using FF\n"));
SWFF_ErrorCodeToString(hr, &szCodeString[0]);
wsprintf(g_szOutput, "Make sure JOYSTICKID1 has Force Feedback\n"
"Code = %lx: %s\n", hr, szCodeString);
nprintf(("Sandeep", g_szOutput));
return -1;
}
nprintf(("Sandeep", "There is FF on this joystick! (The peasants cheer)\n"));
SWFF_DestroyAllEffects(pDiDevice);
if (joy_ff_create_effects())
return -1;
Joy_ff_enabled = 1;
}
return 0;
}
void joy_ff_shutdown()
{
if (Joy_ff_enabled) {
pSpring->Stop();
joy_ff_stop_effects();
SWFF_DestroyAllEffects(pDiDevice);
pDiDevice->Unacquire();
pDiDevice->Release();
pDi->Release();
}
}
HRESULT joy_ff_handle_error(HRESULT hr, char *eff_name = NULL)
{
if (FAILED(hr)) {
TCHAR szCodeString[256];
SWFF_ErrorCodeToString(hr, szCodeString);
if (eff_name)
nprintf(("Joystick", "FF: Error for %s: %s\n", eff_name, szCodeString));
else
nprintf(("Joystick", "FF: Error: %s\n", szCodeString));
}
return hr;
}
int joy_ff_create_std_periodic(LPDIRECTINPUTEFFECT *eff, int type, int dur, int per, int ang = 0, int mag = 10000, int att = 0, int fade = 0)
{
joy_ff_handle_error(SWFF_CreatePeriodicEffect(pDiDevice, eff, type, dur, per, ang, mag, 0, att, 0, fade, 0, -1));
if (!*eff)
return -1;
return 0;
}
void joy_ff_start_effect(LPDIRECTINPUTEFFECT eff, char *name)
{
HRESULT hr;
nprintf(("Joystick", "FF: Starting effect %s\n", name));
hr = joy_ff_handle_error(eff->Start(1, 0));
if (hr == DIERR_INPUTLOST) {
joy_reacquire_ff();
joy_ff_handle_error(eff->Start(1, 0));
}
}
int joy_ff_create_effects()
{
joy_ff_handle_error(SWFF_CreateConstantForceEffect(
pDiDevice,
&pHitEffect1,
300000, // Duration
0, // Angle
10000, // Magnitude
0, // Attack time
10000, // Attack level
120000, // Fade time
1, // Fade level
-1), "HitEffect1");
if (pHitEffect1)
nprintf(("Joystick", "FF: Hit effect 1 loaded\n"));
else { // bail out early if we can't even load this (because rest probably won't either and failing is slow.
nprintf(("Joystick", "FF: Hit effect 1 failed to load\n"));
return -1;
}
joy_ff_create_std_periodic(
&pHitEffect2,
SINE,
300000, // Duration
100000, // Period
9000, // Angle
10000, // Magnitude
100000, // Attack time
100000); // Fade time
if (pHitEffect2)
nprintf(("Joystick", "FF: Hit effect 2 loaded\n"));
else
nprintf(("Joystick", "FF: Hit effect 2 failed to load\n"));
joy_ff_handle_error(SWFF_CreatePeriodicEffect(
pDiDevice,
&pShootEffect,
SAWTOOTH_DOWN,
160000, // Duration
20000, // Period
0, // Angle
10000, // Magnitude
0,
0,
0,
120000,
0, -1), "ShootEffect");
if (pShootEffect)
nprintf(("Joystick", "FF: Fire primary effect loaded\n"));
else
nprintf(("Joystick", "FF: Fire primary effect failed to load\n"));
joy_ff_handle_error(SWFF_CreateConstantForceEffect(
pDiDevice,
&pSecShootEffect,
200000, // Duration
0, // Angle
10000, // Magnitude
50000, // Attack time
10000, // Attack level
100000, // Fade time
1, // Fade level
-1), "SecShootEffect");
if (pSecShootEffect)
nprintf(("Joystick", "FF: Fire Secondary effect loaded\n"));
else
nprintf(("Joystick", "FF: Fire Secondary effect failed to load\n"));
joy_ff_handle_error(SWFF_CreateConditionEffectStruct(&Spring_cond_effect,
pDiDevice,
&pSpring,
SPRING,
INFINITE, // uS
100, // X Coefficient
0, // X Offset
100, // Y Coefficient
0, // Y Offset
-1), // button play mask
"Spring");
if (pSpring) {
nprintf(("Joystick", "FF: Spring effect loaded\n"));
joy_ff_start_effect(pSpring, "Spring");
} else
nprintf(("Joystick", "FF: Spring effect failed to load\n"));
init_periodic_effect_struct(
&Struct_afterburn1,
SINE,
INFINITE, // Duration
20000, // Period
0, // Angle
8000); // Magnitude
pDiDevice->CreateEffect(Struct_afterburn1.guid, &Struct_afterburn1.effect, &pAfterburn1, NULL);
if (pAfterburn1)
nprintf(("Joystick", "FF: Afterburner effect 1 loaded\n"));
else
nprintf(("Joystick", "FF: Afterburner effect 1 failed to load\n"));
init_periodic_effect_struct(
&Struct_afterburn2,
SINE,
INFINITE, // Duration
120000, // Period
9000, // Angle
4400); // Magnitude
pDiDevice->CreateEffect(Struct_afterburn2.guid, &Struct_afterburn2.effect, &pAfterburn2, NULL);
if (pAfterburn2)
nprintf(("Joystick", "FF: Afterburner effect 2 loaded\n"));
else
nprintf(("Joystick", "FF: Afterburner effect 2 failed to load\n"));
init_periodic_effect_struct(
&Struct_dock,
SQUARE_HIGH,
125000, // Duration
100000, // Period
9000, // Angle
4000); // Magnitude
pDiDevice->CreateEffect(Struct_dock.guid, &Struct_dock.effect, &pDock, NULL);
if (pDock)
nprintf(("Joystick", "FF: Dock effect loaded\n"));
else
nprintf(("Joystick", "FF: Dock effect failed to load\n"));
init_periodic_effect_struct(
&Struct_explode,
SAWTOOTH_DOWN,
500000, // Duration
20000, // Period
9000, // Angle
10000, // Magnitude
0, // Attack time
500000); // Fade time
pDiDevice->CreateEffect(Struct_explode.guid, &Struct_explode.effect, &pExplode, NULL);
if (pExplode)
nprintf(("Joystick", "FF: Explosion effect loaded\n"));
else
nprintf(("Joystick", "FF: Explosion effect failed to load\n"));
init_periodic_effect_struct(
&Struct_deathroll1,
SINE,
INFINITE, // Duration
200000, // Period
0, // Angle
10000, // Magnitude
2000000, // Attack time
0); // Fade time
pDiDevice->CreateEffect(Struct_deathroll1.guid, &Struct_deathroll1.effect, &pDeathroll1, NULL);
if (pDeathroll1)
nprintf(("Joystick", "FF: Deathroll effect 1 loaded\n"));
else
nprintf(("Joystick", "FF: Deathroll effect 1 failed to load\n"));
init_periodic_effect_struct(
&Struct_deathroll2,
SINE,
INFINITE, // Duration
200000, // Period
9000, // Angle
10000, // Magnitude
2000000, // Attack time
0); // Fade time
pDiDevice->CreateEffect(Struct_deathroll2.guid, &Struct_deathroll2.effect, &pDeathroll2, NULL);
if (pDeathroll2)
nprintf(("Joystick", "FF: Deathroll effect 2 loaded\n"));
else
nprintf(("Joystick", "FF: Deathroll effect 2 failed to load\n"));
return 0;
}
void joy_ff_stop_effects()
{
joy_ff_afterburn_off();
if (pDeathroll1)
pDeathroll1->Stop();
if (pDeathroll2)
pDeathroll2->Stop();
}
void joy_ff_mission_init(vec3d v)
{
v.xyz.z = 0.0f;
// joy_ff_handling_scaler = (int) ((vm_vec_mag(&v) - 1.3f) * 10.5f);
joy_ff_handling_scaler = (int) ((vm_vec_mag(&v) + 1.3f) * 5.0f);
// joy_ff_handling_scaler = (int) (vm_vec_mag(&v) * 7.5f);
}
void joy_ff_adjust_handling(int speed)
{
int v;
v = speed * joy_ff_handling_scaler * 2 / 3;
// v += joy_ff_handling_scaler * joy_ff_handling_scaler * 6 / 7 + 250;
v += joy_ff_handling_scaler * 45 - 500;
if (v > 10000)
v = 10000;
if (pSpring) {
if (Spring_cond_effect.DIConditionStruct[0].lPositiveCoefficient != v) {
HRESULT hr;
Spring_cond_effect.DIConditionStruct[0].lPositiveCoefficient = v;
Spring_cond_effect.DIConditionStruct[0].lNegativeCoefficient = v;
Spring_cond_effect.DIConditionStruct[1].lPositiveCoefficient = v;
Spring_cond_effect.DIConditionStruct[1].lNegativeCoefficient = v;
nprintf(("Joystick", "FF: New handling force = %d\n", v));
hr = joy_ff_handle_error(pSpring->SetParameters(&Spring_cond_effect.DIEffectStruct, DIEP_TYPESPECIFICPARAMS), "Spring");
if (hr == DIERR_INPUTLOST) {
joy_reacquire_ff();
joy_ff_handle_error(pSpring->SetParameters(&Spring_cond_effect.DIEffectStruct, DIEP_TYPESPECIFICPARAMS), "Spring");
}
}
}
}
void joy_ff_change_effect(di_periodic_effect_struct *s, LPDIRECTINPUTEFFECT eff, int gain = -1, int dur = 0, int flags = -1)
{
int reload = 0;
if ((gain >= 0) && ((int) s->effect.dwGain != gain)) {
s->effect.dwGain = gain;
nprintf(("Joystick", "FF: Gain reset to %d\n", gain));
reload = 1;
}
if (dur && ((int) s->effect.dwDuration != dur)) {
s->effect.dwDuration = dur;
nprintf(("Joystick", "FF: Duration reset to %d\n", dur));
reload = 1;
}
if (flags == -1) {
flags = DIEP_DURATION | DIEP_SAMPLEPERIOD | DIEP_GAIN | DIEP_DIRECTION | DIEP_ENVELOPE | DIEP_TYPESPECIFICPARAMS;
nprintf(("Joystick", "FF: Doing full reload of effect\n"));
}
if (flags != (DIEP_DURATION | DIEP_GAIN))
reload = 1;
if (reload) {
HRESULT hr;
nprintf(("Joystick", "FF: Swapping in a new effect\n"));
hr = joy_ff_handle_error(eff->SetParameters(&s->effect, flags));
if (hr == DIERR_INPUTLOST) {
joy_reacquire_ff();
joy_ff_handle_error(eff->SetParameters(&s->effect, flags));
}
} else
nprintf(("Joystick", "FF: Swap effect requested, but nothing changed\n"));
}
int joy_ff_effect_playing(LPDIRECTINPUTEFFECT eff)
{
DWORD flags;
eff->GetEffectStatus(&flags);
return (flags & DIEGES_PLAYING);
}
void joy_ff_docked()
{
if (pDock) {
pDock->Stop();
if (joy_ff_handle_error(SWFF_SetGain(pDock, 10000), "Dock") == DIERR_INPUTLOST) {
joy_reacquire_ff();
joy_ff_handle_error(SWFF_SetGain(pDock, 10000), "Dock");
}
joy_ff_start_effect(pDock, "Dock");
}
}
void joy_ff_play_reload_effect()
{
if (pDock) {
pDock->Stop();
if (joy_ff_handle_error(SWFF_SetGain(pDock, 5000), "Dock (Reload)") == DIERR_INPUTLOST) {
joy_reacquire_ff();
joy_ff_handle_error(SWFF_SetGain(pDock, 5000), "Dock (Reload)");
}
joy_ff_start_effect(pDock, "Dock (Reload)");
}
}
int Joy_ff_afterburning = 0;
void joy_ff_afterburn_on()
{
if (pAfterburn1) {
pAfterburn1->Stop();
joy_ff_change_effect(&Struct_afterburn1, pAfterburn1, 5000, INFINITE, DIEP_DURATION | DIEP_GAIN);
joy_ff_start_effect(pAfterburn1, "Afterburn1");
}
if (pAfterburn2) {
pAfterburn2->Stop();
joy_ff_change_effect(&Struct_afterburn2, pAfterburn2, 5000, INFINITE, DIEP_DURATION | DIEP_GAIN);
joy_ff_start_effect(pAfterburn2, "Afterburn2");
}
nprintf(("Joystick", "FF: Afterburn started\n"));
Joy_ff_afterburning = 1;
}
void joy_ff_afterburn_off()
{
if (!Joy_ff_afterburning)
return;
if (pAfterburn1) {
pAfterburn1->Stop();
}
if (pAfterburn2) {
pAfterburn2->Stop();
}
Joy_ff_afterburning = 0;
nprintf(("Joystick", "FF: Afterburn stopped\n"));
}
void joy_ff_deathroll()
{
if (pDeathroll1) {
pDeathroll1->Stop();
joy_ff_start_effect(pDeathroll1, "Deathroll1");
}
if (pDeathroll2) {
pDeathroll2->Stop();
joy_ff_start_effect(pDeathroll2, "Deathroll2");
}
}
void joy_ff_explode()
{
if (pDeathroll1)
pDeathroll1->Stop();
if (pDeathroll2)
pDeathroll2->Stop();
if (pExplode) {
pExplode->Stop();
joy_ff_start_effect(pExplode, "Explode");
}
}
void joy_ff_fly_by(int mag)
{
int gain;
if (Joy_ff_afterburning)
return;
gain = mag * 120 + 4000;
if (gain > 10000)
gain = 10000;
if (pAfterburn1) {
pAfterburn1->Stop();
joy_ff_change_effect(&Struct_afterburn1, pAfterburn1, gain, 6000 * mag + 400000, DIEP_DURATION | DIEP_GAIN);
joy_ff_start_effect(pAfterburn1, "Afterburn1 (Fly by)");
}
if (pAfterburn2) {
pAfterburn2->Stop();
joy_ff_change_effect(&Struct_afterburn2, pAfterburn2, gain, 6000 * mag + 400000, DIEP_DURATION | DIEP_GAIN);
joy_ff_start_effect(pAfterburn2, "Afterburn2 (Fly by)");
}
}
void joy_reacquire_ff()
{
if (!Joy_ff_enabled)
return;
nprintf(("Joystick", "FF: Reacquiring\n"));
pDiDevice->Acquire();
joy_ff_start_effect(pSpring, "Spring");
}
void joy_unacquire_ff()
{
}
void joy_ff_play_dir_effect(float x, float y)
{
int idegs, imag;
float degs;
if (!Joy_ff_enabled)
return;
if (!pHitEffect1 || !pHitEffect2)
return;
if (joy_ff_effect_playing(pHitEffect1) || joy_ff_effect_playing(pHitEffect2)) {
nprintf(("Joystick", "FF: HitEffect already playing. Skipping\n"));
return;
}
if (Joy_ff_directional_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;
if (joy_ff_handle_error(SWFF_SetDirectionGain(pHitEffect1, idegs, imag), "HitEffect1") == DIERR_INPUTLOST) {
joy_reacquire_ff();
joy_ff_handle_error(SWFF_SetDirectionGain(pHitEffect1, idegs, imag), "HitEffect1");
}
idegs += 9000;
if (idegs >= 36000)
idegs -= 36000;
if (joy_ff_handle_error(SWFF_SetDirectionGain(pHitEffect2, idegs, imag), "HitEffect2") == DIERR_INPUTLOST) {
joy_reacquire_ff();
joy_ff_handle_error(SWFF_SetDirectionGain(pHitEffect2, idegs, imag), "HitEffect2");
}
}
joy_ff_start_effect(pHitEffect1, "HitEffect1");
joy_ff_start_effect(pHitEffect2, "HitEffect2");
//nprintf(("Joystick", "FF: Dir: %d, Mag = %d\n", idegs, imag));
}
void joy_ff_play_vector_effect(vec3d *v, float scaler)
{
vec3d vf;
float x, y;
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)
y = -vm_vec_mag(&vf);
else
y = vm_vec_mag(&vf);
joy_ff_play_dir_effect(-x, -y);
}
static int secondary_ff_level = 0;
void joy_ff_play_secondary_shoot(int gain)
{
if (!Joy_ff_enabled)
return;
if (!pSecShootEffect)
return;
gain = gain * 100 + 2500;
if (gain > 10000)
gain = 10000;
if (gain != secondary_ff_level) {
if (joy_ff_handle_error(SWFF_SetGain(pSecShootEffect, gain), "SecShootEffect") == DIERR_INPUTLOST) {
joy_reacquire_ff();
joy_ff_handle_error(SWFF_SetGain(pSecShootEffect, gain), "SecShootEffect");
}
if (joy_ff_handle_error(SWFF_SetDuration(pSecShootEffect, 150000 + gain * 25), "SecShootEffect") == DIERR_INPUTLOST) {
joy_reacquire_ff();
joy_ff_handle_error(SWFF_SetDuration(pSecShootEffect, 150000 + gain * 25), "SecShootEffect");
}
secondary_ff_level = gain;
nprintf(("Joystick", "FF: Secondary force = %d\n", gain));
}
pSecShootEffect->Stop();
joy_ff_start_effect(pSecShootEffect, "SecShootEffect");
}
static int primary_ff_level = 0;
void joy_ff_play_primary_shoot(int gain)
{
if (!Joy_ff_enabled)
return;
if (!pShootEffect)
return;
if (gain > 10000)
gain = 10000;
if (gain != primary_ff_level) {
if (joy_ff_handle_error(SWFF_SetGain(pShootEffect, gain), "ShootEffect") == DIERR_INPUTLOST) {
joy_reacquire_ff();
joy_ff_handle_error(SWFF_SetGain(pShootEffect, gain), "ShootEffect");
}
primary_ff_level = gain;
}
pShootEffect->Stop();
joy_ff_start_effect(pShootEffect, "ShootEffect");
}
void init_periodic_effect_struct(di_periodic_effect_struct *effect, int type, int dur, int per, int ang, int mag, int att, int fade)
{
// type-specific stuff
DWORD dwPhase = 0;
GUID guid = GUID_Square;
switch (type) {
case SINE:
guid = GUID_Sine;
break;
case COSINE:
guid = GUID_Sine;
dwPhase = 9000;
break;
case SQUARE_HIGH:
guid = GUID_Square;
break;
case SQUARE_LOW:
guid = GUID_Square;
dwPhase = 18000;
break;
case TRIANGLE_UP:
guid = GUID_Triangle;
break;
case TRIANGLE_DOWN:
guid = GUID_Triangle;
dwPhase = 18000;
break;
case SAWTOOTH_UP:
guid = GUID_SawtoothUp;
break;
case SAWTOOTH_DOWN:
guid = GUID_SawtoothDown;
break;
default:
Int3(); // illegal
break;
}
effect->guid = guid;
effect->periodic_struct.dwMagnitude = mag;
effect->periodic_struct.lOffset = 0;
effect->periodic_struct.dwPhase = dwPhase;
effect->periodic_struct.dwPeriod = per;
effect->envelope_struct.dwSize = sizeof(DIENVELOPE);
effect->envelope_struct.dwAttackTime = att;
effect->envelope_struct.dwAttackLevel = 0;
effect->envelope_struct.dwFadeTime = fade;
effect->envelope_struct.dwFadeLevel = 0;
effect->axes[0] = DIJOFS_X;
effect->axes[1] = DIJOFS_Y;
effect->direction[0] = ang;
effect->direction[1] = 0;
effect->effect.dwSize = sizeof(DIEFFECT);
effect->effect.dwFlags = DIEFF_OBJECTOFFSETS | DIEFF_POLAR;
effect->effect.dwDuration = dur;
effect->effect.dwSamplePeriod = HZ_TO_uS(100);
effect->effect.dwGain = 10000;
effect->effect.dwTriggerButton = DIEB_NOTRIGGER;
effect->effect.dwTriggerRepeatInterval = 0;
effect->effect.cAxes = 2;
effect->effect.rgdwAxes = effect->axes;
effect->effect.rglDirection = effect->direction;
effect->effect.lpEnvelope = &effect->envelope_struct;
effect->effect.cbTypeSpecificParams = sizeof(effect->periodic_struct);
effect->effect.lpvTypeSpecificParams = &effect->periodic_struct;
}