]> icculus.org git repositories - taylor/freespace2.git/blob - src/io/joy_ff.cpp
clean up and simplify effects and updating
[taylor/freespace2.git] / src / io / joy_ff.cpp
1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell 
5  * or otherwise commercially exploit the source or things you created based on the 
6  * source.
7  *
8 */ 
9
10
11 #include "pstypes.h"
12 #include "vecmat.h"
13 #include "osregistry.h"
14 #include "joy_ff.h"
15 #include "osapi.h"
16
17
18 static int Joy_ff_enabled = 0;
19 static int Joy_ff_acquired = 0;
20 static SDL_Haptic *haptic = NULL;
21 static int joy_ff_handling_scaler = 0;
22 static int Joy_ff_directional_hit_effect_enabled = 1;
23 static int Joy_rumble = 0;
24 static int Joy_ff_afterburning = 0;
25
26 typedef struct {
27         SDL_HapticEffect eff;
28         int id;
29         int loaded;
30 } haptic_effect_t;
31
32 static haptic_effect_t pHitEffect1;
33 static haptic_effect_t pHitEffect2;
34 static haptic_effect_t pAfterburn1;
35 static haptic_effect_t pAfterburn2;
36 static haptic_effect_t pShootEffect;
37 static haptic_effect_t pSecShootEffect;
38 static haptic_effect_t pSpring;
39 static haptic_effect_t pDock;
40
41 static void joy_ff_create_effects();
42 static int joy_ff_effect_playing(haptic_effect_t *eff);
43 static void joy_ff_start_effect(haptic_effect_t *eff, const char *name);
44
45 extern SDL_Joystick *sdljoy;
46
47
48 int joy_ff_init()
49 {
50         int ff_enabled = 0;
51
52         ff_enabled = os_config_read_uint("Controls", "EnableJoystickFF", 0);
53
54         if ( !ff_enabled || !SDL_JoystickIsHaptic(sdljoy) ) {
55                 return 0;
56         }
57
58         if (SDL_InitSubSystem(SDL_INIT_HAPTIC) < 0) {
59                 mprintf(("  ERROR: Unable to initialize haptic subsystem\n"));
60                 return -1;
61         }
62
63         haptic = SDL_HapticOpenFromJoystick(sdljoy);
64
65         if (haptic == NULL) {
66                 mprintf(("  ERROR: Unable to open haptic joystick\n"));
67                 SDL_QuitSubSystem(SDL_INIT_HAPTIC);
68                 return -1;
69         }
70
71         if ( SDL_HapticRumbleSupported(haptic) ) {
72                 SDL_HapticRumbleInit(haptic);
73                 Joy_rumble = 1;
74         }
75
76         mprintf(("  Rumble  : %s\n", Joy_rumble ? "Yes" : "No"));
77         mprintf(("  Axes    : %d\n", SDL_HapticNumAxes(haptic)));
78         mprintf(("  Max effects     : %d\n", SDL_HapticNumEffects(haptic)));
79         mprintf(("  Running effects : %d\n", SDL_HapticNumEffectsPlaying(haptic)));
80
81         joy_ff_create_effects();
82
83         Joy_ff_enabled = 1;
84         Joy_ff_acquired = 1;
85
86         Joy_ff_directional_hit_effect_enabled = os_config_read_uint("Controls", "EnableHitEffect", 1);
87
88         return 0;
89 }
90
91 void joy_ff_shutdown()
92 {
93         if ( !Joy_ff_enabled ) {
94                 return;
95         }
96
97         Joy_rumble = 0;
98
99         SDL_HapticClose(haptic);
100         haptic = NULL;
101
102         SDL_QuitSubSystem(SDL_INIT_HAPTIC);
103
104         Joy_ff_acquired = 0;
105         Joy_ff_enabled = 0;
106 }
107
108 static void joy_ff_create_effects()
109 {
110         // clear all SDL errors
111         SDL_ClearError();
112
113         unsigned int supported = 0;
114
115         supported = SDL_HapticQuery(haptic);
116
117         if ( !(supported & SDL_HAPTIC_CONSTANT) ) {
118                 mprintf(("  Constant Force  : not supported\n"));
119         }
120
121         if ( !(supported & SDL_HAPTIC_SINE) ) {
122                 mprintf(("  Sine Wave       : not supported\n"));
123         }
124
125         if ( !(supported & SDL_HAPTIC_SAWTOOTHDOWN) ) {
126                 mprintf(("  Sawtooth Down   : not supported\n"));
127         }
128
129         if ( !(supported & SDL_HAPTIC_SPRING) ) {
130                 mprintf(("  Spring          : not supported\n"));
131         }
132 /*
133         if ( !(supported & SDL_HAPTIC_SQUARE) ) {
134                 mprintf(("  Square          : not supported\n"));
135         }
136 */
137         if ( !(supported & SDL_HAPTIC_TRIANGLE) ) {
138                 mprintf(("  Triangle        : not supported\n"));
139         }
140
141
142         if (supported & SDL_HAPTIC_CONSTANT) {
143                 // pHitEffect1
144                 memset(&pHitEffect1, 0, sizeof(haptic_effect_t));
145
146                 pHitEffect1.eff.type = SDL_HAPTIC_CONSTANT;
147                 pHitEffect1.eff.constant.direction.type = SDL_HAPTIC_POLAR;
148                 pHitEffect1.eff.constant.direction.dir[0] = 0;
149                 pHitEffect1.eff.constant.length = 300;
150                 pHitEffect1.eff.constant.level = 0x7FFF;
151                 pHitEffect1.eff.constant.attack_length = 0;
152                 pHitEffect1.eff.constant.attack_level = 0x7FFF;
153                 pHitEffect1.eff.constant.fade_length = 120;
154                 pHitEffect1.eff.constant.fade_level = 1;
155
156                 pHitEffect1.id = SDL_HapticNewEffect(haptic, &pHitEffect1.eff);
157
158                 if (pHitEffect1.id < 0) {
159                         mprintf(("    Hit effect 1 failed to load:\n      %s\n", SDL_GetError()));
160                 } else {
161                         pHitEffect1.loaded = 1;
162                 }
163         }
164
165         if (supported & SDL_HAPTIC_SINE) {
166                 // pHitEffect2
167                 memset(&pHitEffect2, 0, sizeof(haptic_effect_t));
168
169                 pHitEffect2.eff.type = SDL_HAPTIC_SINE;
170                 pHitEffect2.eff.periodic.direction.type = SDL_HAPTIC_POLAR;
171                 pHitEffect2.eff.periodic.direction.dir[0] = 9000;
172                 pHitEffect2.eff.periodic.length = 300;
173                 pHitEffect2.eff.periodic.period = 100;
174                 pHitEffect2.eff.periodic.magnitude = 0x7FFF;
175                 pHitEffect2.eff.periodic.attack_length = 100;
176                 pHitEffect2.eff.periodic.fade_length = 100;
177
178                 pHitEffect2.id = SDL_HapticNewEffect(haptic, &pHitEffect2.eff);
179
180                 if (pHitEffect2.id < 0) {
181                         mprintf(("    Hit effect 2 failed to load:\n      %s\n", SDL_GetError()));
182                 } else {
183                         pHitEffect2.loaded = 1;
184                 }
185         }
186
187         if (supported & SDL_HAPTIC_SAWTOOTHDOWN) {
188                 // pShootEffect
189                 memset(&pShootEffect, 0, sizeof(haptic_effect_t));
190
191                 pShootEffect.eff.type = SDL_HAPTIC_SAWTOOTHDOWN;
192                 pShootEffect.eff.periodic.direction.type = SDL_HAPTIC_POLAR;
193                 pShootEffect.eff.periodic.direction.dir[0] = 0;
194                 pShootEffect.eff.periodic.length = 160;
195                 pShootEffect.eff.periodic.period = 20;
196                 pShootEffect.eff.periodic.magnitude = 0x7FFF;
197                 pShootEffect.eff.periodic.fade_length = 120;
198
199                 pShootEffect.id = SDL_HapticNewEffect(haptic, &pShootEffect.eff);
200
201                 if (pShootEffect.id < 0) {
202                         mprintf(("    Fire primary effect failed to load:\n      %s\n", SDL_GetError()));
203                 } else {
204                         pShootEffect.loaded = 1;
205                 }
206         }
207
208         if (supported & SDL_HAPTIC_CONSTANT) {
209                 // pSecShootEffect
210                 memset(&pSecShootEffect, 0, sizeof(haptic_effect_t));
211
212                 pSecShootEffect.eff.type = SDL_HAPTIC_CONSTANT;
213                 pSecShootEffect.eff.constant.direction.type = SDL_HAPTIC_POLAR;
214                 pSecShootEffect.eff.constant.direction.dir[0] = 0;
215                 pSecShootEffect.eff.constant.length = 200;
216                 pSecShootEffect.eff.constant.level = 0x7FFF;
217                 pSecShootEffect.eff.constant.attack_length = 50;
218                 pSecShootEffect.eff.constant.attack_level = 0x7FFF;
219                 pSecShootEffect.eff.constant.fade_length = 100;
220                 pSecShootEffect.eff.constant.fade_level = 1;
221
222                 pSecShootEffect.id = SDL_HapticNewEffect(haptic, &pSecShootEffect.eff);
223
224                 if (pSecShootEffect.id < 0) {
225                         mprintf(("    Fire secondary effect failed to load:\n      %s\n", SDL_GetError()));
226                 } else {
227                         pSecShootEffect.loaded = 1;
228                 }
229         }
230
231         if (supported & SDL_HAPTIC_SPRING) {
232                 // pSpring
233                 memset(&pSpring, 0, sizeof(haptic_effect_t));
234
235                 pSpring.eff.type = SDL_HAPTIC_SPRING;
236                 pSpring.eff.condition.length = SDL_HAPTIC_INFINITY;
237
238                 for (int i = 0; i < SDL_HapticNumAxes(haptic); i++) {
239                         pSpring.eff.condition.right_sat[i] = 0x7FFF;
240                         pSpring.eff.condition.left_sat[i] = 0x7FFF;
241                         pSpring.eff.condition.right_coeff[i] = 0x147;
242                         pSpring.eff.condition.left_coeff[i] = 0x147;
243                 }
244
245                 pSpring.id = SDL_HapticNewEffect(haptic, &pSpring.eff);
246
247                 if (pSpring.id < 0) {
248                         mprintf(("    Spring effect failed to load:\n      %s\n", SDL_GetError()));
249                 } else {
250                         pSpring.loaded = 1;
251                         joy_ff_start_effect(&pSpring, "Spring");
252                 }
253         }
254
255         if (supported & SDL_HAPTIC_SINE) {
256                 // pAfterburn1
257                 memset(&pAfterburn1, 0, sizeof(haptic_effect_t));
258
259                 pAfterburn1.eff.type = SDL_HAPTIC_SINE;
260                 pAfterburn1.eff.periodic.direction.type = SDL_HAPTIC_POLAR;
261                 pAfterburn1.eff.periodic.direction.dir[0] = 0;
262                 pAfterburn1.eff.periodic.length = SDL_HAPTIC_INFINITY;
263                 pAfterburn1.eff.periodic.period = 20;
264                 pAfterburn1.eff.periodic.magnitude = 0x3332;
265
266                 pAfterburn1.id = SDL_HapticNewEffect(haptic, &pAfterburn1.eff);
267
268                 if (pAfterburn1.id < 0) {
269                         mprintf(("    Afterburn effect 1 failed to load:\n      %s\n", SDL_GetError()));
270                 } else {
271                         pAfterburn1.loaded = 1;
272                 }
273
274                 // pAfterburn2
275                 memset(&pAfterburn2, 0, sizeof(haptic_effect_t));
276
277                 pAfterburn2.eff.type = SDL_HAPTIC_SINE;
278                 pAfterburn2.eff.periodic.direction.type = SDL_HAPTIC_POLAR;
279                 pAfterburn2.eff.periodic.direction.dir[0] = 9000;
280                 pAfterburn2.eff.periodic.length = 125;
281                 pAfterburn2.eff.periodic.period = 100;
282                 pAfterburn2.eff.periodic.magnitude = 0x1999;
283
284                 pAfterburn2.id = SDL_HapticNewEffect(haptic, &pAfterburn2.eff);
285
286                 if (pAfterburn2.id < 0) {
287                         mprintf(("    Afterburn effect 2 failed to load:\n      %s\n", SDL_GetError()));
288                 } else {
289                         pAfterburn2.loaded = 1;
290                 }
291         }
292
293
294 //      if (supported & SDL_HAPTIC_SQUARE) {
295         if (supported & SDL_HAPTIC_TRIANGLE) {
296                 // pDock
297                 memset(&pDock, 0, sizeof(haptic_effect_t));
298
299         //      pDock.eff.type = SDL_HAPTIC_SQUARE;
300                 pDock.eff.type = SDL_HAPTIC_TRIANGLE;
301                 pDock.eff.periodic.direction.type = SDL_HAPTIC_POLAR;
302                 pDock.eff.periodic.direction.dir[0] = 9000;
303                 pDock.eff.periodic.length = 125;
304                 pDock.eff.periodic.period = 100;
305                 pDock.eff.periodic.magnitude = 0x3332;
306
307                 pDock.id = SDL_HapticNewEffect(haptic, &pDock.eff);
308
309                 if (pDock.id < 0) {
310                         mprintf(("    Dock effect failed to load:\n      %s\n", SDL_GetError()));
311                 } else {
312                         pDock.loaded = 1;
313                 }
314         }
315 }
316
317 static bool joy_ff_can_play()
318 {
319         return (Joy_ff_enabled && Joy_ff_acquired);
320 }
321
322 static void joy_ff_update_effect(haptic_effect_t *eff, const char *name)
323 {
324         if ( !eff->loaded ) {
325                 return;
326         }
327
328         if ( SDL_HapticUpdateEffect(haptic, eff->id, &eff->eff) < 0 ) {
329                 mprintf(("HapticERROR:  Unable to update %s:\n  %s\n", name, SDL_GetError()));
330         }
331 }
332
333 static void joy_ff_start_effect(haptic_effect_t *eff, const char *name)
334 {
335         if ( !eff->loaded ) {
336                 return;
337         }
338
339 //      nprintf(("Joystick", "FF: Starting effect %s\n", name));
340
341         if ( SDL_HapticRunEffect(haptic, eff->id, 1) ) {
342                 mprintf(("HapticERROR:  Unable to run %s:\n  %s\n", name, SDL_GetError()));
343         }
344 }
345
346 void joy_ff_stop_effects()
347 {
348         if ( !Joy_ff_enabled ) {
349                 return;
350         }
351
352         SDL_HapticStopAll(haptic);
353 }
354
355 void joy_ff_mission_init(vector v)
356 {
357         v.xyz.z = 0.0f;
358
359         joy_ff_handling_scaler = (int) ((vm_vec_mag(&v) + 1.3f) * 5.0f);
360
361         Joy_ff_afterburning = 0;
362 }
363
364 void joy_reacquire_ff()
365 {
366         if ( !Joy_ff_enabled ) {
367                 return;
368         }
369
370         if (Joy_ff_acquired) {
371                 return;
372         }
373
374         joy_ff_start_effect(&pSpring, "Spring");
375
376         Joy_ff_acquired = 1;
377 }
378
379 void joy_unacquire_ff()
380 {
381         if ( !Joy_ff_enabled ) {
382                 return;
383         }
384
385         if ( !Joy_ff_acquired ) {
386                 return;
387         }
388
389         joy_ff_stop_effects();
390
391         Joy_ff_acquired = 0;
392 }
393
394 void joy_ff_play_vector_effect(vector *v, float scaler)
395 {
396         vector vf;
397         float x, y;
398
399         if ( !joy_ff_can_play() ) {
400                 return;
401         }
402
403 //      nprintf(("Joystick", "FF: vec = { %f, %f, %f } s = %f\n", v->xyz.x, v->xyz.y, v->xyz.z, scaler));
404         vm_vec_copy_scale(&vf, v, scaler);
405         x = vf.xyz.x;
406         vf.xyz.x = 0.0f;
407
408         if (vf.xyz.y + vf.xyz.z < 0.0f) {
409                 y = -vm_vec_mag(&vf);
410         } else {
411                 y = vm_vec_mag(&vf);
412         }
413
414         joy_ff_play_dir_effect(-x, -y);
415 }
416
417 void joy_ff_play_dir_effect(float x, float y)
418 {
419         int idegs, imag;
420         float degs;
421
422         if ( !joy_ff_can_play() ) {
423                 return;
424         }
425
426         // allow for at least one of the effects to work
427         if ( !pHitEffect1.loaded && !pHitEffect2.loaded ) {
428                 return;
429         }
430
431         if (joy_ff_effect_playing(&pHitEffect1) || joy_ff_effect_playing(&pHitEffect2)) {
432                 nprintf(("Joystick", "FF: HitEffect already playing.  Skipping\n"));
433                 return;
434         }
435
436         if (Joy_ff_directional_hit_effect_enabled) {
437                 if (x > 8000.0f) {
438                         x = 8000.0f;
439                 } else if (x < -8000.0f) {
440                         x = -8000.0f;
441                 }
442
443                 if (y > 8000.0f) {
444                         y = 8000.0f;
445                 } else if (y < -8000.0f) {
446                         y = -8000.0f;
447                 }
448
449                 imag = (int) fl_sqrt(x * x + y * y);
450                 if (imag > 10000) {
451                         imag = 10000;
452                 }
453
454                 degs = (float)atan2(x, y);
455                 idegs = (int) (degs * 18000.0f / PI) + 90;
456                 while (idegs < 0) {
457                         idegs += 36000;
458                 }
459
460                 while (idegs >= 36000) {
461                         idegs -= 36000;
462                 }
463
464                 if (pHitEffect1.loaded) {
465                         pHitEffect1.eff.constant.direction.dir[0] = idegs;
466                         pHitEffect1.eff.constant.level = (Sint16)(32767.0f * (imag / 10000.0f));
467
468                         joy_ff_update_effect(&pHitEffect1, "pHitEffect1");
469                 }
470
471                 idegs += 9000;
472                 if (idegs >= 36000)
473                         idegs -= 36000;
474
475                 if (pHitEffect2.loaded) {
476                         pHitEffect2.eff.periodic.direction.dir[0] = idegs;
477                         pHitEffect2.eff.periodic.magnitude = (Sint16)(32767.0f * (imag / 10000.0f));
478
479                         joy_ff_update_effect(&pHitEffect2, "pHitEffect2");
480                 }
481         }
482
483         joy_ff_start_effect(&pHitEffect1, "HitEffect1");
484         joy_ff_start_effect(&pHitEffect2, "HitEffect2");
485 }
486
487 void joy_ff_play_primary_shoot(int gain)
488 {
489         if ( !joy_ff_can_play() ) {
490                 return;
491         }
492
493         if ( !pShootEffect.loaded && !Joy_rumble ) {
494                 return;
495         }
496
497         if (gain < 1) {
498                 gain = 1;
499         } else if (gain > 10000) {
500                 gain = 10000;
501         }
502
503         if (pShootEffect.loaded) {
504                 SDL_HapticStopEffect(haptic, pShootEffect.id);
505
506                 static int primary_ff_level = 10000;
507
508                 if (gain != primary_ff_level) {
509                         pShootEffect.eff.periodic.magnitude = (Sint16)(32767.0f * (gain / 10000.0f));
510
511                         joy_ff_update_effect(&pShootEffect, "pShootEffect");
512
513                         primary_ff_level = gain;
514                 }
515
516                 joy_ff_start_effect(&pShootEffect, "ShootEffect");
517         } else if (Joy_rumble) {
518                 SDL_HapticRumblePlay(haptic, (gain / 10000.0f) * 0.5f, 100);
519         }
520 }
521
522 void joy_ff_play_secondary_shoot(int gain)
523 {
524         if ( !joy_ff_can_play() ) {
525                 return;
526         }
527
528         if ( !pSecShootEffect.loaded && !Joy_rumble ) {
529                 return;
530         }
531
532         gain = gain * 100 + 2500;
533
534         if (gain < 1) {
535                 gain = 1;
536         } else if (gain > 10000) {
537                 gain = 10000;
538         }
539
540         if (pSecShootEffect.loaded) {
541                 SDL_HapticStopEffect(haptic, pSecShootEffect.id);
542
543                 static int secondary_ff_level = 10000;
544
545                 if (gain != secondary_ff_level) {
546                         pSecShootEffect.eff.constant.level = (Sint16)(32767.0f * (gain / 10000.0f));
547                         pSecShootEffect.eff.constant.length = (150000 + gain * 25) / 1000;
548
549                         joy_ff_update_effect(&pSecShootEffect, "pSecShootEffect");
550
551                         secondary_ff_level = gain;
552                         nprintf(("Joystick", "FF: Secondary force = 0x%04x\n", pSecShootEffect.eff.constant.level));
553                 }
554
555                 joy_ff_start_effect(&pSecShootEffect, "SecShootEffect");
556         } else if (Joy_rumble) {
557                 SDL_HapticRumblePlay(haptic, (gain / 10000.0f) * 0.5f, (150000 + gain * 25) / 1000);
558         }
559 }
560
561 void joy_ff_adjust_handling(int speed)
562 {
563         int v;
564         short coeff = 0;
565
566         if ( !joy_ff_can_play() ) {
567                 return;
568         }
569
570         if ( !pSpring.loaded ) {
571                 return;
572         }
573
574         static int last_speed = -1000;
575
576         if (speed == last_speed) {
577                 return;
578         }
579
580         last_speed = speed;
581
582         v = speed * joy_ff_handling_scaler * 2 / 3;
583 //      v += joy_ff_handling_scaler * joy_ff_handling_scaler * 6 / 7 + 250;
584         v += joy_ff_handling_scaler * 45 - 500;
585
586         if (v < 0) {
587                 v = 0;
588         } else if (v > 10000) {
589                 v = 10000;
590         }
591
592         coeff = (short)(32767.0f * (v / 10000.0f));
593
594         for (int i = 0; i < SDL_HapticNumAxes(haptic); i++) {
595                 pSpring.eff.condition.right_coeff[i] = coeff;
596                 pSpring.eff.condition.left_coeff[i] = coeff;
597         }
598
599 //      nprintf(("Joystick", "FF: New handling force = 0x%04x\n", coeff));
600
601         joy_ff_update_effect(&pSpring, "pSpring");
602 }
603
604 static int joy_ff_effect_playing(haptic_effect_t *eff)
605 {
606         if ( !eff->loaded ) {
607                 return 0;
608         } else {
609                 return (SDL_HapticGetEffectStatus(haptic, eff->id) > 0);
610         }
611 }
612
613 void joy_ff_docked()
614 {
615         if ( !joy_ff_can_play() ) {
616                 return;
617         }
618
619         if ( !pDock.loaded ) {
620                 return;
621         }
622
623         SDL_HapticStopEffect(haptic, pDock.id);
624
625         pDock.eff.periodic.magnitude = 0x3332;
626
627         joy_ff_update_effect(&pDock, "pDock");
628
629         joy_ff_start_effect(&pDock, "Dock");
630 }
631
632 void joy_ff_play_reload_effect()
633 {
634         if ( !joy_ff_can_play() ) {
635                 return;
636         }
637
638         if ( !pDock.loaded ) {
639                 return;
640         }
641
642         SDL_HapticStopEffect(haptic, pDock.id);
643
644         pDock.eff.periodic.magnitude = 0x1999;
645
646         joy_ff_update_effect(&pDock, "pDock");
647
648         joy_ff_start_effect(&pDock, "Dock (Reload)");
649 }
650
651 void joy_ff_afterburn_on()
652 {
653         if ( !joy_ff_can_play() ) {
654                 return;
655         }
656
657         if (Joy_ff_afterburning) {
658                 return;
659         }
660
661         if ( !(pAfterburn1.loaded && pAfterburn2.loaded) ) {
662                 return;
663         }
664
665         SDL_HapticStopEffect(haptic, pAfterburn1.id);
666
667         pAfterburn1.eff.periodic.length = SDL_HAPTIC_INFINITY;
668         pAfterburn1.eff.periodic.magnitude = 0x3332;
669
670         joy_ff_update_effect(&pAfterburn1, "pAfterburn1");
671
672         SDL_HapticStopEffect(haptic, pAfterburn2.id);
673
674         pAfterburn2.eff.periodic.length = SDL_HAPTIC_INFINITY;
675         pAfterburn2.eff.periodic.magnitude = 0x1999;
676
677         joy_ff_update_effect(&pAfterburn2, "pAfterburn2");
678
679         joy_ff_start_effect(&pAfterburn1, "Afterburn1");
680         joy_ff_start_effect(&pAfterburn2, "Afterburn2");
681
682 //      nprintf(("Joystick", "FF: Afterburn started\n"));
683
684         Joy_ff_afterburning = 1;
685 }
686
687 void joy_ff_afterburn_off()
688 {
689         if ( !joy_ff_can_play() ) {
690                 return;
691         }
692
693         if ( !Joy_ff_afterburning ) {
694                 return;
695         }
696
697         if (pAfterburn1.loaded) {
698                 SDL_HapticStopEffect(haptic, pAfterburn1.id);
699         }
700
701         if (pAfterburn2.loaded) {
702                 SDL_HapticStopEffect(haptic, pAfterburn2.id);
703         }
704
705         Joy_ff_afterburning = 0;
706
707 //      nprintf(("Joystick", "FF: Afterburn stopped\n"));
708 }
709
710 void joy_ff_explode()
711 {
712         if ( !joy_ff_can_play() ) {
713                 return;
714         }
715
716         if (pAfterburn1.loaded) {
717                 SDL_HapticStopEffect(haptic, pAfterburn1.id);
718         }
719
720         if (pAfterburn2.loaded) {
721                 SDL_HapticStopEffect(haptic, pAfterburn2.id);
722         }
723
724         if (pShootEffect.loaded) {
725                 SDL_HapticStopEffect(haptic, pShootEffect.id);
726
727                 pShootEffect.eff.periodic.direction.dir[0] = 9000;
728                 pShootEffect.eff.periodic.length = 500;
729                 pShootEffect.eff.periodic.magnitude = 0x7FFF;
730                 pShootEffect.eff.periodic.fade_length = 500;
731
732                 joy_ff_update_effect(&pShootEffect, "pShootEffect");
733
734                 joy_ff_start_effect(&pShootEffect, "ShootEffect (Explode)");
735         } else if (Joy_rumble) {
736                 SDL_HapticRumblePlay(haptic, 0.75f, 500);
737         }
738
739         Joy_ff_afterburning = 0;
740 }
741
742 void joy_ff_fly_by(int mag)
743 {
744         int gain;
745
746         if ( !joy_ff_can_play() ) {
747                 return;
748         }
749
750         if (Joy_ff_afterburning) {
751                 return;
752         }
753
754         if ( !(pAfterburn1.loaded && pAfterburn2.loaded) ) {
755                 return;
756         }
757
758         gain = mag * 120 + 4000;
759
760         if (gain < 1) {
761                 gain = 1;
762         } else if (gain > 10000) {
763                 gain = 10000;
764         }
765
766         SDL_HapticStopEffect(haptic, pAfterburn1.id);
767
768         pAfterburn1.eff.periodic.length = (6000 * mag + 400000) / 1000;
769         pAfterburn1.eff.periodic.magnitude = (Sint16)(32767.0f * (gain / 10000.0f));
770
771         joy_ff_update_effect(&pAfterburn1, "pAfterburn1");
772
773         SDL_HapticStopEffect(haptic, pAfterburn2.id);
774
775         pAfterburn2.eff.periodic.length = (6000 * mag + 400000) / 1000;
776         pAfterburn2.eff.periodic.magnitude = (Sint16)(32767.0f * (gain / 10000.0f));
777
778         joy_ff_update_effect(&pAfterburn2, "pAfterburn2");
779
780         joy_ff_start_effect(&pAfterburn1, "Afterburn1 (Fly by)");
781         joy_ff_start_effect(&pAfterburn2, "Afterburn2 (Fly by)");
782 }
783
784 void joy_ff_deathroll()
785 {
786         if ( !joy_ff_can_play() ) {
787                 return;
788         }
789
790         if (pAfterburn1.loaded && pAfterburn2.loaded) {
791                 SDL_HapticStopEffect(haptic, pAfterburn1.id);
792
793                 pAfterburn1.eff.periodic.length = SDL_HAPTIC_INFINITY;
794                 pAfterburn1.eff.periodic.period = 200;
795                 pAfterburn1.eff.periodic.magnitude = 0x7FFF;
796                 pAfterburn1.eff.periodic.attack_length = 200;
797
798                 joy_ff_update_effect(&pAfterburn1, "pAfterburn1");
799
800                 SDL_HapticStopEffect(haptic, pAfterburn2.id);
801
802                 pAfterburn2.eff.periodic.length = SDL_HAPTIC_INFINITY;
803                 pAfterburn2.eff.periodic.period = 200;
804                 pAfterburn2.eff.periodic.magnitude = 0x7FFF;
805                 pAfterburn2.eff.periodic.attack_length = 200;
806
807                 joy_ff_update_effect(&pAfterburn2, "pAfterburn2");
808
809                 joy_ff_start_effect(&pAfterburn1, "Afterburn1 (Death Roll)");
810                 joy_ff_start_effect(&pAfterburn2, "Afterburn2 (Death Roll)");
811
812                 Joy_ff_afterburning = 1;
813         }
814 }