Refactor game controls bindings

This introduces abstractions for a game key (`wxGameKey`), a game
control (`wxGameControl`) and a common global handler for in-game
user input processing (`wxGameControlState`).
User configuration is changed from a vector to a map of `wxGameControl`
to a set of `wxUserInput`, which simplifies input configuration updates.

User input processing for in-game controls is now unified between
keyboard and joypad input, and is much faster in general since access
to game control state is now always logarithmic rather than linear.
This comes at the expense of slightly slower user input configuration
updates. However, in the worst case scenario, this is still done in
O(log(n)).

This removes all uses of `wxJoyKeyBinding`. However, some uses of the
key, mod, joy triplets remain and will be cleaned up in follow-up PRs.

Issue: #745
This commit is contained in:
Fabrice de Gans 2022-07-23 16:27:36 -07:00 committed by Rafael Kitover
parent 3f2d3c139d
commit 51473a7c53
15 changed files with 870 additions and 495 deletions

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-11-24 21:24+0000\n"
"POT-Creation-Date: 2022-07-23 17:25-0700\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -21,51 +21,51 @@ msgstr ""
msgid "visualboyadvance-m"
msgstr ""
#: wxvbam.cpp:442
#: wxvbam.cpp:446
msgid "Could not create main window"
msgstr ""
#: wxvbam.cpp:513
#: wxvbam.cpp:517
msgid "Save built-in XRC file and exit"
msgstr ""
#: wxvbam.cpp:516
#: wxvbam.cpp:520
msgid "Save built-in vba-over.ini and exit"
msgstr ""
#: wxvbam.cpp:519
#: wxvbam.cpp:523
msgid "Print configuration path and exit"
msgstr ""
#: wxvbam.cpp:522
#: wxvbam.cpp:526
msgid "Start in full-screen mode"
msgstr ""
#: wxvbam.cpp:525
#: wxvbam.cpp:529
msgid "Set a configuration file"
msgstr ""
#: wxvbam.cpp:529
#: wxvbam.cpp:533
msgid "Delete shared link state first, if it exists"
msgstr ""
#: wxvbam.cpp:536
#: wxvbam.cpp:540
msgid "List all settable options and exit"
msgstr ""
#: wxvbam.cpp:539
#: wxvbam.cpp:543
msgid "ROM file"
msgstr ""
#: wxvbam.cpp:541
#: wxvbam.cpp:545
msgid "<config>=<value>"
msgstr ""
#: wxvbam.cpp:572
#: wxvbam.cpp:576
msgid "Configuration/build error: can't find built-in xrc"
msgstr ""
#: wxvbam.cpp:580
#: wxvbam.cpp:584
#, c-format
msgid ""
"Wrote built-in configuration to %s.\n"
@ -74,11 +74,11 @@ msgid ""
"built-in:"
msgstr ""
#: wxvbam.cpp:595
#: wxvbam.cpp:599
msgid "Configuration is read from, in order:"
msgstr ""
#: wxvbam.cpp:609
#: wxvbam.cpp:613
#, c-format
msgid ""
"Wrote built-in override file to %s\n"
@ -86,13 +86,13 @@ msgid ""
"from search path:"
msgstr ""
#: wxvbam.cpp:615
#: wxvbam.cpp:619
msgid ""
"\n"
"\tbuilt-in"
msgstr ""
#: wxvbam.cpp:626
#: wxvbam.cpp:630
msgid ""
"Options set from the command line are saved if any configuration changes are "
"made in the user interface.\n"
@ -101,155 +101,155 @@ msgid ""
"\n"
msgstr ""
#: wxvbam.cpp:647
#: wxvbam.cpp:651
msgid ""
"The commands available for the Keyboard/* option are:\n"
"\n"
msgstr ""
#: wxvbam.cpp:659
#: wxvbam.cpp:663
msgid "Configuration file not found."
msgstr ""
#: wxvbam.cpp:692
#: wxvbam.cpp:696
msgid "Bad configuration option or multiple ROM files given:\n"
msgstr ""
#: guiinit.cpp:80
#: guiinit.cpp:83
msgid "Start!"
msgstr ""
#: guiinit.cpp:99 xrc/NetLink.xrc:99
#: guiinit.cpp:102 xrc/NetLink.xrc:99
msgid "Connect"
msgstr ""
#: guiinit.cpp:116
#: guiinit.cpp:119
msgid "You must enter a valid host name"
msgstr ""
#: guiinit.cpp:117
#: guiinit.cpp:120
msgid "Host name invalid"
msgstr ""
#: guiinit.cpp:135
msgid "Waiting for clients..."
msgstr ""
#: guiinit.cpp:136
#, c-format
msgid "Server IP address is: %s\n"
msgstr ""
#: guiinit.cpp:138
msgid "Waiting for connection..."
msgid "Waiting for clients..."
msgstr ""
#: guiinit.cpp:139
#, c-format
msgid "Server IP address is: %s\n"
msgstr ""
#: guiinit.cpp:141
msgid "Waiting for connection..."
msgstr ""
#: guiinit.cpp:142
#, c-format
msgid "Connecting to %s\n"
msgstr ""
#: guiinit.cpp:172
#: guiinit.cpp:175
msgid ""
"Error occurred.\n"
"Please try again."
msgstr ""
#: guiinit.cpp:239 guiinit.cpp:292
#: guiinit.cpp:242 guiinit.cpp:295
msgid "Select cheat file"
msgstr ""
#: guiinit.cpp:240
#: guiinit.cpp:243
msgid "VBA cheat lists (*.clt)|*.clt|CHT cheat lists (*.cht)|*.cht"
msgstr ""
#: guiinit.cpp:259 panel.cpp:447
#: guiinit.cpp:262 panel.cpp:447
msgid "Loaded cheats"
msgstr ""
#: guiinit.cpp:293
#: guiinit.cpp:296
msgid "VBA cheat lists (*.clt)|*.clt"
msgstr ""
#: guiinit.cpp:311
#: guiinit.cpp:314
msgid "Saved cheats"
msgstr ""
#: guiinit.cpp:342 guiinit.cpp:361
#: guiinit.cpp:345 guiinit.cpp:364
msgid "Restore old values?"
msgstr ""
#: guiinit.cpp:343 guiinit.cpp:362
#: guiinit.cpp:346 guiinit.cpp:365
msgid "Removing cheats"
msgstr ""
#: guiinit.cpp:753 xrc/JoyPanel.xrc:364
#: guiinit.cpp:756 xrc/JoyPanel.xrc:364
msgid "GameShark"
msgstr ""
#: guiinit.cpp:754 cmdevents.cpp:675
#: guiinit.cpp:757 cmdevents.cpp:675
msgid "GameGenie"
msgstr ""
#: guiinit.cpp:756
#: guiinit.cpp:759
msgid "Generic Code"
msgstr ""
#: guiinit.cpp:757
#: guiinit.cpp:760
msgid "GameShark Advance"
msgstr ""
#: guiinit.cpp:758
#: guiinit.cpp:761
msgid "CodeBreaker Advance"
msgstr ""
#: guiinit.cpp:759
#: guiinit.cpp:762
msgid "Flashcart CHT"
msgstr ""
#: guiinit.cpp:827 guiinit.cpp:1082
#: guiinit.cpp:830 guiinit.cpp:1085
msgid "Number cannot be empty"
msgstr ""
#: guiinit.cpp:865
#: guiinit.cpp:868
#, c-format
msgid "Search produced %d results. Please refine better"
msgstr ""
#: guiinit.cpp:877
#: guiinit.cpp:880
msgid "Search produced no results"
msgstr ""
#: guiinit.cpp:1040
#: guiinit.cpp:1043
msgid "8-bit "
msgstr ""
#: guiinit.cpp:1044
#: guiinit.cpp:1047
msgid "16-bit "
msgstr ""
#: guiinit.cpp:1048
#: guiinit.cpp:1051
msgid "32-bit "
msgstr ""
#: guiinit.cpp:1054
#: guiinit.cpp:1057
msgid "signed decimal"
msgstr ""
#: guiinit.cpp:1058
#: guiinit.cpp:1061
msgid "unsigned decimal"
msgstr ""
#: guiinit.cpp:1062
#: guiinit.cpp:1065
msgid "unsigned hexadecimal"
msgstr ""
#: guiinit.cpp:1540
#: guiinit.cpp:1543
#, c-format
msgid "%d frames = %.2f ms"
msgstr ""
#: guiinit.cpp:1552
#: guiinit.cpp:1555
msgid "Default device"
msgstr ""
@ -294,62 +294,62 @@ msgstr ""
msgid "Confirm"
msgstr ""
#: guiinit.cpp:2739
#: guiinit.cpp:2740
msgid "Main icon not found"
msgstr ""
#: guiinit.cpp:2749
#: guiinit.cpp:2750
msgid "Browse"
msgstr ""
#: guiinit.cpp:2763
#: guiinit.cpp:2764
msgid "Main display panel not found"
msgstr ""
#: guiinit.cpp:2928
#: guiinit.cpp:2929
#, c-format
msgid "Duplicate menu accelerator: %s for %s and %s; keeping first"
msgstr ""
#: guiinit.cpp:2942
#: guiinit.cpp:2943
#, c-format
msgid "Menu accelerator %s for %s overrides default for %s ; keeping menu"
msgstr ""
#: guiinit.cpp:3081
#: guiinit.cpp:3082
#, c-format
msgid "Invalid menu item %s; removing"
msgstr ""
#: guiinit.cpp:3289
#: guiinit.cpp:3290
msgid "Code"
msgstr ""
#: guiinit.cpp:3298
#: guiinit.cpp:3299
msgid "Description"
msgstr ""
#: guiinit.cpp:3372 xrc/CheatAdd.xrc:31
#: guiinit.cpp:3373 xrc/CheatAdd.xrc:31
msgid "Address"
msgstr ""
#: guiinit.cpp:3373
#: guiinit.cpp:3374
msgid "Old Value"
msgstr ""
#: guiinit.cpp:3374
#: guiinit.cpp:3375
msgid "New Value"
msgstr ""
#: guiinit.cpp:3895
#: guiinit.cpp:3898
msgid "Menu commands"
msgstr ""
#: guiinit.cpp:3918
#: guiinit.cpp:3921
msgid "Other commands"
msgstr ""
#: guiinit.cpp:4029
#: guiinit.cpp:4032
msgid "JoyBus host invalid; disabling"
msgstr ""
@ -729,7 +729,7 @@ msgstr ""
msgid "Error saving snapshot file %s"
msgstr ""
#: cmdevents.cpp:1182 sys.cpp:449
#: cmdevents.cpp:1182 sys.cpp:450
#, c-format
msgid "Wrote snapshot %s"
msgstr ""
@ -855,102 +855,102 @@ msgstr ""
msgid "Network is not supported in local mode."
msgstr ""
#: opts.cpp:566 opts.cpp:869
#: opts.cpp:648 opts.cpp:950
#, c-format
msgid "Invalid value %s for option %s; valid values are %s%s%s"
msgstr ""
#: opts.cpp:583 opts.cpp:881
#: opts.cpp:665 opts.cpp:962
#, c-format
msgid "Invalid value %d for option %s; valid values are %d - %d"
msgstr ""
#: opts.cpp:590 opts.cpp:599 opts.cpp:889 opts.cpp:897
#: opts.cpp:672 opts.cpp:681 opts.cpp:970 opts.cpp:978
#, c-format
msgid "Invalid value %f for option %s; valid values are %f - %f"
msgstr ""
#: opts.cpp:659 opts.cpp:680 opts.cpp:965 opts.cpp:991
#: opts.cpp:738 opts.cpp:759 opts.cpp:1046
#, c-format
msgid "Invalid key binding %s for %s"
msgstr ""
#: opts.cpp:852
#: opts.cpp:933
#, c-format
msgid "Invalid flag option %s - %s ignored"
msgstr ""
#: sys.cpp:124 sys.cpp:180
#: sys.cpp:125 sys.cpp:181
msgid "No game in progress to record"
msgstr ""
#: sys.cpp:137
#: sys.cpp:138
#, c-format
msgid "Cannot open output file %s"
msgstr ""
#: sys.cpp:144 sys.cpp:164 sys.cpp:310
#: sys.cpp:145 sys.cpp:165 sys.cpp:311
msgid "Error writing game recording"
msgstr ""
#: sys.cpp:185
#: sys.cpp:186
msgid "Cannot play game recording while recording"
msgstr ""
#: sys.cpp:198
#: sys.cpp:199
#, c-format
msgid "Cannot open recording file %s"
msgstr ""
#: sys.cpp:205 sys.cpp:215
#: sys.cpp:206 sys.cpp:216
msgid "Error reading game recording"
msgstr ""
#: sys.cpp:321
#: sys.cpp:322
msgid "Playback ended"
msgstr ""
#: sys.cpp:340
#: sys.cpp:341
#, c-format
msgid "%d%%(%d, %d fps)"
msgstr ""
#: sys.cpp:348
#: sys.cpp:349
#, c-format
msgid "%d%%"
msgstr ""
#: sys.cpp:767 xrc/GBPrinter.xrc:65
#: sys.cpp:769 xrc/GBPrinter.xrc:65
msgid "&Discard"
msgstr ""
#: sys.cpp:801
#: sys.cpp:803
msgid "Image files (*.bmp;*.jpg;*.png)|*.bmp;*.jpg;*.png|"
msgstr ""
#: sys.cpp:810
#: sys.cpp:812
msgid "Save printer image to"
msgstr ""
#: sys.cpp:824 sys.cpp:1007
#: sys.cpp:826 sys.cpp:1009
#, c-format
msgid "Wrote printer output to %s"
msgstr ""
#: sys.cpp:829 sys.cpp:900
#: sys.cpp:831 sys.cpp:902
msgid "&Close"
msgstr ""
#: sys.cpp:895
#: sys.cpp:897
msgid "Printed"
msgstr ""
#: sys.cpp:1197
#: sys.cpp:1199
#, c-format
msgid "Error opening pseudo tty: %s"
msgstr ""
#: sys.cpp:1296
#: sys.cpp:1298
#, c-format
msgid "Error setting up server socket (%d)"
msgstr ""
@ -1046,70 +1046,70 @@ msgstr ""
msgid "Error writing rewind state"
msgstr ""
#: panel.cpp:2290
#: panel.cpp:2198
msgid "Failed to set glXSwapIntervalEXT"
msgstr ""
#: panel.cpp:2299
#: panel.cpp:2207
msgid "Failed to set glXSwapIntervalSGI"
msgstr ""
#: panel.cpp:2308
#: panel.cpp:2216
msgid "Failed to set glXSwapIntervalMESA"
msgstr ""
#: panel.cpp:2314
#: panel.cpp:2222
msgid "No support for wglGetExtensionsStringEXT"
msgstr ""
#: panel.cpp:2317
#: panel.cpp:2225
msgid "No support for WGL_EXT_swap_control"
msgstr ""
#: panel.cpp:2326
#: panel.cpp:2234
msgid "Failed to set wglSwapIntervalEXT"
msgstr ""
#: panel.cpp:2332
#: panel.cpp:2240
msgid "No VSYNC available on this platform"
msgstr ""
#: panel.cpp:2428
#: panel.cpp:2336
msgid "memory allocation error"
msgstr ""
#: panel.cpp:2431
#: panel.cpp:2339
msgid "error initializing codec"
msgstr ""
#: panel.cpp:2434
#: panel.cpp:2342
msgid "error writing to output file"
msgstr ""
#: panel.cpp:2437
#: panel.cpp:2345
msgid "can't guess output format from file name"
msgstr ""
#: panel.cpp:2442
#: panel.cpp:2350
msgid "programming error; aborting!"
msgstr ""
#: panel.cpp:2454 panel.cpp:2483
#: panel.cpp:2362 panel.cpp:2391
#, c-format
msgid "Unable to begin recording to %s (%s)"
msgstr ""
#: panel.cpp:2511
#: panel.cpp:2419
#, c-format
msgid "Error in audio/video recording (%s); aborting"
msgstr ""
#: panel.cpp:2517
#: panel.cpp:2425
#, c-format
msgid "Error in audio recording (%s); aborting"
msgstr ""
#: panel.cpp:2527
#: panel.cpp:2435
#, c-format
msgid "Error in video recording (%s); aborting"
msgstr ""
@ -1170,14 +1170,14 @@ msgstr ""
msgid "CONTROL"
msgstr ""
#: widgets/sdljoy.cpp:137
#: widgets/sdljoy.cpp:177
#, c-format
msgid "Connected joystick %d: %s"
msgid "Connected %s: %s"
msgstr ""
#: widgets/sdljoy.cpp:152
#: widgets/sdljoy.cpp:192
#, c-format
msgid "Disconnected joystick %d"
msgid "Disconnected %s"
msgstr ""
#: xaudio2.cpp:34

View File

@ -726,6 +726,7 @@ set(
wayland.cpp
strutils.cpp
wxutil.cpp
widgets/gamecontrol.cpp
widgets/keyedit.cpp
widgets/joyedit.cpp
widgets/userinput.cpp
@ -759,6 +760,7 @@ set(
wxhead.h
wayland.h
wxutil.h
widgets/wx/gamecontrol.h
widgets/wx/keyedit.h
widgets/wx/joyedit.h
widgets/wx/sdljoy.h

View File

@ -23,6 +23,9 @@
#include <wx/txtstrm.h>
#include <wx/wfstream.h>
#include "opts.h"
#include "wx/gamecontrol.h"
#include "wx/userinput.h"
#include "../gba/CheatSearch.h"
#if defined(__WXGTK__)
@ -1667,32 +1670,29 @@ public:
bool clear = ev.GetId() == XRCID("Clear");
// For the individual clear buttons, we assume their name is
// "Clear" + joynames[i]
// "Clear" + control_name
// ClearUp for Up; ClearR for R etc
for (int i = 0; i < NUM_KEYS; ++i) {
wxJoyKeyTextCtrl* tc = XRCCTRL_D(*p, joynames[i], wxJoyKeyTextCtrl);
wxString singleClearButton("Clear" + joynames[i]);
for (const wxGameKey& game_key : kAllGameKeys) {
const wxString control_name = GameKeyToString(game_key);
wxJoyKeyTextCtrl* tc = XRCCTRL_D(*p, control_name, wxJoyKeyTextCtrl);
wxString singleClearButton("Clear" + control_name);
if (ev.GetId() == XRCID(singleClearButton.c_str())) {
tc->SetValue(wxEmptyString);
return;
}
}
for (int i = 0; i < NUM_KEYS; i++) {
wxJoyKeyTextCtrl* tc = XRCCTRL_D(*p, joynames[i], wxJoyKeyTextCtrl);
for (const wxGameKey& game_key : kAllGameKeys) {
wxJoyKeyTextCtrl* tc =
XRCCTRL_D(*p, GameKeyToString(game_key), wxJoyKeyTextCtrl);
if (clear)
if (clear) {
tc->SetValue(wxEmptyString);
else {
wxJoyKeyBinding_v a;
if (defkeys_keyboard[i].key)
a.push_back(defkeys_keyboard[i]);
for (auto bind : defkeys_joystick[i])
a.push_back(bind);
tc->SetValue(wxJoyKeyTextCtrl::ToString(a));
} else {
tc->SetValue(
wxUserInput::SpanToString(
kDefaultBindings.find(
wxGameControl(0, game_key))->second));
}
}
}
@ -3835,9 +3835,10 @@ bool MainFrame::BindControls()
cb->SetValidator(wxBoolIntValidator(&gopts.default_stick, i + 1));
wxWindow *prev = NULL, *prevp = NULL;
for (int j = 0; j < NUM_KEYS; j++) {
wxJoyKeyTextCtrl* tc = XRCCTRL_D(*w, joynames[j], wxJoyKeyTextCtrl);
CheckThrowXRCError(tc, joynames[j]);
for (const wxGameKey& game_key : kAllGameKeys) {
const wxString control_name = GameKeyToString(game_key);
wxJoyKeyTextCtrl* tc = XRCCTRL_D(*w, control_name, wxJoyKeyTextCtrl);
CheckThrowXRCError(tc, control_name);
wxWindow* p = tc->GetParent();
if (p == prevp)
@ -3845,7 +3846,7 @@ bool MainFrame::BindControls()
prev = tc;
prevp = p;
tc->SetValidator(wxJoyKeyValidator(&gopts.joykey_bindings[i][j]));
tc->SetValidator(wxJoyKeyValidator(wxGameControl(i, game_key)));
}
JoyPadConfigHandler[i].p = w;
@ -3855,8 +3856,9 @@ bool MainFrame::BindControls()
w->Connect(XRCID("Clear"), wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(JoyPadConfig_t::JoypadConfigButtons),
NULL, &JoyPadConfigHandler[i]);
for (int j = 0; j < NUM_KEYS; ++j) {
w->Connect(XRCID(wxString("Clear" + joynames[j]).c_str()),
for (const wxGameKey& game_key : kAllGameKeys) {
const wxString control_name = GameKeyToString(game_key);
w->Connect(XRCID(wxString("Clear" + control_name).c_str()),
wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(JoyPadConfig_t::JoypadConfigButtons),
NULL, &JoyPadConfigHandler[i]);

View File

@ -1,7 +1,11 @@
#include "wxvbam.h"
#include <vector>
#include <algorithm>
#include <wx/log.h>
#include <wx/display.h>
#include "wx/gamecontrol.h"
#include "wx/userinput.h"
#include "wxvbam.h"
#include "strutils.h"
/*
@ -13,7 +17,7 @@
-p/--profile=hz
*/
#define WJKB newWxJoyKeyBinding
#define WJKB wxUserInput::FromLegacyKeyModJoy
/* not sure how well other compilers support field-init syntax */
#define STROPT(c, n, d, v) \
@ -131,40 +135,138 @@ const wxAcceleratorEntryUnicode default_accels[] = {
};
const int num_def_accels = sizeof(default_accels) / sizeof(default_accels[0]);
// Note: this must match GUI widget names or GUI won't work
// This table's order determines tab order as well
const wxString joynames[NUM_KEYS] = {
wxT("Up"), wxT("Down"), wxT("Left"), wxT("Right"),
wxT("A"), wxT("B"), wxT("L"), wxT("R"),
wxT("Select"), wxT("Start"),
wxT("MotionUp"), wxT("MotionDown"), wxT("MotionLeft"), wxT("MotionRight"),
wxT("MotionIn"), wxT("MotionOut"), wxT("AutoA"), wxT("AutoB"),
wxT("Speed"), wxT("Capture"), wxT("GS")
};
const std::map<wxGameControl, std::set<wxUserInput>> kDefaultBindings = {
{ wxGameControl(0, wxGameKey::Up), {
WJKB(wxT('W')),
WJKB(11, WXJB_BUTTON, 1),
WJKB(1, WXJB_AXIS_MINUS, 1),
WJKB(3, WXJB_AXIS_MINUS, 1),
}},
{ wxGameControl(0, wxGameKey::Down), {
WJKB(wxT('S')),
WJKB(12, WXJB_BUTTON, 1),
WJKB(1, WXJB_AXIS_PLUS, 1),
WJKB(3, WXJB_AXIS_PLUS, 1),
}},
{ wxGameControl(0, wxGameKey::Left), {
WJKB(wxT('A')),
WJKB(13, WXJB_BUTTON, 1),
WJKB(0, WXJB_AXIS_MINUS, 1),
WJKB(2, WXJB_AXIS_MINUS, 1),
}},
{ wxGameControl(0, wxGameKey::Right), {
WJKB(wxT('D')),
WJKB(14, WXJB_BUTTON, 1),
WJKB(0, WXJB_AXIS_PLUS, 1),
WJKB(2, WXJB_AXIS_PLUS, 1),
}},
{ wxGameControl(0, wxGameKey::A), {
WJKB(wxT('L')),
WJKB(0, WXJB_BUTTON, 1),
}},
{ wxGameControl(0, wxGameKey::B), {
WJKB(wxT('K')),
WJKB(1, WXJB_BUTTON, 1),
}},
{ wxGameControl(0, wxGameKey::L), {
WJKB(wxT('I')),
WJKB(2, WXJB_BUTTON, 1),
WJKB(9, WXJB_BUTTON, 1),
WJKB(4, WXJB_AXIS_PLUS, 1),
}},
{ wxGameControl(0, wxGameKey::R), {
WJKB(wxT('O')),
WJKB(3, WXJB_BUTTON, 1),
WJKB(10, WXJB_BUTTON, 1),
WJKB(5, WXJB_AXIS_PLUS, 1),
}},
{ wxGameControl(0, wxGameKey::Select), {
WJKB(WXK_BACK),
WJKB(4, WXJB_BUTTON, 1),
}},
{ wxGameControl(0, wxGameKey::Start), {
WJKB(WXK_RETURN),
WJKB(6, WXJB_BUTTON, 1),
}},
{ wxGameControl(0, wxGameKey::MotionUp), {}},
{ wxGameControl(0, wxGameKey::MotionDown), {}},
{ wxGameControl(0, wxGameKey::MotionLeft), {}},
{ wxGameControl(0, wxGameKey::MotionRight), {}},
{ wxGameControl(0, wxGameKey::MotionIn), {}},
{ wxGameControl(0, wxGameKey::MotionOut), {}},
{ wxGameControl(0, wxGameKey::AutoA), {}},
{ wxGameControl(0, wxGameKey::AutoB), {}},
{ wxGameControl(0, wxGameKey::Speed), {
WJKB(WXK_SPACE),
}},
{ wxGameControl(0, wxGameKey::Capture), {}},
{ wxGameControl(0, wxGameKey::Gameshark), {}},
wxJoyKeyBinding defkeys_keyboard[NUM_KEYS] = {
WJKB(wxT('W')), WJKB(wxT('S')), WJKB(wxT('A')), WJKB(wxT('D')),
WJKB(wxT('L')), WJKB(wxT('K')), WJKB(wxT('I')), WJKB(wxT('O')),
WJKB(WXK_BACK), WJKB(WXK_RETURN),
WJKB(0), WJKB(0), WJKB(0), WJKB(0),
WJKB(0), WJKB(0), WJKB(0), WJKB(0),
WJKB(WXK_SPACE), WJKB(0), WJKB(0)
};
{ wxGameControl(1, wxGameKey::Up), {}},
{ wxGameControl(1, wxGameKey::Down), {}},
{ wxGameControl(1, wxGameKey::Left), {}},
{ wxGameControl(1, wxGameKey::Right), {}},
{ wxGameControl(1, wxGameKey::A), {}},
{ wxGameControl(1, wxGameKey::B), {}},
{ wxGameControl(1, wxGameKey::L), {}},
{ wxGameControl(1, wxGameKey::R), {}},
{ wxGameControl(1, wxGameKey::Select), {}},
{ wxGameControl(1, wxGameKey::Start), {}},
{ wxGameControl(1, wxGameKey::MotionUp), {}},
{ wxGameControl(1, wxGameKey::MotionDown), {}},
{ wxGameControl(1, wxGameKey::MotionLeft), {}},
{ wxGameControl(1, wxGameKey::MotionRight), {}},
{ wxGameControl(1, wxGameKey::MotionIn), {}},
{ wxGameControl(1, wxGameKey::MotionOut), {}},
{ wxGameControl(1, wxGameKey::AutoA), {}},
{ wxGameControl(1, wxGameKey::AutoB), {}},
{ wxGameControl(1, wxGameKey::Speed), {}},
{ wxGameControl(1, wxGameKey::Capture), {}},
{ wxGameControl(1, wxGameKey::Gameshark), {}},
std::vector<std::vector<wxJoyKeyBinding>> defkeys_joystick = {
{ WJKB(11, WXJB_BUTTON, 1), WJKB(1, WXJB_AXIS_MINUS, 1), WJKB(3, WXJB_AXIS_MINUS, 1) },
{ WJKB(12, WXJB_BUTTON, 1), WJKB(1, WXJB_AXIS_PLUS, 1), WJKB(3, WXJB_AXIS_PLUS, 1) },
{ WJKB(13, WXJB_BUTTON, 1), WJKB(0, WXJB_AXIS_MINUS, 1), WJKB(2, WXJB_AXIS_MINUS, 1) },
{ WJKB(14, WXJB_BUTTON, 1), WJKB(0, WXJB_AXIS_PLUS, 1), WJKB(2, WXJB_AXIS_PLUS, 1) },
{ WJKB(0, WXJB_BUTTON, 1) },
{ WJKB(1, WXJB_BUTTON, 1) },
{ WJKB(2, WXJB_BUTTON, 1), WJKB( 9, WXJB_BUTTON, 1), WJKB(4, WXJB_AXIS_PLUS, 1) },
{ WJKB(3, WXJB_BUTTON, 1), WJKB(10, WXJB_BUTTON, 1), WJKB(5, WXJB_AXIS_PLUS, 1) },
{ WJKB(4, WXJB_BUTTON, 1) },
{ WJKB(6, WXJB_BUTTON, 1) },
{}, {}, {}, {},
{}, {}, {}, {},
{}, {}, {}
{ wxGameControl(2, wxGameKey::Up), {}},
{ wxGameControl(2, wxGameKey::Down), {}},
{ wxGameControl(2, wxGameKey::Left), {}},
{ wxGameControl(2, wxGameKey::Right), {}},
{ wxGameControl(2, wxGameKey::A), {}},
{ wxGameControl(2, wxGameKey::B), {}},
{ wxGameControl(2, wxGameKey::L), {}},
{ wxGameControl(2, wxGameKey::R), {}},
{ wxGameControl(2, wxGameKey::Select), {}},
{ wxGameControl(2, wxGameKey::Start), {}},
{ wxGameControl(2, wxGameKey::MotionUp), {}},
{ wxGameControl(2, wxGameKey::MotionDown), {}},
{ wxGameControl(2, wxGameKey::MotionLeft), {}},
{ wxGameControl(2, wxGameKey::MotionRight), {}},
{ wxGameControl(2, wxGameKey::MotionIn), {}},
{ wxGameControl(2, wxGameKey::MotionOut), {}},
{ wxGameControl(2, wxGameKey::AutoA), {}},
{ wxGameControl(2, wxGameKey::AutoB), {}},
{ wxGameControl(2, wxGameKey::Speed), {}},
{ wxGameControl(2, wxGameKey::Capture), {}},
{ wxGameControl(2, wxGameKey::Gameshark), {}},
{ wxGameControl(3, wxGameKey::Up), {}},
{ wxGameControl(3, wxGameKey::Down), {}},
{ wxGameControl(3, wxGameKey::Left), {}},
{ wxGameControl(3, wxGameKey::Right), {}},
{ wxGameControl(3, wxGameKey::A), {}},
{ wxGameControl(3, wxGameKey::B), {}},
{ wxGameControl(3, wxGameKey::L), {}},
{ wxGameControl(3, wxGameKey::R), {}},
{ wxGameControl(3, wxGameKey::Select), {}},
{ wxGameControl(3, wxGameKey::Start), {}},
{ wxGameControl(3, wxGameKey::MotionUp), {}},
{ wxGameControl(3, wxGameKey::MotionDown), {}},
{ wxGameControl(3, wxGameKey::MotionLeft), {}},
{ wxGameControl(3, wxGameKey::MotionRight), {}},
{ wxGameControl(3, wxGameKey::MotionIn), {}},
{ wxGameControl(3, wxGameKey::MotionOut), {}},
{ wxGameControl(3, wxGameKey::AutoA), {}},
{ wxGameControl(3, wxGameKey::AutoB), {}},
{ wxGameControl(3, wxGameKey::Speed), {}},
{ wxGameControl(3, wxGameKey::Capture), {}},
{ wxGameControl(3, wxGameKey::Gameshark), {}},
};
wxAcceleratorEntry_v sys_accels;
@ -385,19 +487,6 @@ bool opt_lt(const opt_desc& opt1, const opt_desc& opt2)
return wxStrcmp(opt1.opt, opt2.opt) < 0;
}
// set default input keys
void set_default_keys()
{
for (int i = 0; i < NUM_KEYS; i++) {
gopts.joykey_bindings[0][i].clear();
if (defkeys_keyboard[i].key)
gopts.joykey_bindings[0][i].push_back(defkeys_keyboard[i]);
for (auto bind : defkeys_joystick[i])
gopts.joykey_bindings[0][i].push_back(bind);
}
}
// FIXME: simulate MakeInstanceFilename(vbam.ini) using subkeys (Slave%d/*)
void load_opts()
{
@ -473,13 +562,7 @@ void load_opts()
for (cont = cfg->GetFirstEntry(e, key_idx); cont;
cont = cfg->GetNextEntry(e, key_idx)) {
int i;
for (i = 0; i < NUM_KEYS; i++)
if (e == joynames[i])
break;
if (i == NUM_KEYS) {
if (!StringToGameKey(e)) {
s.append(e);
//wxLogWarning(_("Invalid option %s present; removing if possible"), s.c_str());
item_del.push_back(s);
@ -644,23 +727,20 @@ void load_opts()
}
}
// Initialize game control bindings to populate the configuration map.
gopts.game_control_bindings.insert(kDefaultBindings.begin(), kDefaultBindings.end());
// joypad is special
set_default_keys();
for (int i = 0; i < 4; i++) {
for (int j = 0; j < NUM_KEYS; j++) {
wxString optname;
optname.Printf(wxT("Joypad/%d/%s"), i + 1, joynames[j].c_str());
bool gotit = cfg->Read(optname, &s);
if (gotit) {
gopts.joykey_bindings[i][j] = wxJoyKeyTextCtrl::FromString(s);
if (s.size() && !gopts.joykey_bindings[i][j].size())
for (auto& iter : gopts.game_control_bindings) {
const wxString optname = iter.first.ToString();
if (cfg->Read(optname, &s)) {
iter.second = wxUserInput::FromString(s);
if (!s.empty() && iter.second.empty()) {
wxLogWarning(_("Invalid key binding %s for %s"), s.c_str(), optname.c_str());
} else {
s = wxJoyKeyTextCtrl::ToString(gopts.joykey_bindings[i][j]);
cfg->Write(optname, s);
}
} else {
s = wxUserInput::SpanToString(iter.second);
cfg->Write(optname, s);
}
}
@ -758,21 +838,23 @@ void update_opts()
}
}
// for joypad, use ToString comparisons. It may trigger changes
// even when there are none (e.g. multi-binding ordering changes)
// not worth worrying about
for (int i = 0; i < 4; i++) {
for (int j = 0; j < NUM_KEYS; j++) {
wxString s, o;
wxString optname;
optname.Printf(wxT("Joypad/%d/%s"), i + 1, joynames[j].c_str());
s = wxJoyKeyTextCtrl::ToString(gopts.joykey_bindings[i][j], wxT(','), true);
cfg->Read(optname, &o);
if (o != s)
cfg->Write(optname, s);
// For joypad, compare the wxUserInput sets. Since wxUserInput guarantees a
// certain ordering, it is possible that the user control in the panel shows
// a different ordering than the one that will be eventually saved, but this
// is nothing to worry about.
bool game_bindings_changed = false;
for (auto &iter : gopts.game_control_bindings) {
wxString option_name = iter.first.ToString();
std::set<wxUserInput> saved_config =
wxUserInput::FromString(cfg->Read(option_name, ""));
if (saved_config != iter.second) {
game_bindings_changed = true;
cfg->Write(option_name, wxUserInput::SpanToString(iter.second));
}
}
if (game_bindings_changed) {
wxGameControlState::Instance().OnGameBindingsChanged();
}
// for keyboard, first remove any commands that aren't bound at all
if (cfg->HasGroup(wxT("/Keyboard"))) {
@ -968,33 +1050,20 @@ bool opt_set(const wxString& name, const wxString& val)
}
return true;
} else if (!wxStrncmp(name, wxT("Joypad"), wxStrlen(wxT("Joypad")))) {
if (parts[1] < wxT('1') || parts[1] > wxT('4') || parts.size() < 3)
return false;
int jno = parts[1][0] - wxT('1');
int kno;
for (kno = 0; kno < NUM_KEYS; kno++)
if (!wxStrcmp(joynames[kno], parts[2]))
break;
if (kno == NUM_KEYS)
return false;
if (val.empty())
gopts.joykey_bindings[jno][kno].clear();
else {
auto b = wxJoyKeyTextCtrl::FromString(val);
if (!b.size())
wxLogWarning(_("Invalid key binding %s for %s"), val.c_str(), name.c_str());
else
gopts.joykey_bindings[jno][kno] = b;
}
const std::optional<wxGameControl> game_control =
wxGameControl::FromString(name);
if (game_control) {
if (val.empty()) {
gopts.game_control_bindings[game_control.value()].clear();
} else {
gopts.game_control_bindings[game_control.value()] =
wxUserInput::FromString(val);
}
return true;
} else
}
return false;
}
}

View File

@ -1,13 +1,20 @@
#ifndef WX_OPTS_H
#define WX_OPTS_H
#include <vector>
#include <map>
#define NUM_KEYS 21
extern const wxString joynames[NUM_KEYS];
extern wxJoyKeyBinding defkeys_keyboard[NUM_KEYS]; // keyboard defaults
#include <wx/string.h>
#include <wx/vidmode.h>
extern std::vector<std::vector<wxJoyKeyBinding>> defkeys_joystick; // joystick defaults
#include "wx/gamecontrol.h"
#include "wx/keyedit.h"
#include "wx/userinput.h"
// Forward declaration.
class wxFileHistory;
// Default joystick bindings.
extern const std::map<wxGameControl, std::set<wxUserInput>> kDefaultBindings;
extern struct opts_t {
opts_t();
@ -57,7 +64,7 @@ extern struct opts_t {
int statusbar;
/// Joypad
wxJoyKeyBinding_v joykey_bindings[4][NUM_KEYS];
std::map<wxGameControl, std::set<wxUserInput>> game_control_bindings;
int autofire_rate;
int default_stick;
@ -122,8 +129,6 @@ extern const int num_opts;
extern const wxAcceleratorEntryUnicode default_accels[];
extern const int num_def_accels;
// call to setup default keys.
void set_default_keys();
// call to load config (once)
// will write defaults for options not present and delete bad opts
// will also initialize opts[] array translations

View File

@ -24,6 +24,7 @@
#include "drawing.h"
#include "filters.h"
#include "wx/joyedit.h"
#include "wx/gamecontrol.h"
#include "wx/userinput.h"
#include "wxvbam.h"
#include "wxutil.h"
@ -34,9 +35,6 @@
#include <windows.h>
#endif
// release all buttons currently pressed
static void clear_input_press();
int emulating;
IMPLEMENT_DYNAMIC_CLASS(GameArea, wxPanel)
@ -957,7 +955,7 @@ GameArea::~GameArea()
void GameArea::OnKillFocus(wxFocusEvent& ev)
{
clear_input_press();
wxGameControlState::Instance().Reset();
ev.Skip();
}
@ -977,7 +975,7 @@ void GameArea::Pause()
// when the game is paused like this, we should not allow any
// input to remain pressed, because they could be released
// outside of the game zone and we would not know about it.
clear_input_press();
wxGameControlState::Instance().Reset();
if (loaded != IMAGE_UNKNOWN)
soundPause();
@ -1208,99 +1206,12 @@ void GameArea::OnIdle(wxIdleEvent& event)
}
}
// Note: keys will get stuck if they are released while window has no focus
// can't really do anything about it, except scan for pressed keys on
// activate events. Maybe later.
static uint32_t bmask[NUM_KEYS] = {
KEYM_UP, KEYM_DOWN, KEYM_LEFT, KEYM_RIGHT, KEYM_A, KEYM_B, KEYM_L, KEYM_R,
KEYM_SELECT, KEYM_START, KEYM_MOTION_UP, KEYM_MOTION_DOWN, KEYM_MOTION_LEFT,
KEYM_MOTION_RIGHT, KEYM_MOTION_IN, KEYM_MOTION_OUT, KEYM_AUTO_A, KEYM_AUTO_B, KEYM_SPEED, KEYM_CAPTURE,
KEYM_GS
};
static std::set<wxUserInput> keys_pressed;
static void clear_input_press()
{
int i;
for (i = 0; i < 4; ++i)
{
joypress[i] = 0;
}
keys_pressed.clear();
}
struct game_key {
int player;
int key_num;
int bind_num;
wxJoyKeyBinding_v& b;
};
// Populates |vec| with the game keys currently pressed.
static void game_keys_pressed(int key, int mod, int joy, std::vector<game_key>* vec)
{
for (int player = 0; player < 4; player++)
for (int key_num = 0; key_num < NUM_KEYS; key_num++) {
wxJoyKeyBinding_v& b = gopts.joykey_bindings[player][key_num];
for (size_t bind_num = 0; bind_num < b.size(); bind_num++)
if (b[bind_num].key == key && b[bind_num].mod == mod && b[bind_num].joy == joy)
vec->push_back({player, key_num, (int)bind_num, b});
}
}
static bool process_user_input(bool down, const wxUserInput& user_input)
{
int key = user_input.key();
int mod = user_input.mod();
int joy = user_input.joy();
std::vector<game_key> game_keys;
game_keys_pressed(key, mod, joy, &game_keys);
const bool is_game_key = !game_keys.empty();
// check if key is already pressed
auto iter = keys_pressed.find(user_input);
if (iter != keys_pressed.end()) {
// double press is noop
if (down)
return is_game_key;
// if released, forget it
iter = keys_pressed.erase(iter);
} else {
// double release is noop
if (!down)
return is_game_key;
// otherwise remember it
keys_pressed.emplace(user_input);
}
for (auto&& game_key : game_keys) {
if (down) {
// press button
joypress[game_key.player] |= bmask[game_key.key_num];
}
else {
// only release if no others pressed
size_t bind2;
auto b = game_key.b;
for (bind2 = 0; bind2 < game_key.b.size(); bind2++) {
if ((size_t)game_key.bind_num == bind2 || (b[bind2].key == key && b[bind2].mod == mod && b[bind2].joy == joy))
continue;
}
if (bind2 == b.size()) {
// release button
joypress[game_key.player] &= ~bmask[game_key.key_num];
}
}
}
return is_game_key;
return wxGameControlState::Instance().OnInputPressed(user_input);
else
return wxGameControlState::Instance().OnInputReleased(user_input);
}
static void draw_black_background(wxWindow* win) {
@ -1356,7 +1267,7 @@ static void process_keyboard_event(const wxKeyEvent& ev, bool down)
break;
}
if (process_user_input(down, wxUserInput::FromLegacyJoyKeyBinding({key, mod, 0}))) {
if (process_user_input(down, wxUserInput::FromLegacyKeyModJoy(key, mod, 0))) {
wxWakeUpIdle();
};
}
@ -1403,17 +1314,17 @@ void GameArea::OnSDLJoy(wxJoyEvent& ev)
// mutually exclusive key types unpress their opposite
// TODO: Move creation of these "ghost" events to wxJoyPoller.
if (mod == WXJB_AXIS_PLUS) {
process_user_input(false, wxUserInput::FromLegacyJoyKeyBinding({key, WXJB_AXIS_MINUS, joy}));
process_user_input(false, wxUserInput::FromLegacyKeyModJoy(key, WXJB_AXIS_MINUS, joy));
process_user_input(ev.control_value() != 0, wxUserInput::FromJoyEvent(ev));
} else if (mod == WXJB_AXIS_MINUS) {
process_user_input(false, wxUserInput::FromLegacyJoyKeyBinding({key, WXJB_AXIS_PLUS, joy}));
process_user_input(false, wxUserInput::FromLegacyKeyModJoy(key, WXJB_AXIS_PLUS, joy));
process_user_input(ev.control_value() != 0, wxUserInput::FromJoyEvent(ev));
} else if (mod >= WXJB_HAT_FIRST && mod <= WXJB_HAT_LAST) {
int value = ev.control_value();
process_user_input(value & SDL_HAT_UP, wxUserInput::FromLegacyJoyKeyBinding({key, WXJB_HAT_N, joy}));
process_user_input(value & SDL_HAT_DOWN, wxUserInput::FromLegacyJoyKeyBinding({key, WXJB_HAT_S, joy}));
process_user_input(value & SDL_HAT_RIGHT, wxUserInput::FromLegacyJoyKeyBinding({key, WXJB_HAT_E, joy}));
process_user_input(value & SDL_HAT_LEFT, wxUserInput::FromLegacyJoyKeyBinding({key, WXJB_HAT_W, joy}));
process_user_input(value & SDL_HAT_UP, wxUserInput::FromLegacyKeyModJoy(key, WXJB_HAT_N, joy));
process_user_input(value & SDL_HAT_DOWN, wxUserInput::FromLegacyKeyModJoy(key, WXJB_HAT_S, joy));
process_user_input(value & SDL_HAT_RIGHT, wxUserInput::FromLegacyKeyModJoy(key, WXJB_HAT_E, joy));
process_user_input(value & SDL_HAT_LEFT, wxUserInput::FromLegacyKeyModJoy(key, WXJB_HAT_W, joy));
} else {
process_user_input(ev.control_value() != 0, wxUserInput::FromJoyEvent(ev));
}

View File

@ -1,4 +1,5 @@
#include "../common/SoundSDL.h"
#include "wx/gamecontrol.h"
#include "wxvbam.h"
#include "SDL.h"
#include <wx/ffile.h>
@ -28,7 +29,7 @@ uint16_t systemGbPalette[24] = {
int RGB_LOW_BITS_MASK;
// these are local, though.
int joypress[4], autofire, autohold;
int autofire, autohold;
static int sensorx[4], sensory[4], sensorz[4];
bool pause_next;
bool turbo;
@ -251,7 +252,7 @@ uint32_t systemReadJoypad(int joy)
if (joy < 0 || joy > 3)
joy = gopts.default_stick - 1;
uint32_t ret = joypress[joy];
uint32_t ret = wxGameControlState::Instance().GetJoypad(joy);
if (turbo)
ret |= KEYM_SPEED;
@ -533,13 +534,14 @@ void systemUpdateSolarSensor()
void systemUpdateMotionSensor()
{
for (int i = 0; i < 4; i++) {
const uint32_t joy_value = wxGameControlState::Instance().GetJoypad(i);
if (!sensorx[i])
sensorx[i] = 2047;
if (!sensory[i])
sensory[i] = 2047;
if (joypress[i] & KEYM_MOTION_LEFT) {
if (joy_value & KEYM_MOTION_LEFT) {
sunBars--;
if (sunBars < 1)
@ -552,7 +554,7 @@ void systemUpdateMotionSensor()
if (sensorx[i] < 2047)
sensorx[i] = 2057;
} else if (joypress[i] & KEYM_MOTION_RIGHT) {
} else if (joy_value & KEYM_MOTION_RIGHT) {
sunBars++;
if (sunBars > 100)
@ -577,7 +579,7 @@ void systemUpdateMotionSensor()
sensorx[i] = 2047;
}
if (joypress[i] & KEYM_MOTION_UP) {
if (joy_value & KEYM_MOTION_UP) {
sensory[i] += 3;
if (sensory[i] > 2197)
@ -585,7 +587,7 @@ void systemUpdateMotionSensor()
if (sensory[i] < 2047)
sensory[i] = 2057;
} else if (joypress[i] & KEYM_MOTION_DOWN) {
} else if (joy_value & KEYM_MOTION_DOWN) {
sensory[i] -= 3;
if (sensory[i] < 1897)
@ -610,7 +612,7 @@ void systemUpdateMotionSensor()
const int highZ = 1800;
const int accelZ = 3;
if (joypress[i] & KEYM_MOTION_IN) {
if (joy_value & KEYM_MOTION_IN) {
sensorz[i] += accelZ;
if (sensorz[i] > highZ)
@ -618,7 +620,7 @@ void systemUpdateMotionSensor()
if (sensorz[i] < centerZ)
sensorz[i] = centerZ + (accelZ * 300);
} else if (joypress[i] & KEYM_MOTION_OUT) {
} else if (joy_value & KEYM_MOTION_OUT) {
sensorz[i] -= accelZ;
if (sensorz[i] < lowZ)

View File

@ -0,0 +1,291 @@
#include "wx/gamecontrol.h"
#include "../strutils.h"
#include "opts.h"
#include "wx/log.h"
#include "wxlogdebug.h"
namespace {
constexpr uint32_t kBitKeyA = (1 << 0);
constexpr uint32_t kBitKeyB = (1 << 1);
constexpr uint32_t kBitKeySelect = (1 << 2);
constexpr uint32_t kBitKeyStart = (1 << 3);
constexpr uint32_t kBitKeyRight = (1 << 4);
constexpr uint32_t kBitKeyLeft = (1 << 5);
constexpr uint32_t kBitKeyUp = (1 << 6);
constexpr uint32_t kBitKeyDown = (1 << 7);
constexpr uint32_t kBitKeyR = (1 << 8);
constexpr uint32_t kBitKeyL = (1 << 9);
constexpr uint32_t kBitKeySpeed = (1 << 10);
constexpr uint32_t kBitKeyCapture = (1 << 11);
constexpr uint32_t kBitKeyGameShark = (1 << 12);
constexpr uint32_t kBitKeyAutoA = (1 << 13);
constexpr uint32_t kBitKeyAutoB = (1 << 14);
constexpr uint32_t kBitKeyMotionUp = (1 << 15);
constexpr uint32_t kBitKeyMotionDown = (1 << 16);
constexpr uint32_t kBitKeyMotionLeft = (1 << 17);
constexpr uint32_t kBitKeyMotionRight = (1 << 18);
constexpr uint32_t kBitKeyMotionIn = (1 << 19);
constexpr uint32_t kBitKeyMotionOut = (1 << 20);
constexpr std::array<uint32_t, kNbGameKeys> kBitMask = {
kBitKeyUp,
kBitKeyDown,
kBitKeyLeft,
kBitKeyRight,
kBitKeyA,
kBitKeyB,
kBitKeyL,
kBitKeyR,
kBitKeySelect,
kBitKeyStart,
kBitKeyMotionUp,
kBitKeyMotionDown,
kBitKeyMotionLeft,
kBitKeyMotionRight,
kBitKeyMotionIn,
kBitKeyMotionOut,
kBitKeyAutoA,
kBitKeyAutoB,
kBitKeySpeed,
kBitKeyCapture,
kBitKeyGameShark,
};
inline int GameKeyToInt(const wxGameKey& game_key) {
return static_cast<std::underlying_type<wxGameKey>::type>(
game_key);
}
// Returns true if `joypad` is in a valid joypad range.
inline bool JoypadInRange(const int& joypad) {
constexpr int kMinJoypadIndex = 0;
return joypad >= kMinJoypadIndex && joypad < kNbJoypads;
}
} // namespace
wxString GameKeyToString(const wxGameKey& game_key) {
// Note: this must match GUI widget names or GUI won't work
// This array's order determines tab order as well
static const std::array<wxString, kNbGameKeys> kGameKeyStrings = {
wxT("Up"),
wxT("Down"),
wxT("Left"),
wxT("Right"),
wxT("A"),
wxT("B"),
wxT("L"),
wxT("R"),
wxT("Select"),
wxT("Start"),
wxT("MotionUp"),
wxT("MotionDown"),
wxT("MotionLeft"),
wxT("MotionRight"),
wxT("MotionIn"),
wxT("MotionOut"),
wxT("AutoA"),
wxT("AutoB"),
wxT("Speed"),
wxT("Capture"),
wxT("GS"),
};
return kGameKeyStrings[GameKeyToInt(game_key)];
}
std::optional<wxGameKey> StringToGameKey(const wxString& input) {
static const std::map<wxString, wxGameKey> kStringToGameKey = {
{ wxT("Up"), wxGameKey::Up },
{ wxT("Down"), wxGameKey::Down },
{ wxT("Left"), wxGameKey::Left },
{ wxT("Right"), wxGameKey::Right },
{ wxT("A"), wxGameKey::A },
{ wxT("B"), wxGameKey::B },
{ wxT("L"), wxGameKey::L },
{ wxT("R"), wxGameKey::R },
{ wxT("Select"), wxGameKey::Select },
{ wxT("Start"), wxGameKey::Start },
{ wxT("MotionUp"), wxGameKey::MotionUp },
{ wxT("MotionDown"), wxGameKey::MotionDown },
{ wxT("MotionLeft"), wxGameKey::MotionLeft },
{ wxT("MotionRight"), wxGameKey::MotionRight },
{ wxT("MotionIn"), wxGameKey::MotionIn },
{ wxT("MotionOut"), wxGameKey::MotionOut },
{ wxT("AutoA"), wxGameKey::AutoA },
{ wxT("AutoB"), wxGameKey::AutoB },
{ wxT("Speed"), wxGameKey::Speed },
{ wxT("Capture"), wxGameKey::Capture },
{ wxT("GS"), wxGameKey::Gameshark },
};
const auto iter = kStringToGameKey.find(input);
if (iter == kStringToGameKey.end()) {
return std::nullopt;
}
return iter->second;
}
// static
std::optional<wxGameControl> wxGameControl::FromString(const wxString &name) {
static const wxString kJoypad(wxT("Joypad"));
if (!wxStrncmp(name, kJoypad, kJoypad.size())) {
wxLogDebug("Doesn't start with joypad");
return std::nullopt;
}
auto parts = str_split(name, wxT("/"));
if (parts.size() != 3) {
wxLogDebug("Wrong split size: %d", parts.size());
return std::nullopt;
}
const int joypad = parts[1][0] - wxT('1');
if (!JoypadInRange(joypad)) {
wxLogDebug("Wrong joypad index: %d", joypad);
return std::nullopt;
}
std::optional<wxGameKey> game_key = StringToGameKey(parts[2]);
if (!game_key) {
wxLogDebug("Failed to parse game_key: %s", parts[2]);
return std::nullopt;
}
return wxGameControl(joypad, game_key.value());
}
wxGameControl::wxGameControl(int joypad, wxGameKey game_key) :
joypad_(joypad),
game_key_(game_key),
config_string_(wxString::Format(
wxT("Joypad/%d/%s"), joypad_ + 1, GameKeyToString(game_key_))) {
assert(JoypadInRange(joypad_));
}
wxGameControl::~wxGameControl() = default;
bool wxGameControl::operator==(const wxGameControl& other) const {
return joypad_ == other.joypad_ && game_key_ == other.game_key_;
}
bool wxGameControl::operator!=(const wxGameControl& other) const {
return !(*this == other);
}
bool wxGameControl::operator<(const wxGameControl& other) const {
if (joypad_ != other.joypad_) {
return joypad_ < other.joypad_;
}
if (game_key_ != other.game_key_) {
return game_key_ < other.game_key_;
}
return false;
}
bool wxGameControl::operator<=(const wxGameControl& other) const {
return !(*this > other);
}
bool wxGameControl::operator>(const wxGameControl& other) const {
return other < *this;
}
bool wxGameControl::operator>=(const wxGameControl& other) const {
return !(*this < other);
}
wxGameControlState& wxGameControlState::Instance() {
static wxGameControlState g_game_control_state;
return g_game_control_state;
}
wxGameControlState::wxGameControlState() : joypads_({0, 0, 0, 0}) {}
wxGameControlState::~wxGameControlState() = default;
bool wxGameControlState::OnInputPressed(const wxUserInput& user_input) {
assert(user_input);
const auto& game_keys = input_bindings_.find(user_input);
if (game_keys == input_bindings_.end()) {
// No associated game control for `user_input`.
return false;
}
auto iter = keys_pressed_.find(user_input);
if (iter != keys_pressed_.end()) {
// Double press is noop.
return true;
}
// Remember the key pressed.
keys_pressed_.emplace(user_input);
// Update all corresponding controls.
for (const wxGameControl& game_control : game_keys->second) {
active_controls_[game_control].emplace(user_input);
joypads_[game_control.joypad_] |=
kBitMask[GameKeyToInt(game_control.game_key_)];
}
return true;
}
bool wxGameControlState::OnInputReleased(const wxUserInput& user_input) {
assert(user_input);
const auto& game_keys = input_bindings_.find(user_input);
if (game_keys == input_bindings_.end()) {
// No associated game control for `user_input`.
return false;
}
auto iter = keys_pressed_.find(user_input);
if (iter == keys_pressed_.end()) {
// Double release is noop.
return true;
}
// Release the key pressed.
keys_pressed_.erase(iter);
// Update all corresponding controls.
for (const wxGameControl& game_control : game_keys->second) {
auto active_controls = active_controls_.find(game_control);
if (active_controls == active_controls_.end()) {
// This should never happen.
assert(false);
return true;
}
active_controls->second.erase(user_input);
if (active_controls->second.empty()) {
// Actually release control.
active_controls_.erase(active_controls);
joypads_[game_control.joypad_] &=
~kBitMask[GameKeyToInt(game_control.game_key_)];
}
}
return true;
}
void wxGameControlState::Reset() {
active_controls_.clear();
keys_pressed_.clear();
joypads_.fill(0);
}
void wxGameControlState::OnGameBindingsChanged() {
// We should reset to ensure no key remains accidentally pressed following a
// configuration change.
Reset();
input_bindings_.clear();
for (const auto& iter : gopts.game_control_bindings) {
for (const auto& user_input : iter.second) {
input_bindings_[user_input].emplace(iter.first);
}
}
}
uint32_t wxGameControlState::GetJoypad(int joypad) const {
assert(JoypadInRange(joypad));
return joypads_[joypad];
}

View File

@ -2,6 +2,7 @@
#include <wx/tokenzr.h>
#include "opts.h"
#include "strutils.h"
#include "wx/userinput.h"
@ -13,13 +14,6 @@ BEGIN_EVENT_TABLE(wxJoyKeyTextCtrl, wxKeyTextCtrl)
EVT_SDLJOY(wxJoyKeyTextCtrl::OnJoy)
END_EVENT_TABLE()
// Initializer for struct wxJoyKeyBinding
wxJoyKeyBinding newWxJoyKeyBinding(int key, int mod, int joy)
{
struct wxJoyKeyBinding tmp = {key, mod, joy};
return tmp;
}
int wxJoyKeyTextCtrl::DigitalButton(const wxJoyEvent& event)
{
int16_t sdlval = event.control_value();
@ -164,25 +158,6 @@ wxString wxJoyKeyTextCtrl::ToString(int mod, int key, int joy, bool isConfig)
return s;
}
wxString wxJoyKeyTextCtrl::ToString(wxJoyKeyBinding_v keys, wxChar sep, bool isConfig)
{
wxString ret;
for (size_t i = 0; i < keys.size(); i++) {
if (i > 0)
ret += sep;
wxString key = ToString(keys[i].mod, keys[i].key, keys[i].joy, isConfig);
if (key.empty())
return wxEmptyString;
ret += key;
}
return ret;
}
wxString wxJoyKeyTextCtrl::FromAccelToString(wxAcceleratorEntry_v keys, wxChar sep, bool isConfig)
{
wxString ret;
@ -311,22 +286,6 @@ bool wxJoyKeyTextCtrl::FromString(const wxString& s, int& mod, int& key, int& jo
return ParseString(s, s.size(), mod, key, joy);
}
wxJoyKeyBinding_v wxJoyKeyTextCtrl::FromString(const wxString& s, wxChar sep)
{
wxJoyKeyBinding_v ret, empty;
int mod, key, joy;
if (s.size() == 0)
return empty;
for (const auto& token : str_split_with_sep(s, sep)) {
if (!ParseString(token, token.size(), mod, key, joy))
return empty;
wxJoyKeyBinding jb = { key, mod, joy };
ret.insert(ret.begin(), jb);
}
return ret;
}
wxAcceleratorEntry_v wxJoyKeyTextCtrl::ToAccelFromString(const wxString& s, wxChar sep)
{
wxAcceleratorEntry_v ret, empty;
@ -346,28 +305,22 @@ IMPLEMENT_CLASS(wxJoyKeyValidator, wxValidator)
bool wxJoyKeyValidator::TransferToWindow()
{
if (!val)
return false;
wxJoyKeyTextCtrl* jk = wxDynamicCast(GetWindow(), wxJoyKeyTextCtrl);
if (!jk)
return false;
jk->SetValue(wxJoyKeyTextCtrl::ToString(*val));
jk->SetValue(wxUserInput::SpanToString(gopts.game_control_bindings[val_]));
return true;
}
bool wxJoyKeyValidator::TransferFromWindow()
{
if (!val)
return false;
wxJoyKeyTextCtrl* jk = wxDynamicCast(GetWindow(), wxJoyKeyTextCtrl);
if (!jk)
return false;
*val = wxJoyKeyTextCtrl::FromString(jk->GetValue());
gopts.game_control_bindings[val_] = wxUserInput::FromString(jk->GetValue());
return true;
}

View File

@ -1,7 +1,10 @@
#include "wx/userinput.h"
#include "wx/joyedit.h"
#include "wx/sdljoy.h"
#include "wx/string.h"
#include "wxutil.h"
#include "../../wx/strutils.h"
// static
wxUserInput wxUserInput::Invalid() {
@ -25,33 +28,47 @@ wxUserInput wxUserInput::FromJoyEvent(const wxJoyEvent& event) {
}
// static
wxUserInput wxUserInput::FromLegacyJoyKeyBinding(const wxJoyKeyBinding& binding) {
return wxUserInput(binding.joy == 0 ? Device::Keyboard : Device::Joystick,
binding.mod,
binding.key,
binding.joy);
wxUserInput wxUserInput::FromLegacyKeyModJoy(int key, int mod, int joy) {
return wxUserInput(joy == 0 ? Device::Keyboard : Device::Joystick,
mod,
key,
joy);
}
// static
wxUserInput wxUserInput::FromString(const wxString& string) {
// TODO: Move the implementation here once all callers have been updated.
int mod = 0;
int key = 0;
int joy = 0;
if (!wxJoyKeyTextCtrl::FromString(string, mod, key, joy)) {
return wxUserInput::Invalid();
}
return wxUserInput::FromLegacyJoyKeyBinding({key, mod, joy});
std::set<wxUserInput> wxUserInput::FromString(const wxString& string) {
std::set<wxUserInput> user_inputs;
if (string.empty()) {
return user_inputs;
}
wxString wxUserInput::ToString() {
if (!config_string_.IsNull()) {
return config_string_;
for (const auto& token : str_split_with_sep(string, wxT(","))) {
int mod, key, joy;
if (!wxJoyKeyTextCtrl::ParseString(token, token.size(), mod, key, joy)) {
user_inputs.clear();
return user_inputs;
}
user_inputs.emplace(FromLegacyKeyModJoy(key, mod, joy));
}
return user_inputs;
}
// static
wxString wxUserInput::SpanToString(const std::set<wxUserInput>& user_inputs, bool is_config) {
wxString config_string;
if (user_inputs.empty()) {
return config_string;
}
for (const wxUserInput& user_input : user_inputs) {
config_string += user_input.ToString(is_config) + wxT(',');
}
return config_string.SubString(0, config_string.size() - 2);
}
wxString wxUserInput::ToString(bool is_config) const {
// TODO: Move the implementation here once all callers have been updated.
config_string_ = wxJoyKeyTextCtrl::ToString(mod_, key_, joy_);
return config_string_;
return wxJoyKeyTextCtrl::ToString(mod_, key_, joy_, is_config);
}
bool wxUserInput::operator==(const wxUserInput& other) const {
@ -89,6 +106,7 @@ bool wxUserInput::operator>=(const wxUserInput& other) const {
// Actual underlying constructor.
wxUserInput::wxUserInput(Device device, int mod, uint8_t key, unsigned joy) :
device_(device),
joystick_(joy == 0 ? wxJoystick::Invalid() : wxJoystick::FromLegacyPlayerIndex(joy)),
mod_(mod),
key_(key),
joy_(joy) {}

View File

@ -0,0 +1,139 @@
#ifndef _WX_GAME_CONTROL_H_
#define _WX_GAME_CONTROL_H_
#include <array>
#include <map>
#include <optional>
#include <set>
#include <wx/string.h>
#include "wx/userinput.h"
// Forward declaration.
class wxGameControlState;
// Represents an in-game input.
enum class wxGameKey {
Up = 0,
Down,
Left,
Right,
A,
B,
L,
R,
Select,
Start,
MotionUp,
MotionDown,
MotionLeft,
MotionRight,
MotionIn,
MotionOut,
AutoA,
AutoB,
Speed,
Capture,
Gameshark,
Last = Gameshark
};
inline constexpr int kNbGameKeys =
static_cast<std::underlying_type<wxGameKey>::type>(wxGameKey::Last) + 1;
inline constexpr int kNbJoypads = 4;
inline constexpr std::array<wxGameKey, kNbGameKeys> kAllGameKeys = {
wxGameKey::Up,
wxGameKey::Down,
wxGameKey::Left,
wxGameKey::Right,
wxGameKey::A,
wxGameKey::B,
wxGameKey::L,
wxGameKey::R,
wxGameKey::Select,
wxGameKey::Start,
wxGameKey::MotionUp,
wxGameKey::MotionDown,
wxGameKey::MotionLeft,
wxGameKey::MotionRight,
wxGameKey::MotionIn,
wxGameKey::MotionOut,
wxGameKey::AutoA,
wxGameKey::AutoB,
wxGameKey::Speed,
wxGameKey::Capture,
wxGameKey::Gameshark,
};
// Conversion utility method. Returns empty string on failure.
// This is O(1).
wxString GameKeyToString(const wxGameKey& game_key);
// Conversion utility method. Returns std::nullopt on failure.
// This is O(log(kNbGameKeys)).
std::optional<wxGameKey> StringToGameKey(const wxString& input);
// Abstraction for an in-game control, wich is made of a player index (from 0
// to 3), and a wxGameKey.
class wxGameControl {
public:
// Converts a string to a wxGameControl. Returns std::nullopt on failure.
static std::optional<wxGameControl> FromString(const wxString& name);
wxGameControl(int joypad, wxGameKey game_key);
~wxGameControl();
wxString ToString() const { return config_string_; };
bool operator==(const wxGameControl& other) const;
bool operator!=(const wxGameControl& other) const;
bool operator<(const wxGameControl& other) const;
bool operator<=(const wxGameControl& other) const;
bool operator>(const wxGameControl& other) const;
bool operator>=(const wxGameControl& other) const;
private:
const int joypad_;
const wxGameKey game_key_;
const wxString config_string_;
friend class wxGameControlState;
};
// Tracks in-game input and computes the joypad value used to send control input
// data to the emulator.
class wxGameControlState {
public:
// This is a global singleton.
static wxGameControlState& Instance();
// Disable copy constructor and assignment operator.
wxGameControlState(const wxGameControlState&) = delete;
wxGameControlState& operator=(const wxGameControlState&) = delete;
// Processes `user_input` and updates the internal tracking state.
// Returns true if `user_input` corresponds to a game input.
bool OnInputPressed(const wxUserInput& user_input);
bool OnInputReleased(const wxUserInput& user_input);
// Clears all input.
void Reset();
// Recomputes internal bindinds. This is a potentially slow operation and
// should only be called when the game input configuration has been changed.
void OnGameBindingsChanged();
uint32_t GetJoypad(int joypad) const;
private:
wxGameControlState();
~wxGameControlState();
std::map<wxUserInput, std::set<wxGameControl>> input_bindings_;
std::map<wxGameControl, std::set<wxUserInput>> active_controls_;
std::set<wxUserInput> keys_pressed_;
std::array<uint32_t, kNbJoypads> joypads_;
};
#endif // _WX_GAME_CONTROL_H_

View File

@ -5,20 +5,10 @@
// The value is the symbolic name of the key pressed
// Supports manual clearing (bs), multiple keys in widget, automatic tab on key
#include "wx/gamecontrol.h"
#include "wx/keyedit.h"
#include "wx/sdljoy.h"
typedef struct wxJoyKeyBinding {
int key; // key code; listed first for easy static init
int mod; // modifier flags
int joy; // joystick # (starting at 1)
// if joy is non-0, key = control number, and mod = control type
} wxJoyKeyBinding;
// Initializer for struct wxJoyKeyBinding
wxJoyKeyBinding newWxJoyKeyBinding(int key = 0, int mod = 0, int joy = 0);
typedef std::vector<wxJoyKeyBinding> wxJoyKeyBinding_v;
#include "wx/userinput.h"
// joystick control types
// mod for joysticks
@ -52,13 +42,8 @@ public:
static int DigitalButton(const wxJoyEvent& event);
// convert mod+key to accel string, separated by -
static wxString ToString(int mod, int key, int joy, bool isConfig = false);
// convert multiple keys, separated by multikey
static wxString ToString(wxJoyKeyBinding_v keys, wxChar sep = wxT(','), bool isConfig = false);
// parses single key string into mod+key
static bool FromString(const wxString& s, int& mod, int& key, int& joy);
// parse multi-key string into array
// returns empty array on parse errors
static wxJoyKeyBinding_v FromString(const wxString& s, wxChar sep = wxT(','));
// parse a single key in given wxChar array up to given len
static bool ParseString(const wxString& s, int len, int& mod, int& key, int& joy);
// parse multi-key string into array
@ -77,30 +62,30 @@ protected:
// A simple copy-only validator
class wxJoyKeyValidator : public wxValidator {
public:
wxJoyKeyValidator(wxJoyKeyBinding_v* v)
wxJoyKeyValidator(const wxGameControl v)
: wxValidator()
, val(v)
, val_(v)
{
}
wxJoyKeyValidator(const wxJoyKeyValidator& v)
: wxValidator()
, val(v.val)
, val_(v.val_)
{
}
wxObject* Clone() const
wxObject* Clone() const override
{
return new wxJoyKeyValidator(val);
return new wxJoyKeyValidator(val_);
}
bool TransferToWindow();
bool TransferFromWindow();
bool Validate(wxWindow* p)
bool TransferToWindow() override;
bool TransferFromWindow() override;
bool Validate(wxWindow* p) override
{
(void)p; // unused params
return true;
}
protected:
wxJoyKeyBinding_v* val;
const wxGameControl val_;
DECLARE_CLASS(wxJoyKeyValidator)
};

View File

@ -1,9 +1,9 @@
#ifndef _WX_USER_INPUT_H_
#define _WX_USER_INPUT_H_
#include <optional>
#include <set>
#include <wx/event.h>
#include "wx/joyedit.h"
#include "wx/sdljoy.h"
// Abstraction for a user input, which can come from a keyboard or a joystick.
@ -12,7 +12,7 @@
//
// TODO: Right now, this class is implemented as a thin wrapper around the key,
// mod and joy user input representation used in many places in the code base.
// This is to ease a transition away from the wxJoyKeyBinding type, which
// This is to ease a transition away from the key, mod, joy triplet, which
// wxUserInput will eventually replace.
class wxUserInput {
public:
@ -33,21 +33,25 @@ public:
// Constructor from a wxJoyEvent.
static wxUserInput FromJoyEvent(const wxJoyEvent& event);
// Constructor from a configuration string. Returns wxUserInput::Invalid()
// on parsing failure.
static wxUserInput FromString(const wxString& string);
// Constructor from a configuration string. Returns empty set on failure.
static std::set<wxUserInput> FromString(const wxString& string);
// TODO: Remove this once all uses of wxJoyKeyBinding have been removed.
static wxUserInput FromLegacyJoyKeyBinding(const wxJoyKeyBinding& binding);
// TODO: Remove this once all uses have been removed.
static wxUserInput FromLegacyKeyModJoy(int key = 0, int mod = 0, int joy = 0);
// Converts to a configuration string. Computed on first call, and cached
// for further calls.
wxString ToString();
// Converts a set of wxUserInput into a configuration string. This
// recomputes the configuration string every time and should not be used
// for comparison purposes.
// TODO: Replace std::set with std::span when the code base uses C++20.
static wxString SpanToString(
const std::set<wxUserInput>& user_inputs, bool is_config = false);
// TODO: Remove these accessors once all callers have been removed.
int mod() const { return mod_; }
int key() const { return key_; }
int joy() const { return joy_; }
// Converts to a configuration string.
wxString ToString(bool is_config = false) const;
wxJoystick joystick() const { return joystick_; }
bool is_valid() const { return device_ != Device::Invalid; }
operator bool() const { return is_valid(); }
bool operator==(const wxUserInput& other) const;
bool operator!=(const wxUserInput& other) const;
@ -59,12 +63,11 @@ public:
private:
wxUserInput(Device device, int mod, uint8_t key, unsigned joy);
Device device_;
int mod_;
uint8_t key_;
unsigned joy_;
wxString config_string_;
const Device device_;
const wxJoystick joystick_;
const int mod_;
const uint8_t key_;
const unsigned joy_;
};

View File

@ -27,6 +27,7 @@
// The built-in vba-over.ini
#include "builtin-over.h"
#include "wx/gamecontrol.h"
#include "wx/userinput.h"
IMPLEMENT_APP(wxvbamApp)
@ -178,8 +179,6 @@ wxString wxvbamApp::GetConfigurationPath()
break;
}
}
// use default keys for input.
set_default_keys();
}
return data_path;
@ -430,6 +429,10 @@ bool wxvbamApp::OnInit()
}
}
// Initialize game bindings here, after defaults bindings, vbam.ini bindings
// and command line overrides have been applied.
wxGameControlState::Instance().OnGameBindingsChanged();
// create the main window
int x = windowPositionX;
int y = windowPositionY;
@ -933,19 +936,12 @@ void MainFrame::SetJoystick()
return;
std::set<wxJoystick> needed_joysticks;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < NUM_KEYS; j++) {
wxJoyKeyBinding_v b = gopts.joykey_bindings[i][j];
for (size_t k = 0; k < b.size(); k++) {
int jn = b[k].joy;
if (jn) {
needed_joysticks.insert(
wxJoystick::FromLegacyPlayerIndex(jn));
for (const auto& iter : gopts.game_control_bindings) {
for (const auto& input_iter : iter.second) {
needed_joysticks.emplace(input_iter.joystick());
}
}
}
}
joy.PollJoysticks(needed_joysticks);
joy.PollJoysticks(std::move(needed_joysticks));
}
void MainFrame::StopJoyPollTimer()

View File

@ -805,8 +805,7 @@ void systemStopGamePlayback();
// true if turbo mode (like pressing turbo button constantly)
extern bool turbo;
// mask of key press flags; see below
extern int joypress[4], autofire, autohold;
extern int autofire, autohold;
// FIXME: these defines should be global to project and used instead of raw numbers
#define KEYM_A (1 << 0)