1 /* bzflag
2  * Copyright (c) 1993-2021 Tim Riker
3  *
4  * This package is free software;  you can redistribute it and/or
5  * modify it under the terms of the license found in the file
6  * named COPYING that should have accompanied this file.
7  *
8  * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
9  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
10  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
11  */
12 
13 /* common headers */
14 #include "common.h"
15 
16 /* interface header */
17 #include "DXJoystick.h"
18 
19 #pragma comment(lib, "dinput8.lib")
20 #pragma comment(lib, "dxguid.lib")
21 
22 /* system headers */
23 #include <vector>
24 #include <string>
25 #include <map>
26 #include <stdlib.h>
27 
28 /* local impl. headers */
29 #ifdef HAVE_SDL
30 #  include "SDL2Window.h"
31 #else
32 #  include "WinWindow.h"
33 #endif
34 #include "ErrorHandler.h"
35 #include "TextUtils.h"
36 
37 std::vector<DIDEVICEINSTANCE> DXJoystick::devices;
38 std::map<std::string, LPDIRECTINPUTEFFECT> DXJoystick::effectDatabase;
39 
DXJoystick()40 DXJoystick::DXJoystick() : device(NULL)
41 {
42     HINSTANCE hinst = GetModuleHandle(NULL);
43     HRESULT success = DirectInput8Create(hinst, DIRECTINPUT_VERSION, IID_IDirectInput8,
44                                          (void**)&directInput, NULL);
45 
46     if (success != DI_OK)
47     {
48         DXError("Could not initialize DirectInput", success);
49         return;
50     }
51     numberOfHats = 0;
52     enumerateDevices();
53 }
54 
~DXJoystick()55 DXJoystick::~DXJoystick()
56 {
57     // unacquire the joystick
58     if (device)
59         device->Unacquire();
60 
61     // release DX objects
62     if (device)
63     {
64         device->Release();
65         device = NULL;
66     }
67     if (directInput)
68     {
69         directInput->Release();
70         directInput = NULL;
71     }
72 }
73 
EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE * pdidoi,VOID * pContext)74 BOOL CALLBACK EnumObjectsCallback( const DIDEVICEOBJECTINSTANCE* pdidoi, VOID* pContext )
75 {
76     DXJoystick *stick = ( DXJoystick * )pContext;
77 
78     if ( pdidoi->guidType == GUID_POV )
79         stick->numberOfHats++;
80 
81     return DIENUM_CONTINUE;
82 }
83 
initJoystick(const char * joystickName)84 void          DXJoystick::initJoystick(const char* joystickName)
85 {
86     // turn it off
87     if (!joystickName || strcasecmp(joystickName, "off") == 0)
88     {
89         device = NULL;
90         return;
91     }
92 
93     /*
94      * Find this device, and try to initialize it.
95      */
96     GUID thisDevice;
97     for (unsigned int i = 0; i < devices.size(); i++)
98     {
99         if (strcmp(joystickName, devices[i].tszProductName) == 0)
100         {
101             thisDevice = devices[i].guidInstance;
102             break;
103         }
104     }
105     HRESULT success = directInput->CreateDevice(thisDevice,&device, NULL);
106 
107     if (success != DI_OK)
108     {
109         DXError("Could not initialize device", success);
110         return;
111     }
112 
113     /*
114      * Set device cooperation level - all input on this device goes to BZFlag,
115      * because we're greedy.
116      */
117 #ifdef HAVE_SDL
118     success = device->SetCooperativeLevel(SDLWindow::getHandle(),
119                                           DISCL_BACKGROUND | DISCL_EXCLUSIVE);
120 #else
121     success = device->SetCooperativeLevel(WinWindow::getHandle(),
122                                           DISCL_BACKGROUND | DISCL_EXCLUSIVE);
123 #endif
124 
125     if (success != DI_OK)
126     {
127         // couldn't grab device, what to do now?
128         DXError("Could not set exclusive mode", success);
129         device = NULL;
130         return;
131     }
132 
133 
134     // enumerate the axes and stuff
135     success = device->EnumObjects(EnumObjectsCallback,(VOID*)this, DIDFT_POV);
136 
137     if (success != DI_OK)
138     {
139         // couldn't grab device, what to do now?
140         DXError("Could not enumerate options", success);
141         device = NULL;
142         return;
143     }
144 
145     /*
146      * Set the device data format.  We want buttons and axis, so we'll use a
147      * predefined Joystick structure.
148      */
149 
150     success = device->SetDataFormat(&c_dfDIJoystick);
151 
152     if (success != DI_OK)
153     {
154         // couldn't set data format, what to do now?
155         DXError("Could not set data format", success);
156         device = NULL;
157         return;
158     }
159 
160     /*
161      * Set data ranges for all axes, and find out which ones succeeded.
162      * This has the side effect of ensuring that the joystick has at least two axes.
163      */
164 
165     // we assume the presence of an X and Y axis and bail if there's not one
166     axes["X"]  = true;
167     xAxis = "X";
168     axes["Y"]  = true;
169     yAxis = "Y";
170     // the rest we assume don't exist, and correct ourselves if we're wrong
171     axes["Z"]  = false;
172     axes["Rx"] = false;
173     axes["Ry"] = false;
174     axes["Rz"] = false;
175     axes["Slider1"] = false;
176     axes["Slider2"] = false;
177 
178     DIPROPRANGE range;
179     range.diph.dwSize       = sizeof(range);
180     range.diph.dwHeaderSize = sizeof(range.diph);
181     range.diph.dwHow    = DIPH_BYOFFSET;
182     range.lMin          = -1000;
183     range.lMax          = +1000;
184 
185     range.diph.dwObj = DIJOFS_X;
186     success = device->SetProperty(DIPROP_RANGE, &range.diph);
187     if (success != DI_OK)
188     {
189         // couldn't set x axis range, what to do now?
190         DXError("Could not set X-axis range", success);
191         device = NULL;
192         return;
193     }
194 
195     range.diph.dwObj = DIJOFS_Y;
196     success = device->SetProperty(DIPROP_RANGE, &range.diph);
197     if (success != DI_OK)
198     {
199         // check out the sliders and see if we can map one of them to Y
200         // this little trick should allow most wheels to work out of the box
201         range.diph.dwObj = DIJOFS_SLIDER(0);
202         success = device->SetProperty(DIPROP_RANGE, &range.diph);
203         if (success == DI_OK)
204             yAxis = "Slider 1";
205         else
206         {
207             // couldn't set y axis range, what to do now?
208             DXError("Could not set Y-axis range", success);
209             device = NULL;
210             return;
211         }
212     }
213 
214     range.diph.dwObj = DIJOFS_Z;
215     success = device->SetProperty(DIPROP_RANGE, &range.diph);
216     if (success == DI_OK)
217         axes["Z"] = true;
218 
219     range.diph.dwObj = DIJOFS_RX;
220     success = device->SetProperty(DIPROP_RANGE, &range.diph);
221     if (success == DI_OK)
222         axes["Rx"] = true;
223 
224     range.diph.dwObj = DIJOFS_RY;
225     success = device->SetProperty(DIPROP_RANGE, &range.diph);
226     if (success == DI_OK)
227         axes["Ry"] = true;
228 
229     range.diph.dwObj = DIJOFS_RZ;
230     success = device->SetProperty(DIPROP_RANGE, &range.diph);
231     if (success == DI_OK)
232         axes["Rz"] = true;
233 
234     range.diph.dwObj = DIJOFS_SLIDER(0);
235     success = device->SetProperty(DIPROP_RANGE, &range.diph);
236     if (success == DI_OK)
237         axes["Slider 1"] = true;
238 
239     range.diph.dwObj = DIJOFS_SLIDER(1);
240     success = device->SetProperty(DIPROP_RANGE, &range.diph);
241     if (success == DI_OK)
242         axes["Slider 2"] = true;
243 
244     hataxes.assign(numberOfHats * 2, 0); // two axes each
245 
246     /*
247      * Acquire the device so that we can get input from it.
248      */
249 
250     reaquireDevice();
251 }
252 
joystick() const253 bool          DXJoystick::joystick() const
254 {
255     return (device != NULL);
256 }
257 
getJoy(int & x,int & y)258 void          DXJoystick::getJoy(int& x, int& y)
259 {
260     if (!device) return;
261 
262     DIJOYSTATE state = pollDevice();
263 
264     if (xAxis == "X")  x = state.lX;
265     else if (xAxis == "Y")  x = state.lY;
266     else if (xAxis == "Z")  x = state.lZ;
267     else if (xAxis == "Rx") x = state.lRx;
268     else if (xAxis == "Ry") x = state.lRy;
269     else if (xAxis == "Rz") x = state.lRz;
270     else if (xAxis == "Slider 1") x = state.rglSlider[0];
271     else if (xAxis == "Slider 2") x = state.rglSlider[1];
272 
273     if (yAxis == "X")  y = state.lX;
274     else if (yAxis == "Y")  y = state.lY;
275     else if (yAxis == "Z")  y = state.lZ;
276     else if (yAxis == "Rx") y = state.lRx;
277     else if (yAxis == "Ry") y = state.lRy;
278     else if (yAxis == "Rz") y = state.lRz;
279     else if (yAxis == "Slider 1") y = state.rglSlider[0];
280     else if (yAxis == "Slider 2") y = state.rglSlider[1];
281 
282     // ballistics
283     x = (x * abs(x)) / 1000;
284     y = (y * abs(y)) / 1000;
285 
286     return;
287 }
288 
getJoyButtons()289 unsigned long DXJoystick::getJoyButtons()
290 {
291     if (!device) return 0;
292 
293     DIJOYSTATE state = pollDevice();
294 
295     unsigned long buttons = 0;
296 
297     for (int i = 0; i < 32; i++)
298     {
299         if (state.rgbButtons[i] & 0x80)
300             buttons |= (1 << i);
301     }
302 
303     return buttons;
304 }
305 
getNumHats()306 int    DXJoystick::getNumHats()
307 {
308     return numberOfHats;
309 }
310 
getJoyHat(int hat,float & hatX,float & hatY)311 void      DXJoystick::getJoyHat(int hat, float &hatX, float &hatY)
312 {
313     DIJOYSTATE state = pollDevice();
314 
315     for (int i = 0; i < numberOfHats; i++)
316     {
317         DWORD hatPos = state.rgdwPOV[i];
318 
319         float hatX = 0;
320         float hatY = 0;
321         BOOL hatCentered = (LOWORD(hatPos) == 0xFFFF);
322         if (hatCentered)
323         {
324             hataxes[i * 2]     = hatX = 0;
325             hataxes[i * 2 + 1] = hatY = 0;
326         }
327         else
328         {
329             // hatPos is indicated clockwise from north, we transform it so it
330             // can be fed into sinf() and cosf() which start counting from east
331             float angle = (float)hatPos/100.0f - 90;
332 
333             hataxes[i * 2]     = hatX = cosf(angle * ((float)M_PI/180.0f));
334             hataxes[i * 2 + 1] = hatY = sinf(angle * ((float)M_PI/180.0f));
335         }
336     }
337 
338     hatX = hatY = 0;
339     if (!device) return;
340     if (hat >= numberOfHats) return;
341     hatX = hataxes[hat * 2];
342     hatY = hataxes[hat * 2 + 1];
343 }
344 
pollDevice()345 DIJOYSTATE    DXJoystick::pollDevice()
346 {
347     DIJOYSTATE state;
348 
349     HRESULT success = device->Poll();
350     // umm, ignore that result...yeah
351 
352     success = device->GetDeviceState(sizeof(DIJOYSTATE), &state);
353     if (success != DI_OK)
354     {
355         // got no state, what's wrong?
356         DXError("Acquisition succeeded, but could not get joystick status", success);
357     }
358 
359     return state;
360 }
361 
getJoyDevices(std::vector<std::string> & list) const362 void          DXJoystick::getJoyDevices(std::vector<std::string> &list) const
363 {
364     for (unsigned int i = 0; i < devices.size(); i++)
365         list.push_back(devices[i].tszProductName);
366 }
367 
getJoyDeviceAxes(std::vector<std::string> & list) const368 void          DXJoystick::getJoyDeviceAxes(std::vector<std::string> &list) const
369 {
370     list.clear();
371     std::map<std::string,bool>::const_iterator itr = axes.begin();
372     while (itr != axes.end())
373     {
374         if (itr->second == true)
375             list.push_back(itr->first);
376         ++itr;
377     }
378 }
379 
setXAxis(const std::string & axis)380 void          DXJoystick::setXAxis(const std::string &axis)
381 {
382     if (axes[axis] == false) return;
383     xAxis = axis;
384 }
385 
setYAxis(const std::string & axis)386 void          DXJoystick::setYAxis(const std::string &axis)
387 {
388     if (axes[axis] == false) return;
389     yAxis = axis;
390 }
391 
392 /*
393  * Cool force feedback functions.
394  */
395 
ffHasRumble() const396 bool          DXJoystick::ffHasRumble() const
397 {
398     if (!device)
399         return false;
400 
401     DIDEVCAPS caps;
402     caps.dwSize = sizeof(DIDEVCAPS);
403 
404     HRESULT success = device->GetCapabilities(&caps);
405 
406     if (success != DI_OK)
407     {
408         // couldn't get capabilities, assume no force feedback
409         printError("Could not get joystick capabilities, assuming no force feedback");
410         return false;
411     }
412 
413     // if we support force feedback, assume we support rumble
414     if (caps.dwFlags & DIDC_FORCEFEEDBACK)
415         return true;
416 
417     return false;
418 }
419 
ffRumble(int count,float delay,float duration,float strong_motor,float weak_motor)420 void          DXJoystick::ffRumble(int count, float delay, float duration,
421                                    float strong_motor, float weak_motor)
422 {
423     if (!ffHasRumble())
424         return;
425 
426     /*
427      * Create a constant "rumbling" effect with the specified parameters
428      * Note that on joysticks that support "real" force feedback this will
429      * probably just feel like a constant pressure.
430      */
431     DICONSTANTFORCE constantForce;
432 
433     /* This is about consistent with the relative strength of the motors
434      * in the Logitech Cordless Rumblepad, which seems to be what the Linux
435      * FF_RUMBLE API (which bzflag's is patterned after) was designed for.
436      */
437     float combined = strong_motor + weak_motor / 2.0f;
438     if (combined > 1.0f)
439         combined = 1.0f;
440 
441     constantForce.lMagnitude = (LONG)(DI_FFNOMINALMAX * combined);
442 
443     HRESULT success = DI_OK;
444 
445     // Generate a string to identify a specific rumble effect,
446     // based on the parameters of the rumble
447     std::string effectType = TextUtils::format("R%d|%d|%d|%d|%d", count, delay, duration, strong_motor, weak_motor);
448 
449     // Check if we need to create the effect
450     EffectMap::iterator itr = effectDatabase.find(effectType);
451     if (itr == effectDatabase.end())
452     {
453 
454         /*
455          * Wasn't in effect database, so build it
456          */
457         DWORD axes[2] = {DIJOFS_X, DIJOFS_Y};
458         LONG  dir[2] = {1, 1};
459 
460         LPDIRECTINPUTEFFECT createdEffect = NULL;
461 
462         DIEFFECT effect;
463         effect.dwSize = sizeof(DIEFFECT);
464         // coordinate system really doesn't matter for rumbles but we need to specify it.
465         effect.dwFlags = DIEFF_OBJECTOFFSETS | DIEFF_CARTESIAN;
466         // duration
467         effect.dwDuration = (DWORD)(duration * DI_SECONDS);
468         // defaults
469         effect.dwSamplePeriod = 0;
470         effect.dwGain = DI_FFNOMINALMAX;
471         effect.dwTriggerButton = DIEB_NOTRIGGER;
472         effect.dwTriggerRepeatInterval = 0;
473         // x and y axes
474         effect.cAxes = 2;
475         effect.rgdwAxes = &axes[0];
476         // direction doesn't matter
477         effect.rglDirection = &dir[0];
478         // no envelope
479         effect.lpEnvelope = NULL;
480         // use the constant force data
481         effect.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
482         effect.lpvTypeSpecificParams = &constantForce;
483         // start delay
484         effect.dwStartDelay = (DWORD)(delay * DI_SECONDS);
485 
486         // create the effect
487         success = device->CreateEffect(GUID_ConstantForce, &effect, &createdEffect, NULL);
488 
489         if ((success != DI_OK) || (createdEffect == NULL))
490         {
491             DXError("Could not create rumble effect", success);
492             return;
493         }
494 
495         // Store the effect for later use
496         effectDatabase[effectType] = createdEffect;
497     }
498 
499     // play the thing
500     if (effectDatabase[effectType])
501         success = effectDatabase[effectType]->Start(count, 0);
502 
503     if (success != DI_OK)
504     {
505         // uh-oh, no worky
506         DXError("Could not play rumble effect", success);
507     }
508 
509     return;
510 }
511 
ffDirectionalConstant(int count,float delay,float duration,float x_direction,float y_direction,float strength)512 void    DXJoystick::ffDirectionalConstant(int count, float delay, float duration,
513         float x_direction, float y_direction,
514         float strength)
515 {
516     if (!ffHasDirectional())
517         return;
518 
519     /*
520      * Create a constant effect with the specified parameters
521      */
522     DICONSTANTFORCE constantForce;
523 
524     constantForce.lMagnitude = (LONG)(DI_FFNOMINALMAX * strength);
525 
526     HRESULT success = DI_OK;
527 
528     // Generate a string to identify a specific constant effect,
529     // based on the parameters of the effect
530     std::string effectType = TextUtils::format("C%d|%d|%d|%d|%d|%d", count, delay, duration, x_direction, y_direction,
531                              strength);
532 
533     // Check if we need to create the effect
534     EffectMap::iterator itr = effectDatabase.find(effectType);
535     if (itr == effectDatabase.end())
536     {
537 
538         /*
539          * Wasn't in effect database, so build it
540          */
541         DWORD axes[2] = {DIJOFS_X, DIJOFS_Y};
542         LONG  dir[2] = {(int)(1000.0f * x_direction),
543                         (int)(1000.0f * y_direction)
544                        };
545 
546         LPDIRECTINPUTEFFECT createdEffect = NULL;
547 
548         DIEFFECT effect;
549         effect.dwSize = sizeof(DIEFFECT);
550         // cartesian coordinate system
551         effect.dwFlags = DIEFF_OBJECTOFFSETS | DIEFF_CARTESIAN;
552         // duration
553         effect.dwDuration = (DWORD)(duration * DI_SECONDS);
554         // defaults
555         effect.dwSamplePeriod = 0;
556         effect.dwGain = DI_FFNOMINALMAX;
557         effect.dwTriggerButton = DIEB_NOTRIGGER;
558         effect.dwTriggerRepeatInterval = 0;
559         // x and y axes
560         effect.cAxes = 2;
561         effect.rgdwAxes = &axes[0];
562         // direction
563         effect.rglDirection = &dir[0];
564         // no envelope
565         effect.lpEnvelope = NULL;
566         // use the constant force data
567         effect.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
568         effect.lpvTypeSpecificParams = &constantForce;
569         // start delay
570         effect.dwStartDelay = (DWORD)(delay * DI_SECONDS);
571 
572         // create the effect
573         success = device->CreateEffect(GUID_ConstantForce, &effect, &createdEffect, NULL);
574 
575         if ((success != DI_OK) || (createdEffect == NULL))
576         {
577             DXError("Could not create directional constant effect", success);
578             return;
579         }
580 
581         // Store the effect for later use
582         effectDatabase[effectType] = createdEffect;
583     }
584 
585     // play the thing
586     if (effectDatabase[effectType])
587         success = effectDatabase[effectType]->Start(count, 0);
588 
589     if (success != DI_OK)
590     {
591         // uh-oh, no worky
592         DXError("Could not play directional constant effect", success);
593     }
594 
595     return;
596 }
597 
ffDirectionalPeriodic(int count,float delay,float duration,float x_direction,float y_direction,float amplitude,float period,PeriodicType type)598 void    DXJoystick::ffDirectionalPeriodic(int count, float delay, float duration,
599         float x_direction, float y_direction,
600         float amplitude, float period, PeriodicType type)
601 {
602     if (!ffHasDirectional())
603         return;
604 
605     /*
606      * Create a constant effect with the specified parameters
607      */
608     DIPERIODIC periodicForce;
609 
610     periodicForce.dwMagnitude = (DWORD)(DI_FFNOMINALMAX * amplitude);
611     periodicForce.lOffset = 0;
612     periodicForce.dwPhase = 0;
613     periodicForce.dwPeriod = (int)(DI_SECONDS * period);
614 
615     HRESULT success = DI_OK;
616 
617     // Generate a string to identify a specific periodic effect,
618     // based on the parameters of the effect
619     std::string effectType = TextUtils::format("P%d|%d|%d|%d|%d|%d|%d|%d", count, delay, duration, x_direction, y_direction,
620                              amplitude, period, type);
621 
622     // Check if we need to create the effect
623     EffectMap::iterator itr = effectDatabase.find(effectType);
624     if (itr == effectDatabase.end())
625     {
626 
627         /*
628          * Wasn't in effect database, so build it
629          */
630         DWORD axes[2] = {DIJOFS_X, DIJOFS_Y};
631         LONG  dir[2] = {(int)(1000.0f * x_direction),
632                         (int)(1000.0f * y_direction)
633                        };
634 
635         LPDIRECTINPUTEFFECT createdEffect = NULL;
636 
637         DIEFFECT effect;
638         effect.dwSize = sizeof(DIEFFECT);
639         // cartesian coordinate system
640         effect.dwFlags = DIEFF_OBJECTOFFSETS | DIEFF_CARTESIAN;
641         // duration
642         effect.dwDuration = (DWORD)(duration * DI_SECONDS);
643         // defaults
644         effect.dwSamplePeriod = 0;
645         effect.dwGain = DI_FFNOMINALMAX;
646         effect.dwTriggerButton = DIEB_NOTRIGGER;
647         effect.dwTriggerRepeatInterval = 0;
648         // x and y axes
649         effect.cAxes = 2;
650         effect.rgdwAxes = &axes[0];
651         // direction
652         effect.rglDirection = &dir[0];
653         // no envelope
654         effect.lpEnvelope = NULL;
655         // use the constant force data
656         effect.cbTypeSpecificParams = sizeof(DIPERIODIC);
657         effect.lpvTypeSpecificParams = &periodicForce;
658         // start delay
659         effect.dwStartDelay = (DWORD)(delay * DI_SECONDS);
660 
661         // create the effect
662         GUID guid;
663         switch (type)
664         {
665         case BzfJoystick::FF_Sine:
666             guid = GUID_Sine;
667             break;
668         case BzfJoystick::FF_Square:
669             guid = GUID_Square;
670             break;
671         case BzfJoystick::FF_Triangle:
672             guid = GUID_Triangle;
673             break;
674         case BzfJoystick::FF_SawtoothUp:
675             guid = GUID_SawtoothUp;
676             break;
677         case BzfJoystick::FF_SawtoothDown:
678             guid = GUID_SawtoothDown;
679             break;
680         default:
681             DXError("Unknown directional periodic effect type", type);
682             return;
683         }
684         success = device->CreateEffect(guid, &effect, &createdEffect, NULL);
685 
686         if ((success != DI_OK) || (createdEffect == NULL))
687         {
688             DXError("Could not create directional periodic effect", success);
689             return;
690         }
691 
692         // Store the effect for later use
693         effectDatabase[effectType] = createdEffect;
694     }
695 
696     // play the thing
697     if (effectDatabase[effectType])
698         success = effectDatabase[effectType]->Start(count, 0);
699 
700     if (success != DI_OK)
701     {
702         // uh-oh, no worky
703         DXError("Could not play directional periodic effect", success);
704     }
705 
706     return;
707 }
708 
ffDirectionalResistance(float time,float coefficient,float saturation,ResistanceType type)709 void    DXJoystick::ffDirectionalResistance(float time, float coefficient,
710         float saturation, ResistanceType type)
711 {
712     if (!ffHasDirectional())
713         return;
714 
715     /*
716      * Create a resistance effect with the specified parameters
717      */
718     DICONDITION resistForce[2];
719 
720     resistForce[0].lOffset = 0;
721     resistForce[0].lPositiveCoefficient = resistForce[0].lNegativeCoefficient = (int)(10000 * coefficient);
722     resistForce[0].dwPositiveSaturation = resistForce[0].dwNegativeSaturation = (int)(10000 * saturation);
723     resistForce[0].lDeadBand = 0;
724 
725     resistForce[1].lOffset = 0;
726     resistForce[1].lPositiveCoefficient = resistForce[1].lNegativeCoefficient = (int)(10000 * coefficient);
727     resistForce[1].dwPositiveSaturation = resistForce[1].dwNegativeSaturation = (int)(10000 * saturation);
728     resistForce[1].lDeadBand = 0;
729 
730     HRESULT success = DI_OK;
731 
732     // Generate a string to identify a specific resistance effect,
733     // based on the parameters of the effect
734     std::string effectType = TextUtils::format("F%d|%d|%d|%d", time, coefficient, saturation, type);
735 
736     // Check if we need to create the effect
737     EffectMap::iterator itr = effectDatabase.find(effectType);
738     if (itr == effectDatabase.end())
739     {
740 
741         /*
742          * Wasn't in effect database, so build it
743          */
744         DWORD axes[2] = {DIJOFS_X, DIJOFS_Y};
745         LONG  dir[2] = {1, 1};
746 
747         LPDIRECTINPUTEFFECT createdEffect = NULL;
748 
749         DIEFFECT effect;
750         effect.dwSize = sizeof(DIEFFECT);
751         // cartesian coordinate system
752         effect.dwFlags = DIEFF_OBJECTOFFSETS | DIEFF_CARTESIAN;
753         // duration
754         effect.dwDuration = (DWORD)(time * DI_SECONDS);
755         // defaults
756         effect.dwSamplePeriod = 0;
757         effect.dwGain = DI_FFNOMINALMAX;
758         effect.dwTriggerButton = DIEB_NOTRIGGER;
759         effect.dwTriggerRepeatInterval = 0;
760         // x and y axes
761         effect.cAxes = 2;
762         effect.rgdwAxes = &axes[0];
763         // direction
764         effect.rglDirection = &dir[0];
765         // no envelope
766         effect.lpEnvelope = NULL;
767         // use the constant force data
768         effect.cbTypeSpecificParams = sizeof(DICONDITION) * 2;
769         effect.lpvTypeSpecificParams = &resistForce[0];
770         // start delay
771         effect.dwStartDelay = 0;
772 
773         // create the effect
774         GUID guid;
775         switch (type)
776         {
777         case BzfJoystick::FF_Position:
778             guid = GUID_Spring;
779             break;
780         case BzfJoystick::FF_Velocity:
781             guid = GUID_Damper;
782             break;
783         case BzfJoystick::FF_Acceleration:
784             guid = GUID_Inertia;
785             break;
786         default:
787             DXError("Unknown directional resistance effect type", type);
788             return;
789         }
790         success = device->CreateEffect(guid, &effect, &createdEffect, NULL);
791 
792         if ((success != DI_OK) || (createdEffect == NULL))
793         {
794             DXError("Could not create directional resistance effect", success);
795             return;
796         }
797 
798         // Store the effect for later use
799         effectDatabase[effectType] = createdEffect;
800     }
801 
802     // play the thing
803     if (effectDatabase[effectType])
804         success = effectDatabase[effectType]->Start(1, 0);
805 
806     if (success != DI_OK)
807     {
808         // uh-oh, no worky
809         DXError("Could not play directional resistance effect", success);
810     }
811 
812     return;
813 }
814 
ffHasDirectional() const815 bool    DXJoystick::ffHasDirectional() const
816 {
817     /* FIXME: sadly, there's no easy way to figure out what TYPE of
818        force feedback a windows joystick supports :( */
819     return ffHasRumble();
820 }
821 
enumerateDevices()822 void DXJoystick::enumerateDevices()
823 {
824     if (!directInput)
825         return;
826 
827     devices.clear();
828 
829     HRESULT success = directInput->EnumDevices(DI8DEVCLASS_GAMECTRL,
830                       &deviceEnumCallback, NULL,
831                       DIEDFL_ATTACHEDONLY);
832 
833     if (success != DI_OK)
834     {
835         DXError("Could not enumerate DirectInput devices", success);
836         return;
837     }
838 }
839 
reaquireDevice()840 void DXJoystick::reaquireDevice()
841 {
842     if (!device)
843         return;
844 
845     // try to reaquire the device
846     HRESULT success = device->Acquire();
847 
848     if (success != DI_OK)
849     {
850         // couldn't acquire, what to do now?
851         device = NULL;
852         DXError("Could not acquire device", success);
853     }
854 }
855 
resetFF()856 void DXJoystick::resetFF()
857 {
858     if (!device)
859         return;
860 
861     HRESULT success = device->SendForceFeedbackCommand(DISFFC_RESET);
862 
863     if (success != DI_OK)
864     {
865         // couldn't reset, what to do now?
866         device = NULL;
867         DXError("Could not reset force feedback device", success);
868     }
869 }
870 
871 /* error handling */
872 
DXError(const char * situation,HRESULT problem)873 void DXJoystick::DXError(const char* situation, HRESULT problem)
874 {
875     // uh-oh, no worky
876     char buffer[40] = {0};
877 
878     // some stuff we can handle
879     if (problem == (HRESULT)DIERR_UNPLUGGED)
880     {
881         device = NULL;
882         printError("Joystick device in use has been unplugged.");
883         enumerateDevices();
884         return;
885     }
886     if (problem == (HRESULT)DIERR_INPUTLOST)
887     {
888         reaquireDevice();
889         return;
890     }
891     if (problem == (HRESULT)DIERR_DEVICEFULL)
892     {
893         printError("DirectInput device is full.  Resetting FF state.");
894         resetFF();
895         return;
896     }
897 
898     // print error messages
899     if (problem == (HRESULT)DIERR_DEVICENOTREG)
900         sprintf(buffer, "Device not registered");
901     else if (problem == (HRESULT)DIERR_INVALIDPARAM)
902         sprintf(buffer, "Invalid parameter");
903     else if (problem == (HRESULT)DIERR_NOTINITIALIZED)
904         sprintf(buffer, "Device not initialized");
905     else if (problem == (HRESULT)DI_BUFFEROVERFLOW)
906         sprintf(buffer, "Buffer overflow");
907     else if (problem == (HRESULT)DIERR_BADDRIVERVER)
908         sprintf(buffer, "Bad or incompatible device driver");
909     else if (problem == (HRESULT)DIERR_EFFECTPLAYING)
910         sprintf(buffer, "Effect already playing");
911     else if (problem == (HRESULT)DIERR_INCOMPLETEEFFECT)
912         sprintf(buffer, "Incomplete effect");
913     else if (problem == (HRESULT)DIERR_MOREDATA)
914         sprintf(buffer, "Return buffer not large enough");
915     else if (problem == (HRESULT)DIERR_NOTACQUIRED)
916         sprintf(buffer, "Device not acquired");
917     else if (problem == (HRESULT)DIERR_NOTDOWNLOADED)
918         sprintf(buffer, "Effect not downloaded");
919     else if (problem == (HRESULT)DIERR_NOTINITIALIZED)
920         sprintf(buffer, "Device not initialized");
921     else if (problem == (HRESULT)DIERR_OUTOFMEMORY)
922         sprintf(buffer, "Out of memory");
923     else if (problem == (HRESULT)DIERR_UNSUPPORTED)
924         sprintf(buffer, "Action not supported by driver");
925     else
926         sprintf(buffer, "Unknown error (%d)", (int)problem);
927     printError(TextUtils::format("%s (%s).", situation, buffer));
928 }
929 
930 /* Nasty callbacks 'cause DirectX sucks */
931 
deviceEnumCallback(LPCDIDEVICEINSTANCE device,void * UNUSED (pvRef))932 BOOL CALLBACK DXJoystick::deviceEnumCallback(LPCDIDEVICEINSTANCE device, void* UNUSED(pvRef))
933 {
934     if (!device)
935         return DIENUM_STOP;
936 
937     devices.push_back(*device);
938 
939     return DIENUM_CONTINUE;
940 }
941 
942 
943 // Local Variables: ***
944 // mode: C++ ***
945 // tab-width: 4 ***
946 // c-basic-offset: 4 ***
947 // indent-tabs-mode: nil ***
948 // End: ***
949 // ex: shiftwidth=4 tabstop=4
950