mirror of
https://github.com/snesrev/zelda3.git
synced 2025-12-19 18:05:55 -05:00
Gamepad buttons can now be remapped
This commit is contained in:
129
config.c
129
config.c
@@ -22,6 +22,7 @@ Config g_config;
|
||||
#define C(x) REMAP_SDL_KEYCODE(x) | kKeyMod_Ctrl
|
||||
#define N 0
|
||||
static const uint16 kDefaultKbdControls[kKeys_Total] = {
|
||||
0,
|
||||
// Controls
|
||||
_(SDLK_UP), _(SDLK_DOWN), _(SDLK_LEFT), _(SDLK_RIGHT), _(SDLK_RSHIFT), _(SDLK_RETURN), _(SDLK_x), _(SDLK_z), _(SDLK_s), _(SDLK_a), _(SDLK_c), _(SDLK_v),
|
||||
// LoadState
|
||||
@@ -53,6 +54,7 @@ typedef struct KeyNameId {
|
||||
#define M(n) {#n, kKeys_##n, kKeys_##n##_Last - kKeys_##n + 1}
|
||||
#define S(n) {#n, kKeys_##n, 1}
|
||||
static const KeyNameId kKeyNameId[] = {
|
||||
{"Null", kKeys_Null, 65535},
|
||||
M(Controls), M(Load), M(Save), M(Replay), M(LoadRef), M(ReplayRef),
|
||||
S(CheatLife), S(CheatKeys), S(CheatEquipment), S(CheatWalkThroughWalls),
|
||||
S(ClearKeyLog), S(StopReplay), S(Fullscreen), S(Reset),
|
||||
@@ -69,7 +71,7 @@ static KeyMapHashEnt *keymap_hash;
|
||||
static int keymap_hash_size;
|
||||
static bool has_keynameid[countof(kKeyNameId)];
|
||||
|
||||
bool KeyMapHash_Add(uint16 key, uint16 cmd) {
|
||||
static bool KeyMapHash_Add(uint16 key, uint16 cmd) {
|
||||
if ((keymap_hash_size & 0xff) == 0) {
|
||||
if (keymap_hash_size > 10000)
|
||||
Die("Too many keys");
|
||||
@@ -101,12 +103,12 @@ static int KeyMapHash_Find(uint16 key) {
|
||||
return ent->cmd;
|
||||
i = ent->next;
|
||||
}
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int FindCmdForSdlKey(SDL_Keycode code, SDL_Keymod mod) {
|
||||
if (code & ~(SDLK_SCANCODE_MASK | 0x1ff))
|
||||
return -1;
|
||||
return 0;
|
||||
int key = mod & KMOD_ALT ? kKeyMod_Alt : 0;
|
||||
key |= mod & KMOD_CTRL ? kKeyMod_Ctrl : 0;
|
||||
key |= mod & KMOD_SHIFT ? kKeyMod_Shift : 0;
|
||||
@@ -117,7 +119,7 @@ int FindCmdForSdlKey(SDL_Keycode code, SDL_Keymod mod) {
|
||||
static void ParseKeyArray(char *value, int cmd, int size) {
|
||||
char *s;
|
||||
int i = 0;
|
||||
for (; i < size && (s = NextDelim(&value, ',')) != NULL; i++, cmd++) {
|
||||
for (; i < size && (s = NextDelim(&value, ',')) != NULL; i++, cmd += (cmd != 0)) {
|
||||
if (*s == 0)
|
||||
continue;
|
||||
int key_with_mod = 0;
|
||||
@@ -142,14 +144,120 @@ static void ParseKeyArray(char *value, int cmd, int size) {
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct GamepadMapEnt {
|
||||
uint32 modifiers;
|
||||
uint16 cmd, next;
|
||||
} GamepadMapEnt;
|
||||
|
||||
static uint16 joymap_first[kGamepadBtn_Count];
|
||||
static GamepadMapEnt *joymap_ents;
|
||||
static int joymap_size;
|
||||
static bool has_joypad_controls;
|
||||
|
||||
static int CountBits32(uint32 n) {
|
||||
int count = 0;
|
||||
for (; n != 0; count++)
|
||||
n &= (n - 1);
|
||||
return count;
|
||||
}
|
||||
|
||||
static void GamepadMap_Add(int button, uint32 modifiers, uint16 cmd) {
|
||||
if ((joymap_size & 0xff) == 0) {
|
||||
if (joymap_size > 1000)
|
||||
Die("Too many joypad keys");
|
||||
joymap_ents = realloc(joymap_ents, sizeof(GamepadMapEnt) * (joymap_size + 64));
|
||||
if (!joymap_ents) Die("realloc failure");
|
||||
}
|
||||
uint16 *p = &joymap_first[button];
|
||||
// Insert it as early as possible but before after any entry with more modifiers.
|
||||
int cb = CountBits32(modifiers);
|
||||
while (*p && cb < CountBits32(joymap_ents[*p - 1].modifiers))
|
||||
p = &joymap_ents[*p - 1].next;
|
||||
int i = joymap_size++;
|
||||
GamepadMapEnt *ent = &joymap_ents[i];
|
||||
ent->modifiers = modifiers;
|
||||
ent->cmd = cmd;
|
||||
ent->next = *p;
|
||||
*p = i + 1;
|
||||
}
|
||||
|
||||
int FindCmdForGamepadButton(int button, uint32 modifiers) {
|
||||
GamepadMapEnt *ent;
|
||||
for(int e = joymap_first[button]; e != 0; e = ent->next) {
|
||||
ent = &joymap_ents[e - 1];
|
||||
if ((modifiers & ent->modifiers) == ent->modifiers)
|
||||
return ent->cmd;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ParseGamepadButtonName(const char **value) {
|
||||
const char *s = *value;
|
||||
// Longest substring first
|
||||
static const char *const kGamepadKeyNames[] = {
|
||||
"Back", "Guide", "Start", "L3", "R3",
|
||||
"L1", "R1", "DpadUp", "DpadDown", "DpadLeft", "DpadRight", "L2", "R2",
|
||||
"Lb", "Rb", "A", "B", "X", "Y"
|
||||
};
|
||||
static const uint8 kGamepadKeyIds[] = {
|
||||
kGamepadBtn_Back, kGamepadBtn_Guide, kGamepadBtn_Start, kGamepadBtn_L3, kGamepadBtn_R3,
|
||||
kGamepadBtn_L1, kGamepadBtn_R1, kGamepadBtn_DpadUp, kGamepadBtn_DpadDown, kGamepadBtn_DpadLeft, kGamepadBtn_DpadRight, kGamepadBtn_L2, kGamepadBtn_R2,
|
||||
kGamepadBtn_L1, kGamepadBtn_R1, kGamepadBtn_A, kGamepadBtn_B, kGamepadBtn_X, kGamepadBtn_Y,
|
||||
};
|
||||
for (size_t i = 0; i != countof(kGamepadKeyNames); i++) {
|
||||
const char *r = StringStartsWithNoCase(s, kGamepadKeyNames[i]);
|
||||
if (r) {
|
||||
*value = r;
|
||||
return kGamepadKeyIds[i];
|
||||
}
|
||||
}
|
||||
return kGamepadBtn_Invalid;
|
||||
}
|
||||
|
||||
static const uint8 kDefaultGamepadCmds[] = {
|
||||
kGamepadBtn_DpadUp, kGamepadBtn_DpadDown, kGamepadBtn_DpadLeft, kGamepadBtn_DpadRight, kGamepadBtn_Back, kGamepadBtn_Start,
|
||||
kGamepadBtn_B, kGamepadBtn_A, kGamepadBtn_Y, kGamepadBtn_X, kGamepadBtn_L1, kGamepadBtn_R1,
|
||||
};
|
||||
|
||||
static void ParseGamepadArray(char *value, int cmd, int size) {
|
||||
char *s;
|
||||
int i = 0;
|
||||
for (; i < size && (s = NextDelim(&value, ',')) != NULL; i++, cmd += (cmd != 0)) {
|
||||
if (*s == 0)
|
||||
continue;
|
||||
uint32 modifiers = 0;
|
||||
const char *ss = s;
|
||||
for (;;) {
|
||||
int button = ParseGamepadButtonName(&ss);
|
||||
if (button == kGamepadBtn_Invalid) BAD: {
|
||||
fprintf(stderr, "Unknown gamepad button: '%s'\n", s);
|
||||
break;
|
||||
}
|
||||
while (*ss == ' ' || *ss == '\t') ss++;
|
||||
if (*ss == '+') {
|
||||
ss++;
|
||||
modifiers |= 1 << button;
|
||||
} else if (*ss == 0) {
|
||||
GamepadMap_Add(button, modifiers, cmd);
|
||||
break;
|
||||
} else
|
||||
goto BAD;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void RegisterDefaultKeys() {
|
||||
for (int i = 0; i < countof(kKeyNameId); i++) {
|
||||
for (int i = 1; i < countof(kKeyNameId); i++) {
|
||||
if (!has_keynameid[i]) {
|
||||
int size = kKeyNameId[i].size, k = kKeyNameId[i].id;
|
||||
for (int j = 0; j < size; j++, k++)
|
||||
KeyMapHash_Add(kDefaultKbdControls[k], k);
|
||||
}
|
||||
}
|
||||
if (!has_joypad_controls) {
|
||||
for (int i = 0; i < countof(kDefaultGamepadCmds); i++)
|
||||
GamepadMap_Add(kDefaultGamepadCmds[i], 0, kKeys_Controls + i);
|
||||
}
|
||||
}
|
||||
|
||||
static int GetIniSection(const char *s) {
|
||||
@@ -163,6 +271,8 @@ static int GetIniSection(const char *s) {
|
||||
return 3;
|
||||
if (StringEqualsNoCase(s, "[Features]"))
|
||||
return 4;
|
||||
if (StringEqualsNoCase(s, "[GamepadMap]"))
|
||||
return 5;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -205,6 +315,15 @@ static bool HandleIniConfig(int section, const char *key, char *value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (section == 5) {
|
||||
for (int i = 0; i < countof(kKeyNameId); i++) {
|
||||
if (StringEqualsNoCase(key, kKeyNameId[i].name)) {
|
||||
if (i == 1)
|
||||
has_joypad_controls = true;
|
||||
ParseGamepadArray(value, kKeyNameId[i].id, kKeyNameId[i].size);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (section == 1) {
|
||||
if (StringEqualsNoCase(key, "WindowSize")) {
|
||||
char *s;
|
||||
|
||||
26
config.h
26
config.h
@@ -3,7 +3,8 @@
|
||||
#include <SDL_keycode.h>
|
||||
|
||||
enum {
|
||||
kKeys_Controls = 0,
|
||||
kKeys_Null,
|
||||
kKeys_Controls,
|
||||
kKeys_Controls_Last = kKeys_Controls + 11,
|
||||
kKeys_Load,
|
||||
kKeys_Load_Last = kKeys_Load + 19,
|
||||
@@ -78,9 +79,30 @@ enum {
|
||||
kMsuEnabled_MsuDeluxe = 2,
|
||||
kMsuEnabled_Opuz = 4,
|
||||
};
|
||||
|
||||
enum {
|
||||
kGamepadBtn_Invalid = -1,
|
||||
kGamepadBtn_A,
|
||||
kGamepadBtn_B,
|
||||
kGamepadBtn_X,
|
||||
kGamepadBtn_Y,
|
||||
kGamepadBtn_Back,
|
||||
kGamepadBtn_Guide,
|
||||
kGamepadBtn_Start,
|
||||
kGamepadBtn_L3,
|
||||
kGamepadBtn_R3,
|
||||
kGamepadBtn_L1,
|
||||
kGamepadBtn_R1,
|
||||
kGamepadBtn_DpadUp,
|
||||
kGamepadBtn_DpadDown,
|
||||
kGamepadBtn_DpadLeft,
|
||||
kGamepadBtn_DpadRight,
|
||||
kGamepadBtn_L2,
|
||||
kGamepadBtn_R2,
|
||||
kGamepadBtn_Count,
|
||||
};
|
||||
|
||||
extern Config g_config;
|
||||
|
||||
void ParseConfigFile(const char *filename);
|
||||
int FindCmdForSdlKey(SDL_Keycode code, SDL_Keymod mod);
|
||||
int FindCmdForGamepadButton(int button, uint32 modifiers);
|
||||
|
||||
74
main.c
74
main.c
@@ -37,6 +37,7 @@ static void LoadLinkGraphics();
|
||||
static void RenderNumber(uint8 *dst, size_t pitch, int n, bool big);
|
||||
static void HandleInput(int keyCode, int modCode, bool pressed);
|
||||
static void HandleCommand(uint32 j, bool pressed);
|
||||
static int RemapSdlButton(int button);
|
||||
static void HandleGamepadInput(int button, bool pressed);
|
||||
static void HandleGamepadAxisInput(int gamepad_id, int axis, int value);
|
||||
static void OpenOneGamepad(int i);
|
||||
@@ -68,6 +69,8 @@ static int g_ppu_render_flags = 0;
|
||||
static int g_snes_width, g_snes_height;
|
||||
static int g_sdl_audio_mixer_volume = SDL_MIX_MAXVOLUME;
|
||||
static struct RendererFuncs g_renderer_funcs;
|
||||
static uint32 g_gamepad_modifiers;
|
||||
static uint16 g_gamepad_last_cmd[kGamepadBtn_Count];
|
||||
|
||||
void NORETURN Die(const char *error) {
|
||||
#if defined(NDEBUG) && defined(_WIN32)
|
||||
@@ -77,16 +80,6 @@ void NORETURN Die(const char *error) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void SetButtonState(int button, bool pressed) {
|
||||
// set key in constroller
|
||||
if (pressed) {
|
||||
g_input1_state |= 1 << button;
|
||||
} else {
|
||||
g_input1_state &= ~(1 << button);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ChangeWindowScale(int scale_step) {
|
||||
if ((SDL_GetWindowFlags(g_window) & (SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_MINIMIZED | SDL_WINDOW_MAXIMIZED)) != 0)
|
||||
return;
|
||||
@@ -419,11 +412,12 @@ int main(int argc, char** argv) {
|
||||
HandleGamepadAxisInput(event.caxis.which, event.caxis.axis, event.caxis.value);
|
||||
break;
|
||||
case SDL_CONTROLLERBUTTONDOWN:
|
||||
HandleGamepadInput(event.cbutton.button, event.cbutton.state == SDL_PRESSED);
|
||||
break;
|
||||
case SDL_CONTROLLERBUTTONUP:
|
||||
HandleGamepadInput(event.cbutton.button, event.cbutton.state == SDL_PRESSED);
|
||||
case SDL_CONTROLLERBUTTONUP: {
|
||||
int b = RemapSdlButton(event.cbutton.button);
|
||||
if (b >= 0)
|
||||
HandleGamepadInput(b, event.type == SDL_CONTROLLERBUTTONDOWN);
|
||||
break;
|
||||
}
|
||||
case SDL_MOUSEWHEEL:
|
||||
if (SDL_GetModState() & KMOD_CTRL && event.wheel.y != 0)
|
||||
ChangeWindowScale(event.wheel.y > 0 ? 1 : -1);
|
||||
@@ -572,8 +566,11 @@ static void HandleCommand_Locked(uint32 j, bool pressed);
|
||||
|
||||
static void HandleCommand(uint32 j, bool pressed) {
|
||||
if (j <= kKeys_Controls_Last) {
|
||||
static const uint8 kKbdRemap[12] = { 4, 5, 6, 7, 2, 3, 8, 0, 9, 1, 10, 11 };
|
||||
SetButtonState(kKbdRemap[j], pressed);
|
||||
static const uint8 kKbdRemap[] = { 0, 4, 5, 6, 7, 2, 3, 8, 0, 9, 1, 10, 11 };
|
||||
if (pressed)
|
||||
g_input1_state |= 1 << kKbdRemap[j];
|
||||
else
|
||||
g_input1_state &= ~(1 << kKbdRemap[j]);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -656,7 +653,7 @@ static void HandleCommand_Locked(uint32 j, bool pressed) {
|
||||
|
||||
static void HandleInput(int keyCode, int keyMod, bool pressed) {
|
||||
int j = FindCmdForSdlKey(keyCode, keyMod);
|
||||
if (j >= 0)
|
||||
if (j != 0)
|
||||
HandleCommand(j, pressed);
|
||||
}
|
||||
|
||||
@@ -668,23 +665,37 @@ static void OpenOneGamepad(int i) {
|
||||
}
|
||||
}
|
||||
|
||||
static void HandleGamepadInput(int button, bool pressed) {
|
||||
static int RemapSdlButton(int button) {
|
||||
switch (button) {
|
||||
case SDL_CONTROLLER_BUTTON_A: SetButtonState(0, pressed); break;
|
||||
case SDL_CONTROLLER_BUTTON_X: SetButtonState(1, pressed); break;
|
||||
case SDL_CONTROLLER_BUTTON_BACK: SetButtonState(2, pressed); break;
|
||||
case SDL_CONTROLLER_BUTTON_START: SetButtonState(3, pressed); break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_UP: SetButtonState(4, pressed); break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: SetButtonState(5, pressed); break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: SetButtonState(6, pressed); break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: SetButtonState(7, pressed); break;
|
||||
case SDL_CONTROLLER_BUTTON_B: SetButtonState(8, pressed); break;
|
||||
case SDL_CONTROLLER_BUTTON_Y: SetButtonState(9, pressed); break;
|
||||
case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: SetButtonState(10, pressed); break;
|
||||
case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: SetButtonState(11, pressed); break;
|
||||
case SDL_CONTROLLER_BUTTON_A: return kGamepadBtn_A;
|
||||
case SDL_CONTROLLER_BUTTON_B: return kGamepadBtn_B;
|
||||
case SDL_CONTROLLER_BUTTON_X: return kGamepadBtn_X;
|
||||
case SDL_CONTROLLER_BUTTON_Y: return kGamepadBtn_Y;
|
||||
case SDL_CONTROLLER_BUTTON_BACK: return kGamepadBtn_Back;
|
||||
case SDL_CONTROLLER_BUTTON_GUIDE: return kGamepadBtn_Guide;
|
||||
case SDL_CONTROLLER_BUTTON_START: return kGamepadBtn_Start;
|
||||
case SDL_CONTROLLER_BUTTON_LEFTSTICK: return kGamepadBtn_L3;
|
||||
case SDL_CONTROLLER_BUTTON_RIGHTSTICK: return kGamepadBtn_R3;
|
||||
case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: return kGamepadBtn_L1;
|
||||
case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: return kGamepadBtn_R1;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_UP: return kGamepadBtn_DpadUp;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: return kGamepadBtn_DpadDown;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: return kGamepadBtn_DpadLeft;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return kGamepadBtn_DpadRight;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static void HandleGamepadInput(int button, bool pressed) {
|
||||
if (!!(g_gamepad_modifiers & (1 << button)) == pressed)
|
||||
return;
|
||||
g_gamepad_modifiers ^= 1 << button;
|
||||
if (pressed)
|
||||
g_gamepad_last_cmd[button] = FindCmdForGamepadButton(button, g_gamepad_modifiers);
|
||||
if (g_gamepad_last_cmd[button] != 0)
|
||||
HandleCommand(g_gamepad_last_cmd[button], pressed);
|
||||
}
|
||||
|
||||
static void HandleVolumeAdjustment(int volume_adjustment) {
|
||||
#if SYSTEM_VOLUME_MIXER_AVAILABLE
|
||||
int current_volume = GetApplicationVolume();
|
||||
@@ -748,6 +759,9 @@ static void HandleGamepadAxisInput(int gamepad_id, int axis, int value) {
|
||||
buttons = kSegmentToButtons[(uint8)(angle + 16 + 64) >> 5];
|
||||
}
|
||||
g_gamepad_buttons = buttons;
|
||||
} else if ((axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT || axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT)) {
|
||||
if (value < 12000 || value >= 16000) // hysteresis
|
||||
HandleGamepadInput(axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT ? kGamepadBtn_L2 : kGamepadBtn_R2, value >= 12000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
10
util.c
10
util.c
@@ -28,13 +28,13 @@ bool StringEqualsNoCase(const char *a, const char *b) {
|
||||
}
|
||||
}
|
||||
|
||||
bool StringStartsWithNoCase(const char *a, const char *b) {
|
||||
for (;;) {
|
||||
int aa = ToLower(*a++), bb = ToLower(*b++);
|
||||
const char *StringStartsWithNoCase(const char *a, const char *b) {
|
||||
for (;; a++, b++) {
|
||||
int aa = ToLower(*a), bb = ToLower(*b);
|
||||
if (bb == 0)
|
||||
return true;
|
||||
return a;
|
||||
if (aa != bb)
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
2
util.h
2
util.h
@@ -29,7 +29,7 @@ char *NextLineStripComments(char **s);
|
||||
char *NextPossiblyQuotedString(char **s);
|
||||
char *SplitKeyValue(char *p);
|
||||
bool StringEqualsNoCase(const char *a, const char *b);
|
||||
bool StringStartsWithNoCase(const char *a, const char *b);
|
||||
const char *StringStartsWithNoCase(const char *a, const char *b);
|
||||
bool ParseBool(const char *value, bool *result);
|
||||
const char *SkipPrefix(const char *big, const char *little);
|
||||
void StrSet(char **rv, const char *s);
|
||||
|
||||
@@ -163,3 +163,8 @@ Replay= Ctrl+F1,Ctrl+F2,Ctrl+F3,Ctrl+F4,Ctrl+F5,Ctrl+F6,Ctrl+F7,Ctrl+F8,Ctrl+F9,
|
||||
|
||||
LoadRef = 1,2,3,4,5,6,7,8,9,0,-,=,Backspace
|
||||
ReplayRef = Ctrl+1,Ctrl+2,Ctrl+3,Ctrl+4,Ctrl+5,Ctrl+6,Ctrl+7,Ctrl+8,Ctrl+9,Ctrl+0,Ctrl+-,Ctrl+=,Ctrl+Backspace
|
||||
|
||||
[GamepadMap]
|
||||
# Any keys used in KeyMap can be used also in this section.
|
||||
# The shoulder button is called L1/Lb and L2, and the thumbstick button is called L3
|
||||
Controls = DpadUp, DpadDown, DpadLeft, DpadRight, Back, Start, B, A, Y, X, Lb, Rb
|
||||
|
||||
Reference in New Issue
Block a user