paulo@0: /* PC joystick code paulo@0: paulo@0: Copyright (C) 2006 Damian Yerrick paulo@0: paulo@0: This work is free software; you can redistribute it and/or modify paulo@0: it under the terms of the GNU General Public License as published by paulo@0: the Free Software Foundation; either version 2 of the License, or paulo@0: (at your option) any later version. paulo@0: paulo@0: This program is distributed in the hope that it will be useful, paulo@0: but WITHOUT ANY WARRANTY; without even the implied warranty of paulo@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the paulo@0: GNU General Public License for more details. paulo@0: paulo@0: You should have received a copy of the GNU General Public License paulo@0: along with this program; if not, write to the Free Software paulo@0: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA paulo@0: paulo@0: Original game concept and design by Alexey Pajitnov. paulo@0: The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg, paulo@0: or The Tetris Company LLC. paulo@0: paulo@0: */ paulo@0: paulo@0: #include "pcjoy.h" paulo@0: #include paulo@0: #include paulo@0: #include "ljpath.h" paulo@0: paulo@0: extern const FONT *aver32, *aver16; paulo@0: extern int bgColor, fgColor, hiliteColor; paulo@0: paulo@0: static volatile int lastScancodePressed = -1; paulo@0: static void (*oldKeyListener)(int scancode); paulo@0: volatile int wantsClose = 0; paulo@0: void ezPlaySample(const char *filename, int vol); paulo@0: paulo@0: static void ljpcKeyListener(int scancode) { paulo@0: if (!(scancode & 0x80)) { paulo@0: lastScancodePressed = scancode; paulo@0: } paulo@0: if (oldKeyListener) { paulo@0: oldKeyListener(scancode); paulo@0: } paulo@0: } END_OF_FUNCTION(ljpcKeyListener); paulo@0: paulo@0: paulo@0: /** paulo@0: * Presses the escape key when the user clicks the close box. paulo@0: */ paulo@0: static void setWantsClose() { paulo@0: wantsClose = 1; paulo@0: } paulo@0: paulo@0: static int withJoystick = 0; paulo@0: paulo@0: #define N_VKEYS 14 paulo@0: #define N_PLAYERS 2 paulo@0: paulo@0: static const char keysFileName[] = "lj-keys.043"; paulo@0: paulo@0: static const struct pkeyMapping defaultKeymappings[N_PLAYERS][N_VKEYS] = { paulo@0: { paulo@0: // [0]: player 1 paulo@0: {-1, -1, KEY_UP}, paulo@0: {-1, -1, KEY_DOWN}, paulo@0: {-1, -1, KEY_LEFT}, paulo@0: {-1, -1, KEY_RIGHT}, paulo@0: {-1, -1, KEY_Z}, paulo@0: {-1, -1, KEY_X}, paulo@0: {-1, -1, KEY_S}, paulo@0: {-1, -1, KEY_SPACE}, paulo@0: {-1, -1, KEY_C}, paulo@0: {-1, -1, KEY_W}, paulo@0: {-1, -1, KEY_Q}, paulo@0: {-1, -1, KEY_E}, paulo@0: {-1, -1, KEY_ENTER}, paulo@0: {-1, -1, KEY_D} paulo@0: }, paulo@0: { paulo@0: // [1]: player 2 paulo@0: {-1, -1, KEY_UP}, paulo@0: {-1, -1, KEY_DOWN}, paulo@0: {-1, -1, KEY_LEFT}, paulo@0: {-1, -1, KEY_RIGHT}, paulo@0: {-1, -1, KEY_Z}, paulo@0: {-1, -1, KEY_X}, paulo@0: {-1, -1, KEY_S}, paulo@0: {-1, -1, KEY_SPACE}, paulo@0: {-1, -1, KEY_C}, paulo@0: {-1, -1, KEY_W}, paulo@0: {-1, -1, KEY_Q}, paulo@0: {-1, -1, KEY_E}, paulo@0: {-1, -1, KEY_ENTER}, paulo@0: {-1, -1, KEY_D} paulo@0: } paulo@0: }; paulo@0: paulo@0: static struct pkeyMapping keymappings[N_PLAYERS][N_VKEYS]; paulo@0: paulo@0: void getPkeyName(char *dst, int j, int s, int a) { paulo@0: if (j < 0) { paulo@0: if (a >= 0 && a < KEY_MAX) { paulo@0: usprintf(dst, "Key %d (%s)", a, scancode_to_name(a)); paulo@0: } else { paulo@0: usprintf(dst, "Key %d (???"")", a); paulo@0: } paulo@0: } else if (s < 0) { paulo@0: usprintf(dst, paulo@0: "Joy %d button %s", paulo@0: j, paulo@0: joy[j].button[a].name); paulo@0: } else if (a < 0) { paulo@0: usprintf(dst, paulo@0: "Joy %d stick %s axis %s -", paulo@0: j, paulo@0: joy[j].stick[s].name, paulo@0: joy[j].stick[s].axis[~a].name); paulo@0: } else { paulo@0: usprintf(dst, paulo@0: "Joy %d stick %s axis %s +", paulo@0: j, paulo@0: joy[j].stick[s].name, paulo@0: joy[j].stick[s].axis[a].name); paulo@0: } paulo@0: } paulo@0: paulo@0: static int getPkeyState(int j, int s, int a) { paulo@0: int k; paulo@0: if (j < 0) { paulo@0: k = key[a]; paulo@0: } else if (s < 0) { paulo@0: k = joy[j].button[a].b; paulo@0: } else if (a < 0) { paulo@0: k = joy[j].stick[s].axis[~a].d1; paulo@0: } else { paulo@0: k = joy[j].stick[s].axis[a].d2; paulo@0: } paulo@0: return k; paulo@0: } paulo@0: paulo@0: const char *const vkeyNames[] = { paulo@0: "Hard Drop (Up)", paulo@0: "Soft Drop (Down)", paulo@0: "Left", paulo@0: "Right", paulo@0: "Rotate Left", paulo@0: "Rotate Right", paulo@0: "Hold", paulo@0: "Item (unused)", paulo@0: "Alt. Rotate Left", paulo@0: "Rotate Left Twice", paulo@0: "Far Left", paulo@0: "Far Right", paulo@0: "Alt. Firm Drop", paulo@0: "Alt. Hold", paulo@0: "Macro G", paulo@0: "Macro H" paulo@0: }; paulo@0: paulo@0: static int getVkeyState(int player, int vkey) { paulo@0: int j = keymappings[player][vkey].joy; paulo@0: int s = keymappings[player][vkey].stick; paulo@0: int a = keymappings[player][vkey].axis; paulo@0: paulo@0: return getPkeyState(j, s, a); paulo@0: } paulo@0: paulo@0: LJBits readPad(unsigned int player) { paulo@0: int keys = 0; paulo@0: poll_joystick(); paulo@0: paulo@0: for (int i = 0; paulo@0: i < N_VKEYS; paulo@0: ++i) { paulo@0: if (getVkeyState(player, i)) { paulo@0: keys |= 1 << i; paulo@0: } paulo@0: } paulo@0: return keys; paulo@0: } paulo@0: paulo@0: LJBits menuReadPad(void) { paulo@0: if (key[KEY_ENTER]) { paulo@0: return VKEY_ROTR | VKEY_START; paulo@0: } else if (key[KEY_ESC]) { paulo@0: return VKEY_ROTL; paulo@0: } else if (key[KEY_UP]) { paulo@0: return VKEY_UP; paulo@0: } else if (key[KEY_DOWN]) { paulo@0: return VKEY_DOWN; paulo@0: } else if (key[KEY_LEFT]) { paulo@0: return VKEY_LEFT; paulo@0: } else if (key[KEY_RIGHT]) { paulo@0: return VKEY_RIGHT; paulo@0: } else { paulo@0: return readPad(0) | readPad(1); paulo@0: } paulo@0: } paulo@0: paulo@0: // These contain the states of ALL buttons on ALL paulo@0: // joysticks paulo@0: static LJBits lastConfigButtons[8]; paulo@0: static LJBits lastConfigStickAxis[8]; paulo@0: paulo@0: static int newButton(int *outJ, int *outS, int *outA) { paulo@0: poll_joystick(); paulo@0: int found = 0; paulo@0: paulo@0: if (lastScancodePressed >= 0) { paulo@0: *outJ = -1; paulo@0: *outS = -1; paulo@0: *outA = lastScancodePressed; paulo@0: lastScancodePressed = -1; paulo@0: return 1; paulo@0: } paulo@0: paulo@0: for (int j = 0; paulo@0: j < num_joysticks; paulo@0: ++j) { paulo@0: LJBits cur = 0; paulo@0: paulo@0: for (int b = 0; b < joy[j].num_buttons; ++b) { paulo@0: if (joy[j].button[b].b) { paulo@0: if (!(lastConfigButtons[j] & (1 << b)) && !found) { paulo@0: *outJ = j; paulo@0: *outS = -1; paulo@0: *outA = b; paulo@0: found = 1; paulo@0: } paulo@0: cur |= 1 << b; paulo@0: } paulo@0: } paulo@0: lastConfigButtons[j] = cur; paulo@0: } paulo@0: if (found) { paulo@0: return 1; paulo@0: } paulo@0: paulo@0: for (int j = 0; paulo@0: j < num_joysticks; paulo@0: ++j) { paulo@0: LJBits cur = 0; paulo@0: LJBits mask = 1; paulo@0: paulo@0: for (int s = 0; s < joy[j].num_sticks; ++s) { paulo@0: for (int a = 0; a < joy[j].stick[s].num_axis; ++a) { paulo@0: if (joy[j].stick[s].axis[a].d1) { paulo@0: if (!(lastConfigStickAxis[j] & mask) && !found) { paulo@0: *outJ = j; paulo@0: *outS = s; paulo@0: *outA = ~a; paulo@0: found = 1; paulo@0: } paulo@0: cur |= mask; paulo@0: } paulo@0: mask <<= 1; paulo@0: if (joy[j].stick[s].axis[a].d2) { paulo@0: if (!(lastConfigStickAxis[j] & mask) && !found) { paulo@0: *outJ = j; paulo@0: *outS = s; paulo@0: *outA = a; paulo@0: found = 1; paulo@0: } paulo@0: cur |= mask; paulo@0: } paulo@0: mask <<= 1; paulo@0: } paulo@0: } paulo@0: lastConfigStickAxis[j] = cur; paulo@0: } paulo@0: return found; paulo@0: } paulo@0: paulo@0: static void clearNewButton(void) { paulo@0: int j, s, a; paulo@0: while (newButton(&j, &s, &a)); paulo@0: } paulo@0: paulo@0: void loadKeys(const char *filename) { paulo@0: FILE *fp = ljfopen(filename, "rb"); paulo@0: paulo@0: memcpy(keymappings, defaultKeymappings, sizeof(keymappings)); paulo@0: if (fp) { paulo@0: for (unsigned int player = 0; player < 2; ++player) { paulo@0: for (unsigned int vkey = 0; paulo@0: vkey < N_VKEYS; paulo@0: ++vkey) { paulo@0: int j = fgetc(fp); paulo@0: int s = fgetc(fp); paulo@0: int a = fgetc(fp); paulo@0: paulo@0: if (a == EOF) { paulo@0: break; paulo@0: } paulo@0: keymappings[player][vkey].joy = j; paulo@0: keymappings[player][vkey].stick = s; paulo@0: keymappings[player][vkey].axis = a; paulo@0: } paulo@0: } paulo@0: fclose(fp); paulo@0: } paulo@0: } paulo@0: paulo@0: void saveKeys(const char *filename) { paulo@0: FILE *fp = ljfopen(filename, "wb"); paulo@0: paulo@0: if (fp) { paulo@0: for (unsigned int player = 0; player < 2; ++player) { paulo@0: for (unsigned int vkey = 0; paulo@0: vkey < N_VKEYS && !feof(fp); paulo@0: ++vkey) { paulo@0: fputc(keymappings[player][vkey].joy, fp); paulo@0: fputc(keymappings[player][vkey].stick, fp); paulo@0: fputc(keymappings[player][vkey].axis, fp); paulo@0: } paulo@0: } paulo@0: fclose(fp); paulo@0: } paulo@0: } paulo@0: paulo@0: #define VKEY_ROWHT 24 paulo@0: #define VKEY_TOP 120 paulo@0: paulo@0: void drawVkeyRow(int vkey, int hilite) { paulo@0: char name[256]; paulo@0: int y = VKEY_TOP + vkey * VKEY_ROWHT; paulo@0: paulo@0: rectfill(screen, paulo@0: 16, y, 719, y + VKEY_ROWHT - 1, paulo@0: hilite ? hiliteColor : bgColor); paulo@0: textout_ex(screen, aver16, vkeyNames[vkey], 24, y + 4, fgColor, -1); paulo@0: for (int player = 0; player < N_PLAYERS; ++player) { paulo@0: if (player + 1 == hilite) { paulo@0: rect(screen, paulo@0: 240 + 240 * player, y, paulo@0: 479 + 240 * player, y + VKEY_ROWHT - 1, paulo@0: fgColor); paulo@0: } paulo@0: getPkeyName(name, paulo@0: keymappings[player][vkey].joy, paulo@0: keymappings[player][vkey].stick, paulo@0: keymappings[player][vkey].axis); paulo@0: textout_ex(screen, aver16, name, 240 + 240 * player, y + 4, fgColor, -1); paulo@0: } paulo@0: } paulo@0: paulo@0: /** paulo@0: * Waits for a key or button press. If any key but Esc is pressed, paulo@0: * reassigns the vkey. Otherwise, does nothing. paulo@0: * @param vkey the index of the vkey to reassign paulo@0: * @return 0 if key not changed; nonzero if key was changed paulo@0: */ paulo@0: static int changeKey(int player, int vkey) { paulo@0: int changed = 0; paulo@0: int phase = 0; paulo@0: paulo@0: clearNewButton(); paulo@0: while (!changed) { paulo@0: int j, s, a; paulo@0: paulo@0: if (phase == 5) { paulo@0: drawVkeyRow(vkey, 0); paulo@0: } else if (phase == 0) { paulo@0: drawVkeyRow(vkey, player + 1); paulo@0: phase = 15; paulo@0: } paulo@0: --phase; paulo@0: paulo@0: if (keypressed()) { paulo@0: int scancode; paulo@0: ureadkey(&scancode); paulo@0: if (scancode == KEY_ESC) { paulo@0: changed = -1; paulo@0: } paulo@0: } paulo@0: if (vkey < N_VKEYS && newButton(&j, &s, &a)) { paulo@0: if (j >= 0 || s > 0 || a != KEY_ESC) { paulo@0: keymappings[player][vkey].joy = j; paulo@0: keymappings[player][vkey].stick = s; paulo@0: keymappings[player][vkey].axis = a; paulo@0: ezPlaySample("nextS_wav", 128); paulo@0: changed = 1; paulo@0: } else { paulo@0: changed = -1; paulo@0: } paulo@0: } paulo@0: if (wantsClose) { paulo@0: changed = -1; paulo@0: } paulo@0: paulo@0: rest(30); paulo@0: } paulo@0: paulo@0: // Draw new vkey value paulo@0: drawVkeyRow(vkey, 0); paulo@0: return changed > 0; paulo@0: } paulo@0: paulo@0: void configureKeys(void) { paulo@0: clear_to_color(screen, bgColor); paulo@0: textout_ex(screen, aver32, "LOCKJAW > Game Keys", 16, 32, fgColor, -1); paulo@0: textout_ex(screen, aver16, paulo@0: "Use arrow keys to select a key. To reassign the selected key, press Enter", paulo@0: 40, 80, fgColor, -1); paulo@0: textout_ex(screen, aver16, paulo@0: "and then the key you want to use. When done, press Esc.", paulo@0: 40, 96, fgColor, -1); paulo@0: paulo@0: // Draw each vkey's name paulo@0: for (int vkey = 0; vkey < N_VKEYS; ++vkey) { paulo@0: drawVkeyRow(vkey, 0); paulo@0: } paulo@0: paulo@0: clearNewButton(); paulo@0: int vkey = 0; paulo@0: int player = 0; paulo@0: paulo@0: drawVkeyRow(vkey, player + 1); paulo@0: paulo@0: while (vkey >= 0 && !wantsClose) { paulo@0: int scancode = 0; paulo@0: paulo@0: rest(30); paulo@0: if (keypressed()) { paulo@0: ureadkey(&scancode); paulo@0: } paulo@0: paulo@0: switch (scancode) { paulo@0: paulo@0: case KEY_RIGHT: paulo@0: if (player < N_PLAYERS - 1) { paulo@0: ezPlaySample("shift_wav", 128); paulo@0: drawVkeyRow(vkey, 0); paulo@0: ++player; paulo@0: drawVkeyRow(vkey, player + 1); paulo@0: } paulo@0: break; paulo@0: paulo@0: case KEY_LEFT: paulo@0: if (player > 0) { paulo@0: ezPlaySample("shift_wav", 128); paulo@0: drawVkeyRow(vkey, 0); paulo@0: --player; paulo@0: drawVkeyRow(vkey, player + 1); paulo@0: } paulo@0: break; paulo@0: paulo@0: case KEY_UP: paulo@0: if (vkey > 0) { paulo@0: ezPlaySample("shift_wav", 128); paulo@0: drawVkeyRow(vkey, 0); paulo@0: --vkey; paulo@0: drawVkeyRow(vkey, player + 1); paulo@0: } paulo@0: break; paulo@0: case KEY_DOWN: paulo@0: if (vkey < N_VKEYS - 1) { paulo@0: ezPlaySample("shift_wav", 128); paulo@0: drawVkeyRow(vkey, 0); paulo@0: ++vkey; paulo@0: drawVkeyRow(vkey, player + 1); paulo@0: } paulo@0: break; paulo@0: case KEY_ENTER: paulo@0: ezPlaySample("rotate_wav", 96); paulo@0: while (vkey < N_VKEYS && changeKey(player, vkey)) { paulo@0: rest(150); paulo@0: ++vkey; paulo@0: } paulo@0: if (vkey >= N_VKEYS) { paulo@0: ezPlaySample("land_wav", 128); paulo@0: vkey = N_VKEYS - 1; paulo@0: } else { paulo@0: ezPlaySample("nextO_wav", 128); paulo@0: } paulo@0: drawVkeyRow(vkey, player + 1); paulo@0: break; paulo@0: case KEY_ESC: paulo@0: vkey = -1; paulo@0: break; paulo@0: } paulo@0: } paulo@0: saveKeys(keysFileName); paulo@0: ezPlaySample("line_wav", 128); paulo@0: } paulo@0: paulo@0: paulo@0: void initKeys(void) { paulo@0: LOCK_FUNCTION(ljpcKeyListener); paulo@0: LOCK_VARIABLE(lastScancodePressed); paulo@0: LOCK_VARIABLE(oldKeyListener); paulo@0: LOCK_FUNCTION(setWantsClose); paulo@0: LOCK_VARIABLE(wantsClose); paulo@0: paulo@0: oldKeyListener = keyboard_lowlevel_callback; paulo@0: keyboard_lowlevel_callback = ljpcKeyListener; paulo@0: withJoystick = !install_joystick(JOY_TYPE_AUTODETECT); paulo@0: loadKeys(keysFileName); paulo@0: set_close_button_callback(setWantsClose); paulo@0: } paulo@0: paulo@0: