+}
+
+static boolean is_string_suffix(char *string, char *suffix)
+{
+ int string_len = strlen(string);
+ int suffix_len = strlen(suffix);
+
+ if (suffix_len > string_len)
+ return FALSE;
+
+ return (strEqual(&string[string_len - suffix_len], suffix));
+}
+
+#define MAX_CHEAT_INPUT_LEN 32
+
+static void HandleKeysSpecial(Key key)
+{
+ static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
+ char letter = getCharFromKey(key);
+ int cheat_input_len = strlen(cheat_input);
+ int i;
+
+ if (letter == 0)
+ return;
+
+ if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
+ {
+ for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
+ cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
+
+ cheat_input_len = MAX_CHEAT_INPUT_LEN;
+ }
+
+ cheat_input[cheat_input_len++] = letter;
+ cheat_input[cheat_input_len] = '\0';
+
+#if DEBUG_EVENTS
+ Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
+#endif
+
+ if (game_status == GAME_MODE_MAIN)
+ {
+ if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
+ is_string_suffix(cheat_input, ":ist"))
+ {
+ InsertSolutionTape();
+ }
+ else if (is_string_suffix(cheat_input, ":reload-graphics") ||
+ is_string_suffix(cheat_input, ":rg"))
+ {
+ ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
+ DrawMainMenu();
+ }
+ else if (is_string_suffix(cheat_input, ":reload-sounds") ||
+ is_string_suffix(cheat_input, ":rs"))
+ {
+ ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
+ DrawMainMenu();
+ }
+ else if (is_string_suffix(cheat_input, ":reload-music") ||
+ is_string_suffix(cheat_input, ":rm"))
+ {
+ ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
+ DrawMainMenu();
+ }
+ else if (is_string_suffix(cheat_input, ":reload-artwork") ||
+ is_string_suffix(cheat_input, ":ra"))
+ {
+ ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
+ 1 << ARTWORK_TYPE_SOUNDS |
+ 1 << ARTWORK_TYPE_MUSIC);
+ DrawMainMenu();
+ }
+ else if (is_string_suffix(cheat_input, ":dump-level") ||
+ is_string_suffix(cheat_input, ":dl"))
+ {
+ DumpLevel(&level);
+ }
+ else if (is_string_suffix(cheat_input, ":dump-tape") ||
+ is_string_suffix(cheat_input, ":dt"))
+ {
+ DumpTape(&tape);
+ }
+ else if (is_string_suffix(cheat_input, ":fix-tape") ||
+ is_string_suffix(cheat_input, ":ft"))
+ {
+ /* fix single-player tapes that contain player input for more than one
+ player (due to a bug in 3.3.1.2 and earlier versions), which results
+ in playing levels with more than one player in multi-player mode,
+ even though the tape was originally recorded in single-player mode */
+
+ /* remove player input actions for all players but the first one */
+ for (i = 1; i < MAX_PLAYERS; i++)
+ tape.player_participates[i] = FALSE;
+
+ tape.changed = TRUE;
+ }
+ else if (is_string_suffix(cheat_input, ":save-native-level") ||
+ is_string_suffix(cheat_input, ":snl"))
+ {
+ SaveNativeLevel(&level);
+ }
+ }
+ else if (game_status == GAME_MODE_PLAYING)
+ {
+#ifdef DEBUG
+ if (is_string_suffix(cheat_input, ".q"))
+ DEBUG_SetMaximumDynamite();
+#endif
+ }
+ else if (game_status == GAME_MODE_EDITOR)
+ {
+ if (is_string_suffix(cheat_input, ":dump-brush") ||
+ is_string_suffix(cheat_input, ":DB"))
+ {
+ DumpBrush();
+ }
+ else if (is_string_suffix(cheat_input, ":DDB"))
+ {
+ DumpBrush_Small();
+ }
+ }
+}
+
+void HandleKey(Key key, int key_status)
+{
+ boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
+ static struct SetupKeyboardInfo ski;
+ static struct SetupShortcutInfo ssi;
+ static struct
+ {
+ Key *key_custom;
+ Key *key_snap;
+ Key key_default;
+ byte action;
+ } key_info[] =
+ {
+ { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
+ { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
+ { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
+ { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
+ { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
+ { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
+ };
+ int joy = 0;
+ int i;
+
+ if (game_status == GAME_MODE_PLAYING)
+ {
+ /* only needed for single-step tape recording mode */
+ static boolean clear_snap_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
+ static boolean clear_drop_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
+ static boolean element_snapped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
+ static boolean element_dropped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
+ int pnr;
+
+ for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
+ {
+ byte key_action = 0;
+
+ if (setup.input[pnr].use_joystick)
+ continue;
+
+ ski = setup.input[pnr].key;
+
+ for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
+ if (key == *key_info[i].key_custom)
+ key_action |= key_info[i].action;
+
+ /* use combined snap+direction keys for the first player only */
+ if (pnr == 0)
+ {
+ ssi = setup.shortcut;
+
+ for (i = 0; i < NUM_DIRECTIONS; i++)
+ if (key == *key_info[i].key_snap)
+ key_action |= key_info[i].action | JOY_BUTTON_SNAP;
+ }
+
+ /* clear delayed snap and drop actions in single step mode (see below) */
+ if (tape.single_step)
+ {
+ if (clear_snap_button[pnr])
+ {
+ stored_player[pnr].action &= ~KEY_BUTTON_SNAP;
+ clear_snap_button[pnr] = FALSE;
+ }
+
+ if (clear_drop_button[pnr])
+ {
+ stored_player[pnr].action &= ~KEY_BUTTON_DROP;
+ clear_drop_button[pnr] = FALSE;
+ }
+ }
+
+ if (key_status == KEY_PRESSED)
+ stored_player[pnr].action |= key_action;
+ else
+ stored_player[pnr].action &= ~key_action;
+
+ if (tape.single_step && tape.recording && tape.pausing)
+ {
+ if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
+ {
+ TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
+
+ /* if snap key already pressed, don't snap when releasing (below) */
+ if (stored_player[pnr].action & KEY_BUTTON_SNAP)
+ element_snapped[pnr] = TRUE;
+
+ /* if drop key already pressed, don't drop when releasing (below) */
+ if (stored_player[pnr].action & KEY_BUTTON_DROP)
+ element_dropped[pnr] = TRUE;
+ }
+#if 1
+ else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
+ {
+ if (level.game_engine_type == GAME_ENGINE_TYPE_EM ||
+ level.game_engine_type == GAME_ENGINE_TYPE_SP)
+ {
+#if 0
+ printf("::: drop key pressed\n");
+#endif
+
+ if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
+ getRedDiskReleaseFlag_SP() == 0)
+ stored_player[pnr].action &= ~KEY_BUTTON_DROP;
+
+ TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
+ }
+ }
+#endif
+ else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON)
+ {
+ if (key_action & KEY_BUTTON_SNAP)
+ {
+ /* if snap key was released without moving (see above), snap now */
+ if (!element_snapped[pnr])
+ {
+ TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);