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