]> icculus.org git repositories - taylor/freespace2.git/blob - src/io/joy_ff.cpp
try to avoid too much rumble
[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_CONSTANT) {
144                 // pHitEffect1
145                 memset(&pHitEffect1, 0, sizeof(haptic_effect_t));
146
147                 pHitEffect1.eff.type = SDL_HAPTIC_CONSTANT;
148                 pHitEffect1.eff.constant.direction.type = SDL_HAPTIC_POLAR;
149                 pHitEffect1.eff.constant.direction.dir[0] = 0;
150                 pHitEffect1.eff.constant.length = 300;
151                 pHitEffect1.eff.constant.level = 0x7FFF;
152                 pHitEffect1.eff.constant.attack_length = 0;
153                 pHitEffect1.eff.constant.attack_level = 0x7FFF;
154                 pHitEffect1.eff.constant.fade_length = 120;
155                 pHitEffect1.eff.constant.fade_level = 1;
156
157                 pHitEffect1.id = SDL_HapticNewEffect(haptic, &pHitEffect1.eff);
158
159                 if (pHitEffect1.id < 0) {
160                         mprintf(("    Hit effect 1 failed to load:\n      %s\n", SDL_GetError()));
161                 } else {
162                         pHitEffect1.loaded = 1;
163                 }
164         }
165
166         if (supported & SDL_HAPTIC_SINE) {
167                 // pHitEffect2
168                 memset(&pHitEffect2, 0, sizeof(haptic_effect_t));
169
170                 pHitEffect2.eff.type = SDL_HAPTIC_SINE;
171                 pHitEffect2.eff.periodic.direction.type = SDL_HAPTIC_POLAR;
172                 pHitEffect2.eff.periodic.direction.dir[0] = 9000;
173                 pHitEffect2.eff.periodic.length = 300;
174                 pHitEffect2.eff.periodic.period = 100;
175                 pHitEffect2.eff.periodic.magnitude = 0x7FFF;
176                 pHitEffect2.eff.periodic.attack_length = 100;
177                 pHitEffect2.eff.periodic.fade_length = 100;
178
179                 pHitEffect2.id = SDL_HapticNewEffect(haptic, &pHitEffect2.eff);
180
181                 if (pHitEffect2.id < 0) {
182                         mprintf(("    Hit effect 2 failed to load:\n      %s\n", SDL_GetError()));
183                 } else {
184                         pHitEffect2.loaded = 1;
185                 }
186         }
187
188         if (supported & SDL_HAPTIC_SAWTOOTHDOWN) {
189                 // pShootEffect
190                 memset(&pShootEffect, 0, sizeof(haptic_effect_t));
191
192                 pShootEffect.eff.type = SDL_HAPTIC_SAWTOOTHDOWN;
193                 pShootEffect.eff.periodic.direction.type = SDL_HAPTIC_POLAR;
194                 pShootEffect.eff.periodic.direction.dir[0] = 0;
195                 pShootEffect.eff.periodic.length = 160;
196                 pShootEffect.eff.periodic.period = 20;
197                 pShootEffect.eff.periodic.magnitude = 0x7FFF;
198                 pShootEffect.eff.periodic.fade_length = 120;
199
200                 pShootEffect.id = SDL_HapticNewEffect(haptic, &pShootEffect.eff);
201
202                 if (pShootEffect.id < 0) {
203                         mprintf(("    Fire primary effect failed to load:\n      %s\n", SDL_GetError()));
204                 } else {
205                         pShootEffect.loaded = 1;
206                 }
207         }
208
209         if (supported & SDL_HAPTIC_CONSTANT) {
210                 // pSecShootEffect
211                 memset(&pSecShootEffect, 0, sizeof(haptic_effect_t));
212
213                 pSecShootEffect.eff.type = SDL_HAPTIC_CONSTANT;
214                 pSecShootEffect.eff.constant.direction.type = SDL_HAPTIC_POLAR;
215                 pSecShootEffect.eff.constant.direction.dir[0] = 0;
216                 pSecShootEffect.eff.constant.length = 200;
217                 pSecShootEffect.eff.constant.level = 0x7FFF;
218                 pSecShootEffect.eff.constant.attack_length = 50;
219                 pSecShootEffect.eff.constant.attack_level = 0x7FFF;
220                 pSecShootEffect.eff.constant.fade_length = 100;
221                 pSecShootEffect.eff.constant.fade_level = 1;
222
223                 pSecShootEffect.id = SDL_HapticNewEffect(haptic, &pSecShootEffect.eff);
224
225                 if (pSecShootEffect.id < 0) {
226                         mprintf(("    Fire secondary effect failed to load:\n      %s\n", SDL_GetError()));
227                 } else {
228                         pSecShootEffect.loaded = 1;
229                 }
230         }
231
232         if (supported & SDL_HAPTIC_SPRING) {
233                 // pSpring
234                 memset(&pSpring, 0, sizeof(haptic_effect_t));
235
236                 pSpring.eff.type = SDL_HAPTIC_SPRING;
237                 pSpring.eff.condition.length = SDL_HAPTIC_INFINITY;
238
239                 for (int i = 0; i < SDL_HapticNumAxes(haptic); i++) {
240                         pSpring.eff.condition.right_sat[i] = 0x7FFF;
241                         pSpring.eff.condition.left_sat[i] = 0x7FFF;
242                         pSpring.eff.condition.right_coeff[i] = 0x147;
243                         pSpring.eff.condition.left_coeff[i] = 0x147;
244                 }
245
246                 pSpring.id = SDL_HapticNewEffect(haptic, &pSpring.eff);
247
248                 if (pSpring.id < 0) {
249                         mprintf(("    Spring effect failed to load:\n      %s\n", SDL_GetError()));
250                 } else {
251                         pSpring.loaded = 1;
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         joy_ff_adjust_handling(0);
364
365         if ( !joy_ff_effect_playing(&pSpring) ) {
366                 joy_ff_start_effect(&pSpring, "Spring");
367         }
368
369         // reset afterburn effects to default values
370
371         pAfterburn1.eff.periodic.length = SDL_HAPTIC_INFINITY;
372         pAfterburn1.eff.periodic.period = 20;
373         pAfterburn1.eff.periodic.magnitude = 0x3332;
374         pAfterburn1.eff.periodic.attack_length = 0;
375
376         joy_ff_update_effect(&pAfterburn1, "pAfterburn1 (init)");
377
378         pAfterburn2.eff.periodic.length = SDL_HAPTIC_INFINITY;
379         pAfterburn2.eff.periodic.period = 100;
380         pAfterburn2.eff.periodic.magnitude = 0x1999;
381         pAfterburn2.eff.periodic.attack_length = 0;
382
383         joy_ff_update_effect(&pAfterburn2, "pAfterburn2 (init)");
384
385         // reset primary shoot effect to default values
386
387         pShootEffect.eff.periodic.direction.dir[0] = 0;
388         pShootEffect.eff.periodic.length = 160;
389         pShootEffect.eff.periodic.fade_length = 120;
390
391         joy_ff_update_effect(&pShootEffect, "pShootEffect (init)");
392 }
393
394 void joy_reacquire_ff()
395 {
396         if ( !Joy_ff_enabled ) {
397                 return;
398         }
399
400         if (Joy_ff_acquired) {
401                 return;
402         }
403
404         joy_ff_start_effect(&pSpring, "Spring");
405
406         Joy_ff_acquired = 1;
407 }
408
409 void joy_unacquire_ff()
410 {
411         if ( !Joy_ff_enabled ) {
412                 return;
413         }
414
415         if ( !Joy_ff_acquired ) {
416                 return;
417         }
418
419         joy_ff_stop_effects();
420
421         Joy_ff_acquired = 0;
422 }
423
424 void joy_ff_play_vector_effect(vector *v, float scaler)
425 {
426         vector vf;
427         float x, y;
428
429         if ( !joy_ff_can_play() ) {
430                 return;
431         }
432
433 //      nprintf(("Joystick", "FF: vec = { %f, %f, %f } s = %f\n", v->xyz.x, v->xyz.y, v->xyz.z, scaler));
434         vm_vec_copy_scale(&vf, v, scaler);
435         x = vf.xyz.x;
436         vf.xyz.x = 0.0f;
437
438         if (vf.xyz.y + vf.xyz.z < 0.0f) {
439                 y = -vm_vec_mag(&vf);
440         } else {
441                 y = vm_vec_mag(&vf);
442         }
443
444         joy_ff_play_dir_effect(-x, -y);
445 }
446
447 void joy_ff_play_dir_effect(float x, float y)
448 {
449         int idegs, imag;
450         float degs;
451
452         if ( !joy_ff_can_play() ) {
453                 return;
454         }
455
456         // allow for at least one of the effects to work
457         if ( !pHitEffect1.loaded && !pHitEffect2.loaded ) {
458                 return;
459         }
460
461         if (joy_ff_effect_playing(&pHitEffect1) || joy_ff_effect_playing(&pHitEffect2)) {
462                 nprintf(("Joystick", "FF: HitEffect already playing.  Skipping\n"));
463                 return;
464         }
465
466         if (Joy_ff_directional_hit_effect_enabled) {
467                 if (x > 8000.0f) {
468                         x = 8000.0f;
469                 } else if (x < -8000.0f) {
470                         x = -8000.0f;
471                 }
472
473                 if (y > 8000.0f) {
474                         y = 8000.0f;
475                 } else if (y < -8000.0f) {
476                         y = -8000.0f;
477                 }
478
479                 imag = (int) fl_sqrt(x * x + y * y);
480                 if (imag > 10000) {
481                         imag = 10000;
482                 }
483
484                 degs = (float)atan2(x, y);
485                 idegs = (int) (degs * 18000.0f / PI) + 90;
486                 while (idegs < 0) {
487                         idegs += 36000;
488                 }
489
490                 while (idegs >= 36000) {
491                         idegs -= 36000;
492                 }
493
494                 if (pHitEffect1.loaded) {
495                         pHitEffect1.eff.constant.direction.dir[0] = idegs;
496                         pHitEffect1.eff.constant.level = (Sint16)(32767.0f * (imag / 10000.0f));
497
498                         joy_ff_update_effect(&pHitEffect1, "pHitEffect1");
499                 }
500
501                 idegs += 9000;
502                 if (idegs >= 36000)
503                         idegs -= 36000;
504
505                 if (pHitEffect2.loaded) {
506                         pHitEffect2.eff.periodic.direction.dir[0] = idegs;
507                         pHitEffect2.eff.periodic.magnitude = (Sint16)(32767.0f * (imag / 10000.0f));
508
509                         joy_ff_update_effect(&pHitEffect2, "pHitEffect2");
510                 }
511         }
512
513         joy_ff_start_effect(&pHitEffect1, "HitEffect1");
514         joy_ff_start_effect(&pHitEffect2, "HitEffect2");
515 }
516
517 void joy_ff_play_primary_shoot(int gain)
518 {
519         if ( !joy_ff_can_play() ) {
520                 return;
521         }
522
523         if ( !pShootEffect.loaded && !Joy_rumble ) {
524                 return;
525         }
526
527         CAP(gain, 1, 10000);
528
529         if (pShootEffect.loaded) {
530                 SDL_HapticStopEffect(haptic, pShootEffect.id);
531
532                 static int primary_ff_level = 10000;
533
534                 if (gain != primary_ff_level) {
535                         pShootEffect.eff.periodic.magnitude = (Sint16)(32767.0f * (gain / 10000.0f));
536
537                         joy_ff_update_effect(&pShootEffect, "pShootEffect");
538
539                         primary_ff_level = gain;
540                 }
541
542                 joy_ff_start_effect(&pShootEffect, "ShootEffect");
543         } else if (Joy_rumble) {
544                 static int rumble_timeout = 1;
545
546                 if ( timestamp_elapsed(rumble_timeout) ) {
547                         SDL_HapticRumblePlay(haptic, gain / 10000.0f, 100);
548
549                         rumble_timeout = timestamp(100);
550                 }
551         }
552 }
553
554 void joy_ff_play_secondary_shoot(int gain)
555 {
556         if ( !joy_ff_can_play() ) {
557                 return;
558         }
559
560         if ( !pSecShootEffect.loaded && !Joy_rumble ) {
561                 return;
562         }
563
564         gain = gain * 100 + 2500;
565
566         CAP(gain, 1, 10000);
567
568         if (pSecShootEffect.loaded) {
569                 SDL_HapticStopEffect(haptic, pSecShootEffect.id);
570
571                 static int secondary_ff_level = 10000;
572
573                 if (gain != secondary_ff_level) {
574                         pSecShootEffect.eff.constant.level = (Sint16)(32767.0f * (gain / 10000.0f));
575                         pSecShootEffect.eff.constant.length = (150000 + gain * 25) / 1000;
576
577                         joy_ff_update_effect(&pSecShootEffect, "pSecShootEffect");
578
579                         secondary_ff_level = gain;
580                         nprintf(("Joystick", "FF: Secondary force = 0x%04x\n", pSecShootEffect.eff.constant.level));
581                 }
582
583                 joy_ff_start_effect(&pSecShootEffect, "SecShootEffect");
584         } else if (Joy_rumble) {
585                 static int rumble_timeout = 1;
586
587                 if ( timestamp_elapsed(rumble_timeout) ) {
588                         int duration = (150000 + gain * 25) / 1000;
589
590                         SDL_HapticRumblePlay(haptic, gain / 10000.0f, duration);
591
592                         rumble_timeout = timestamp(duration);
593                 }
594         }
595 }
596
597 void joy_ff_adjust_handling(int speed)
598 {
599         int v;
600         short coeff = 0;
601
602         if ( !joy_ff_can_play() ) {
603                 return;
604         }
605
606         if ( !pSpring.loaded ) {
607                 return;
608         }
609
610         static int last_speed = -1000;
611
612         if (speed == last_speed) {
613                 return;
614         }
615
616         last_speed = speed;
617
618         v = speed * joy_ff_handling_scaler * 2 / 3;
619 //      v += joy_ff_handling_scaler * joy_ff_handling_scaler * 6 / 7 + 250;
620         v += joy_ff_handling_scaler * 45 - 500;
621
622         CAP(v, 0, 10000);
623
624         coeff = (short)(32767.0f * (v / 10000.0f));
625
626         for (int i = 0; i < SDL_HapticNumAxes(haptic); i++) {
627                 pSpring.eff.condition.right_coeff[i] = coeff;
628                 pSpring.eff.condition.left_coeff[i] = coeff;
629         }
630
631 //      nprintf(("Joystick", "FF: New handling force = 0x%04x\n", coeff));
632
633         joy_ff_update_effect(&pSpring, "pSpring");
634 }
635
636 static int joy_ff_effect_playing(haptic_effect_t *eff)
637 {
638         if ( !eff->loaded ) {
639                 return 0;
640         } else {
641                 return (SDL_HapticGetEffectStatus(haptic, eff->id) > 0);
642         }
643 }
644
645 void joy_ff_docked()
646 {
647         if ( !joy_ff_can_play() ) {
648                 return;
649         }
650
651         if ( !pDock.loaded ) {
652                 return;
653         }
654
655         SDL_HapticStopEffect(haptic, pDock.id);
656
657         pDock.eff.periodic.magnitude = 0x3332;
658
659         joy_ff_update_effect(&pDock, "pDock");
660
661         joy_ff_start_effect(&pDock, "Dock");
662 }
663
664 void joy_ff_play_reload_effect()
665 {
666         if ( !joy_ff_can_play() ) {
667                 return;
668         }
669
670         if ( !pDock.loaded ) {
671                 return;
672         }
673
674         SDL_HapticStopEffect(haptic, pDock.id);
675
676         pDock.eff.periodic.magnitude = 0x1999;
677
678         joy_ff_update_effect(&pDock, "pDock (reload)");
679
680         joy_ff_start_effect(&pDock, "Dock (Reload)");
681 }
682
683 void joy_ff_afterburn_on()
684 {
685         if ( !joy_ff_can_play() ) {
686                 return;
687         }
688
689         if (Joy_ff_afterburning) {
690                 return;
691         }
692
693         if ( !(pAfterburn1.loaded && pAfterburn2.loaded) ) {
694                 return;
695         }
696
697         SDL_HapticStopEffect(haptic, pAfterburn1.id);
698
699         pAfterburn1.eff.periodic.length = SDL_HAPTIC_INFINITY;
700         pAfterburn1.eff.periodic.magnitude = 0x3332;
701
702         joy_ff_update_effect(&pAfterburn1, "pAfterburn1");
703
704         SDL_HapticStopEffect(haptic, pAfterburn2.id);
705
706         pAfterburn2.eff.periodic.length = SDL_HAPTIC_INFINITY;
707         pAfterburn2.eff.periodic.magnitude = 0x1999;
708
709         joy_ff_update_effect(&pAfterburn2, "pAfterburn2");
710
711         joy_ff_start_effect(&pAfterburn1, "Afterburn1");
712         joy_ff_start_effect(&pAfterburn2, "Afterburn2");
713
714 //      nprintf(("Joystick", "FF: Afterburn started\n"));
715
716         Joy_ff_afterburning = 1;
717 }
718
719 void joy_ff_afterburn_off()
720 {
721         if ( !joy_ff_can_play() ) {
722                 return;
723         }
724
725         if ( !Joy_ff_afterburning ) {
726                 return;
727         }
728
729         if (pAfterburn1.loaded) {
730                 SDL_HapticStopEffect(haptic, pAfterburn1.id);
731         }
732
733         if (pAfterburn2.loaded) {
734                 SDL_HapticStopEffect(haptic, pAfterburn2.id);
735         }
736
737         Joy_ff_afterburning = 0;
738
739 //      nprintf(("Joystick", "FF: Afterburn stopped\n"));
740 }
741
742 void joy_ff_explode()
743 {
744         if ( !joy_ff_can_play() ) {
745                 return;
746         }
747
748         if (pAfterburn1.loaded) {
749                 SDL_HapticStopEffect(haptic, pAfterburn1.id);
750         }
751
752         if (pAfterburn2.loaded) {
753                 SDL_HapticStopEffect(haptic, pAfterburn2.id);
754         }
755
756         if (pShootEffect.loaded) {
757                 SDL_HapticStopEffect(haptic, pShootEffect.id);
758
759                 pShootEffect.eff.periodic.direction.dir[0] = 9000;
760                 pShootEffect.eff.periodic.length = 500;
761                 pShootEffect.eff.periodic.magnitude = 0x7FFF;
762                 pShootEffect.eff.periodic.fade_length = 500;
763
764                 joy_ff_update_effect(&pShootEffect, "pShootEffect (explode)");
765
766                 joy_ff_start_effect(&pShootEffect, "ShootEffect (Explode)");
767         } else if (Joy_rumble) {
768                 static int rumble_timeout = 1;
769
770                 if ( timestamp_elapsed(rumble_timeout) ) {
771                         SDL_HapticRumblePlay(haptic, 1.0f, 500);
772
773                         rumble_timeout = timestamp(500);
774                 }
775         }
776
777         Joy_ff_afterburning = 0;
778 }
779
780 void joy_ff_fly_by(int mag)
781 {
782         int gain;
783
784         if ( !joy_ff_can_play() ) {
785                 return;
786         }
787
788         if (Joy_ff_afterburning) {
789                 return;
790         }
791
792         if ( !(pAfterburn1.loaded && pAfterburn2.loaded) ) {
793                 return;
794         }
795
796         gain = mag * 120 + 4000;
797
798         CAP(gain, 1, 10000);
799
800         SDL_HapticStopEffect(haptic, pAfterburn1.id);
801
802         pAfterburn1.eff.periodic.length = (6000 * mag + 400000) / 1000;
803         pAfterburn1.eff.periodic.magnitude = (Sint16)(26212.0f * (gain / 10000.0f));
804
805         joy_ff_update_effect(&pAfterburn1, "pAfterburn1 (flyby)");
806
807         SDL_HapticStopEffect(haptic, pAfterburn2.id);
808
809         pAfterburn2.eff.periodic.length = (6000 * mag + 400000) / 1000;
810         pAfterburn2.eff.periodic.magnitude = (Sint16)(13106.0f * (gain / 10000.0f));
811
812         joy_ff_update_effect(&pAfterburn2, "pAfterburn2 (flyby)");
813
814         joy_ff_start_effect(&pAfterburn1, "Afterburn1 (Fly by)");
815         joy_ff_start_effect(&pAfterburn2, "Afterburn2 (Fly by)");
816 }
817
818 void joy_ff_deathroll()
819 {
820         if ( !joy_ff_can_play() ) {
821                 return;
822         }
823
824         if (pAfterburn1.loaded && pAfterburn2.loaded) {
825                 SDL_HapticStopEffect(haptic, pAfterburn1.id);
826
827                 pAfterburn1.eff.periodic.length = SDL_HAPTIC_INFINITY;
828                 pAfterburn1.eff.periodic.period = 200;
829                 pAfterburn1.eff.periodic.magnitude = 0x7FFF;
830                 pAfterburn1.eff.periodic.attack_length = 200;
831
832                 joy_ff_update_effect(&pAfterburn1, "pAfterburn1 (deathroll)");
833
834                 SDL_HapticStopEffect(haptic, pAfterburn2.id);
835
836                 pAfterburn2.eff.periodic.length = SDL_HAPTIC_INFINITY;
837                 pAfterburn2.eff.periodic.period = 200;
838                 pAfterburn2.eff.periodic.magnitude = 0x7FFF;
839                 pAfterburn2.eff.periodic.attack_length = 200;
840
841                 joy_ff_update_effect(&pAfterburn2, "pAfterburn2 (deathroll)");
842
843                 joy_ff_start_effect(&pAfterburn1, "Afterburn1 (Death Roll)");
844                 joy_ff_start_effect(&pAfterburn2, "Afterburn2 (Death Roll)");
845
846                 Joy_ff_afterburning = 1;
847         }
848 }