Plumb multimappings all the way through.

This commit is contained in:
Henrik Rydgård 2023-04-01 00:12:14 +02:00
parent f178d1bd85
commit 0e1c42ce70
8 changed files with 167 additions and 68 deletions

View file

@ -131,6 +131,15 @@ public:
if (keyCode < other.keyCode) return true;
return false;
}
// Needed for composition.
bool operator > (const InputMapping &other) const {
if (deviceId > other.deviceId) return true;
if (deviceId < other.deviceId) return false;
if (keyCode > other.keyCode) return true;
return false;
}
// This one is iffy with the != ANY checks. Should probably be a named method.
bool operator == (const InputMapping &other) const {
if (deviceId != other.deviceId && deviceId != DEVICE_ID_ANY && other.deviceId != DEVICE_ID_ANY) return false;
if (keyCode != other.keyCode) return false;

View file

@ -13,6 +13,8 @@
#include "Core/CoreParameter.h"
#include "Core/System.h"
using KeyMap::MultiInputMapping;
// TODO: Possibly make these thresholds configurable?
static float GetDeviceAxisThreshold(int device) {
return device == DEVICE_ID_MOUSE ? AXIS_BIND_THRESHOLD_MOUSE : AXIS_BIND_THRESHOLD;
@ -219,19 +221,27 @@ bool ControlMapper::UpdatePSPState(const InputMapping &changedMapping) {
mappingBit = RotatePSPKeyCode(mappingBit);
}
std::vector<InputMapping> inputMappings;
std::vector<MultiInputMapping> inputMappings;
if (!KeyMap::InputMappingsFromPspButton(mappingBit, &inputMappings, false))
continue;
// If a mapping could consist of a combo, we could trivially check it here.
for (auto &mapping : inputMappings) {
for (auto &multiMapping : inputMappings) {
if (multiMapping.empty())
continue;
// Check if the changed mapping was involved in this PSP key.
if (changedMapping == mapping) {
if (multiMapping.mappings.contains(changedMapping)) {
changedButtonMask |= mask;
}
auto iter = curInput_.find(mapping);
if (iter != curInput_.end() && iter->second > GetDeviceAxisThreshold(iter->first.deviceId)) {
// Check if all inputs are "on".
bool all = true;
for (auto mapping : multiMapping.mappings) {
auto iter = curInput_.find(mapping);
bool down = iter != curInput_.end() && iter->second > GetDeviceAxisThreshold(iter->first.deviceId);
if (!down)
all = false;
}
if (all) {
buttonMask |= mask;
}
}
@ -243,7 +253,7 @@ bool ControlMapper::UpdatePSPState(const InputMapping &changedMapping) {
// OK, handle all the virtual keys next. For these we need to do deltas here and send events.
for (int i = 0; i < VIRTKEY_COUNT; i++) {
int vkId = i + VIRTKEY_FIRST;
std::vector<InputMapping> inputMappings;
std::vector<MultiInputMapping> inputMappings;
if (!KeyMap::InputMappingsFromPspButton(vkId, &inputMappings, false))
continue;
@ -253,20 +263,27 @@ bool ControlMapper::UpdatePSPState(const InputMapping &changedMapping) {
float threshold = 1.0f;
bool touchedByMapping = false;
float value = 0.0f;
for (auto &mapping : inputMappings) {
if (mapping == changedMapping) {
for (auto &multiMapping : inputMappings) {
if (multiMapping.mappings.contains(changedMapping)) {
touchedByMapping = true;
}
auto iter = curInput_.find(mapping);
if (iter != curInput_.end()) {
if (mapping.IsAxis()) {
threshold = GetDeviceAxisThreshold(iter->first.deviceId);
value += MapAxisValue(iter->second, vkId, mapping, changedMapping, &touchedByMapping);
float product = 1.0f; // We multiply the various inputs in a combo mapping with each other.
for (auto mapping : multiMapping.mappings) {
auto iter = curInput_.find(mapping);
if (iter != curInput_.end()) {
if (mapping.IsAxis()) {
threshold = GetDeviceAxisThreshold(iter->first.deviceId);
product *= MapAxisValue(iter->second, vkId, mapping, changedMapping, &touchedByMapping);
} else {
product *= iter->second;
}
} else {
value += iter->second;
product = 0.0f;
}
}
value += product;
}
if (!touchedByMapping) {
@ -280,8 +297,12 @@ bool ControlMapper::UpdatePSPState(const InputMapping &changedMapping) {
// that still works, though a bit weaker. We could also zero here, but you never know who relies on such strange tricks..
// Note: This is an old problem, it didn't appear with the refactoring.
if (!changedMapping.IsAxis()) {
for (auto &mapping : inputMappings) {
if (mapping.IsAxis()) {
for (auto &multiMapping : inputMappings) {
bool anyAxis = false;
for (auto &mapping : multiMapping.mappings) {
if (mapping.IsAxis()) {
anyAxis = true;
}
curInput_[mapping] = ReduceMagnitude(curInput_[mapping]);
}
}

View file

@ -45,6 +45,17 @@ std::set<int> g_seenDeviceIds;
bool g_swapDpadWithLStick = false;
// Utility...
void SingleInputMappingFromPspButton(int btn, std::vector<InputMapping> *mappings, bool ignoreMouse) {
std::vector<MultiInputMapping> multiMappings;
InputMappingsFromPspButton(btn, &multiMappings, ignoreMouse);
mappings->clear();
for (auto &mapping : multiMappings) {
_dbg_assert_(!mapping.empty());
mappings->push_back(mapping.mappings[0]);
}
}
// TODO: This is such a mess...
void UpdateNativeMenuKeys() {
std::vector<InputMapping> confirmKeys, cancelKeys;
@ -55,14 +66,14 @@ void UpdateNativeMenuKeys() {
int cancelKey = g_Config.iButtonPreference == PSP_SYSTEMPARAM_BUTTON_CROSS ? CTRL_CIRCLE : CTRL_CROSS;
// Mouse mapping might be problematic in UI, so let's ignore mouse for UI
InputMappingsFromPspButton(confirmKey, &confirmKeys, true);
InputMappingsFromPspButton(cancelKey, &cancelKeys, true);
InputMappingsFromPspButton(CTRL_LTRIGGER, &tabLeft, true);
InputMappingsFromPspButton(CTRL_RTRIGGER, &tabRight, true);
InputMappingsFromPspButton(CTRL_UP, &upKeys, true);
InputMappingsFromPspButton(CTRL_DOWN, &downKeys, true);
InputMappingsFromPspButton(CTRL_LEFT, &leftKeys, true);
InputMappingsFromPspButton(CTRL_RIGHT, &rightKeys, true);
SingleInputMappingFromPspButton(confirmKey, &confirmKeys, true);
SingleInputMappingFromPspButton(cancelKey, &cancelKeys, true);
SingleInputMappingFromPspButton(CTRL_LTRIGGER, &tabLeft, true);
SingleInputMappingFromPspButton(CTRL_RTRIGGER, &tabRight, true);
SingleInputMappingFromPspButton(CTRL_UP, &upKeys, true);
SingleInputMappingFromPspButton(CTRL_DOWN, &downKeys, true);
SingleInputMappingFromPspButton(CTRL_LEFT, &leftKeys, true);
SingleInputMappingFromPspButton(CTRL_RIGHT, &rightKeys, true);
#ifdef __ANDROID__
// Hardcode DPAD on Android
@ -493,7 +504,7 @@ bool InputMappingToPspButton(const InputMapping &mapping, std::vector<int> *pspB
bool found = false;
for (auto iter = g_controllerMap.begin(); iter != g_controllerMap.end(); ++iter) {
for (auto iter2 = iter->second.begin(); iter2 != iter->second.end(); ++iter2) {
if (*iter2 == mapping) {
if (iter2->EqualsSingleMapping(mapping)) {
if (pspButtons)
pspButtons->push_back(CheckAxisSwap(iter->first));
found = true;
@ -503,12 +514,12 @@ bool InputMappingToPspButton(const InputMapping &mapping, std::vector<int> *pspB
return found;
}
bool InputMappingsFromPspButton(int btn, std::vector<InputMapping> *mappings, bool ignoreMouse) {
bool InputMappingsFromPspButton(int btn, std::vector<MultiInputMapping> *mappings, bool ignoreMouse) {
bool mapped = false;
for (auto iter = g_controllerMap.begin(); iter != g_controllerMap.end(); ++iter) {
if (iter->first == btn) {
for (auto iter2 = iter->second.begin(); iter2 != iter->second.end(); ++iter2) {
if (mappings && (!ignoreMouse || iter2->deviceId != DEVICE_ID_MOUSE)) {
if (mappings && (!ignoreMouse || iter2->HasMouse())) {
mapped = true;
mappings->push_back(*iter2);
}
@ -525,8 +536,11 @@ MappedAnalogAxes MappedAxesForDevice(int deviceId) {
auto findAxisId = [&](int btn) -> MappedAnalogAxis {
MappedAnalogAxis info{ -1 };
for (const auto &key : g_controllerMap[btn]) {
if (key.deviceId == deviceId) {
info.axisId = TranslateKeyCodeToAxis(key.keyCode, &info.direction);
// Only consider single mappings, combos don't make much sense for these.
if (key.mappings.empty()) continue;
auto &mapping = key.mappings[0];
if (mapping.deviceId == deviceId) {
info.axisId = TranslateKeyCodeToAxis(mapping.keyCode, &info.direction);
return info;
}
}
@ -562,7 +576,7 @@ void RemoveButtonMapping(int btn) {
bool IsKeyMapped(int device, int key) {
for (auto &iter : g_controllerMap) {
for (auto &mappedKey : iter.second) {
if (mappedKey == InputMapping(device, key)) {
if (mappedKey.mappings.contains(InputMapping(device, key))) {
return true;
}
}
@ -570,7 +584,7 @@ bool IsKeyMapped(int device, int key) {
return false;
}
bool ReplaceSingleKeyMapping(int btn, int index, InputMapping key) {
bool ReplaceSingleKeyMapping(int btn, int index, MultiInputMapping key) {
// Check for duplicate
for (int i = 0; i < (int)g_controllerMap[btn].size(); ++i) {
if (i != index && g_controllerMap[btn][i] == key) {
@ -584,14 +598,18 @@ bool ReplaceSingleKeyMapping(int btn, int index, InputMapping key) {
KeyMap::g_controllerMap[btn][index] = key;
g_controllerMapGeneration++;
g_seenDeviceIds.insert(key.deviceId);
for (auto &mapping : key.mappings) {
g_seenDeviceIds.insert(mapping.deviceId);
}
UpdateNativeMenuKeys();
return true;
}
void SetInputMapping(int btn, const InputMapping &key, bool replace) {
if (key.keyCode < 0)
void SetInputMapping(int btn, const MultiInputMapping &key, bool replace) {
if (key.empty()) {
g_controllerMap.erase(btn);
return;
}
if (replace) {
RemoveButtonMapping(btn);
g_controllerMap[btn].clear();
@ -605,7 +623,9 @@ void SetInputMapping(int btn, const InputMapping &key, bool replace) {
}
g_controllerMapGeneration++;
g_seenDeviceIds.insert(key.deviceId);
for (auto &mapping : key.mappings) {
g_seenDeviceIds.insert(mapping.deviceId);
}
UpdateNativeMenuKeys();
}
@ -667,12 +687,14 @@ void LoadFromIni(IniFile &file) {
SplitString(value, ',', mappings);
for (size_t j = 0; j < mappings.size(); j++) {
// TODO: Properly parse multiple mappings.
std::vector<std::string> parts;
SplitString(mappings[j], '-', parts);
int deviceId = atoi(parts[0].c_str());
int keyCode = atoi(parts[1].c_str());
SetInputMapping(psp_button_names[i].key, InputMapping(deviceId, keyCode), false);
SetInputMapping(psp_button_names[i].key, MultiInputMapping(InputMapping(deviceId, keyCode)), false);
g_seenDeviceIds.insert(deviceId);
}
}
@ -684,13 +706,15 @@ void SaveToIni(IniFile &file) {
Section *controls = file.GetOrCreateSection("ControlMapping");
for (size_t i = 0; i < ARRAY_SIZE(psp_button_names); i++) {
std::vector<InputMapping> keys;
std::vector<MultiInputMapping> keys;
InputMappingsFromPspButton(psp_button_names[i].key, &keys, false);
std::string value;
for (size_t j = 0; j < keys.size(); j++) {
auto mapping = keys[j].mappings[0];
char temp[128];
sprintf(temp, "%i-%i", keys[j].deviceId, keys[j].keyCode);
sprintf(temp, "%i-%i", mapping.deviceId, mapping.keyCode);
value += temp;
if (j != keys.size() - 1)
value += ",";
@ -756,8 +780,8 @@ void AutoConfForPad(const std::string &name) {
#endif
// Add a couple of convenient keyboard mappings by default, too.
g_controllerMap[VIRTKEY_PAUSE].push_back(InputMapping(DEVICE_ID_KEYBOARD, NKCODE_ESCAPE));
g_controllerMap[VIRTKEY_FASTFORWARD].push_back(InputMapping(DEVICE_ID_KEYBOARD, NKCODE_TAB));
g_controllerMap[VIRTKEY_PAUSE].push_back(MultiInputMapping(InputMapping(DEVICE_ID_KEYBOARD, NKCODE_ESCAPE)));
g_controllerMap[VIRTKEY_FASTFORWARD].push_back(MultiInputMapping(InputMapping(DEVICE_ID_KEYBOARD, NKCODE_TAB)));
g_controllerMapGeneration++;
}

View file

@ -24,6 +24,7 @@
#include "Common/Input/InputState.h" // InputMapping
#include "Common/Input/KeyCodes.h" // keyboard keys
#include "Common/Data/Collections/TinySet.h"
#include "Core/KeyMapDefaults.h"
#define KEYMAP_ERROR_KEY_ALREADY_USED -1
@ -77,8 +78,6 @@ enum {
const float AXIS_BIND_THRESHOLD = 0.75f;
const float AXIS_BIND_THRESHOLD_MOUSE = 0.01f;
typedef std::map<int, std::vector<InputMapping>> KeyMapping;
struct MappedAnalogAxis {
int axisId;
int direction;
@ -104,10 +103,50 @@ struct MappedAnalogAxes {
class IniFile;
namespace KeyMap {
// Combo of InputMappings.
struct MultiInputMapping {
MultiInputMapping() {}
explicit MultiInputMapping(const InputMapping &mapping) {
mappings.push_back(mapping);
}
bool operator <(const MultiInputMapping &other) {
for (size_t i = 0; i < mappings.capacity(); i++) {
// If one ran out of entries, the other wins.
if (mappings.size() == i && other.mappings.size() > i) return true;
if (mappings.size() >= i && other.mappings.size() == i) return false;
if (mappings[i] < other.mappings[i]) return true;
if (mappings[i] > other.mappings[i]) return false;
}
return false;
}
bool operator ==(const MultiInputMapping &other) const {
return mappings == other.mappings;
}
bool EqualsSingleMapping(const InputMapping &other) const {
return mappings.size() == 1 && mappings[0] == other;
}
bool empty() const {
return mappings.empty();
}
bool HasMouse() const {
for (auto &m : mappings) {
return m.deviceId == DEVICE_ID_MOUSE;
}
return false;
}
FixedTinyVec<InputMapping, 3> mappings;
};
typedef std::map<int, std::vector<MultiInputMapping>> KeyMapping;
extern KeyMapping g_controllerMap;
extern std::set<int> g_seenDeviceIds;
extern int g_controllerMapGeneration;
// Key & Button names
struct KeyMap_IntStrPair {
int key;
@ -126,14 +165,16 @@ namespace KeyMap {
// Use to translate input mappings to and from PSP buttons. You should have already translated
// your platform's keys to InputMapping keys.
// Note that this one does not handle combos, since there's only one input.
bool InputMappingToPspButton(const InputMapping &mapping, std::vector<int> *pspButtons);
bool InputMappingsFromPspButton(int btn, std::vector<InputMapping> *keys, bool ignoreMouse);
bool InputMappingsFromPspButton(int btn, std::vector<MultiInputMapping> *keys, bool ignoreMouse);
// Configure the key or axis mapping.
// Any configuration will be saved to the Core config.
void SetInputMapping(int psp_key, const InputMapping &key, bool replace);
void SetInputMapping(int psp_key, const MultiInputMapping &key, bool replace);
// Return false if bind was a duplicate and got removed
bool ReplaceSingleKeyMapping(int btn, int index, InputMapping key);
bool ReplaceSingleKeyMapping(int btn, int index, MultiInputMapping key);
MappedAnalogAxes MappedAxesForDevice(int deviceId);

View file

@ -341,9 +341,9 @@ static const DefMappingStruct defaultVRRightController[] = {
static void SetDefaultKeyMap(int deviceId, const DefMappingStruct *array, size_t count, bool replace) {
for (size_t i = 0; i < count; i++) {
if (array[i].direction == 0)
SetInputMapping(array[i].pspKey, InputMapping(deviceId, array[i].keyOrAxis), replace);
SetInputMapping(array[i].pspKey, MultiInputMapping(InputMapping(deviceId, array[i].keyOrAxis)), replace);
else
SetInputMapping(array[i].pspKey, InputMapping(deviceId, array[i].keyOrAxis, array[i].direction), replace);
SetInputMapping(array[i].pspKey, MultiInputMapping(InputMapping(deviceId, array[i].keyOrAxis, array[i].direction)), replace);
}
g_seenDeviceIds.insert(deviceId);
}

View file

@ -51,6 +51,8 @@
#include "android/jni/app-android.h"
#endif
using KeyMap::MultiInputMapping;
class SingleControlMapper : public UI::LinearLayout {
public:
SingleControlMapper(int pspKey, std::string keyName, ScreenManager *scrm, UI::LinearLayoutParams *layoutParams = nullptr);
@ -66,7 +68,7 @@ private:
UI::EventReturn OnReplace(UI::EventParams &params);
UI::EventReturn OnReplaceAll(UI::EventParams &params);
void MappedCallback(InputMapping key);
void MappedCallback(MultiInputMapping key);
enum Action {
NONE,
@ -134,13 +136,17 @@ void SingleControlMapper::Refresh() {
LinearLayout *rightColumn = root->Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(rightColumnWidth, WRAP_CONTENT)));
rightColumn->SetSpacing(2.0f);
std::vector<InputMapping> mappings;
std::vector<MultiInputMapping> mappings;
KeyMap::InputMappingsFromPspButton(pspKey_, &mappings, false);
rows_.clear();
for (size_t i = 0; i < mappings.size(); i++) {
std::string deviceName = GetDeviceName(mappings[i].deviceId);
std::string keyName = KeyMap::GetKeyOrAxisName(mappings[i]);
// TODO: Correctly handle multis.
auto &mapping = mappings[i].mappings[0];
std::string deviceName = GetDeviceName(mapping.deviceId);
std::string keyName = KeyMap::GetKeyOrAxisName(mapping);
LinearLayout *row = rightColumn->Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));
row->SetSpacing(2.0f);
@ -162,7 +168,7 @@ void SingleControlMapper::Refresh() {
}
}
void SingleControlMapper::MappedCallback(InputMapping kdf) {
void SingleControlMapper::MappedCallback(MultiInputMapping kdf) {
switch (action_) {
case ADD:
KeyMap::SetInputMapping(pspKey_, kdf, false);
@ -346,7 +352,7 @@ bool KeyMappingNewKeyDialog::key(const KeyInput &key) {
return true;
mapped_ = true;
InputMapping kdf(key.deviceId, key.keyCode);
MultiInputMapping kdf(InputMapping(key.deviceId, key.keyCode));
TriggerFinish(DR_YES);
if (callback_ && pspBtn_ != VIRTKEY_SPEED_ANALOG)
callback_(kdf);
@ -378,7 +384,7 @@ bool KeyMappingNewMouseKeyDialog::key(const KeyInput &key) {
}
mapped_ = true;
InputMapping kdf(key.deviceId, key.keyCode);
MultiInputMapping kdf(InputMapping(key.deviceId, key.keyCode));
TriggerFinish(DR_YES);
g_Config.bMapMouse = false;
if (callback_)
@ -409,7 +415,7 @@ void KeyMappingNewKeyDialog::axis(const AxisInput &axis) {
if (axis.value > AXIS_BIND_THRESHOLD) {
mapped_ = true;
InputMapping kdf(axis.deviceId, axis.axisId, 1);
MultiInputMapping kdf(InputMapping(axis.deviceId, axis.axisId, 1));
TriggerFinish(DR_YES);
if (callback_)
callback_(kdf);
@ -417,7 +423,7 @@ void KeyMappingNewKeyDialog::axis(const AxisInput &axis) {
if (axis.value < -AXIS_BIND_THRESHOLD) {
mapped_ = true;
InputMapping kdf(axis.deviceId, axis.axisId, -1);
MultiInputMapping kdf(InputMapping(axis.deviceId, axis.axisId, -1));
TriggerFinish(DR_YES);
if (callback_)
callback_(kdf);
@ -432,7 +438,7 @@ void KeyMappingNewMouseKeyDialog::axis(const AxisInput &axis) {
if (axis.value > AXIS_BIND_THRESHOLD) {
mapped_ = true;
InputMapping kdf(axis.deviceId, axis.axisId, 1);
MultiInputMapping kdf(InputMapping(axis.deviceId, axis.axisId, 1));
TriggerFinish(DR_YES);
if (callback_)
callback_(kdf);
@ -440,7 +446,7 @@ void KeyMappingNewMouseKeyDialog::axis(const AxisInput &axis) {
if (axis.value < -AXIS_BIND_THRESHOLD) {
mapped_ = true;
InputMapping kdf(axis.deviceId, axis.axisId, -1);
MultiInputMapping kdf(InputMapping(axis.deviceId, axis.axisId, -1));
TriggerFinish(DR_YES);
if (callback_)
callback_(kdf);
@ -1147,7 +1153,7 @@ UI::EventReturn VisualMappingScreen::OnBindAll(UI::EventParams &e) {
return UI::EVENT_DONE;
}
void VisualMappingScreen::HandleKeyMapping(InputMapping key) {
void VisualMappingScreen::HandleKeyMapping(MultiInputMapping key) {
KeyMap::SetInputMapping(nextKey_, key, replace_);
if (bindAll_ < 0) {

View file

@ -56,7 +56,7 @@ private:
class KeyMappingNewKeyDialog : public PopupScreen {
public:
explicit KeyMappingNewKeyDialog(int btn, bool replace, std::function<void(InputMapping)> callback, std::shared_ptr<I18NCategory> i18n)
explicit KeyMappingNewKeyDialog(int btn, bool replace, std::function<void(KeyMap::MultiInputMapping)> callback, std::shared_ptr<I18NCategory> i18n)
: PopupScreen(i18n->T("Map Key"), "Cancel", ""), pspBtn_(btn), callback_(callback) {}
const char *tag() const override { return "KeyMappingNewKey"; }
@ -75,14 +75,14 @@ protected:
private:
int pspBtn_;
std::function<void(InputMapping)> callback_;
std::function<void(KeyMap::MultiInputMapping)> callback_;
bool mapped_ = false; // Prevent double registrations
double delayUntil_ = 0.0f;
};
class KeyMappingNewMouseKeyDialog : public PopupScreen {
public:
KeyMappingNewMouseKeyDialog(int btn, bool replace, std::function<void(InputMapping)> callback, std::shared_ptr<I18NCategory> i18n)
KeyMappingNewMouseKeyDialog(int btn, bool replace, std::function<void(KeyMap::MultiInputMapping)> callback, std::shared_ptr<I18NCategory> i18n)
: PopupScreen(i18n->T("Map Mouse"), "", ""), pspBtn_(btn), callback_(callback), mapped_(false) {}
const char *tag() const override { return "KeyMappingNewMouseKey"; }
@ -99,7 +99,7 @@ protected:
private:
int pspBtn_;
std::function<void(InputMapping)> callback_;
std::function<void(KeyMap::MultiInputMapping)> callback_;
bool mapped_; // Prevent double registrations
};
@ -189,7 +189,7 @@ protected:
private:
UI::EventReturn OnMapButton(UI::EventParams &e);
UI::EventReturn OnBindAll(UI::EventParams &e);
void HandleKeyMapping(InputMapping key);
void HandleKeyMapping(KeyMap::MultiInputMapping key);
void MapNext(bool successive);
MockPSP *psp_ = nullptr;

View file

@ -244,8 +244,6 @@ int DinputDevice::UpdateState() {
AxisInput axis;
axis.deviceId = DEVICE_ID_PAD_0 + pDevNum;
auto axesToSquare = KeyMap::MappedAxesForDevice(axis.deviceId);
SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lX, last_lX_, JOYSTICK_AXIS_X);
SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lY, last_lY_, JOYSTICK_AXIS_Y);
SendNativeAxis(DEVICE_ID_PAD_0 + pDevNum, js.lZ, last_lZ_, JOYSTICK_AXIS_Z);