/****h* Steering.Wheel.SDK/SteeringWheelSDK[1.00.002] * NAME * Steering Wheel SDK * COPYRIGHT * The Logitech Steering Wheel SDK, including all accompanying * documentation, is protected by intellectual property laws. All * rights not expressly granted by Logitech are reserved. * PURPOSE * The Steering Wheel SDK is an addition to Microsoft's DirectInput * in DirectX. It is aimed at driving games and enables to * dramatically shorten development time and improve implementation * quality for various types of game controllers (USB/gameport * wheels/joysticks/game pads, FF enabled or not). The Steering Wheel * SDK has been developed and tested for wheels and joysticks using * the Logitech, Microsoft and Immersion drivers. It also works with * Logitech rumble pads (it has not been tested with non-Logitech * rumble pads). By using the Steering Wheel SDK you have the * guarantee that all wheels and joysticks will function * flawlessly. No more situations where force feedback in a game * behaves very differently from one wheel/joystick to another, which * in turn results in user frustration and product returns. The * Steering Wheel SDK comes with a very intuitive and easy to use * interface which enables to read the wheel/joystick's axes and * buttons, and also to create all the force feedback effects that * are necessary for a good and complete implementation. See the * following files to get started: * - readme.txt: tells you how to get started. * - SampleInGameImplementation.cpp: shows line by line how to * use the Steering Wheel SDK's interface to do a complete * implementation for PC game controllers in your driving * game. The idea is to develop support for the steering * wheel. But if a user plugs in a joystick or game pad he can * play as well and get force feedback or rumble. If a joystick * is plugged in, all forces generated by the Steering Wheel * SDK will be played on the X axis and there will be a * constant spring on the Y axis. * - SteeringWheelSDK.cpp: demonstrates force feedback * effects. Just compile, run and plug in a FF wheel, joystick * or rumble pad. See usage at top of SteeringWheelSDK.cpp. * For more details see DirectInput documentation which is part of * Microsoft's DirectX. * AUTHOR * Christophe Juncker (cj@wingmanteam.com) ****** */ #include "LogiWheel.h" #include "LogiWheelUtils.h" using namespace LogitechSteeringWheel; using namespace LogitechControllerInput; DWORD g_forceActuatorsResetTriggered = 0xffffffff; WNDPROC g_OldWheelWnd; LRESULT CALLBACK WheelWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); /****f* Steering.Wheel.SDK/Wheel(ControllerInput*.controllerInput) * NAME * Wheel(ControllerInput* controllerInput) -- Does necessary * initialization. * INPUTS * controllerInput - handle to instance of Controller Input SDK. * SEE ALSO * SampleInGameImplementation.cpp or SteeringWheelSDKDemo.cpp to see an * example. ****** */ Wheel::Wheel(ControllerInput* controllerInput) { for (INT index_ = 0; index_ < LogitechSteeringWheel::LG_MAX_CONTROLLERS; index_++) { InitVars(index_); } _ASSERT(NULL != controllerInput); if (NULL != controllerInput) { m_controllerInput = controllerInput; m_controllerProperties = new ControllerProperties(controllerInput->GetGameHWnd()); //Replace the Window Procedure and Store the Old Window Procedure g_OldWheelWnd = (WNDPROC)(LONG_PTR)GetWindowLongPtr(controllerInput->GetGameHWnd(), GWLP_WNDPROC); SetWindowLongPtr(controllerInput->GetGameHWnd(), GWLP_WNDPROC, (__int3264)(LONG_PTR)WheelWindowProc); } } Wheel::~Wheel() { for (INT index_ = 0; index_ < LogitechSteeringWheel::LG_MAX_CONTROLLERS; index_++) { if (NULL != m_controllerForce[index_]) { m_controllerForce[index_]->ReleaseEffects(); delete m_controllerForce[index_]; m_controllerForce[index_] = NULL; } // Turn off all LEDs PlayLeds(index_, 0.0f, 1.0f, 2.0f); } if (NULL != m_controllerProperties) { delete m_controllerProperties; m_controllerProperties = NULL; } } VOID Wheel::InitVars(CONST INT index) { m_isAirborne[index] = FALSE; m_damperWasPlaying[index] = FALSE; m_springWasPlaying[index] = FALSE; m_controllerForce[index] = NULL; for (INT jj = 0; jj < LG_NUMBER_FORCE_EFFECTS; jj++) { m_wasPlayingBeforeAirborne[index][jj] = FALSE; } } /****f* Steering.Wheel.SDK/Update() * NAME * VOID Wheel::Update() -- keeps forces and controller connections up * to date. * NOTES * Must be called every frame. * SEE ALSO * SampleInGameImplementation.cpp or SteeringWheelSDKDemo.cpp to see * an example. ****** */ VOID Wheel::Update() { _ASSERT(NULL != m_controllerProperties); _ASSERT(NULL != m_controllerInput); for (INT index_ = 0; index_ < LogitechSteeringWheel::LG_MAX_CONTROLLERS; index_++) { if (NULL != m_controllerInput && NULL != m_controllerProperties) { if (m_controllerInput->IsConnected(index_)) { if (NULL == m_controllerForce[index_]) { m_controllerForce[index_] = new ControllerForceManager(); _ASSERT(NULL != m_controllerForce[index_]); if (NULL != m_controllerForce[index_]) { m_controllerForce[index_]->SetDeviceHandle(m_controllerInput->GetDeviceHandle(index_)); } } } else { if (NULL != m_controllerForce[index_]) { m_controllerForce[index_]->ReleaseEffects(); delete m_controllerForce[index_]; m_controllerForce[index_] = NULL; InitVars(index_); } } } } // update controller properties std::vector currentlyConnectedPIDs_; for (INT index_ = 0; index_ < LogitechSteeringWheel::LG_MAX_CONTROLLERS; index_++) { if (NULL != m_controllerInput) { if (m_controllerInput->IsConnected(index_)) { currentlyConnectedPIDs_.push_back(m_controllerInput->GetProductID(index_)); } } } if (NULL != m_controllerProperties) { m_controllerProperties->Update(currentlyConnectedPIDs_); } } /****f* Steering.Wheel.SDK/IsConnected(INT.index) * NAME * BOOL IsConnected(INT index) -- Check if a game controller is * connected at the specified index. * INPUTS * index - index of the game controller that we want to check. Index * 0 corresponds to the first game controller connected. Index 1 to * the second game controller. * RETURN VALUE * TRUE if a PC wheel/joystick/game pad is connected, FALSE otherwise. * SEE ALSO * IsConnected(INT.index,DeviceType.deviceType) * IsConnected(INT.index,ManufacturerName.manufacturerName) * IsConnected(INT.index,ModelName.modelName) * SampleInGameImplementation.cpp or SteeringWheelSDKDemo.cpp to see * an example. ****** */ BOOL Wheel::IsConnected(CONST INT index) { _ASSERT(NULL != m_controllerInput); if (NULL != m_controllerInput) { return m_controllerInput->IsConnected(index); } return FALSE; } /****f* Steering.Wheel.SDK/IsConnected(INT.index,DeviceType.deviceType) * NAME * BOOL IsConnected(INT index, DeviceType deviceType) -- Check if a * game controller is connected at the specified index. * INPUTS * index - index of the game controller that we want to check. Index * 0 corresponds to the first game controller connected. Index 1 to * the second game controller. * * deviceType - type of the device to check for. Possible types are: * - LG_DEVICE_TYPE_WHEEL * - LG_DEVICE_TYPE_JOYSTICK * - LG_DEVICE_TYPE_GAMEPAD * RETURN VALUE * TRUE if a PC controller of specified type is connected, FALSE * otherwise. * SEE ALSO * IsConnected(INT.index) * IsConnected(INT.index,ManufacturerName.manufacturerName) * IsConnected(INT.index,ModelName.modelName) * SampleInGameImplementation.cpp or SteeringWheelSDKDemo.cpp to see * an example. ****** */ BOOL Wheel::IsConnected(CONST INT index, CONST DeviceType deviceType) { _ASSERT(NULL != m_controllerInput); if (NULL != m_controllerInput) { return m_controllerInput->IsConnected(index, deviceType); } return FALSE; } /****f* Steering.Wheel.SDK/IsConnected(INT.index,ManufacturerName.manufacturerName) * NAME * BOOL IsConnected(INT index, ManufacturerName manufacturerName) -- * Check if a game controller is connected at the specified index. * INPUTS * index - index of the game controller that we want to check. Index * 0 corresponds to the first game controller connected. Index 1 to * the second game controller. * * manufacturerName - name of the manufacturer the device has been * made by. Possible names are: * - LG_MANUFACTURER_LOGITECH * - LG_MANUFACTURER_MICROSOFT * - LG_MANUFACTURER_OTHER * RETURN VALUE * TRUE if a PC controller of specified manufacturer is connected, * FALSE otherwise. * SEE ALSO * IsConnected(INT.index) * IsConnected(INT.index,DeviceType.deviceType) * IsConnected(INT.index,ModelName.modelName) * SampleInGameImplementation.cpp or SteeringWheelSDKDemo.cpp to see * an example. ****** */ BOOL Wheel::IsConnected(CONST INT index, CONST ManufacturerName manufacturerName) { _ASSERT(NULL != m_controllerInput); if (NULL != m_controllerInput) { return m_controllerInput->IsConnected(index, manufacturerName); } return FALSE; } /****f* Steering.Wheel.SDK/IsConnected(INT.index,ModelName.modelName) * NAME * BOOL IsConnected(INT index, ModelName modelName) -- Check if a game * controller is connected at the specified index. * INPUTS * index - index of the game controller that we want to check. Index * 0 corresponds to the first game controller connected. Index 1 to * the second game controller. * * modelName - name of the model of the device. Possible models are: * - LG_MODEL_G27 * - LG_MODEL_G25 * - LG_MODEL_MOMO_RACING * - LG_MODEL_MOMO_FORCE * - LG_MODEL_DRIVING_FORCE_PRO * - LG_MODEL_DRIVING_FORCE * - LG_MODEL_NASCAR_RACING_WHEEL * - LG_MODEL_FORMULA_FORCE * - LG_MODEL_FORMULA_FORCE_GP * - LG_MODEL_FORCE_3D_PRO * - LG_MODEL_EXTREME_3D_PRO * - LG_MODEL_FREEDOM_24 * - LG_MODEL_ATTACK_3 * - LG_MODEL_FORCE_3D * - LG_MODEL_STRIKE_FORCE_3D * - LG_MODEL_RUMBLEPAD * - LG_MODEL_RUMBLEPAD_2 * - LG_MODEL_CORDLESS_RUMBLEPAD_2 * - LG_MODEL_CORDLESS_GAMEPAD * - LG_MODEL_DUAL_ACTION_GAMEPAD * - LG_MODEL_PRECISION_GAMEPAD_2 * - LG_MODEL_CHILLSTREAM * RETURN VALUE * TRUE if specific PC controller is connected, FALSE otherwise. * SEE ALSO * IsConnected(INT.index) * IsConnected(INT.index,DeviceType.deviceType) * IsConnected(INT.index,ManufacturerName.manufacturerName) * SampleInGameImplementation.cpp or SteeringWheelSDKDemo.cpp to see * an example. ****** */ BOOL Wheel::IsConnected(CONST INT index, CONST ModelName modelName) { _ASSERT(NULL != m_controllerInput); if (NULL != m_controllerInput) { return m_controllerInput->IsConnected(index, modelName); } return FALSE; } /****f* Steering.Wheel.SDK/GetState(INT.index) * NAME * DIJOYSTATE2* GetState(INT index) -- Get the state of the * controller. * INPUTS * index - index of the game controller. Index 0 corresponds to the * first game controller connected. Index 1 to the second game * controller. * RETURN VALUE * DIJOYSTATE2 structure containing the device's positional * information for axes, POVs and buttons. * SEE ALSO * SampleInGameImplementation.cpp or SteeringWheelSDKDemo.cpp to see * an example. ****** */ DIJOYSTATE2* Wheel::GetState(CONST INT index) { _ASSERT(NULL != m_controllerInput); if (NULL != m_controllerInput) { return m_controllerInput->GetStateDInput(index); } return NULL; } /****f* Steering.Wheel.SDK/GetFriendlyProductName(INT.index) * NAME * LPCTSTR GetFriendlyProductName(INT index) -- Get the device's * friendly product name. * INPUTS * index - index of the game controller. Index 0 corresponds to the * first game controller connected. Index 1 to the second game * controller. * RETURN VALUE * Device friendly product name. * SEE ALSO * SampleInGameImplementation.cpp or SteeringWheelSDKDemo.cpp to see * an example. ****** */ LPCTSTR Wheel::GetFriendlyProductName(CONST INT index) { _ASSERT(NULL != m_controllerInput); if (NULL != m_controllerInput) { return m_controllerInput->GetFriendlyProductName(index); } return _T(""); } /****f* Steering.Wheel.SDK/ButtonTriggered(INT.index,INT.buttonNbr) * NAME * BOOL ButtonTriggered(INT index, INT buttonNbr) -- Check if a * certain button was triggered. * INPUTS * index - index of the game controller that we want to check. Index * 0 corresponds to the first game controller connected. Index 1 to * the second game controller. * * buttonNbr - the number of the button that we want to * check. Possible numbers are: 0 to 127. * RETURN VALUE * TRUE if the button was triggered, FALSE otherwise. * SEE ALSO * ButtonIsPressed(INT.index,INT.buttonNbr) * ButtonReleased(INT.index,INT.buttonNbr) ****** */ BOOL Wheel::ButtonTriggered(CONST INT index, CONST INT buttonNbr) { _ASSERT(NULL != m_controllerInput); if (NULL != m_controllerInput) { return m_controllerInput->ButtonTriggered(index, buttonNbr); } return FALSE; } /****f* Steering.Wheel.SDK/ButtonReleased(INT.index,INT.buttonNbr) * NAME * BOOL ButtonReleased(INT index, INT buttonNbr) -- Check if a certain * button was released. * INPUTS * index - index of the game controller that we want to check. Index * 0 corresponds to the first game controller connected. Index 1 to * the second game controller. * * buttonNbr - the number of the button that we want to * check. Possible numbers are: 0 to 127. * RETURN VALUE * TRUE if the button was released, FALSE otherwise. * SEE ALSO * ButtonIsPressed(INT.index,INT.buttonNbr) * ButtonTriggered(INT.index,INT.buttonNbr) ****** */ BOOL Wheel::ButtonReleased(CONST INT index, CONST INT buttonNbr) { _ASSERT(NULL != m_controllerInput); if (NULL != m_controllerInput) { return m_controllerInput->ButtonReleased(index, buttonNbr); } return FALSE; } /****f* Steering.Wheel.SDK/ButtonIsPressed(INT.index,INT.buttonNbr) * NAME * BOOL ButtonIsPressed(INT index, INT buttonNbr) -- Check if a * certain button is being pressed. * INPUTS * index - index of the game controller that we want to check. Index * 0 corresponds to the first game controller connected. Index 1 to * the second game controller. * * buttonNbr - the number of the button that we want to * check. Possible numbers are: 0 to 127. * RETURN VALUE * TRUE if the button is being pressed, FALSE otherwise. * SEE ALSO * ButtonReleased(INT.index,INT.buttonNbr) * ButtonTriggered(INT.index,INT.buttonNbr) ****** */ BOOL Wheel::ButtonIsPressed(CONST INT index, CONST INT buttonNbr) { _ASSERT(NULL != m_controllerInput); if (NULL != m_controllerInput) { return m_controllerInput->ButtonIsPressed(index, buttonNbr); } return FALSE; } /****f* Steering.Wheel.SDK/SetPreferredControllerProperties(ControllerPropertiesData.properties) * NAME * HRESULT SetPreferredControllerProperties(ControllerPropertiesData * properties) -- set preferred wheel properties. * INPUTS * properties - structure containing all the fields to be set. * RETURN VALUE * E_INVALIDARG if argument is wrong (individual settings out of * bounds). * E_FAIL if Logitech Gaming Software is older than 5.03. * S_OK otherwise. * NOTES * This function merely sets the game's preference. The Steering Wheel * SDK will attempt to set the wheel's settings when necessary. * SEE ALSO * GetCurrentControllerProperties(INT.index,ControllerPropertiesData&.properties) * GetShifterMode(INT.index) * SampleInGameImplementation.cpp or SteeringWheelSDKDemo.cpp to see * an example. ****** */ HRESULT Wheel::SetPreferredControllerProperties(CONST ControllerPropertiesData properties) { _ASSERT(NULL != m_controllerProperties); HRESULT ret_ = E_FAIL; if (NULL != m_controllerProperties) { ret_ = m_controllerProperties->SetPreferred(properties); g_forceActuatorsResetTriggered = GetTickCount(); } return ret_; } /****f* Steering.Wheel.SDK/GetCurrentControllerProperties(INT.index,ControllerPropertiesData&.properties) * NAME * BOOL GetCurrentControllerProperties(INT index, * ControllerPropertiesData& properties) -- get current properties. * INPUTS * index - index of the game controller. * properties - structure to receive current properties. * RETURN VALUE * TRUE if current value was received from Logitech driver. * FALSE if function failed or current value is default value. * NOTES * Function will fail and return default properties if user has older * than 5.03 Logitech Gaming Software installed. * SEE ALSO * SetPreferredControllerProperties(ControllerPropertiesData.properties) * GetShifterMode(INT.index) * SampleInGameImplementation.cpp or SteeringWheelSDKDemo.cpp to see * an example. ****** */ BOOL Wheel::GetCurrentControllerProperties(CONST INT index, ControllerPropertiesData& properties) { _ASSERT(NULL != m_controllerProperties); _ASSERT(NULL != m_controllerInput); if (NULL != m_controllerProperties && NULL != m_controllerInput) { return m_controllerProperties->GetCurrent(m_controllerInput->GetProductID(index), properties); } return FALSE; } /****f* Steering.Wheel.SDK/GetShifterMode(INT.index) * NAME * INT GetShifterMode(INT index) -- get current shifter mode (gated or * sequential). * INPUTS * index - index of the game controller. * RETURN VALUE * 1 if shifter is gated * 0 if shifter is sequential * -1 if unknown * SEE ALSO * SetPreferredControllerProperties(ControllerPropertiesData.properties) * GetCurrentControllerProperties(INT.index,ControllerPropertiesData&.properties) * SampleInGameImplementation.cpp or SteeringWheelSDKDemo.cpp to see an * example. ****** */ INT Wheel::GetShifterMode(CONST INT index) { _ASSERT(NULL != m_controllerProperties); _ASSERT(NULL != m_controllerInput); if (NULL != m_controllerProperties && NULL != m_controllerInput) { if (IsConnected(index, LG_MODEL_G25)) { return m_controllerProperties->GetShifterMode(m_controllerInput->GetDeviceHandle(index)); } else if (IsConnected(index, LG_MODEL_G27)) { return 1; } } return 0; } /****f* Steering.Wheel.SDK/PlayLeds(INT.index,FLOAT.currentRPM,FLOAT.rpmFirstLedTurnsOn,FLOAT.rpmRedLine) * NAME * HRESULT PlayLeds(INT index, FLOAT currentRPM, FLOAT * rpmFirstLedTurnsOn, FLOAT rpmRedLine) -- play LEDs on G27. * INPUTS * index - index of the game controller. * currentRPM - current RPM. * rpmFirstLedTurnsOn - RPM when first LEDs are to turn on. * rpmRedLine - just below this RPM, all LEDs will be on. Just above, * all LEDs will start flashing. * SEE ALSO * SampleInGameImplementation.cpp or SteeringWheelSDKDemo.cpp to see * an example. ****** */ HRESULT Wheel::PlayLeds(CONST INT index, CONST FLOAT currentRPM, CONST FLOAT rpmFirstLedTurnsOn, CONST FLOAT rpmRedLine) { if (NULL == m_controllerInput) { return E_POINTER; } LPDIRECTINPUTDEVICE8 deviceHandle_ = m_controllerInput->GetDeviceHandle(index); if (NULL == deviceHandle_) { return E_POINTER; } return m_leds.Play(deviceHandle_, currentRPM, rpmFirstLedTurnsOn, rpmRedLine); } /****f* Steering.Wheel.SDK/HasForceFeedback(INT.index) * NAME * BOOL HasForceFeedback(INT index) -- Check if a game controller has * force feedback. * INPUTS * index - index of the game controller that we want to check. Index * 0 corresponds to the first game controller connected. Index 1 to * the second game controller. * RETURN VALUE * TRUE if the specified device can do force feedback, FALSE * otherwise. ****** */ BOOL Wheel::HasForceFeedback(CONST INT index) { _ASSERT(NULL != m_controllerInput); if (NULL != m_controllerInput) { return m_controllerInput->HasForceFeedback(index); } return FALSE; } /****f* Steering.Wheel.SDK/IsPlaying(INT.index,ForceType.forceType) * NAME * BOOL IsPlaying(INT index, ForceType forceType) -- check if a * certain force effect is currently playing. * INPUTS * index - index of the game controller that we want to check. Index * 0 corresponds to the first game controller connected. Index 1 to * the second game controller. * * forceType - the type of the force that we want to check to see if * it is playing. Possible types are: * - LG_FORCE_SPRING * - LG_FORCE_CONSTANT * - LG_FORCE_DAMPER * - LG_FORCE_SIDE_COLLISION * - LG_FORCE_FRONTAL_COLLISION * - LG_FORCE_DIRT_ROAD * - LG_FORCE_BUMPY_ROAD * - LG_FORCE_SLIPPERY_ROAD * - LG_FORCE_SURFACE_EFFECT * - LG_FORCE_CAR_AIRBORNE * RETURN VALUE * TRUE if the force is playing, FALSE otherwise. ****** */ BOOL Wheel::IsPlaying(CONST INT index, CONST ForceType forceType) { if (!IsConnected(index)) return FALSE; if (!HasForceFeedback(index)) return FALSE; _ASSERT(NULL != m_controllerForce[index]); if (NULL != m_controllerForce[index]) { if (forceType == LG_FORCE_CAR_AIRBORNE) { return m_isAirborne[index]; } return m_controllerForce[index]->IsPlaying(forceType); } return FALSE; } /****f* Steering.Wheel.SDK/GenerateNonLinearValues(INT.index,INT.nonLinCoeff) * NAME * HRESULT GenerateNonLinearValues(int index, int nonLinCoeff) -- * Generate non-linear values for the game controller's axis. * FUNCTION * Gaming wheels/joysticks/game pads have very different behavior from * real steering wheels. The reason for single-turn wheels is that * they only do up to three quarters of a turn lock to lock, compared * to about 3 turns for a real car. * This directly affects the steering ratio (15:1 to 20:1 for a real * car, but only 4:1 for a gaming wheel!). Joysticks and game pads * have a much shorter range of movement than a real steering wheel as * well. * Because of this very short steering ratio or short range, the * gaming wheel/joystick/game pad will feel highly sensitive which may * make game play very difficult. * Especially it may be difficult to drive in a straight line at speed * (tendency to swerve back and forth). * One way to get around this problem is to use a sensitivity * curve. This is a curve that defines the sensitivity of the game * controller depending on speed. This type of curve is usually used * for game pads to make up for their low physical range. The result * of applying such a curve is that at high speed the car's wheels * will physically turn less than if the car is moving very slowly. * For example the car's wheels may turn 60 degrees lock to lock at * low speed but only 10 degrees lock to lock at higher speeds. If * you calculate the resulting steering ratio for 10 degrees lock to * lock you find that if you use a steering wheel that turns 180 * degrees lock to lock the ratio is equal to 180/10 = 18, which * corresponds to a real car's steering ratio. * If the sensitivity curve has been implemented for the * wheel/joystick, adding a non-linear curve probably is not * necessary. But you may find that even after applying a sensitivity * curve, the car still feels a little twitchy on a straight line when * driving fast. This may be because in your game you need more than * 10 degrees lock to lock even at high speeds. Or maybe the car is * moving at very high speeds where even a normal steering ratio is * not good enough to eliminate high sensitivity. * The best way at this point is to add a non-linear curve on top of * the sensitivity curve. * The effect of the non-linear curve with positive nonLinCoeff is * that around center position the wheel/joystick will be less * sensitive. Yet at locked position left or right the car's wheels * will turn the same amount of degrees as without the non-linear * response curve. Therefore the car will become more controllable on * a straight line and game-play will be improved. * There can sometimes be cases where the wheel does not feel * sensitive enough. In that case it is possible to add a non-linear * curve with the inverse effect (makes the steering more sensitive * around center position) by using negative values for * nonLinCoeff. This method lets you define a non-linearity * coefficient which will determine how strongly non-linear the curve * will be. When running the method it will generate a mapping table * in the form of an array. For each of the 1024 entries in this array * there will be a corresponding non-linear value which can be used as * the wheel/joystick's axis position instead of the original * value. See Sample_In-game_Implementation.cs for an example. * INPUTS * index - index to which the concerned game controller is connected. * Index 0 corresponds to the first game controller connected. Index 1 * to the second game controller. * * nonLinCoeff - value representing how much non-linearity should be * applied. Range is -100 to 100. 0 = linear curve, 100 = maximum * non-linear curve with less sensitivity around center, -100 = * maximum non-linearity with more sensitivity around center position. * RETURN VALUE * S_OK if successful, E_FAIL otherwise. * SEE ALSO * GetNonLinearValue(INT.index,INT.inputValue) * SampleInGameImplementation.cpp or SteeringWheelSDKDemo.cpp to see * an example. ****** */ HRESULT Wheel::GenerateNonLinearValues(CONST INT index, CONST INT nonLinCoeff) { _ASSERT(NULL != m_controllerInput); if (NULL != m_controllerInput) { return m_controllerInput->GenerateNonLinearValues(index, nonLinCoeff); } return E_FAIL; } /****f* Steering.Wheel.SDK/GetNonLinearValue(INT.index,INT.inputValue) * NAME * INT GetNonLinearValue(INT index, INT inputValue) -- Get a * non-linear value from a table previously generated. This can be * used for the response of a steering wheel. * INPUTS * index - index of the game controller. Index 0 corresponds to the * first game controller connected. Index 1 to the second game * controller. * * inputValue - value between -32768 and 32767 corresponding to * original value of an axis. * RETURN VALUE * Value between -32768 and 32767 corresponding to the level of * non-linearity previously set with GenerateNonLinearValues(...). * SEE ALSO * GenerateNonLinearValues(INT.index,INT.nonLinCoeff) * SampleInGameImplementation.cpp or SteeringWheelSDKDemo.cpp to see an * example. ****** */ INT Wheel::GetNonLinearValue(CONST INT index, CONST INT inputValue) { _ASSERT(NULL != m_controllerInput); if (NULL != m_controllerInput) { return m_controllerInput->GetNonLinearValue(index, inputValue); } return 0; } /****f* Steering.Wheel.SDK/PlaySpringForce(INT.index,INT.offsetPercentage,INT.saturationPercentage,INT.coefficientPercentage) * NAME * HRESULT PlaySpringForce(INT index, INT offsetPercentage, INT * saturationPercentage, INT coefficientPercentage) -- Play the spring * force. * INPUTS * index - index of the game controller. Index 0 corresponds to the * first wheel/joystick connected. Index 1 to the second * wheel/joystick. * * offsetPercentage - Specifies the center of the spring force effect. * Valid range is -100 to 100. Specifying 0 centers the spring. Any * values outside this range are silently clamped. * * saturationPercentage - Specify the level of saturation of the * spring force effect. The saturation stays constant after a certain * deflection from the center of the spring. It is comparable to a * magnitude. Valid ranges are 0 to 100. Any value higher than 100 is * silently clamped. * * coefficientPercentage - Specify the slope of the effect strength * increase relative to the amount of deflection from the center of * the condition. Higher values mean that the saturation level is * reached sooner. Valid ranges are -100 to 100. Any value outside * the valid range is silently clamped. * NOTES * The dynamic spring force gets played on the X axis. If a joystick * is connected, all forces generated by the Steering Wheel SDK will be * played on the X axis. And in addition there will be a constant * spring on the Y axis. * SEE ALSO * StopSpringForce(INT.index) * SampleInGameImplementation.cpp or SteeringWheelSDKDemo.cpp to see * an example. ****** */ HRESULT Wheel::PlaySpringForce(CONST INT index, CONST INT offsetPercentage, CONST INT saturationPercentage, CONST INT coefficientPercentage) { HRESULT hr_ = E_FAIL; _ASSERT(NULL != m_controllerForce[index]); if (NULL == m_controllerForce[index]) return E_FAIL; _ASSERT(NULL != m_controllerInput); if (NULL == m_controllerInput) return E_FAIL; // If no controller connected, or no ff, or car airborne, or slippery force is playing, do nothing. if (HasForceFeedback(index) == FALSE || m_isAirborne[index] == TRUE || IsPlaying(index, LG_FORCE_SLIPPERY_ROAD)) { return E_FAIL; } // If connected device is a gamepad, do not play the force. A spring // force with variable offset results in annoying constant vibration // for the gamepad, so let's not play it. if (m_controllerInput->IsConnected(index, LG_DEVICE_TYPE_GAMEPAD)) { return S_OK; } SpringForceParams params_; params_.m_diCondition[0].lOffset = Utils::FromPercentage(offsetPercentage, -100, 100, -DI_FFNOMINALMAX, DI_FFNOMINALMAX); params_.m_diCondition[0].dwPositiveSaturation = Utils::FromPercentage(saturationPercentage, 0, 100, 0, DI_FFNOMINALMAX); params_.m_diCondition[0].dwNegativeSaturation = params_.m_diCondition[0].dwPositiveSaturation; params_.m_diCondition[0].lPositiveCoefficient = Utils::FromPercentage(coefficientPercentage, -100, 100, -DI_FFNOMINALMAX, DI_FFNOMINALMAX); params_.m_diCondition[0].lNegativeCoefficient = params_.m_diCondition[0].lPositiveCoefficient; params_.m_numFFAxes = m_controllerInput->GetNumberFFAxesDInput(index); // Direction for spring parameters is different for Microsoft joystick // Also offset needs to be reversed for Microsoft joystick... if (IsConnected(index, LG_DEVICE_TYPE_JOYSTICK) && IsConnected(index, LG_MANUFACTURER_MICROSOFT)) { params_.m_diCondition[0].lOffset = -params_.m_diCondition[0].lOffset; params_.m_rglDirection[0] = 0; params_.m_rglDirection[1] = 1; } else { params_.m_rglDirection[0] = 1; params_.m_rglDirection[1] = 0; } LogiSpringForce* force_ = (LogiSpringForce*)m_controllerForce[index]->GetForce(LG_FORCE_SPRING); _ASSERT(NULL != force_); if (NULL == force_) return E_FAIL; if (m_controllerForce[index]->GetForce(LG_FORCE_SPRING)->GetEffectHandle()== NULL) { hr_ = force_->CreateEffect(params_); } else { if (params_.m_diCondition[0].lOffset != force_->GetCurrentForceParams().m_diCondition[0].lOffset || params_.m_diCondition[0].dwPositiveSaturation != force_->GetCurrentForceParams().m_diCondition[0].dwPositiveSaturation || params_.m_diCondition[0].lPositiveCoefficient != force_->GetCurrentForceParams().m_diCondition[0].lPositiveCoefficient) { hr_ = force_->SetParameters(params_); } } if (!force_->IsPlaying()) { hr_ = force_->Start(); } return hr_; } /****f* Steering.Wheel.SDK/StopSpringForce(INT.index) * NAME * HRESULT StopSpringForce(INT index) -- Stop the spring force. * INPUTS * index - index of the game controller. Index 0 corresponds to the * first wheel/joystick connected. Index 1 to the second * wheel/joystick. * SEE ALSO * PlaySpringForce(INT.index,INT.offsetPercentage,INT.saturationPercentage,INT.coefficientPercentage) ****** */ HRESULT Wheel::StopSpringForce(CONST INT index) { _ASSERT(NULL != m_controllerForce[index]); if (NULL == m_controllerForce[index]) return E_FAIL; return m_controllerForce[index]->GetForce(LG_FORCE_SPRING)->Stop(); } /****f* Steering.Wheel.SDK/PlayConstantForce(INT.index,INT.magnitudePercentage) * NAME * HRESULT PlayConstantForce(INT index, INT magnitudePercentage) -- * Play the constant force. * FUNCTION * A constant force works best when continuously updated with a value * tied to the physics engine. * Tie the steering wheel/joystick to the car's physics engine via a * vector force. This will create a centering spring effect, a sliding * effect, a feeling for the car's inertia, and depending on the * physics engine it should also give side collisions (wheel/joystick * jerks in the opposite way of the wall the car just touched). * The vector force could for example be calculated from the lateral * force measured at the front tires. This vector force should be 0 * when at a stop or driving straight. When driving through a turn or * when driving on a banked surface the vector force should have a * magnitude that grows in a proportional way. * INPUTS * index - index of the game controller. Index 0 corresponds to the * first wheel/joystick connected. Index 1 to the second * wheel/joystick. * * magnitudePercentage - Specifies the magnitude of the constant force * effect. A negative value reverses the direction of the force. * Valid ranges for magnitudePercentage are -100 to 100. Any values * outside the valid range are silently clamped. * SEE ALSO * StopConstantForce(INT.index) * SampleInGameImplementation.cpp or SteeringWheelSDKDemo.cpp to see * an example. ****** */ HRESULT Wheel::PlayConstantForce(CONST INT index, CONST INT magnitudePercentage) { HRESULT hr_ = E_FAIL; _ASSERT(NULL != m_controllerForce[index]); if (NULL == m_controllerForce[index]) return E_FAIL; _ASSERT(NULL != m_controllerInput); if (NULL == m_controllerInput) return E_FAIL; // If no controller connected, or no ff, or car airborne, do nothing. if (m_controllerInput->HasForceFeedback(index) == FALSE || m_isAirborne[index] == TRUE) { return E_FAIL; } // If connected device is a gamepad, do not play the force. Since // the constant force is usually tied to the physics and // constantly playing in game it results in annoying constant // vibration for the gamepad, so let's not play it. if (m_controllerInput->IsConnected(index, LG_DEVICE_TYPE_GAMEPAD)) { return S_OK; } ConstantForceParams params_; params_.m_diConstantForce.lMagnitude = Utils::FromPercentage(magnitudePercentage, -100, 100, -DI_FFNOMINALMAX, DI_FFNOMINALMAX); params_.m_numFFAxes = m_controllerInput->GetNumberFFAxesDInput(index); LogiConstantForce* force_ = (LogiConstantForce*)m_controllerForce[index]->GetForce(LG_FORCE_CONSTANT); _ASSERT(NULL != force_); if (NULL == force_) return E_FAIL; if (force_->GetEffectHandle()== NULL) { hr_ = force_->CreateEffect(params_); } else { if (params_.m_diConstantForce.lMagnitude != force_->GetCurrentForceParams().m_diConstantForce.lMagnitude) { hr_ = force_->SetParameters(params_); // For older Formula Force wheel, force a Start twice or // else it doesn't work correctly. if (IsConnected(index, LG_MODEL_FORMULA_FORCE)) { hr_ = force_->Start(); hr_ = force_->Start(); } } } if (!force_->IsPlaying()) hr_ = force_->Start(); return hr_; } /****f* Steering.Wheel.SDK/StopConstantForce(INT.index) * NAME * HRESULT StopConstantForce(INT index) -- stop the constant force. * INPUTS * index - index of the game controller. Index 0 corresponds to the * first wheel/joystick connected. Index 1 to the second * wheel/joystick. * SEE ALSO * PlayConstantForce(INT.index,INT.magnitudePercentage) ****** */ HRESULT Wheel::StopConstantForce(CONST INT index) { _ASSERT(NULL != m_controllerForce[index]); if (NULL == m_controllerForce[index]) return E_FAIL; return m_controllerForce[index]->GetForce(LG_FORCE_CONSTANT)->Stop(); } /****f* Steering.Wheel.SDK/PlayDamperForce(INT.index,INT.coefficientPercentage) * NAME * HRESULT PlayDamperForce(INT index, INT coefficientPercentage) -- * Play the damper force. * FUNCTION * Simulate surfaces that are hard to turn on (mud, car at a stop) or * slippery surfaces (snow, ice). * INPUTS * index - index of the game controller. Index 0 corresponds to the * first wheel/joystick connected. Index 1 to the second * wheel/joystick. * * coefficientPercentage - specify the slope of the effect strength * increase relative to the amount of deflection from the center of * the condition. Higher values mean that the saturation level is * reached sooner. Valid ranges are -100 to 100. Any value outside * the valid range is silently clamped. -100 simulates a very slippery * effect, +100 makes the wheel/joystick very hard to move, simulating * the car at a stop or in mud. * SEE ALSO * StopDamperForce(INT.index) * SampleInGameImplementation.cpp or SteeringWheelSDKDemo.cpp to see * an example. ****** */ HRESULT Wheel::PlayDamperForce(CONST INT index, CONST INT coefficientPercentage) { HRESULT hr_ = E_FAIL; _ASSERT(NULL != m_controllerForce[index]); if (NULL == m_controllerForce[index]) return E_FAIL; _ASSERT(NULL != m_controllerInput); if (NULL == m_controllerInput) return E_FAIL; // If no controller connected, or no ff, or car airborne, do // nothing. if (m_controllerInput->HasForceFeedback(index) == FALSE || m_isAirborne[index] == TRUE || m_controllerForce[index]->IsPlaying(LG_FORCE_SLIPPERY_ROAD)) { return E_FAIL; } DamperForceParams params_; params_.m_diCondition[0].lPositiveCoefficient = Utils::FromPercentage(coefficientPercentage, -100, 100, -DI_FFNOMINALMAX, DI_FFNOMINALMAX); params_.m_diCondition[0].lNegativeCoefficient = params_.m_diCondition[0].lPositiveCoefficient; params_.m_numFFAxes = m_controllerInput->GetNumberFFAxesDInput(index); // Direction for damper parameters is different for Microsoft // joystick if (IsConnected(index, LG_DEVICE_TYPE_JOYSTICK) && IsConnected(index, LG_MANUFACTURER_MICROSOFT)) { params_.m_rglDirection[0] = 0; params_.m_rglDirection[1] = 1; } else { params_.m_rglDirection[0] = 1; params_.m_rglDirection[1] = 0; } LogiDamperForce* force_ = (LogiDamperForce*)m_controllerForce[index]->GetForce(LG_FORCE_DAMPER); _ASSERT(NULL != force_); if (NULL == force_) return E_FAIL; if (force_->GetEffectHandle()== NULL) { hr_ = force_->CreateEffect(params_); } else { if (params_.m_diCondition[0].lPositiveCoefficient != force_->GetCurrentForceParams().m_diCondition[0].lPositiveCoefficient) { hr_ = force_->SetParameters(params_); } } if (!force_->IsPlaying()) { hr_ = force_->Start(); } return hr_; } /****f* Steering.Wheel.SDK/StopDamperForce(INT.index) * NAME * HRESULT StopDamperForce(INT index) -- stop the damper force. * INPUTS * index - index of the game controller. Index 0 corresponds to the * first wheel/joystick connected. Index 1 to the second * wheel/joystick. * SEE ALSO * PlayDamperForce(INT.index,INT.coefficientPercentage) ****** */ HRESULT Wheel::StopDamperForce(CONST INT index) { if (NULL == m_controllerForce[index]) return E_FAIL; return m_controllerForce[index]->GetForce(LG_FORCE_DAMPER)->Stop(); } /****f* Steering.Wheel.SDK/PlaySideCollisionForce(INT.index,INT.magnitudePercentage) * NAME * HRESULT PlaySideCollisionForce(INT index, INT magnitudePercentage) * -- play a side collision force. * INPUTS * index - index of the game controller. Index 0 corresponds to the * first wheel/joystick connected. Index 1 to the second * wheel/joystick. * * magnitudePercentage - Specifies the magnitude of the side collision * force effect. A negative value reverses the direction of the force. * Valid ranges for magnitudePercentage are -100 to 100. Any values * outside the valid range are silently clamped. * NOTES * If you are already using a constant force tied to a vector force * from the physics engine, then you may not need to add side * collisions since depending on your physics engine the side * collisions may automatically be taken care of by the constant * force. ****** */ HRESULT Wheel::PlaySideCollisionForce(CONST INT index, CONST INT magnitudePercentage) { HRESULT hr_ = E_FAIL; _ASSERT(NULL != m_controllerForce[index]); if (NULL == m_controllerForce[index]) return E_FAIL; _ASSERT(NULL != m_controllerInput); if (NULL == m_controllerInput) return E_FAIL; // If no controller connected, or no ff, do nothing. if (m_controllerInput->HasForceFeedback(index) == FALSE) { return E_FAIL; } // If 2 collisions back to back, second weaker collision might overwrite first one, so add their magnitudes. static DWORD timeAtSideCollision_ = 0; static INT combinedMagnitudePercentage_ = 0; DWORD currentTime_ = GetTickCount(); if (currentTime_ - timeAtSideCollision_ > LG_COLLISION_EFFECT_DURATION) { combinedMagnitudePercentage_ = magnitudePercentage; timeAtSideCollision_ = currentTime_; } else { combinedMagnitudePercentage_ = min(combinedMagnitudePercentage_ + magnitudePercentage, 100); } SideCollisionEffectParams params_; params_.m_diConstantForce.lMagnitude = Utils::FromPercentage(combinedMagnitudePercentage_, -100, 100, -DI_FFNOMINALMAX, DI_FFNOMINALMAX); params_.m_numFFAxes = m_controllerInput->GetNumberFFAxesDInput(index); LogiSideCollisionEffect* force_ = (LogiSideCollisionEffect*)m_controllerForce[index]->GetForce(LG_FORCE_SIDE_COLLISION); _ASSERT(NULL != force_); if (NULL == force_) return E_FAIL; if (force_->GetEffectHandle()== NULL) { hr_ = force_->CreateEffect(params_); } else { if (params_.m_diConstantForce.lMagnitude != force_->GetCurrentForceParams().m_diConstantForce.lMagnitude) { hr_ = force_->SetParameters(params_); // For older Formula Force wheel, we need to stop the force before re-starting it. if (IsConnected(index, LG_MODEL_FORMULA_FORCE)) { hr_ = force_->Stop(); } } } hr_ = force_->Start(); return hr_; } /****f* Steering.Wheel.SDK/PlayFrontalCollisionForce(INT.index,INT.magnitudePercentage) * NAME * HRESULT PlayFrontalCollisionForce(INT index, INT * magnitudePercentage) -- Play a frontal collision force. * INPUTS * index - index of the game controller. Index 0 corresponds to the * first wheel/joystick connected. Index 1 to the second * wheel/joystick. * * magnitudePercentage - specifies the magnitude of the frontal * collision force effect. Valid ranges for magnitudePercentage are 0 * to 100. Values higher than 100 are silently clamped. * SEE ALSO * SampleInGameImplementation.cpp or SteeringWheelSDKDemo.cpp to see * an example. ****** */ HRESULT Wheel::PlayFrontalCollisionForce(CONST INT index, CONST INT magnitudePercentage) { HRESULT hr_ = E_FAIL; _ASSERT(NULL != m_controllerForce[index]); if (NULL == m_controllerForce[index]) return E_FAIL; _ASSERT(NULL != m_controllerInput); if (NULL == m_controllerInput) return E_FAIL; // If no controller connected, or no ff, do nothing. if (m_controllerInput->HasForceFeedback(index) == FALSE) { return E_FAIL; } // If 2 collisions back to back, second weaker collision might overwrite first one, so add their magnitudes. static DWORD timeAtFrontalCollision_ = 0; static INT combinedMagnitudePercentage_ = 0; DWORD currentTime_ = GetTickCount(); if (currentTime_ - timeAtFrontalCollision_ > LG_COLLISION_EFFECT_DURATION) { combinedMagnitudePercentage_ = magnitudePercentage; timeAtFrontalCollision_ = currentTime_; } else { combinedMagnitudePercentage_ = min(combinedMagnitudePercentage_ + magnitudePercentage, 100); } FrontalCollisionEffectParams params_; params_.m_diPeriodic.dwMagnitude = Utils::FromPercentage(combinedMagnitudePercentage_, 0, 100, 0, DI_FFNOMINALMAX); params_.m_numFFAxes = m_controllerInput->GetNumberFFAxesDInput(index); LogiFrontalCollisionEffect* force_ = (LogiFrontalCollisionEffect*)m_controllerForce[index]->GetForce(LG_FORCE_FRONTAL_COLLISION); _ASSERT(NULL != force_); if (NULL == force_) return E_FAIL; if (force_->GetEffectHandle()== NULL) { hr_ = force_->CreateEffect(params_); } else { if (params_.m_diPeriodic.dwMagnitude != force_->GetCurrentForceParams().m_diPeriodic.dwMagnitude) { hr_ = force_->SetParameters(params_); // For older Formula Force wheel, we need to stop the force before re-starting it. if (IsConnected(index, LG_MODEL_FORMULA_FORCE)) { hr_ = force_->Stop(); } } } hr_ = force_->Start(); return hr_; } /****f* Steering.Wheel.SDK/PlayDirtRoadEffect(INT.index,INT.magnitudePercentage) * NAME * HRESULT PlayDirtRoadEffect(INT index, INT magnitudePercentage) -- * Play a surface effect that feels like driving on a dirt road. * INPUTS * index - index of the game controller. Index 0 corresponds to the * first wheel/joystick connected. Index 1 to the second * wheel/joystick. * * magnitudePercentage - Specifies the magnitude of the dirt road * effect. Valid ranges for magnitudePercentage are 0 to 100. Values * higher than 100 are silently clamped. * SEE ALSO * StopDirtRoadEffect(INT.index) * PlaySurfaceEffect(INT.index,PeriodicType.type,INT.magnitude,INT.period) ****** */ HRESULT Wheel::PlayDirtRoadEffect(CONST INT index, CONST INT magnitudePercentage) { HRESULT hr_ = E_FAIL; _ASSERT(NULL != m_controllerForce[index]); if (NULL == m_controllerForce[index]) return E_FAIL; _ASSERT(NULL != m_controllerInput); if (NULL == m_controllerInput) return E_FAIL; // If no controller connected, or no ff, or car airborne, do nothing. if (m_controllerInput->HasForceFeedback(index) == FALSE || m_isAirborne[index] == TRUE) { return E_FAIL; } // If older Formula Force wheel, don't play periodic effect cause it gets all screwy. if (IsConnected(index, LG_MODEL_FORMULA_FORCE)) { return S_OK; } DirtRoadEffectParams params_; params_.m_diPeriodic.dwMagnitude = Utils::FromPercentage(magnitudePercentage, -100, 100, -DI_FFNOMINALMAX, DI_FFNOMINALMAX); params_.m_numFFAxes = m_controllerInput->GetNumberFFAxesDInput(index); LogiDirtRoadEffect* force_ = (LogiDirtRoadEffect*)m_controllerForce[index]->GetForce(LG_FORCE_DIRT_ROAD); _ASSERT(NULL != force_); if (NULL == force_) return E_FAIL; if (force_->GetEffectHandle()== NULL) { hr_ = force_->CreateEffect(params_); } else { if (params_.m_diPeriodic.dwMagnitude != force_->GetCurrentForceParams().m_diPeriodic.dwMagnitude) { hr_ = force_->SetParameters(params_); } else { hr_ = S_OK; } } if (!force_->IsPlaying()) hr_ = force_->Start(); return hr_; } /****f* Steering.Wheel.SDK/StopDirtRoadEffect(INT.index) * NAME * HRESULT StopDirtRoadEffect(INT index) -- stop the dirt road effect. * INPUTS * index - index of the game controller. Index 0 corresponds to the * first wheel/joystick connected. Index 1 to the second * wheel/joystick. * SEE ALSO * PlayDirtRoadEffect(INT.index,INT.magnitudePercentage) ****** */ HRESULT Wheel::StopDirtRoadEffect(CONST INT index) { if (NULL == m_controllerForce[index]) return E_FAIL; return m_controllerForce[index]->GetForce(LG_FORCE_DIRT_ROAD)->Stop(); } /****f* Steering.Wheel.SDK/PlayBumpyRoadEffect(INT.index,INT.magnitudePercentage) * NAME * HRESULT PlayBumpyRoadEffect(INT index, INT magnitudePercentage) -- * Play a surface effect that feels like driving on a bumpy road (like * on cobblestones for example). * INPUTS * index - index of the game controller. Index 0 corresponds to the * first wheel/joystick connected. Index 1 to the second * wheel/joystick. * * magnitudePercentage - Specifies the magnitude of the bumpy road * effect. Valid ranges for magnitudePercentage are 0 to 100. Values * higher than 100 are silently clamped. * SEE ALSO * StopBumpyRoadEffect(INT.index) * PlaySurfaceEffect(INT.index,PeriodicType.type,INT.magnitudePercentage,INT.period) ****** */ HRESULT Wheel::PlayBumpyRoadEffect(CONST INT index, CONST INT magnitudePercentage) { HRESULT hr_ = E_FAIL; _ASSERT(NULL != m_controllerForce[index]); if (NULL == m_controllerForce[index]) return E_FAIL; _ASSERT(NULL != m_controllerInput); if (NULL == m_controllerInput) return E_FAIL; // If no controller connected, or no ff, or car airborne, do nothing. if (m_controllerInput->HasForceFeedback(index) == FALSE || m_isAirborne[index] == TRUE) { return E_FAIL; } BumpyRoadEffectParams params_; params_.m_diPeriodic.dwMagnitude = Utils::FromPercentage(magnitudePercentage, -100, 100, -DI_FFNOMINALMAX, DI_FFNOMINALMAX); params_.m_numFFAxes = m_controllerInput->GetNumberFFAxesDInput(index); LogiBumpyRoadEffect* force_ = (LogiBumpyRoadEffect*)m_controllerForce[index]->GetForce(LG_FORCE_BUMPY_ROAD); _ASSERT(NULL != force_); if (NULL == force_) return E_FAIL; if (force_->GetEffectHandle()== NULL) { hr_ = force_->CreateEffect(params_); } else { if (params_.m_diPeriodic.dwMagnitude != force_->GetCurrentForceParams().m_diPeriodic.dwMagnitude) { hr_ = force_->SetParameters(params_); } else { hr_ = S_OK; } } if (!force_->IsPlaying()) hr_ = force_->Start(); return hr_; } /****f* Steering.Wheel.SDK/StopBumpyRoadEffect(INT.index) * NAME * HRESULT StopBumpyRoadEffect(INT index) -- stop the bumpy road * effect. * INPUTS * index - index of the game controller. Index 0 corresponds to the * first wheel/joystick connected. Index 1 to the second * wheel/joystick. * SEE ALSO * PlayBumpyRoadEffect(INT.index,INT.magnitudePercentage) ****** */ HRESULT Wheel::StopBumpyRoadEffect(CONST INT index) { if (NULL == m_controllerForce[index]) return E_FAIL; return m_controllerForce[index]->GetForce(LG_FORCE_BUMPY_ROAD)->Stop(); } /****f* Steering.Wheel.SDK/PlaySlipperyRoadEffect(INT.index,INT.magnitudePercentage) * NAME * HRESULT PlaySlipperyRoadEffect(INT index, INT magnitudePercentage) * -- Play a slippery road effect (snow, ice). * INPUTS * index - index of the game controller. Index 0 corresponds to the * first wheel/joystick connected. Index 1 to the second * wheel/joystick. * * magnitudePercentage - Specifies the magnitude of the slippery road * effect. Valid ranges for magnitudePercentage are 0 to 100. 100 * corresponds to the most slippery effect. * SEE ALSO * StopSlipperyRoadEffect(INT.index) * SampleInGameImplementation.cpp or SteeringWheelSDKDemo.cpp to see * an example. ****** */ HRESULT Wheel::PlaySlipperyRoadEffect(CONST INT index, CONST INT magnitudePercentage) { HRESULT hr_ = E_FAIL; _ASSERT(NULL != m_controllerForce[index]); if (NULL == m_controllerForce[index]) return E_FAIL; _ASSERT(NULL != m_controllerInput); if (NULL == m_controllerInput) return E_FAIL; // If no controller connected, or no ff, or car airborne, do nothing. if (m_controllerInput->HasForceFeedback(index) == FALSE || m_isAirborne[index] == TRUE) { return E_FAIL; } if (IsPlaying(index, LG_FORCE_SPRING)) { StopSpringForce(index); m_springWasPlaying[index] = TRUE; } if (IsPlaying(index, LG_FORCE_DAMPER)) { StopDamperForce(index); m_damperWasPlaying[index] = TRUE; } SlipperyRoadEffectParams params_; params_.m_diCondition[0].lPositiveCoefficient = -Utils::FromPercentage(magnitudePercentage, 0, 100, 0, DI_FFNOMINALMAX); params_.m_diCondition[0].lNegativeCoefficient = params_.m_diCondition[0].lPositiveCoefficient; params_.m_numFFAxes = m_controllerInput->GetNumberFFAxesDInput(index); // Direction for damper parameters is different for Microsoft joystick if (IsConnected(index, LG_DEVICE_TYPE_JOYSTICK) && IsConnected(index, LG_MANUFACTURER_MICROSOFT)) { params_.m_rglDirection[0] = 0; params_.m_rglDirection[1] = 1; } else { params_.m_rglDirection[0] = 1; params_.m_rglDirection[1] = 0; } LogiSlipperyRoadEffect* force_ = (LogiSlipperyRoadEffect*)m_controllerForce[index]->GetForce(LG_FORCE_SLIPPERY_ROAD); _ASSERT(NULL != force_); if (NULL == force_) return E_FAIL; if (force_->GetEffectHandle()== NULL) { hr_ = force_->CreateEffect(params_); } else { if (params_.m_diCondition[0].lPositiveCoefficient != force_->GetCurrentForceParams().m_diCondition[0].lPositiveCoefficient) { hr_ = force_->SetParameters(params_); } } if (!force_->IsPlaying()) hr_ = force_->Start(); return hr_; } /****f* Steering.Wheel.SDK/StopSlipperyRoadEffect(INT.index) * NAME * HRESULT StopSlipperyRoadEffect(INT index) -- stop the slippery road * effect. * INPUTS * index - index of the game controller. Index 0 corresponds to the * first wheel/joystick connected. Index 1 to the second * wheel/joystick. * SEE ALSO * PlaySlipperyRoadEffect(INT.index,INT.magnitudePercentage) ****** */ HRESULT Wheel::StopSlipperyRoadEffect(CONST INT index) { HRESULT hr_; if (NULL == m_controllerForce[index]) return E_FAIL; if (FAILED(hr_ = m_controllerForce[index]->GetForce(LG_FORCE_SLIPPERY_ROAD)->Stop())) return hr_; if (m_springWasPlaying[index] == TRUE) { PlaySpringForce(index); m_springWasPlaying[index] = FALSE; } if (m_damperWasPlaying[index] == TRUE) { PlayDamperForce(index); m_damperWasPlaying[index] = FALSE; } return S_OK; } /****f* Steering.Wheel.SDK/PlaySurfaceEffect(INT.index,PeriodicType.type,INT.magnitudePercentage,INT.period) * NAME * HRESULT PlaySurfaceEffect(INT index, PeriodicType type, INT * magnitudePercentage, INT period) -- play any type of rumble to * simulate surface effects. * INPUTS * index - index of the game controller. Index 0 corresponds to the * first wheel/joystick connected. Index 1 to the second * wheel/joystick. * * type - Specifies the type of force effect. Can be one of the * following values: * - PeriodicType.LG_TYPE_SINE * - PeriodicType.LG_TYPE_SQUARE * * magnitudePercentage - Specifies the magnitude of the surface * effect. Valid ranges for magnitudePercentage are 0 to 100. Values * higher than 100 are silently clamped. * * period - Specifies the period of the periodic force effect. The * value is the duration for one full cycle of the periodic function * measured in milliseconds. A good range of values for the period is * 20 ms (sand) to 120 ms (wooden bridge or cobblestones). For a * surface effect the period should not be any bigger than 150 ms. * SEE ALSO * StopSurfaceEffect(INT.index) * SampleInGameImplementation.cpp or SteeringWheelSDKDemo.cpp to see * an example. ****** */ HRESULT Wheel::PlaySurfaceEffect(CONST INT index, CONST PeriodicType type, CONST INT magnitudePercentage, CONST INT period) { HRESULT hr_ = E_FAIL; _ASSERT(NULL != m_controllerForce[index]); if (NULL == m_controllerForce[index]) return E_FAIL; _ASSERT(NULL != m_controllerInput); if (NULL == m_controllerInput) return E_FAIL; // If no controller connected, or no ff, or car airborne, do nothing. if (m_controllerInput->HasForceFeedback(index) == FALSE || m_isAirborne[index] == TRUE) { return E_FAIL; } // If older Formula Force wheel, don't play periodic effect cause // it gets all screwy. if (IsConnected(index, LG_MODEL_FORMULA_FORCE)) { return S_OK; } SurfaceEffectParams params_; params_.m_diPeriodic.dwMagnitude = Utils::FromPercentage(magnitudePercentage, -100, 100, -DI_FFNOMINALMAX, DI_FFNOMINALMAX); params_.m_diPeriodic.dwPeriod = period * 1000; params_.m_numFFAxes = m_controllerInput->GetNumberFFAxesDInput(index); switch(type) { case LG_TYPE_SINE: params_.m_type = &GUID_Sine; break; case LG_TYPE_SQUARE: params_.m_type = &GUID_Square; break; case LG_TYPE_TRIANGLE: params_.m_type = &GUID_Triangle; break; } LogiSurfaceEffect* force_ = (LogiSurfaceEffect*)m_controllerForce[index]->GetForce(LG_FORCE_SURFACE_EFFECT); _ASSERT(NULL != force_); if (NULL == force_) return E_FAIL; if (force_->GetEffectHandle()== NULL) { hr_ = force_->CreateEffect(params_); } else { // if type different, unload force, create new one if (params_.m_type != force_->GetCurrentForceParams().m_type) { if FAILED(hr_ = force_->Unload()) LOGIWHEELTRACE(_T("PlaySurfaceEffect; Failed to unload force\n")); hr_ = force_->CreateEffect(params_); } else if (params_.m_diPeriodic.dwMagnitude != force_->GetCurrentForceParams().m_diPeriodic.dwMagnitude || params_.m_diPeriodic.dwPeriod != force_->GetCurrentForceParams().m_diPeriodic.dwPeriod || params_.m_diPeriodic.lOffset != force_->GetCurrentForceParams().m_diPeriodic.lOffset) { hr_ = force_->SetParameters(params_); } else { hr_ = S_OK; } } if (!force_->IsPlaying()) hr_ = force_->Start(); return hr_; } /****f* Steering.Wheel.SDK/StopSurfaceEffect(INT.index) * NAME * HRESULT StopSurfaceEffect(INT index) -- stop the surface effect. * INPUTS * index - index of the game controller. Index 0 corresponds to the * first wheel/joystick connected. Index 1 to the second * wheel/joystick. * SEE ALSO * PlaySurfaceEffect(INT.index,PeriodicType.type,INT.magnitudePercentage,INT.period) ****** */ HRESULT Wheel::StopSurfaceEffect(CONST INT index) { if (NULL == m_controllerForce[index]) return E_FAIL; return m_controllerForce[index]->GetForce(LG_FORCE_SURFACE_EFFECT)->Stop(); } /****f* Steering.Wheel.SDK/PlayCarAirborne(INT.index) * NAME * HRESULT PlayCarAirborne(INT index) -- play an effect that simulates * a car that is airborne or where the front wheels do not touch the * ground. * INPUTS * index - index of the game controller. Index 0 corresponds to the * first wheel/joystick connected. Index 1 to the second * wheel/joystick. * SEE ALSO * StopCarAirborne(INT.index) * SampleInGameImplementation.cpp or SteeringWheelSDKDemo.cpp to see * an example. ****** */ HRESULT Wheel::PlayCarAirborne(CONST INT index) { _ASSERT(NULL != m_controllerForce[index]); if (NULL == m_controllerForce[index]) return E_FAIL; _ASSERT(NULL != m_controllerInput); if (NULL == m_controllerInput) return E_FAIL; // If no controller connected, or no ff, do nothing. if (m_controllerInput->HasForceFeedback(index) == FALSE) { return E_FAIL; } if (m_isAirborne[index] == FALSE) { m_isAirborne[index] = TRUE; if (IsPlaying(index, LG_FORCE_SPRING)) { StopSpringForce(index); m_wasPlayingBeforeAirborne[index][LG_FORCE_SPRING] = TRUE; } if (IsPlaying(index, LG_FORCE_CONSTANT)) { StopConstantForce(index); m_wasPlayingBeforeAirborne[index][LG_FORCE_CONSTANT] = TRUE; } if (IsPlaying(index, LG_FORCE_DAMPER)) { StopDamperForce(index); m_wasPlayingBeforeAirborne[index][LG_FORCE_DAMPER] = TRUE; } if (IsPlaying(index, LG_FORCE_DIRT_ROAD)) { StopDirtRoadEffect(index); m_wasPlayingBeforeAirborne[index][LG_FORCE_DIRT_ROAD] = TRUE; } if (IsPlaying(index, LG_FORCE_BUMPY_ROAD)) { StopBumpyRoadEffect(index); m_wasPlayingBeforeAirborne[index][LG_FORCE_BUMPY_ROAD] = TRUE; } if (IsPlaying(index, LG_FORCE_SLIPPERY_ROAD)) { StopSlipperyRoadEffect(index); m_wasPlayingBeforeAirborne[index][LG_FORCE_SLIPPERY_ROAD] = TRUE; } if (IsPlaying(index, LG_FORCE_SURFACE_EFFECT)) { StopSurfaceEffect(index); m_wasPlayingBeforeAirborne[index][LG_FORCE_SURFACE_EFFECT] = TRUE; } } return S_OK; } /****f* Steering.Wheel.SDK/StopCarAirborne(INT.index) * NAME * HRESULT StopCarAirborne(INT index) -- stop the car airborne effect * and resume any forces that were playing before the car was * airborne. * INPUTS * index - index of the game controller. Index 0 corresponds to the * first wheel/joystick connected. Index 1 to the second * wheel/joystick. * SEE ALSO * PlayCarAirborne(INT.index) ****** */ HRESULT Wheel::StopCarAirborne(CONST INT index) { m_isAirborne[index] = FALSE; if (m_wasPlayingBeforeAirborne[index][LG_FORCE_SPRING] == TRUE) { PlaySpringForce(index); } if (m_wasPlayingBeforeAirborne[index][LG_FORCE_CONSTANT] == TRUE) { PlayConstantForce(index); } if (m_wasPlayingBeforeAirborne[index][LG_FORCE_DAMPER] == TRUE) { PlayDamperForce(index); } if (m_wasPlayingBeforeAirborne[index][LG_FORCE_DIRT_ROAD] == TRUE) { PlayDirtRoadEffect(index); } if (m_wasPlayingBeforeAirborne[index][LG_FORCE_BUMPY_ROAD] == TRUE) { PlayBumpyRoadEffect(index); } if (m_wasPlayingBeforeAirborne[index][LG_FORCE_SLIPPERY_ROAD] == TRUE) { PlaySlipperyRoadEffect(index); } if (m_wasPlayingBeforeAirborne[index][LG_FORCE_SURFACE_EFFECT] == TRUE) { PlaySurfaceEffect(index); } // re-initialize variables for (INT jj = 0; jj < LG_NUMBER_FORCE_EFFECTS; jj++) { m_wasPlayingBeforeAirborne[index][jj] = FALSE; } return S_OK; } /****f* Steering.Wheel.SDK/PlaySoftstopForce(INT.index,INT.usableRangePercentage) * NAME * HRESULT PlaySoftstopForce(INT index, INT usableRangePercentage) -- * Play a spring force that acts like a soft stop in order to limit a * wheel's range. * INPUTS * index - index of the game controller. * * usableRangePercentage - Specifies the deadband in percentage of the * softstop force effect. * SEE ALSO * StopSoftstopForce(INT.index) ****** */ HRESULT Wheel::PlaySoftstopForce(CONST INT index, CONST INT usableRangePercentage) { HRESULT hr_ = E_FAIL; _ASSERT(NULL != m_controllerForce[index]); if (NULL == m_controllerForce[index]) return E_FAIL; _ASSERT(NULL != m_controllerInput); if (NULL == m_controllerInput) return E_FAIL; // If no controller connected, or no ff, do nothing. if (m_controllerInput->HasForceFeedback(index) == FALSE) { return E_FAIL; } SoftstopForceParams params_; params_.m_diCondition[0].lDeadBand = Utils::FromPercentage(usableRangePercentage, 0, 100, 0, DI_FFNOMINALMAX); params_.m_numFFAxes = m_controllerInput->GetNumberFFAxesDInput(index); LogiSoftstopForce* force_ = (LogiSoftstopForce*)m_controllerForce[index]->GetForce(LG_FORCE_SOFTSTOP); _ASSERT(NULL != force_); if (NULL == force_) return E_FAIL; if (force_->GetEffectHandle()== NULL) { hr_ = force_->CreateEffect(params_); } else { if (params_.m_diCondition[0].lDeadBand != force_->GetCurrentForceParams().m_diCondition[0].lDeadBand) { hr_ = force_->SetParameters(params_); } } if (!force_->IsPlaying()) hr_ = force_->Start(); return hr_; } /****f* Steering.Wheel.SDK/StopSoftstopForce(INT.index) * NAME * HRESULT StopSoftstopForce(INT index) -- stop the "softstop" spring * force. * INPUTS * index - index of the game controller. * SEE ALSO * PlaySoftstopForce(INT.index,INT.usableRangePercentage) ****** */ HRESULT Wheel::StopSoftstopForce(CONST INT index) { if (NULL == m_controllerForce[index]) return E_FAIL; return m_controllerForce[index]->GetForce(LG_FORCE_SOFTSTOP)->Stop(); } HRESULT Wheel::PlaySpringForce(CONST INT index) { HRESULT hr_ = E_FAIL; _ASSERT(NULL != m_controllerForce[index]); if (NULL == m_controllerForce[index]) return E_FAIL; _ASSERT(NULL != m_controllerInput); if (NULL == m_controllerInput) return E_FAIL; // If no controller connected, or no ff, or car airborne, do nothing. if (m_controllerInput->HasForceFeedback(index) == FALSE || m_isAirborne[index] == TRUE || m_controllerForce[index]->IsPlaying(LG_FORCE_SLIPPERY_ROAD)) { return E_FAIL; } LogiSpringForce* force_ = (LogiSpringForce*)m_controllerForce[index]->GetForce(LG_FORCE_SPRING); _ASSERT(NULL != force_); if (NULL == force_) return E_FAIL; if (force_->GetEffectHandle()== NULL) return E_FAIL; SpringForceParams params_ = force_->GetCurrentForceParams(); hr_ = force_->SetParameters(params_); if (!force_->IsPlaying()) hr_ = force_->Start(); return hr_; } HRESULT Wheel::PlayConstantForce(CONST INT index) { HRESULT hr_ = E_FAIL; _ASSERT(NULL != m_controllerForce[index]); if (NULL == m_controllerForce[index]) return E_FAIL; _ASSERT(NULL != m_controllerInput); if (NULL == m_controllerInput) return E_FAIL; // If no controller connected, or no ff, or car airborne, do // nothing. if (m_controllerInput->HasForceFeedback(index) == FALSE || m_isAirborne[index] == TRUE) { return E_FAIL; } LogiConstantForce* force_ = (LogiConstantForce*)m_controllerForce[index]->GetForce(LG_FORCE_CONSTANT); _ASSERT(NULL != force_); if (NULL == force_) return E_FAIL; if (force_->GetEffectHandle()== NULL) return E_FAIL; ConstantForceParams params_ = force_->GetCurrentForceParams(); hr_ = force_->SetParameters(params_); if (!force_->IsPlaying()) hr_ = force_->Start(); return hr_; } HRESULT Wheel::PlayDamperForce(CONST INT index) { HRESULT hr_ = E_FAIL; _ASSERT(NULL != m_controllerForce[index]); if (NULL == m_controllerForce[index]) return E_FAIL; _ASSERT(NULL != m_controllerInput); if (NULL == m_controllerInput) return E_FAIL; // If no controller connected, or no ff, or car airborne, or // slippery road playing do nothing. if (m_controllerInput->HasForceFeedback(index) == FALSE || m_isAirborne[index] == TRUE || m_controllerForce[index]->IsPlaying(LG_FORCE_SLIPPERY_ROAD)) { return E_FAIL; } LogiDamperForce* force_ = (LogiDamperForce*)m_controllerForce[index]->GetForce(LG_FORCE_DAMPER); _ASSERT(NULL != force_); if (NULL == force_) return E_FAIL; if (force_->GetEffectHandle()== NULL) return E_FAIL; DamperForceParams params_ = force_->GetCurrentForceParams(); hr_ = force_->SetParameters(params_); if (!force_->IsPlaying()) hr_ = force_->Start(); return hr_; } HRESULT Wheel::PlayDirtRoadEffect(CONST INT index) { HRESULT hr_ = E_FAIL; _ASSERT(NULL != m_controllerForce[index]); if (NULL == m_controllerForce[index]) return E_FAIL; _ASSERT(NULL != m_controllerInput); if (NULL == m_controllerInput) return E_FAIL; // If no controller connected, or no ff, or car airborne, do nothing. if (m_controllerInput->HasForceFeedback(index) == FALSE || m_isAirborne[index] == TRUE) { return E_FAIL; } LogiDirtRoadEffect* force_ = (LogiDirtRoadEffect*)m_controllerForce[index]->GetForce(LG_FORCE_DIRT_ROAD); _ASSERT(NULL != force_); if (NULL == force_) return E_FAIL; if (force_->GetEffectHandle()== NULL) return E_FAIL; DirtRoadEffectParams params_ = force_->GetCurrentForceParams(); hr_ = force_->SetParameters(params_); if (!force_->IsPlaying()) hr_ = force_->Start(); return hr_; } HRESULT Wheel::PlayBumpyRoadEffect(CONST INT index) { HRESULT hr_ = E_FAIL; _ASSERT(NULL != m_controllerForce[index]); if (NULL == m_controllerForce[index]) return E_FAIL; _ASSERT(NULL != m_controllerInput); if (NULL == m_controllerInput) return E_FAIL; // If no controller connected, or no ff, or car airborne, do nothing. if (m_controllerInput->HasForceFeedback(index) == FALSE || m_isAirborne[index] == TRUE) { return E_FAIL; } // If older Formula Force wheel, don't play periodic effect cause // it gets all screwy. if (IsConnected(index, LG_MODEL_FORMULA_FORCE)) { return S_OK; } LogiBumpyRoadEffect* force_ = (LogiBumpyRoadEffect*)m_controllerForce[index]->GetForce(LG_FORCE_BUMPY_ROAD); _ASSERT(NULL != force_); if (NULL == force_) return E_FAIL; if (force_->GetEffectHandle()== NULL) return E_FAIL; BumpyRoadEffectParams params_ = force_->GetCurrentForceParams(); hr_ = force_->SetParameters(params_); if (!force_->IsPlaying()) hr_ = force_->Start(); return hr_; } HRESULT Wheel::PlaySlipperyRoadEffect(CONST INT index) { HRESULT hr_ = E_FAIL; _ASSERT(NULL != m_controllerForce[index]); if (NULL == m_controllerForce[index]) return E_FAIL; _ASSERT(NULL != m_controllerInput); if (NULL == m_controllerInput) return E_FAIL; // If no controller connected, or no ff, or car airborne, do nothing. if (m_controllerInput->HasForceFeedback(index) == FALSE || m_isAirborne[index] == TRUE) { return E_FAIL; } LogiSlipperyRoadEffect* force_ = (LogiSlipperyRoadEffect*)m_controllerForce[index]->GetForce(LG_FORCE_SLIPPERY_ROAD); _ASSERT(NULL != force_); if (NULL == force_) return E_FAIL; if (force_->GetEffectHandle()== NULL) return E_FAIL; SlipperyRoadEffectParams params_ = force_->GetCurrentForceParams(); hr_ = force_->SetParameters(params_); if (!force_->IsPlaying()) hr_ = force_->Start(); return hr_; } HRESULT Wheel::PlaySurfaceEffect(CONST INT index) { HRESULT hr_ = E_FAIL; _ASSERT(NULL != m_controllerForce[index]); if (NULL == m_controllerForce[index]) return E_FAIL; _ASSERT(NULL != m_controllerInput); if (NULL == m_controllerInput) return E_FAIL; // If no controller connected, or no ff, or car airborne, do nothing. if (m_controllerInput->HasForceFeedback(index) == FALSE || m_isAirborne[index] == TRUE) { return E_FAIL; } LogiSurfaceEffect* force_ = (LogiSurfaceEffect*)m_controllerForce[index]->GetForce(LG_FORCE_SURFACE_EFFECT); _ASSERT(NULL != force_); if (NULL == force_) return E_FAIL; if (force_->GetEffectHandle()== NULL) return E_FAIL; SurfaceEffectParams params_ = force_->GetCurrentForceParams(); hr_ = force_->SetParameters(params_); if (!force_->IsPlaying()) hr_ = force_->Start(); return hr_; } LRESULT CALLBACK WheelWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { BOOL activateValue_ = FALSE; DEV_BROADCAST_HDR* pHeader_ = NULL; switch (message) { case WM_ACTIVATEAPP: activateValue_ = static_cast(wParam); if (activateValue_) { g_forceActuatorsResetTriggered = GetTickCount(); } /*else { }*/ break; case WM_DEVICECHANGE: switch (wParam) { case DBT_DEVICEARRIVAL: //LOGICONTROLLERTRACE(_T("DBT_DEVICEARRIVAL\n")); pHeader_ = (DEV_BROADCAST_HDR*)lParam; if (pHeader_) { if (pHeader_->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { g_forceActuatorsResetTriggered = GetTickCount(); } } break; case DBT_DEVICEREMOVECOMPLETE: //LOGICONTROLLERTRACE(_T("DBT_DEVICEREMOVECOMPLETE\n")); pHeader_ = (DEV_BROADCAST_HDR*)lParam; if (pHeader_) { if (pHeader_->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { g_forceActuatorsResetTriggered = GetTickCount(); } } break; default: break; } break; default: break; } return CallWindowProc (g_OldWheelWnd, hwnd, message, wParam, lParam); }