1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_EVENTS 0
28 #define DEBUG_EVENTS_BUTTON (DEBUG_EVENTS * 0)
29 #define DEBUG_EVENTS_MOTION (DEBUG_EVENTS * 0)
30 #define DEBUG_EVENTS_WHEEL (DEBUG_EVENTS * 1)
31 #define DEBUG_EVENTS_WINDOW (DEBUG_EVENTS * 0)
32 #define DEBUG_EVENTS_FINGER (DEBUG_EVENTS * 0)
33 #define DEBUG_EVENTS_TEXT (DEBUG_EVENTS * 1)
34 #define DEBUG_EVENTS_KEY (DEBUG_EVENTS * 1)
37 static boolean cursor_inside_playfield = FALSE;
38 static int cursor_mode_last = CURSOR_DEFAULT;
39 static unsigned int special_cursor_delay = 0;
40 static unsigned int special_cursor_delay_value = 1000;
42 static boolean virtual_button_pressed = FALSE;
45 // forward declarations for internal use
46 static void HandleNoEvent(void);
47 static void HandleEventActions(void);
50 // event filter especially needed for SDL event filtering due to
51 // delay problems with lots of mouse motion events when mouse button
52 // not pressed (X11 can handle this with 'PointerMotionHintMask')
54 // event filter addition for SDL2: as SDL2 does not have a function to enable
55 // or disable keyboard auto-repeat, filter repeated keyboard events instead
57 static int FilterEvents(const Event *event)
61 // skip repeated key press events if keyboard auto-repeat is disabled
62 if (event->type == EVENT_KEYPRESS &&
67 if (event->type == EVENT_BUTTONPRESS ||
68 event->type == EVENT_BUTTONRELEASE)
70 ((ButtonEvent *)event)->x -= video.screen_xoffset;
71 ((ButtonEvent *)event)->y -= video.screen_yoffset;
73 else if (event->type == EVENT_MOTIONNOTIFY)
75 ((MotionEvent *)event)->x -= video.screen_xoffset;
76 ((MotionEvent *)event)->y -= video.screen_yoffset;
79 // non-motion events are directly passed to event handler functions
80 if (event->type != EVENT_MOTIONNOTIFY)
83 motion = (MotionEvent *)event;
84 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
85 motion->y >= SY && motion->y < SY + SYSIZE);
87 // do no reset mouse cursor before all pending events have been processed
88 if (gfx.cursor_mode == cursor_mode_last &&
89 ((game_status == GAME_MODE_TITLE &&
90 gfx.cursor_mode == CURSOR_NONE) ||
91 (game_status == GAME_MODE_PLAYING &&
92 gfx.cursor_mode == CURSOR_PLAYFIELD)))
94 SetMouseCursor(CURSOR_DEFAULT);
96 DelayReached(&special_cursor_delay, 0);
98 cursor_mode_last = CURSOR_DEFAULT;
101 // skip mouse motion events without pressed button outside level editor
102 if (button_status == MB_RELEASED &&
103 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
109 // to prevent delay problems, skip mouse motion events if the very next
110 // event is also a mouse motion event (and therefore effectively only
111 // handling the last of a row of mouse motion events in the event queue)
113 static boolean SkipPressedMouseMotionEvent(const Event *event)
115 // nothing to do if the current event is not a mouse motion event
116 if (event->type != EVENT_MOTIONNOTIFY)
119 // only skip motion events with pressed button outside the game
120 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
127 PeekEvent(&next_event);
129 // if next event is also a mouse motion event, skip the current one
130 if (next_event.type == EVENT_MOTIONNOTIFY)
137 static boolean WaitValidEvent(Event *event)
141 if (!FilterEvents(event))
144 if (SkipPressedMouseMotionEvent(event))
150 /* this is especially needed for event modifications for the Android target:
151 if mouse coordinates should be modified in the event filter function,
152 using a properly installed SDL event filter does not work, because in
153 the event filter, mouse coordinates in the event structure are still
154 physical pixel positions, not logical (scaled) screen positions, so this
155 has to be handled at a later stage in the event processing functions
156 (when device pixel positions are already converted to screen positions) */
158 boolean NextValidEvent(Event *event)
160 while (PendingEvent())
161 if (WaitValidEvent(event))
167 static void HandleEvents(void)
170 unsigned int event_frame_delay = 0;
171 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
173 ResetDelayCounter(&event_frame_delay);
175 while (NextValidEvent(&event))
179 case EVENT_BUTTONPRESS:
180 case EVENT_BUTTONRELEASE:
181 HandleButtonEvent((ButtonEvent *) &event);
184 case EVENT_MOTIONNOTIFY:
185 HandleMotionEvent((MotionEvent *) &event);
188 case EVENT_WHEELMOTION:
189 HandleWheelEvent((WheelEvent *) &event);
192 case SDL_WINDOWEVENT:
193 HandleWindowEvent((WindowEvent *) &event);
196 case EVENT_FINGERPRESS:
197 case EVENT_FINGERRELEASE:
198 case EVENT_FINGERMOTION:
199 HandleFingerEvent((FingerEvent *) &event);
202 case EVENT_TEXTINPUT:
203 HandleTextEvent((TextEvent *) &event);
206 case SDL_APP_WILLENTERBACKGROUND:
207 case SDL_APP_DIDENTERBACKGROUND:
208 case SDL_APP_WILLENTERFOREGROUND:
209 case SDL_APP_DIDENTERFOREGROUND:
210 HandlePauseResumeEvent((PauseResumeEvent *) &event);
214 case EVENT_KEYRELEASE:
215 HandleKeyEvent((KeyEvent *) &event);
219 HandleOtherEvents(&event);
223 // do not handle events for longer than standard frame delay period
224 if (DelayReached(&event_frame_delay, event_frame_delay_value))
229 void HandleOtherEvents(Event *event)
234 HandleExposeEvent((ExposeEvent *) event);
237 case EVENT_UNMAPNOTIFY:
239 // This causes the game to stop not only when iconified, but also
240 // when on another virtual desktop, which might be not desired.
241 SleepWhileUnmapped();
247 HandleFocusEvent((FocusChangeEvent *) event);
250 case EVENT_CLIENTMESSAGE:
251 HandleClientMessageEvent((ClientMessageEvent *) event);
254 case SDL_CONTROLLERBUTTONDOWN:
255 case SDL_CONTROLLERBUTTONUP:
256 // for any game controller button event, disable overlay buttons
257 SetOverlayEnabled(FALSE);
259 HandleSpecialGameControllerButtons(event);
262 case SDL_CONTROLLERDEVICEADDED:
263 case SDL_CONTROLLERDEVICEREMOVED:
264 case SDL_CONTROLLERAXISMOTION:
265 case SDL_JOYAXISMOTION:
266 case SDL_JOYBUTTONDOWN:
267 case SDL_JOYBUTTONUP:
268 HandleJoystickEvent(event);
276 static void HandleMouseCursor(void)
278 if (game_status == GAME_MODE_TITLE)
280 // when showing title screens, hide mouse pointer (if not moved)
282 if (gfx.cursor_mode != CURSOR_NONE &&
283 DelayReached(&special_cursor_delay, special_cursor_delay_value))
285 SetMouseCursor(CURSOR_NONE);
288 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
291 // when playing, display a special mouse pointer inside the playfield
293 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
294 cursor_inside_playfield &&
295 DelayReached(&special_cursor_delay, special_cursor_delay_value))
297 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
299 SetMouseCursor(CURSOR_PLAYFIELD);
302 else if (gfx.cursor_mode != CURSOR_DEFAULT)
304 SetMouseCursor(CURSOR_DEFAULT);
307 // this is set after all pending events have been processed
308 cursor_mode_last = gfx.cursor_mode;
320 // execute event related actions after pending events have been processed
321 HandleEventActions();
323 // don't use all CPU time when idle; the main loop while playing
324 // has its own synchronization and is CPU friendly, too
326 if (game_status == GAME_MODE_PLAYING)
329 // always copy backbuffer to visible screen for every video frame
332 // reset video frame delay to default (may change again while playing)
333 SetVideoFrameDelay(MenuFrameDelay);
335 if (game_status == GAME_MODE_QUIT)
340 void ClearAutoRepeatKeyEvents(void)
342 while (PendingEvent())
346 PeekEvent(&next_event);
348 // if event is repeated key press event, remove it from event queue
349 if (next_event.type == EVENT_KEYPRESS &&
350 next_event.key.repeat)
351 WaitEvent(&next_event);
357 void ClearEventQueue(void)
361 while (NextValidEvent(&event))
365 case EVENT_BUTTONRELEASE:
366 button_status = MB_RELEASED;
369 case EVENT_KEYRELEASE:
373 case SDL_CONTROLLERBUTTONUP:
374 HandleJoystickEvent(&event);
379 HandleOtherEvents(&event);
385 static void ClearPlayerMouseAction(void)
387 local_player->mouse_action.lx = 0;
388 local_player->mouse_action.ly = 0;
389 local_player->mouse_action.button = 0;
392 void ClearPlayerAction(void)
396 // simulate key release events for still pressed keys
397 key_joystick_mapping = 0;
398 for (i = 0; i < MAX_PLAYERS; i++)
399 stored_player[i].action = 0;
401 ClearJoystickState();
402 ClearPlayerMouseAction();
405 static void SetPlayerMouseAction(int mx, int my, int button)
407 int lx = getLevelFromScreenX(mx);
408 int ly = getLevelFromScreenY(my);
409 int new_button = (!local_player->mouse_action.button && button);
411 if (local_player->mouse_action.button_hint)
412 button = local_player->mouse_action.button_hint;
414 ClearPlayerMouseAction();
416 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
419 local_player->mouse_action.lx = lx;
420 local_player->mouse_action.ly = ly;
421 local_player->mouse_action.button = button;
423 if (tape.recording && tape.pausing && tape.use_mouse)
425 // un-pause a paused game only if mouse button was newly pressed down
427 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
430 SetTileCursorXY(lx, ly);
433 void SleepWhileUnmapped(void)
435 boolean window_unmapped = TRUE;
437 KeyboardAutoRepeatOn();
439 while (window_unmapped)
443 if (!WaitValidEvent(&event))
448 case EVENT_BUTTONRELEASE:
449 button_status = MB_RELEASED;
452 case EVENT_KEYRELEASE:
453 key_joystick_mapping = 0;
456 case SDL_CONTROLLERBUTTONUP:
457 HandleJoystickEvent(&event);
458 key_joystick_mapping = 0;
461 case EVENT_MAPNOTIFY:
462 window_unmapped = FALSE;
465 case EVENT_UNMAPNOTIFY:
466 // this is only to surely prevent the 'should not happen' case
467 // of recursively looping between 'SleepWhileUnmapped()' and
468 // 'HandleOtherEvents()' which usually calls this funtion.
472 HandleOtherEvents(&event);
477 if (game_status == GAME_MODE_PLAYING)
478 KeyboardAutoRepeatOffUnlessAutoplay();
481 void HandleExposeEvent(ExposeEvent *event)
485 void HandleButtonEvent(ButtonEvent *event)
487 #if DEBUG_EVENTS_BUTTON
488 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
490 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
494 // for any mouse button event, disable playfield tile cursor
495 SetTileCursorEnabled(FALSE);
497 #if defined(HAS_SCREEN_KEYBOARD)
498 if (video.shifted_up)
499 event->y += video.shifted_up_pos;
502 motion_status = FALSE;
504 if (event->type == EVENT_BUTTONPRESS)
505 button_status = event->button;
507 button_status = MB_RELEASED;
509 HandleButton(event->x, event->y, button_status, event->button);
512 void HandleMotionEvent(MotionEvent *event)
514 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
517 motion_status = TRUE;
519 #if DEBUG_EVENTS_MOTION
520 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
521 button_status, event->x, event->y);
524 HandleButton(event->x, event->y, button_status, button_status);
527 void HandleWheelEvent(WheelEvent *event)
531 #if DEBUG_EVENTS_WHEEL
533 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
534 event->which, event->x, event->y);
536 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
537 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
538 event->which, event->x, event->y,
539 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
540 "SDL_MOUSEWHEEL_FLIPPED"));
544 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
545 event->x > 0 ? MB_WHEEL_RIGHT :
546 event->y < 0 ? MB_WHEEL_DOWN :
547 event->y > 0 ? MB_WHEEL_UP : 0);
549 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
550 // accelerated mouse wheel available on Mac and Windows
551 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
553 // no accelerated mouse wheel available on Unix/Linux
554 wheel_steps = DEFAULT_WHEEL_STEPS;
557 motion_status = FALSE;
559 button_status = button_nr;
560 HandleButton(0, 0, button_status, -button_nr);
562 button_status = MB_RELEASED;
563 HandleButton(0, 0, button_status, -button_nr);
566 void HandleWindowEvent(WindowEvent *event)
568 #if DEBUG_EVENTS_WINDOW
569 int subtype = event->event;
572 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
573 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
574 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
575 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
576 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
577 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
578 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
579 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
580 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
581 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
582 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
583 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
584 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
585 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
588 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
589 event_name, event->data1, event->data2);
593 // (not needed, as the screen gets redrawn every 20 ms anyway)
594 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
595 event->event == SDL_WINDOWEVENT_RESIZED ||
596 event->event == SDL_WINDOWEVENT_EXPOSED)
600 if (event->event == SDL_WINDOWEVENT_RESIZED)
602 if (!video.fullscreen_enabled)
604 int new_window_width = event->data1;
605 int new_window_height = event->data2;
607 // if window size has changed after resizing, calculate new scaling factor
608 if (new_window_width != video.window_width ||
609 new_window_height != video.window_height)
611 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
612 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
614 // (extreme window scaling allowed, but cannot be saved permanently)
615 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
616 setup.window_scaling_percent =
617 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
618 MAX_WINDOW_SCALING_PERCENT);
620 video.window_width = new_window_width;
621 video.window_height = new_window_height;
623 if (game_status == GAME_MODE_SETUP)
624 RedrawSetupScreenAfterFullscreenToggle();
629 #if defined(PLATFORM_ANDROID)
632 int new_display_width = event->data1;
633 int new_display_height = event->data2;
635 // if fullscreen display size has changed, device has been rotated
636 if (new_display_width != video.display_width ||
637 new_display_height != video.display_height)
639 int nr = GRID_ACTIVE_NR(); // previous screen orientation
641 video.display_width = new_display_width;
642 video.display_height = new_display_height;
644 SDLSetScreenProperties();
646 // check if screen orientation has changed (should always be true here)
647 if (nr != GRID_ACTIVE_NR())
651 if (game_status == GAME_MODE_SETUP)
652 RedrawSetupScreenAfterScreenRotation(nr);
654 nr = GRID_ACTIVE_NR();
656 overlay.grid_xsize = setup.touch.grid_xsize[nr];
657 overlay.grid_ysize = setup.touch.grid_ysize[nr];
659 for (x = 0; x < MAX_GRID_XSIZE; x++)
660 for (y = 0; y < MAX_GRID_YSIZE; y++)
661 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
669 #define NUM_TOUCH_FINGERS 3
674 SDL_FingerID finger_id;
677 } touch_info[NUM_TOUCH_FINGERS];
679 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
682 int x = event->x * overlay.grid_xsize;
683 int y = event->y * overlay.grid_ysize;
684 int grid_button = overlay.grid_button[x][y];
685 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
686 Key key = (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
687 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
688 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
689 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
690 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
691 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
694 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
695 float event_x = (event->x);
696 float event_y = (event->y - ypos) / (1 - ypos);
697 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
698 event_y > 2.0 / 3.0 && event_y < 1 ?
699 setup.input[0].key.snap :
700 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
701 event_y > 2.0 / 3.0 && event_y < 1 ?
702 setup.input[0].key.drop :
703 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
704 event_y > 0 && event_y < 1.0 / 3.0 ?
705 setup.input[0].key.up :
706 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
707 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
708 setup.input[0].key.left :
709 event_x > 8.0 / 9.0 && event_x < 1 &&
710 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
711 setup.input[0].key.right :
712 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
713 event_y > 2.0 / 3.0 && event_y < 1 ?
714 setup.input[0].key.down :
717 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
719 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
723 virtual_button_pressed = (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
725 // for any touch input event, enable overlay buttons (if activated)
726 SetOverlayEnabled(TRUE);
728 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
729 getKeyNameFromKey(key), key_status_name, event->fingerId);
731 if (key_status == KEY_PRESSED)
732 overlay.grid_button_action |= grid_button_action;
734 overlay.grid_button_action &= ~grid_button_action;
736 // check if we already know this touch event's finger id
737 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
739 if (touch_info[i].touched &&
740 touch_info[i].finger_id == event->fingerId)
742 // Error(ERR_DEBUG, "MARK 1: %d", i);
748 if (i >= NUM_TOUCH_FINGERS)
750 if (key_status == KEY_PRESSED)
752 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
754 // unknown finger id -- get new, empty slot, if available
755 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
757 if (touch_info[i].counter < oldest_counter)
760 oldest_counter = touch_info[i].counter;
762 // Error(ERR_DEBUG, "MARK 2: %d", i);
765 if (!touch_info[i].touched)
767 // Error(ERR_DEBUG, "MARK 3: %d", i);
773 if (i >= NUM_TOUCH_FINGERS)
775 // all slots allocated -- use oldest slot
778 // Error(ERR_DEBUG, "MARK 4: %d", i);
783 // release of previously unknown key (should not happen)
785 if (key != KSYM_UNDEFINED)
787 HandleKey(key, KEY_RELEASED);
789 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
790 getKeyNameFromKey(key), "KEY_RELEASED", i);
795 if (i < NUM_TOUCH_FINGERS)
797 if (key_status == KEY_PRESSED)
799 if (touch_info[i].key != key)
801 if (touch_info[i].key != KSYM_UNDEFINED)
803 HandleKey(touch_info[i].key, KEY_RELEASED);
805 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
806 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
809 if (key != KSYM_UNDEFINED)
811 HandleKey(key, KEY_PRESSED);
813 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
814 getKeyNameFromKey(key), "KEY_PRESSED", i);
818 touch_info[i].touched = TRUE;
819 touch_info[i].finger_id = event->fingerId;
820 touch_info[i].counter = Counter();
821 touch_info[i].key = key;
825 if (touch_info[i].key != KSYM_UNDEFINED)
827 HandleKey(touch_info[i].key, KEY_RELEASED);
829 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
830 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
833 touch_info[i].touched = FALSE;
834 touch_info[i].finger_id = 0;
835 touch_info[i].counter = 0;
836 touch_info[i].key = 0;
841 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
843 static Key motion_key_x = KSYM_UNDEFINED;
844 static Key motion_key_y = KSYM_UNDEFINED;
845 static Key button_key = KSYM_UNDEFINED;
846 static float motion_x1, motion_y1;
847 static float button_x1, button_y1;
848 static SDL_FingerID motion_id = -1;
849 static SDL_FingerID button_id = -1;
850 int move_trigger_distance_percent = setup.touch.move_distance;
851 int drop_trigger_distance_percent = setup.touch.drop_distance;
852 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
853 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
854 float event_x = event->x;
855 float event_y = event->y;
857 if (event->type == EVENT_FINGERPRESS)
859 if (event_x > 1.0 / 3.0)
863 motion_id = event->fingerId;
868 motion_key_x = KSYM_UNDEFINED;
869 motion_key_y = KSYM_UNDEFINED;
871 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
877 button_id = event->fingerId;
882 button_key = setup.input[0].key.snap;
884 HandleKey(button_key, KEY_PRESSED);
886 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
889 else if (event->type == EVENT_FINGERRELEASE)
891 if (event->fingerId == motion_id)
895 if (motion_key_x != KSYM_UNDEFINED)
896 HandleKey(motion_key_x, KEY_RELEASED);
897 if (motion_key_y != KSYM_UNDEFINED)
898 HandleKey(motion_key_y, KEY_RELEASED);
900 motion_key_x = KSYM_UNDEFINED;
901 motion_key_y = KSYM_UNDEFINED;
903 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
905 else if (event->fingerId == button_id)
909 if (button_key != KSYM_UNDEFINED)
910 HandleKey(button_key, KEY_RELEASED);
912 button_key = KSYM_UNDEFINED;
914 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
917 else if (event->type == EVENT_FINGERMOTION)
919 if (event->fingerId == motion_id)
921 float distance_x = ABS(event_x - motion_x1);
922 float distance_y = ABS(event_y - motion_y1);
923 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
924 event_x > motion_x1 ? setup.input[0].key.right :
926 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
927 event_y > motion_y1 ? setup.input[0].key.down :
930 if (distance_x < move_trigger_distance / 2 ||
931 distance_x < distance_y)
932 new_motion_key_x = KSYM_UNDEFINED;
934 if (distance_y < move_trigger_distance / 2 ||
935 distance_y < distance_x)
936 new_motion_key_y = KSYM_UNDEFINED;
938 if (distance_x > move_trigger_distance ||
939 distance_y > move_trigger_distance)
941 if (new_motion_key_x != motion_key_x)
943 if (motion_key_x != KSYM_UNDEFINED)
944 HandleKey(motion_key_x, KEY_RELEASED);
945 if (new_motion_key_x != KSYM_UNDEFINED)
946 HandleKey(new_motion_key_x, KEY_PRESSED);
949 if (new_motion_key_y != motion_key_y)
951 if (motion_key_y != KSYM_UNDEFINED)
952 HandleKey(motion_key_y, KEY_RELEASED);
953 if (new_motion_key_y != KSYM_UNDEFINED)
954 HandleKey(new_motion_key_y, KEY_PRESSED);
960 motion_key_x = new_motion_key_x;
961 motion_key_y = new_motion_key_y;
963 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
966 else if (event->fingerId == button_id)
968 float distance_x = ABS(event_x - button_x1);
969 float distance_y = ABS(event_y - button_y1);
971 if (distance_x < drop_trigger_distance / 2 &&
972 distance_y > drop_trigger_distance)
974 if (button_key == setup.input[0].key.snap)
975 HandleKey(button_key, KEY_RELEASED);
980 button_key = setup.input[0].key.drop;
982 HandleKey(button_key, KEY_PRESSED);
984 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
990 void HandleFingerEvent(FingerEvent *event)
992 #if DEBUG_EVENTS_FINGER
993 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
994 event->type == EVENT_FINGERPRESS ? "pressed" :
995 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
999 event->dx, event->dy,
1003 if (game_status != GAME_MODE_PLAYING)
1006 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1008 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1009 local_player->mouse_action.button_hint =
1010 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1011 event->x < 0.5 ? MB_LEFTBUTTON :
1012 event->x > 0.5 ? MB_RIGHTBUTTON :
1018 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1019 HandleFingerEvent_VirtualButtons(event);
1020 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1021 HandleFingerEvent_WipeGestures(event);
1024 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1026 static int old_mx = 0, old_my = 0;
1027 static int last_button = MB_LEFTBUTTON;
1028 static boolean touched = FALSE;
1029 static boolean tapped = FALSE;
1031 // screen tile was tapped (but finger not touching the screen anymore)
1032 // (this point will also be reached without receiving a touch event)
1033 if (tapped && !touched)
1035 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1040 // stop here if this function was not triggered by a touch event
1044 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1046 // finger started touching the screen
1056 ClearPlayerMouseAction();
1058 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1061 else if (button == MB_RELEASED && touched)
1063 // finger stopped touching the screen
1068 SetPlayerMouseAction(old_mx, old_my, last_button);
1070 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1072 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1077 // finger moved while touching the screen
1079 int old_x = getLevelFromScreenX(old_mx);
1080 int old_y = getLevelFromScreenY(old_my);
1081 int new_x = getLevelFromScreenX(mx);
1082 int new_y = getLevelFromScreenY(my);
1084 if (new_x != old_x || new_y != old_y)
1089 // finger moved left or right from (horizontal) starting position
1091 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1093 SetPlayerMouseAction(old_mx, old_my, button_nr);
1095 last_button = button_nr;
1097 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1101 // finger stays at or returned to (horizontal) starting position
1103 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1105 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1110 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1112 static int old_mx = 0, old_my = 0;
1113 static int last_button = MB_LEFTBUTTON;
1114 static boolean touched = FALSE;
1115 static boolean tapped = FALSE;
1117 // screen tile was tapped (but finger not touching the screen anymore)
1118 // (this point will also be reached without receiving a touch event)
1119 if (tapped && !touched)
1121 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1126 // stop here if this function was not triggered by a touch event
1130 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1132 // finger started touching the screen
1142 ClearPlayerMouseAction();
1144 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1147 else if (button == MB_RELEASED && touched)
1149 // finger stopped touching the screen
1154 SetPlayerMouseAction(old_mx, old_my, last_button);
1156 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1158 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1163 // finger moved while touching the screen
1165 int old_x = getLevelFromScreenX(old_mx);
1166 int old_y = getLevelFromScreenY(old_my);
1167 int new_x = getLevelFromScreenX(mx);
1168 int new_y = getLevelFromScreenY(my);
1170 if (new_x != old_x || new_y != old_y)
1172 // finger moved away from starting position
1174 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1176 // quickly alternate between clicking and releasing for maximum speed
1177 if (FrameCounter % 2 == 0)
1178 button_nr = MB_RELEASED;
1180 SetPlayerMouseAction(old_mx, old_my, button_nr);
1183 last_button = button_nr;
1187 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1191 // finger stays at or returned to starting position
1193 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1195 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1200 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1202 static int old_mx = 0, old_my = 0;
1203 static Key motion_key_x = KSYM_UNDEFINED;
1204 static Key motion_key_y = KSYM_UNDEFINED;
1205 static boolean touched = FALSE;
1206 static boolean started_on_player = FALSE;
1207 static boolean player_is_dropping = FALSE;
1208 static int player_drop_count = 0;
1209 static int last_player_x = -1;
1210 static int last_player_y = -1;
1212 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1221 started_on_player = FALSE;
1222 player_is_dropping = FALSE;
1223 player_drop_count = 0;
1227 motion_key_x = KSYM_UNDEFINED;
1228 motion_key_y = KSYM_UNDEFINED;
1230 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1233 else if (button == MB_RELEASED && touched)
1240 if (motion_key_x != KSYM_UNDEFINED)
1241 HandleKey(motion_key_x, KEY_RELEASED);
1242 if (motion_key_y != KSYM_UNDEFINED)
1243 HandleKey(motion_key_y, KEY_RELEASED);
1245 if (started_on_player)
1247 if (player_is_dropping)
1249 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1251 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1255 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1257 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1261 motion_key_x = KSYM_UNDEFINED;
1262 motion_key_y = KSYM_UNDEFINED;
1264 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1269 int src_x = local_player->jx;
1270 int src_y = local_player->jy;
1271 int dst_x = getLevelFromScreenX(old_mx);
1272 int dst_y = getLevelFromScreenY(old_my);
1273 int dx = dst_x - src_x;
1274 int dy = dst_y - src_y;
1275 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1276 dx > 0 ? setup.input[0].key.right :
1278 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1279 dy > 0 ? setup.input[0].key.down :
1282 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1283 (last_player_x != local_player->jx ||
1284 last_player_y != local_player->jy))
1286 // in case of asymmetric diagonal movement, use "preferred" direction
1288 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1290 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1291 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1293 local_player->last_move_dir = last_move_dir;
1295 // (required to prevent accidentally forcing direction for next movement)
1296 last_player_x = local_player->jx;
1297 last_player_y = local_player->jy;
1300 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1302 started_on_player = TRUE;
1303 player_drop_count = getPlayerInventorySize(0);
1304 player_is_dropping = (player_drop_count > 0);
1306 if (player_is_dropping)
1308 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1310 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1314 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1316 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1319 else if (dx != 0 || dy != 0)
1321 if (player_is_dropping &&
1322 player_drop_count == getPlayerInventorySize(0))
1324 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1326 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1327 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1329 player_is_dropping = FALSE;
1333 if (new_motion_key_x != motion_key_x)
1335 Error(ERR_DEBUG, "---------- %s %s ----------",
1336 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1337 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1339 if (motion_key_x != KSYM_UNDEFINED)
1340 HandleKey(motion_key_x, KEY_RELEASED);
1341 if (new_motion_key_x != KSYM_UNDEFINED)
1342 HandleKey(new_motion_key_x, KEY_PRESSED);
1345 if (new_motion_key_y != motion_key_y)
1347 Error(ERR_DEBUG, "---------- %s %s ----------",
1348 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1349 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1351 if (motion_key_y != KSYM_UNDEFINED)
1352 HandleKey(motion_key_y, KEY_RELEASED);
1353 if (new_motion_key_y != KSYM_UNDEFINED)
1354 HandleKey(new_motion_key_y, KEY_PRESSED);
1357 motion_key_x = new_motion_key_x;
1358 motion_key_y = new_motion_key_y;
1362 static void HandleButtonOrFinger(int mx, int my, int button)
1364 if (game_status != GAME_MODE_PLAYING)
1367 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1369 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1370 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1371 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1372 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1373 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1374 SetPlayerMouseAction(mx, my, button); // special case
1378 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1379 HandleButtonOrFinger_FollowFinger(mx, my, button);
1383 static boolean checkTextInputKeyModState(void)
1385 // when playing, only handle raw key events and ignore text input
1386 if (game_status == GAME_MODE_PLAYING)
1389 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1392 void HandleTextEvent(TextEvent *event)
1394 char *text = event->text;
1395 Key key = getKeyFromKeyName(text);
1397 #if DEBUG_EVENTS_TEXT
1398 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1401 text[0], (int)(text[0]),
1403 getKeyNameFromKey(key),
1407 #if !defined(HAS_SCREEN_KEYBOARD)
1408 // non-mobile devices: only handle key input with modifier keys pressed here
1409 // (every other key input is handled directly as physical key input event)
1410 if (!checkTextInputKeyModState())
1414 // process text input as "classic" (with uppercase etc.) key input event
1415 HandleKey(key, KEY_PRESSED);
1416 HandleKey(key, KEY_RELEASED);
1419 void HandlePauseResumeEvent(PauseResumeEvent *event)
1421 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1425 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1431 void HandleKeyEvent(KeyEvent *event)
1433 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1434 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1435 Key key = GetEventKey(event, with_modifiers);
1436 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1438 #if DEBUG_EVENTS_KEY
1439 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1440 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1441 event->keysym.scancode,
1446 getKeyNameFromKey(key));
1449 #if defined(PLATFORM_ANDROID)
1450 if (key == KSYM_Back)
1452 // always map the "back" button to the "escape" key on Android devices
1455 else if (key == KSYM_Menu)
1457 // the "menu" button can be used to toggle displaying virtual buttons
1458 if (key_status == KEY_PRESSED)
1459 SetOverlayEnabled(!GetOverlayEnabled());
1463 // for any other "real" key event, disable virtual buttons
1464 SetOverlayEnabled(FALSE);
1468 HandleKeyModState(keymod, key_status);
1470 // only handle raw key input without text modifier keys pressed
1471 if (!checkTextInputKeyModState())
1472 HandleKey(key, key_status);
1475 void HandleFocusEvent(FocusChangeEvent *event)
1477 static int old_joystick_status = -1;
1479 if (event->type == EVENT_FOCUSOUT)
1481 KeyboardAutoRepeatOn();
1482 old_joystick_status = joystick.status;
1483 joystick.status = JOYSTICK_NOT_AVAILABLE;
1485 ClearPlayerAction();
1487 else if (event->type == EVENT_FOCUSIN)
1489 /* When there are two Rocks'n'Diamonds windows which overlap and
1490 the player moves the pointer from one game window to the other,
1491 a 'FocusOut' event is generated for the window the pointer is
1492 leaving and a 'FocusIn' event is generated for the window the
1493 pointer is entering. In some cases, it can happen that the
1494 'FocusIn' event is handled by the one game process before the
1495 'FocusOut' event by the other game process. In this case the
1496 X11 environment would end up with activated keyboard auto repeat,
1497 because unfortunately this is a global setting and not (which
1498 would be far better) set for each X11 window individually.
1499 The effect would be keyboard auto repeat while playing the game
1500 (game_status == GAME_MODE_PLAYING), which is not desired.
1501 To avoid this special case, we just wait 1/10 second before
1502 processing the 'FocusIn' event. */
1504 if (game_status == GAME_MODE_PLAYING)
1507 KeyboardAutoRepeatOffUnlessAutoplay();
1510 if (old_joystick_status != -1)
1511 joystick.status = old_joystick_status;
1515 void HandleClientMessageEvent(ClientMessageEvent *event)
1517 if (CheckCloseWindowEvent(event))
1521 void HandleButton(int mx, int my, int button, int button_nr)
1523 static int old_mx = 0, old_my = 0;
1524 boolean button_hold = FALSE;
1525 boolean handle_gadgets = TRUE;
1531 button_nr = -button_nr;
1540 #if defined(PLATFORM_ANDROID)
1541 // when playing, only handle gadgets when using "follow finger" controls
1542 // or when using touch controls in combination with the MM game engine
1543 // or when using gadgets that do not overlap with virtual buttons
1545 (game_status != GAME_MODE_PLAYING ||
1546 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1547 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1548 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1549 !virtual_button_pressed));
1552 if (HandleGlobalAnimClicks(mx, my, button))
1554 // do not handle this button event anymore
1555 return; // force mouse event not to be handled at all
1558 if (handle_gadgets && HandleGadgets(mx, my, button))
1560 // do not handle this button event anymore
1561 mx = my = -32; // force mouse event to be outside screen tiles
1564 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1567 // do not use scroll wheel button events for anything other than gadgets
1568 if (IS_WHEEL_BUTTON(button_nr))
1571 switch (game_status)
1573 case GAME_MODE_TITLE:
1574 HandleTitleScreen(mx, my, 0, 0, button);
1577 case GAME_MODE_MAIN:
1578 HandleMainMenu(mx, my, 0, 0, button);
1581 case GAME_MODE_PSEUDO_TYPENAME:
1582 HandleTypeName(0, KSYM_Return);
1585 case GAME_MODE_LEVELS:
1586 HandleChooseLevelSet(mx, my, 0, 0, button);
1589 case GAME_MODE_LEVELNR:
1590 HandleChooseLevelNr(mx, my, 0, 0, button);
1593 case GAME_MODE_SCORES:
1594 HandleHallOfFame(0, 0, 0, 0, button);
1597 case GAME_MODE_EDITOR:
1598 HandleLevelEditorIdle();
1601 case GAME_MODE_INFO:
1602 HandleInfoScreen(mx, my, 0, 0, button);
1605 case GAME_MODE_SETUP:
1606 HandleSetupScreen(mx, my, 0, 0, button);
1609 case GAME_MODE_PLAYING:
1610 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1611 HandleButtonOrFinger(mx, my, button);
1613 SetPlayerMouseAction(mx, my, button);
1616 if (button == MB_PRESSED && !motion_status && !button_hold &&
1617 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1618 DumpTileFromScreen(mx, my);
1628 static boolean is_string_suffix(char *string, char *suffix)
1630 int string_len = strlen(string);
1631 int suffix_len = strlen(suffix);
1633 if (suffix_len > string_len)
1636 return (strEqual(&string[string_len - suffix_len], suffix));
1639 #define MAX_CHEAT_INPUT_LEN 32
1641 static void HandleKeysSpecial(Key key)
1643 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1644 char letter = getCharFromKey(key);
1645 int cheat_input_len = strlen(cheat_input);
1651 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1653 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1654 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1656 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1659 cheat_input[cheat_input_len++] = letter;
1660 cheat_input[cheat_input_len] = '\0';
1662 #if DEBUG_EVENTS_KEY
1663 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1666 if (game_status == GAME_MODE_MAIN)
1668 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1669 is_string_suffix(cheat_input, ":ist"))
1671 InsertSolutionTape();
1673 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1674 is_string_suffix(cheat_input, ":pst"))
1678 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1679 is_string_suffix(cheat_input, ":rg"))
1681 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1684 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1685 is_string_suffix(cheat_input, ":rs"))
1687 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1690 else if (is_string_suffix(cheat_input, ":reload-music") ||
1691 is_string_suffix(cheat_input, ":rm"))
1693 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1696 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1697 is_string_suffix(cheat_input, ":ra"))
1699 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1700 1 << ARTWORK_TYPE_SOUNDS |
1701 1 << ARTWORK_TYPE_MUSIC);
1704 else if (is_string_suffix(cheat_input, ":dump-level") ||
1705 is_string_suffix(cheat_input, ":dl"))
1709 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1710 is_string_suffix(cheat_input, ":dt"))
1714 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1715 is_string_suffix(cheat_input, ":ft"))
1717 /* fix single-player tapes that contain player input for more than one
1718 player (due to a bug in 3.3.1.2 and earlier versions), which results
1719 in playing levels with more than one player in multi-player mode,
1720 even though the tape was originally recorded in single-player mode */
1722 // remove player input actions for all players but the first one
1723 for (i = 1; i < MAX_PLAYERS; i++)
1724 tape.player_participates[i] = FALSE;
1726 tape.changed = TRUE;
1728 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1729 is_string_suffix(cheat_input, ":snl"))
1731 SaveNativeLevel(&level);
1733 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1734 is_string_suffix(cheat_input, ":fps"))
1736 global.show_frames_per_second = !global.show_frames_per_second;
1739 else if (game_status == GAME_MODE_PLAYING)
1742 if (is_string_suffix(cheat_input, ".q"))
1743 DEBUG_SetMaximumDynamite();
1746 else if (game_status == GAME_MODE_EDITOR)
1748 if (is_string_suffix(cheat_input, ":dump-brush") ||
1749 is_string_suffix(cheat_input, ":DB"))
1753 else if (is_string_suffix(cheat_input, ":DDB"))
1758 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1760 if (letter == 'x') // copy brush to clipboard (small size)
1762 CopyBrushToClipboard_Small();
1764 else if (letter == 'c') // copy brush to clipboard (normal size)
1766 CopyBrushToClipboard();
1768 else if (letter == 'v') // paste brush from Clipboard
1770 CopyClipboardToBrush();
1775 // special key shortcuts for all game modes
1776 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1777 is_string_suffix(cheat_input, ":dea") ||
1778 is_string_suffix(cheat_input, ":DEA"))
1780 DumpGadgetIdentifiers();
1781 DumpScreenIdentifiers();
1785 boolean HandleKeysDebug(Key key, int key_status)
1790 if (key_status != KEY_PRESSED)
1793 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1795 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1797 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1799 if (key == setup.debug.frame_delay_key[i] &&
1800 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1802 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1803 setup.debug.frame_delay[i] : setup.game_frame_delay);
1805 if (!setup.debug.frame_delay_game_only)
1806 MenuFrameDelay = GameFrameDelay;
1808 SetVideoFrameDelay(GameFrameDelay);
1810 if (GameFrameDelay > ONE_SECOND_DELAY)
1811 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1812 else if (GameFrameDelay != 0)
1813 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1814 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1815 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1817 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1824 if (game_status == GAME_MODE_PLAYING)
1828 options.debug = !options.debug;
1830 Error(ERR_DEBUG, "debug mode %s",
1831 (options.debug ? "enabled" : "disabled"));
1835 else if (key == KSYM_v)
1837 Error(ERR_DEBUG, "currently using game engine version %d",
1838 game.engine_version);
1848 void HandleKey(Key key, int key_status)
1850 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1851 static boolean ignore_repeated_key = FALSE;
1852 static struct SetupKeyboardInfo ski;
1853 static struct SetupShortcutInfo ssi;
1862 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1863 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1864 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1865 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1866 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1867 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1872 if (HandleKeysDebug(key, key_status))
1873 return; // do not handle already processed keys again
1875 // map special keys (media keys / remote control buttons) to default keys
1876 if (key == KSYM_PlayPause)
1878 else if (key == KSYM_Select)
1881 HandleSpecialGameControllerKeys(key, key_status);
1883 if (game_status == GAME_MODE_PLAYING)
1885 // only needed for single-step tape recording mode
1886 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1889 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1891 byte key_action = 0;
1893 if (setup.input[pnr].use_joystick)
1896 ski = setup.input[pnr].key;
1898 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1899 if (key == *key_info[i].key_custom)
1900 key_action |= key_info[i].action;
1902 // use combined snap+direction keys for the first player only
1905 ssi = setup.shortcut;
1907 for (i = 0; i < NUM_DIRECTIONS; i++)
1908 if (key == *key_info[i].key_snap)
1909 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1912 if (key_status == KEY_PRESSED)
1913 stored_player[pnr].action |= key_action;
1915 stored_player[pnr].action &= ~key_action;
1917 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
1919 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1921 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1923 // if snap key already pressed, keep pause mode when releasing
1924 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1925 has_snapped[pnr] = TRUE;
1927 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1929 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1931 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1932 getRedDiskReleaseFlag_SP() == 0)
1934 // add a single inactive frame before dropping starts
1935 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1936 stored_player[pnr].force_dropping = TRUE;
1939 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1941 // if snap key was pressed without direction, leave pause mode
1942 if (!has_snapped[pnr])
1943 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1945 has_snapped[pnr] = FALSE;
1948 else if (tape.recording && tape.pausing && !tape.use_mouse)
1950 // prevent key release events from un-pausing a paused game
1951 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1952 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1955 // for MM style levels, handle in-game keyboard input in HandleJoystick()
1956 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1962 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1963 if (key == key_info[i].key_default)
1964 joy |= key_info[i].action;
1969 if (key_status == KEY_PRESSED)
1970 key_joystick_mapping |= joy;
1972 key_joystick_mapping &= ~joy;
1977 if (game_status != GAME_MODE_PLAYING)
1978 key_joystick_mapping = 0;
1980 if (key_status == KEY_RELEASED)
1982 // reset flag to ignore repeated "key pressed" events after key release
1983 ignore_repeated_key = FALSE;
1988 if ((key == KSYM_F11 ||
1989 ((key == KSYM_Return ||
1990 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1991 video.fullscreen_available &&
1992 !ignore_repeated_key)
1994 setup.fullscreen = !setup.fullscreen;
1996 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1998 if (game_status == GAME_MODE_SETUP)
1999 RedrawSetupScreenAfterFullscreenToggle();
2001 // set flag to ignore repeated "key pressed" events
2002 ignore_repeated_key = TRUE;
2007 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2008 key == KSYM_minus || key == KSYM_KP_Subtract ||
2009 key == KSYM_plus || key == KSYM_KP_Add ||
2010 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2011 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2012 video.window_scaling_available &&
2013 !video.fullscreen_enabled)
2015 if (key == KSYM_0 || key == KSYM_KP_0)
2016 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2017 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2018 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2020 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2022 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2023 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2024 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2025 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2027 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2029 if (game_status == GAME_MODE_SETUP)
2030 RedrawSetupScreenAfterFullscreenToggle();
2035 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2036 key == KSYM_Return ||
2037 key == KSYM_Escape)))
2039 // do not handle this key event anymore
2040 if (key != KSYM_Escape) // always allow ESC key to be handled
2044 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2045 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2052 if (game_status == GAME_MODE_MAIN &&
2053 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2055 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2060 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2062 if (key == setup.shortcut.save_game)
2064 else if (key == setup.shortcut.load_game)
2066 else if (key == setup.shortcut.toggle_pause)
2067 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2069 HandleTapeButtonKeys(key);
2070 HandleSoundButtonKeys(key);
2073 if (game_status == GAME_MODE_PLAYING && !network_playing)
2075 int centered_player_nr_next = -999;
2077 if (key == setup.shortcut.focus_player_all)
2078 centered_player_nr_next = -1;
2080 for (i = 0; i < MAX_PLAYERS; i++)
2081 if (key == setup.shortcut.focus_player[i])
2082 centered_player_nr_next = i;
2084 if (centered_player_nr_next != -999)
2086 game.centered_player_nr_next = centered_player_nr_next;
2087 game.set_centered_player = TRUE;
2091 tape.centered_player_nr_next = game.centered_player_nr_next;
2092 tape.set_centered_player = TRUE;
2097 HandleKeysSpecial(key);
2099 if (HandleGadgetsKeyInput(key))
2100 return; // do not handle already processed keys again
2102 switch (game_status)
2104 case GAME_MODE_PSEUDO_TYPENAME:
2105 HandleTypeName(0, key);
2108 case GAME_MODE_TITLE:
2109 case GAME_MODE_MAIN:
2110 case GAME_MODE_LEVELS:
2111 case GAME_MODE_LEVELNR:
2112 case GAME_MODE_SETUP:
2113 case GAME_MODE_INFO:
2114 case GAME_MODE_SCORES:
2116 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2123 if (game_status == GAME_MODE_TITLE)
2124 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2125 else if (game_status == GAME_MODE_MAIN)
2126 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2127 else if (game_status == GAME_MODE_LEVELS)
2128 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2129 else if (game_status == GAME_MODE_LEVELNR)
2130 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2131 else if (game_status == GAME_MODE_SETUP)
2132 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2133 else if (game_status == GAME_MODE_INFO)
2134 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2135 else if (game_status == GAME_MODE_SCORES)
2136 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2140 if (game_status != GAME_MODE_MAIN)
2141 FadeSkipNextFadeIn();
2143 if (game_status == GAME_MODE_TITLE)
2144 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2145 else if (game_status == GAME_MODE_LEVELS)
2146 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2147 else if (game_status == GAME_MODE_LEVELNR)
2148 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2149 else if (game_status == GAME_MODE_SETUP)
2150 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2151 else if (game_status == GAME_MODE_INFO)
2152 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2153 else if (game_status == GAME_MODE_SCORES)
2154 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2158 if (game_status == GAME_MODE_LEVELS)
2159 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2160 else if (game_status == GAME_MODE_LEVELNR)
2161 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2162 else if (game_status == GAME_MODE_SETUP)
2163 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2164 else if (game_status == GAME_MODE_INFO)
2165 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2166 else if (game_status == GAME_MODE_SCORES)
2167 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2170 case KSYM_Page_Down:
2171 if (game_status == GAME_MODE_LEVELS)
2172 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2173 else if (game_status == GAME_MODE_LEVELNR)
2174 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2175 else if (game_status == GAME_MODE_SETUP)
2176 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2177 else if (game_status == GAME_MODE_INFO)
2178 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2179 else if (game_status == GAME_MODE_SCORES)
2180 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2188 case GAME_MODE_EDITOR:
2189 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2190 HandleLevelEditorKeyInput(key);
2193 case GAME_MODE_PLAYING:
2198 RequestQuitGame(setup.ask_on_escape);
2208 if (key == KSYM_Escape)
2210 SetGameStatus(GAME_MODE_MAIN);
2219 void HandleNoEvent(void)
2221 HandleMouseCursor();
2223 switch (game_status)
2225 case GAME_MODE_PLAYING:
2226 HandleButtonOrFinger(-1, -1, -1);
2231 void HandleEventActions(void)
2233 // if (button_status && game_status != GAME_MODE_PLAYING)
2234 if (button_status && (game_status != GAME_MODE_PLAYING ||
2236 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2238 HandleButton(0, 0, button_status, -button_status);
2245 if (network.enabled)
2248 switch (game_status)
2250 case GAME_MODE_MAIN:
2251 DrawPreviewLevelAnimation();
2254 case GAME_MODE_EDITOR:
2255 HandleLevelEditorIdle();
2263 static void HandleTileCursor(int dx, int dy, int button)
2266 ClearPlayerMouseAction();
2273 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2274 (dx < 0 ? MB_LEFTBUTTON :
2275 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2277 else if (!tile_cursor.moving)
2279 int old_xpos = tile_cursor.xpos;
2280 int old_ypos = tile_cursor.ypos;
2281 int new_xpos = old_xpos;
2282 int new_ypos = old_ypos;
2284 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2285 new_xpos = old_xpos + dx;
2287 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2288 new_ypos = old_ypos + dy;
2290 SetTileCursorTargetXY(new_xpos, new_ypos);
2294 static int HandleJoystickForAllPlayers(void)
2298 boolean no_joysticks_configured = TRUE;
2299 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2300 static byte joy_action_last[MAX_PLAYERS];
2302 for (i = 0; i < MAX_PLAYERS; i++)
2303 if (setup.input[i].use_joystick)
2304 no_joysticks_configured = FALSE;
2306 // if no joysticks configured, map connected joysticks to players
2307 if (no_joysticks_configured)
2308 use_as_joystick_nr = TRUE;
2310 for (i = 0; i < MAX_PLAYERS; i++)
2312 byte joy_action = 0;
2314 joy_action = JoystickExt(i, use_as_joystick_nr);
2315 result |= joy_action;
2317 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2318 joy_action != joy_action_last[i])
2319 stored_player[i].action = joy_action;
2321 joy_action_last[i] = joy_action;
2327 void HandleJoystick(void)
2329 static unsigned int joytest_delay = 0;
2330 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2331 static int joytest_last = 0;
2332 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2333 int delay_value = GADGET_FRAME_DELAY;
2334 int joystick = HandleJoystickForAllPlayers();
2335 int keyboard = key_joystick_mapping;
2336 int joy = (joystick | keyboard);
2337 int joytest = joystick;
2338 int left = joy & JOY_LEFT;
2339 int right = joy & JOY_RIGHT;
2340 int up = joy & JOY_UP;
2341 int down = joy & JOY_DOWN;
2342 int button = joy & JOY_BUTTON;
2343 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2344 int dx = (left ? -1 : right ? 1 : 0);
2345 int dy = (up ? -1 : down ? 1 : 0);
2346 boolean use_delay_value_first = (joytest != joytest_last);
2348 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2350 // do not handle this button event anymore
2354 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2355 anyTextGadgetActive()))
2357 // leave name input in main menu or text input gadget
2358 HandleKey(KSYM_Escape, KEY_PRESSED);
2359 HandleKey(KSYM_Escape, KEY_RELEASED);
2364 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2366 if (game_status == GAME_MODE_PLAYING)
2368 // when playing MM style levels, also use delay for keyboard events
2369 joytest |= keyboard;
2371 // only use first delay value for new events, but not for changed events
2372 use_delay_value_first = (!joytest != !joytest_last);
2374 // only use delay after the initial keyboard event
2378 // for any joystick or keyboard event, enable playfield tile cursor
2379 if (dx || dy || button)
2380 SetTileCursorEnabled(TRUE);
2383 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2385 // delay joystick/keyboard actions if axes/keys continually pressed
2386 newbutton = dx = dy = 0;
2390 // first start with longer delay, then continue with shorter delay
2391 joytest_delay_value =
2392 (use_delay_value_first ? delay_value_first : delay_value);
2395 joytest_last = joytest;
2397 switch (game_status)
2399 case GAME_MODE_TITLE:
2400 case GAME_MODE_MAIN:
2401 case GAME_MODE_LEVELS:
2402 case GAME_MODE_LEVELNR:
2403 case GAME_MODE_SETUP:
2404 case GAME_MODE_INFO:
2405 case GAME_MODE_SCORES:
2407 if (anyTextGadgetActive())
2410 if (game_status == GAME_MODE_TITLE)
2411 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2412 else if (game_status == GAME_MODE_MAIN)
2413 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2414 else if (game_status == GAME_MODE_LEVELS)
2415 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2416 else if (game_status == GAME_MODE_LEVELNR)
2417 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2418 else if (game_status == GAME_MODE_SETUP)
2419 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2420 else if (game_status == GAME_MODE_INFO)
2421 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2422 else if (game_status == GAME_MODE_SCORES)
2423 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2428 case GAME_MODE_PLAYING:
2430 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2431 if (tape.playing || keyboard)
2432 newbutton = ((joy & JOY_BUTTON) != 0);
2435 if (newbutton && game.all_players_gone)
2442 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2444 if (joystick & JOY_ACTION)
2445 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2447 else if (tape.recording && tape.pausing && !tape.use_mouse)
2449 if (joystick & JOY_ACTION)
2450 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2453 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2454 HandleTileCursor(dx, dy, button);
2463 void HandleSpecialGameControllerButtons(Event *event)
2468 switch (event->type)
2470 case SDL_CONTROLLERBUTTONDOWN:
2471 key_status = KEY_PRESSED;
2474 case SDL_CONTROLLERBUTTONUP:
2475 key_status = KEY_RELEASED;
2482 switch (event->cbutton.button)
2484 case SDL_CONTROLLER_BUTTON_START:
2488 case SDL_CONTROLLER_BUTTON_BACK:
2496 HandleKey(key, key_status);
2499 void HandleSpecialGameControllerKeys(Key key, int key_status)
2501 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2502 int button = SDL_CONTROLLER_BUTTON_INVALID;
2504 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2505 if (key == KSYM_Rewind)
2506 button = SDL_CONTROLLER_BUTTON_A;
2507 else if (key == KSYM_FastForward || key == KSYM_Menu)
2508 button = SDL_CONTROLLER_BUTTON_B;
2510 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2514 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2515 SDL_CONTROLLERBUTTONUP);
2517 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2518 event.cbutton.button = button;
2519 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2522 HandleJoystickEvent(&event);
2527 boolean DoKeysymAction(int keysym)
2531 Key key = (Key)(-keysym);
2533 HandleKey(key, KEY_PRESSED);
2534 HandleKey(key, KEY_RELEASED);