/* ** input.cpp ** ** This file is part of mkxp. ** ** Copyright (C) 2013 Jonas Kulla ** ** mkxp is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 2 of the License, or ** (at your option) any later version. ** ** mkxp is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with mkxp. If not, see . */ #include "input.h" #include "sharedstate.h" #include "eventthread.h" #include "exception.h" #include "util.h" #include #include #include #include #define BUTTON_CODE_COUNT 24 struct ButtonState { bool pressed; bool triggered; bool repeated; ButtonState() : pressed(false), triggered(false), repeated(false) {} }; struct KbBindingData { SDL_Scancode source; Input::ButtonCode target; }; struct JsBindingData { int source; Input::ButtonCode target; }; struct Binding { Binding(Input::ButtonCode target = Input::None) : target(target) {} virtual bool sourceActive() const = 0; virtual bool sourceRepeatable() const = 0; Input::ButtonCode target; }; /* Keyboard binding */ struct KbBinding : public Binding { KbBinding() {} KbBinding(const KbBindingData &data) : Binding(data.target), source(data.source) {} bool sourceActive() const { return EventThread::keyStates[source]; } bool sourceRepeatable() const { return (source >= SDL_SCANCODE_A && source <= SDL_SCANCODE_0) || (source >= SDL_SCANCODE_RIGHT && source <= SDL_SCANCODE_UP) || (source >= SDL_SCANCODE_F1 && source <= SDL_SCANCODE_F12); } SDL_Scancode source; }; /* Joystick button binding */ struct JsButtonBinding : public Binding { JsButtonBinding() {} JsButtonBinding(const JsBindingData &data) : Binding(data.target), source(data.source) {} bool sourceActive() const { return EventThread::joyState.buttons[source]; } bool sourceRepeatable() const { return true; } int source; }; /* Joystick axis binding */ struct JsAxisBinding : public Binding { JsAxisBinding() {} JsAxisBinding(int *source, int compareValue, Input::ButtonCode target) : Binding(target), source(source), compareValue(compareValue) {} bool sourceActive() const { return (*source == compareValue); } bool sourceRepeatable() const { return true; } int *source; int compareValue; }; /* Mouse button binding */ struct MsBinding : public Binding { MsBinding() {} MsBinding(int buttonIndex, Input::ButtonCode target) : Binding(target), index(buttonIndex) {} bool sourceActive() const { return EventThread::mouseState.buttons[index]; } bool sourceRepeatable() const { return false; } int index; }; /* Not rebindable */ static const KbBindingData staticKbBindings[] = { { SDL_SCANCODE_LEFT, Input::Left }, { SDL_SCANCODE_RIGHT, Input::Right }, { SDL_SCANCODE_UP, Input::Up }, { SDL_SCANCODE_DOWN, Input::Down }, { SDL_SCANCODE_LSHIFT, Input::Shift }, { SDL_SCANCODE_RSHIFT, Input::Shift }, { SDL_SCANCODE_LCTRL, Input::Ctrl }, { SDL_SCANCODE_RCTRL, Input::Ctrl }, { SDL_SCANCODE_LALT, Input::Alt }, { SDL_SCANCODE_RALT, Input::Alt }, { SDL_SCANCODE_F5, Input::F5 }, { SDL_SCANCODE_F6, Input::F6 }, { SDL_SCANCODE_F7, Input::F7 }, { SDL_SCANCODE_F8, Input::F8 }, { SDL_SCANCODE_F9, Input::F9 } }; static elementsN(staticKbBindings); /* Rebindable */ static const KbBindingData defaultKbBindings[] = { { SDL_SCANCODE_SPACE, Input::C }, { SDL_SCANCODE_RETURN, Input::C }, { SDL_SCANCODE_KP_ENTER, Input::C }, /* Treated as alias of RETURN */ { SDL_SCANCODE_ESCAPE, Input::B }, { SDL_SCANCODE_KP_0, Input::B }, { SDL_SCANCODE_LSHIFT, Input::A }, { SDL_SCANCODE_RSHIFT, Input::A }, { SDL_SCANCODE_X, Input::B }, { SDL_SCANCODE_B, Input::None }, { SDL_SCANCODE_D, Input::Z }, { SDL_SCANCODE_Q, Input::L }, { SDL_SCANCODE_W, Input::R }, { SDL_SCANCODE_V, Input::None }, { SDL_SCANCODE_A, Input::X }, { SDL_SCANCODE_S, Input::Y } }; /* RGSS1 */ static const KbBindingData defaultKbBindings1[] = { { SDL_SCANCODE_Z, Input::A }, { SDL_SCANCODE_C, Input::C }, }; /* RGSS2 and higher */ static const KbBindingData defaultKbBindings2[] = { { SDL_SCANCODE_Z, Input::C }, { SDL_SCANCODE_C, Input::None }, }; static elementsN(defaultKbBindings); static elementsN(defaultKbBindings1); static elementsN(defaultKbBindings2); /* Rebindable */ static const JsBindingData defaultJsBindings[] = { { 0, Input::A }, { 1, Input::B }, { 2, Input::C }, { 3, Input::X }, { 4, Input::Y }, { 5, Input::Z }, { 6, Input::L }, { 7, Input::R }, { 8, Input::None }, { 9, Input::None } }; static elementsN(defaultJsBindings); /* Maps ButtonCode enum values to indices * in the button state array */ static const int mapToIndex[] = { 0, 0, 1, 0, 2, 0, 3, 0, 4, 0, 0, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 13, 14, 15, 0, 16, 17, 18, 19, 20, 0, 0, 0, 0, 0, 0, 0, 0, 21, 22, 23 }; static elementsN(mapToIndex); static const Input::ButtonCode dirs[] = { Input::Down, Input::Left, Input::Right, Input::Up }; static const int dirFlags[] = { 1 << Input::Down, 1 << Input::Left, 1 << Input::Right, 1 << Input::Up }; /* Dir4 is always zero on these combinations */ static const int deadDirFlags[] = { dirFlags[0] | dirFlags[3], dirFlags[1] | dirFlags[2] }; static const Input::ButtonCode otherDirs[4][3] = { { Input::Left, Input::Right, Input::Up }, /* Down */ { Input::Down, Input::Up, Input::Right }, /* Left */ { Input::Down, Input::Up, Input::Left }, /* Right */ { Input::Left, Input::Right, Input::Up } /* Up */ }; struct InputPrivate { std::vector kbBindings; std::vector jsABindings; std::vector jsBBindings; std::vector msBindings; /* Collective binding array */ std::vector bindings; ButtonState stateArray[BUTTON_CODE_COUNT*2]; ButtonState *states; ButtonState *statesOld; Input::ButtonCode repeating; unsigned int repeatCount; struct { int active; Input::ButtonCode previous; } dir4Data; struct { int active; } dir8Data; InputPrivate() { initKbBindings(); initJsBindings(); initMsBindings(); states = stateArray; statesOld = stateArray + BUTTON_CODE_COUNT; /* Clear buffers */ clearBuffer(); swapBuffers(); clearBuffer(); repeating = Input::None; repeatCount = 0; dir4Data.active = 0; dir4Data.previous = Input::None; dir8Data.active = 0; } inline ButtonState &getStateCheck(int code) { int index; if (code < 0 || (size_t) code > mapToIndexN-1) index = 0; else index = mapToIndex[code]; return states[index]; } inline ButtonState &getState(Input::ButtonCode code) { return states[mapToIndex[code]]; } inline ButtonState &getOldState(Input::ButtonCode code) { return statesOld[mapToIndex[code]]; } void swapBuffers() { ButtonState *tmp = states; states = statesOld; statesOld = tmp; } void clearBuffer() { const size_t size = sizeof(ButtonState) * BUTTON_CODE_COUNT; memset(states, 0, size); } void initKbBindings() { kbBindings.clear(); for (size_t i = 0; i < staticKbBindingsN; ++i) kbBindings.push_back(KbBinding(staticKbBindings[i])); for (size_t i = 0; i < defaultKbBindingsN; ++i) kbBindings.push_back(KbBinding(defaultKbBindings[i])); if (rgssVer == 1) for (size_t i = 0; i < defaultKbBindings1N; ++i) kbBindings.push_back(KbBinding(defaultKbBindings1[i])); else for (size_t i = 0; i < defaultKbBindings2N; ++i) kbBindings.push_back(KbBinding(defaultKbBindings2[i])); /* Add to binging array */ for (size_t i = 0; i < kbBindings.size(); ++i) bindings.push_back(&kbBindings[i]); } void initJsBindings() { /* Create axis bindings */ jsABindings.resize(4); size_t i = 0; jsABindings[i++] = JsAxisBinding(&EventThread::joyState.xAxis, 0x7FFF, Input::Right); jsABindings[i++] = JsAxisBinding(&EventThread::joyState.xAxis, -0x8000, Input::Left); jsABindings[i++] = JsAxisBinding(&EventThread::joyState.yAxis, 0x7FFF, Input::Down); jsABindings[i++] = JsAxisBinding(&EventThread::joyState.yAxis, -0x8000, Input::Up); /* Create button bindings */ jsBBindings.resize(defaultJsBindingsN); for (size_t i = 0; i < defaultJsBindingsN; ++i) jsBBindings[i] = JsButtonBinding(defaultJsBindings[i]); /* Add to binging array */ for (size_t i = 0; i < jsABindings.size(); ++i) bindings.push_back(&jsABindings[i]); for (size_t i = 0; i < jsBBindings.size(); ++i) bindings.push_back(&jsBBindings[i]); } void initMsBindings() { msBindings.resize(3); size_t i = 0; msBindings[i++] = MsBinding(SDL_BUTTON_LEFT, Input::MouseLeft); msBindings[i++] = MsBinding(SDL_BUTTON_MIDDLE, Input::MouseMiddle); msBindings[i++] = MsBinding(SDL_BUTTON_RIGHT, Input::MouseRight); /* Add to binding array */ for (size_t i = 0; i < msBindings.size(); ++i) bindings.push_back(&msBindings[i]); } void pollBindings(Input::ButtonCode &repeatCand) { for (size_t i = 0; i < bindings.size(); ++i) pollBindingPriv(*bindings[i], repeatCand); updateDir4(); updateDir8(); } void pollBindingPriv(const Binding &b, Input::ButtonCode &repeatCand) { if (!b.sourceActive()) return; if (b.target == Input::None) return; ButtonState &state = getState(b.target); ButtonState &oldState = getOldState(b.target); state.pressed = true; /* Must have been released before to trigger */ if (!oldState.pressed) state.triggered = true; /* Unbound keys don't create/break repeat */ if (repeatCand != Input::None) return; if (repeating != b.target && !oldState.pressed) { if (b.sourceRepeatable()) repeatCand = b.target; else /* Unrepeatable keys still break current repeat */ repeating = Input::None; } } void updateDir4() { int dirFlag = 0; for (size_t i = 0; i < 4; ++i) dirFlag |= (getState(dirs[i]).pressed ? dirFlags[i] : 0); if (dirFlag == deadDirFlags[0] || dirFlag == deadDirFlags[1]) { dir4Data.active = Input::None; return; } if (dir4Data.previous != Input::None) { /* Check if prev still pressed */ if (getState(dir4Data.previous).pressed) { for (size_t i = 0; i < 3; ++i) { Input::ButtonCode other = otherDirs[(dir4Data.previous/2)-1][i]; if (!getState(other).pressed) continue; dir4Data.active = other; return; } } } for (size_t i = 0; i < 4; ++i) { if (!getState(dirs[i]).pressed) continue; dir4Data.active = dirs[i]; dir4Data.previous = dirs[i]; return; } dir4Data.active = Input::None; dir4Data.previous = Input::None; } void updateDir8() { static const int combos[4][4] = { { 2, 1, 3, 0 }, { 1, 4, 0, 7 }, { 3, 0, 6, 9 }, { 0, 7, 9, 8 } }; dir8Data.active = 0; for (size_t i = 0; i < 4; ++i) { Input::ButtonCode one = dirs[i]; if (!getState(one).pressed) continue; for (int j = 0; j < 3; ++j) { Input::ButtonCode other = otherDirs[i][j]; if (!getState(other).pressed) continue; dir8Data.active = combos[(one/2)-1][(other/2)-1]; return; } dir8Data.active = one; return; } } }; Input::Input() { p = new InputPrivate; } void Input::update() { shState->checkShutdown(); p->swapBuffers(); p->clearBuffer(); ButtonCode repeatCand = None; /* Poll all bindings */ p->pollBindings(repeatCand); /* Check for new repeating key */ if (repeatCand != None && repeatCand != p->repeating) { p->repeating = repeatCand; p->repeatCount = 0; p->getState(repeatCand).repeated = true; return; } /* Check if repeating key is still pressed */ if (p->getState(p->repeating).pressed) { p->repeatCount++; bool repeated; if (rgssVer >= 2) repeated = p->repeatCount >= 23 && ((p->repeatCount+1) % 6) == 0; else repeated = p->repeatCount >= 15 && ((p->repeatCount+1) % 4) == 0; p->getState(p->repeating).repeated |= repeated; return; } p->repeating = None; } bool Input::isPressed(int button) { return p->getStateCheck(button).pressed; } bool Input::isTriggered(int button) { return p->getStateCheck(button).triggered; } bool Input::isRepeated(int button) { return p->getStateCheck(button).repeated; } int Input::dir4Value() { return p->dir4Data.active; } int Input::dir8Value() { return p->dir8Data.active; } int Input::mouseX() { RGSSThreadData &rtData = shState->rtData(); if (!EventThread::mouseState.inWindow) return -20; return (EventThread::mouseState.x - rtData.screenOffset.x) * rtData.sizeResoRatio.x; } int Input::mouseY() { RGSSThreadData &rtData = shState->rtData(); if (!EventThread::mouseState.inWindow) return -20; return (EventThread::mouseState.y - rtData.screenOffset.y) * rtData.sizeResoRatio.y; } Input::~Input() { delete p; }