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