#include "mungal4.h" #pragma hdrstop #include "l4padrio.h" #include #pragma comment(lib, "xinput9_1_0.lib") //######################################################################## // Input helpers; the binding tables live in bindings.txt now // (l4padbindings.cpp writes and parses the vRIO-format profile) //######################################################################## namespace { Scalar StickValue(int raw, int dead_zone) { if (raw > -dead_zone && raw < dead_zone) { return (Scalar) 0; } Scalar value = (raw > 0) ? (Scalar)(raw - dead_zone) / (Scalar)(32767 - dead_zone) : (Scalar)(raw + dead_zone) / (Scalar)(32768 - dead_zone); if (value > 1.0f) value = 1.0f; if (value < -1.0f) value = -1.0f; return value; } Scalar Clamp01(Scalar value) { if (value < 0.0f) return 0.0f; if (value > 1.0f) return 1.0f; return value; } Logical KeyDown(int virtual_key) { return (GetAsyncKeyState(virtual_key) & 0x8000) != 0; } } //######################################################################## //############################### PadRIO ################################# //######################################################################## PadRIO *PadRIO::activeInstance = NULL; void PadRIO::SetScreenButton(int unit, Logical pressed) { if (activeInstance != NULL && unit >= 0 && unit < buttonUnits) { activeInstance->screenButton[unit] = pressed ? 1 : 0; } } int PadRIO::GetLampState(int unit) { if (activeInstance != NULL && unit >= 0 && unit < lampCount) { return activeInstance->lampState[unit]; } return 0; } PadRIO::PadRIO() { Check_Pointer(this); queueHead = 0; queueTail = 0; lastPollTick = GetTickCount(); lastPadCheckTick = 0; padIndex = -1; padReported = False; analogRequested = False; throttleAccum = (Scalar) 0; sentThrottle = sentLeftPedal = sentRightPedal = (Scalar) 0; sentJoystickX = sentJoystickY = (Scalar) 0; memset(buttonDown, 0, sizeof(buttonDown)); memset(keypadDown, 0, sizeof(keypadDown)); memset(lampState, 0, sizeof(lampState)); memset(screenButton, 0, sizeof(screenButton)); PadBindings_Load(&profile); invertX = False; invertY = False; const char *flip = getenv("L4PADFLIP"); if (flip != NULL) { if (strchr(flip, 'X') || strchr(flip, 'x')) { invertX = True; } if (strchr(flip, 'Y') || strchr(flip, 'y')) { invertY = True; } } // Report as a v4.2 board, like vRIO does MajorRevision = 4; MinorRevision = 2; activeInstance = this; DEBUG_STREAM << "PadRIO: virtual RIO active (XInput pad + keyboard)\n" << std::flush; } PadRIO::~PadRIO() { Check_Pointer(this); if (activeInstance == this) { activeInstance = NULL; } } Logical PadRIO::TestInstance() const { return True; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // The controls manager drains events every frame; sampling lives here so // button latency does not depend on the analog request cadence (which is // 15 s outside of missions). //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Logical PadRIO::GetNextEvent(RIOEvent *destinationPointer) { Check_Pointer(this); Check_Pointer(destinationPointer); PollInputs(); if (queueTail == queueHead) { return False; } *destinationPointer = eventQueue[queueTail]; queueTail = (queueTail + 1) % queueSize; return True; } void PadRIO::RequestAnalogUpdate() { Check_Pointer(this); analogRequested = True; } void PadRIO::GeneralReset() { Check_Pointer(this); throttleAccum = (Scalar) 0; Throttle = (Scalar) 0; LeftPedal = (Scalar) 0; RightPedal = (Scalar) 0; JoystickX = (Scalar) 0; JoystickY = (Scalar) 0; analogRequested = True; memset(lampState, 0, sizeof(lampState)); memset(keypadDown, 0, sizeof(keypadDown)); for (int i = 0; i < profile.keyButtonCount; ++i) { profile.keyButtons[i].latched = False; profile.keyButtons[i].wasDown = False; } for (int i = 0; i < profile.padButtonCount; ++i) { profile.padButtons[i].latched = False; profile.padButtons[i].wasDown = False; } } void PadRIO::ResetThrottle() { Check_Pointer(this); throttleAccum = (Scalar) 0; Throttle = (Scalar) 0; analogRequested = True; } void PadRIO::SetLamp(int lampNumber, int state) { Check_Pointer(this); if (lampNumber >= 0 && lampNumber < lampCount) { lampState[lampNumber] = (unsigned char) state; } } void PadRIO::QueueEvent(const RIOEvent &an_event) { int next = (queueHead + 1) % queueSize; if (next == queueTail) { // full: drop the oldest event queueTail = (queueTail + 1) % queueSize; } eventQueue[queueHead] = an_event; queueHead = next; } void PadRIO::PollInputs() { unsigned long now = GetTickCount(); if (now - lastPollTick < 10) { return; } Scalar delta_t = (Scalar)(now - lastPollTick) / 1000.0f; if (delta_t > 0.25f) { delta_t = 0.25f; } lastPollTick = now; //--------------------------------------------------------------- // Find / keep the XInput pad. Probing empty slots is slow, so an // absent pad is only re-probed every 3 seconds. //--------------------------------------------------------------- XINPUT_STATE pad; memset(&pad, 0, sizeof(pad)); Logical pad_live = False; if (padIndex >= 0) { pad_live = (XInputGetState((DWORD) padIndex, &pad) == ERROR_SUCCESS); if (!pad_live) { DEBUG_STREAM << "PadRIO: controller " << padIndex << " disconnected\n" << std::flush; padIndex = -1; } } if (padIndex < 0 && (now - lastPadCheckTick) >= 3000) { lastPadCheckTick = now; for (DWORD i = 0; i < 4; ++i) { if (XInputGetState(i, &pad) == ERROR_SUCCESS) { padIndex = (int) i; pad_live = True; DEBUG_STREAM << "PadRIO: controller " << padIndex << " connected\n" << std::flush; break; } } if (padIndex < 0 && !padReported) { padReported = True; DEBUG_STREAM << "PadRIO: no controller found - keyboard only\n" << std::flush; } } //--------------------------------------------------------------- // Buttons: build the desired state from the binding profile // (keyboard + pad, with toggle latches), merge the on-screen // cockpit buttons, then diff against what we last reported. // Keypad addresses (0x50-0x6F) collect separately - they become // arcade KeyEvents, not button events. //--------------------------------------------------------------- unsigned char desired[buttonUnits]; unsigned char keypadDesired[keypadUnits]; memset(desired, 0, sizeof(desired)); memset(keypadDesired, 0, sizeof(keypadDesired)); for (int i = 0; i < profile.keyButtonCount; ++i) { PadKeyButtonBinding *binding = &profile.keyButtons[i]; Logical down = KeyDown(binding->virtualKey); if (binding->toggle && down && !binding->wasDown) { binding->latched = !binding->latched; } binding->wasDown = down; if (binding->toggle ? binding->latched : down) { if (binding->address < buttonUnits) { desired[binding->address] = 1; } else if (binding->address >= 0x50 && binding->address < 0x50 + keypadUnits) { keypadDesired[binding->address - 0x50] = 1; } } } for (int i = 0; i < profile.padButtonCount; ++i) { PadPadButtonBinding *binding = &profile.padButtons[i]; Logical down = pad_live && (pad.Gamepad.wButtons & binding->padMask) != 0; if (binding->toggle && down && !binding->wasDown) { binding->latched = !binding->latched; } binding->wasDown = down; if (binding->toggle ? binding->latched : down) { if (binding->address < buttonUnits) { desired[binding->address] = 1; } else if (binding->address >= 0x50 && binding->address < 0x50 + keypadUnits) { keypadDesired[binding->address - 0x50] = 1; } } } for (int i = 0; i < buttonUnits; ++i) { if (screenButton[i]) { desired[i] = 1; } } for (int unit = 0; unit < buttonUnits; ++unit) { if (desired[unit] != buttonDown[unit]) { buttonDown[unit] = desired[unit]; RIOEvent an_event; an_event.Type = desired[unit] ? ButtonPressedEvent : ButtonReleasedEvent; an_event.Data.Unit = unit; QueueEvent(an_event); } } //--------------------------------------------------------------- // Keypads: presses become the arcade RIO KeyEvents. Unit 0 is the // pilot's internal keypad (0x50-0x5F), unit 1 the external // operator keypad (0x60-0x6F); the key is the hex digit 0-15. //--------------------------------------------------------------- for (int pad_key = 0; pad_key < keypadUnits; ++pad_key) { if (keypadDesired[pad_key] != keypadDown[pad_key]) { keypadDown[pad_key] = keypadDesired[pad_key]; if (keypadDesired[pad_key]) { RIOEvent an_event; an_event.Type = KeyEvent; an_event.Data.Keyboard.Unit = (pad_key >= 0x10) ? 1 : 0; an_event.Data.Keyboard.Key = pad_key & 0x0F; QueueEvent(an_event); } } } //--------------------------------------------------------------- // Axes, from the profile. 'deflect' sources sum into a springy // position; 'rate' sources integrate the throttle (the pod's only // sticky axis) by value per second. //--------------------------------------------------------------- Scalar deflect[BindAxisCount]; Scalar rate[BindAxisCount]; memset(deflect, 0, sizeof(deflect)); memset(rate, 0, sizeof(rate)); for (int i = 0; i < profile.keyAxisCount; ++i) { const PadKeyAxisBinding *binding = &profile.keyAxes[i]; if (KeyDown(binding->virtualKey)) { if (binding->mode == BindKeyRate) { rate[binding->axis] += binding->value; } else { deflect[binding->axis] += binding->value; } } } if (pad_live) { for (int i = 0; i < profile.padAxisCount; ++i) { const PadPadAxisBinding *binding = &profile.padAxes[i]; Scalar value = (Scalar) 0; switch (binding->source) { case BindPadLeftStickX: value = StickValue(pad.Gamepad.sThumbLX, (int)(binding->deadzone * 32767.0f)); break; case BindPadLeftStickY: value = StickValue(pad.Gamepad.sThumbLY, (int)(binding->deadzone * 32767.0f)); break; case BindPadRightStickX: value = StickValue(pad.Gamepad.sThumbRX, (int)(binding->deadzone * 32767.0f)); break; case BindPadRightStickY: value = StickValue(pad.Gamepad.sThumbRY, (int)(binding->deadzone * 32767.0f)); break; case BindPadLeftTrigger: value = (Scalar)(pad.Gamepad.bLeftTrigger) / 255.0f; if (value <= binding->deadzone) value = (Scalar) 0; break; case BindPadRightTrigger: value = (Scalar)(pad.Gamepad.bRightTrigger) / 255.0f; if (value <= binding->deadzone) value = (Scalar) 0; break; } if (binding->invert) { value = -value; } if (binding->rate > 0.0f) { rate[binding->axis] += value * binding->rate; } else { deflect[binding->axis] += value; } } } throttleAccum = Clamp01(throttleAccum + rate[BindAxisThrottle] * delta_t); Scalar x = deflect[BindAxisJoystickX]; Scalar y = deflect[BindAxisJoystickY]; if (x > 1.0f) x = 1.0f; if (x < -1.0f) x = -1.0f; if (y > 1.0f) y = 1.0f; if (y < -1.0f) y = -1.0f; Throttle = Clamp01(throttleAccum + deflect[BindAxisThrottle]); LeftPedal = Clamp01(deflect[BindAxisLeftPedal]); RightPedal = Clamp01(deflect[BindAxisRightPedal]); // The profile encodes the pod's stick sign convention; L4PADFLIP // flips on top of it per axis. JoystickX = invertX ? -x : x; JoystickY = invertY ? -y : y; //--------------------------------------------------------------- // Emit an analog event when asked to, or when anything moved //--------------------------------------------------------------- Logical changed = (Throttle != sentThrottle) || (LeftPedal != sentLeftPedal) || (RightPedal != sentRightPedal) || (JoystickX != sentJoystickX) || (JoystickY != sentJoystickY); if (analogRequested || changed) { analogRequested = False; sentThrottle = Throttle; sentLeftPedal = LeftPedal; sentRightPedal = RightPedal; sentJoystickX = JoystickX; sentJoystickY = JoystickY; RIOEvent an_event; an_event.Type = AnalogEvent; an_event.Data.Unit = 0; QueueEvent(an_event); } }