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