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