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;
43 /* forward declarations for internal use */
44 static void HandleNoEvent(void);
45 static void HandleEventActions(void);
48 /* event filter especially needed for SDL event filtering due to
49 delay problems with lots of mouse motion events when mouse button
50 not pressed (X11 can handle this with 'PointerMotionHintMask') */
52 /* event filter addition for SDL2: as SDL2 does not have a function to enable
53 or disable keyboard auto-repeat, filter repeated keyboard events instead */
55 static int FilterEvents(const Event *event)
59 #if defined(TARGET_SDL2)
60 /* skip repeated key press events if keyboard auto-repeat is disabled */
61 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))
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 #if defined(TARGET_SDL2)
189 case EVENT_WHEELMOTION:
190 HandleWheelEvent((WheelEvent *) &event);
193 case SDL_WINDOWEVENT:
194 HandleWindowEvent((WindowEvent *) &event);
197 case EVENT_FINGERPRESS:
198 case EVENT_FINGERRELEASE:
199 case EVENT_FINGERMOTION:
200 HandleFingerEvent((FingerEvent *) &event);
203 case EVENT_TEXTINPUT:
204 HandleTextEvent((TextEvent *) &event);
207 case SDL_APP_WILLENTERBACKGROUND:
208 case SDL_APP_DIDENTERBACKGROUND:
209 case SDL_APP_WILLENTERFOREGROUND:
210 case SDL_APP_DIDENTERFOREGROUND:
211 HandlePauseResumeEvent((PauseResumeEvent *) &event);
216 case EVENT_KEYRELEASE:
217 HandleKeyEvent((KeyEvent *) &event);
221 HandleOtherEvents(&event);
225 // do not handle events for longer than standard frame delay period
226 if (DelayReached(&event_frame_delay, event_frame_delay_value))
231 void HandleOtherEvents(Event *event)
236 HandleExposeEvent((ExposeEvent *) event);
239 case EVENT_UNMAPNOTIFY:
241 /* This causes the game to stop not only when iconified, but also
242 when on another virtual desktop, which might be not desired. */
243 SleepWhileUnmapped();
249 HandleFocusEvent((FocusChangeEvent *) event);
252 case EVENT_CLIENTMESSAGE:
253 HandleClientMessageEvent((ClientMessageEvent *) event);
256 #if defined(TARGET_SDL)
257 #if defined(TARGET_SDL2)
258 case SDL_CONTROLLERBUTTONDOWN:
259 case SDL_CONTROLLERBUTTONUP:
260 // for any game controller button event, disable overlay buttons
261 SetOverlayEnabled(FALSE);
263 HandleSpecialGameControllerButtons(event);
266 case SDL_CONTROLLERDEVICEADDED:
267 case SDL_CONTROLLERDEVICEREMOVED:
268 case SDL_CONTROLLERAXISMOTION:
270 case SDL_JOYAXISMOTION:
271 case SDL_JOYBUTTONDOWN:
272 case SDL_JOYBUTTONUP:
273 HandleJoystickEvent(event);
277 HandleWindowManagerEvent(event);
286 void HandleMouseCursor()
288 if (game_status == GAME_MODE_TITLE)
290 /* when showing title screens, hide mouse pointer (if not moved) */
292 if (gfx.cursor_mode != CURSOR_NONE &&
293 DelayReached(&special_cursor_delay, special_cursor_delay_value))
295 SetMouseCursor(CURSOR_NONE);
298 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
301 /* when playing, display a special mouse pointer inside the playfield */
303 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
304 cursor_inside_playfield &&
305 DelayReached(&special_cursor_delay, special_cursor_delay_value))
307 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
309 SetMouseCursor(CURSOR_PLAYFIELD);
312 else if (gfx.cursor_mode != CURSOR_DEFAULT)
314 SetMouseCursor(CURSOR_DEFAULT);
317 /* this is set after all pending events have been processed */
318 cursor_mode_last = gfx.cursor_mode;
330 /* execute event related actions after pending events have been processed */
331 HandleEventActions();
333 /* don't use all CPU time when idle; the main loop while playing
334 has its own synchronization and is CPU friendly, too */
336 if (game_status == GAME_MODE_PLAYING)
339 /* always copy backbuffer to visible screen for every video frame */
342 /* reset video frame delay to default (may change again while playing) */
343 SetVideoFrameDelay(MenuFrameDelay);
345 if (game_status == GAME_MODE_QUIT)
350 void ClearEventQueue()
354 while (NextValidEvent(&event))
358 case EVENT_BUTTONRELEASE:
359 button_status = MB_RELEASED;
362 case EVENT_KEYRELEASE:
366 #if defined(TARGET_SDL2)
367 case SDL_CONTROLLERBUTTONUP:
368 HandleJoystickEvent(&event);
374 HandleOtherEvents(&event);
380 void ClearPlayerMouseAction()
382 local_player->mouse_action.lx = 0;
383 local_player->mouse_action.ly = 0;
384 local_player->mouse_action.button = 0;
387 void ClearPlayerAction()
391 /* simulate key release events for still pressed keys */
392 key_joystick_mapping = 0;
393 for (i = 0; i < MAX_PLAYERS; i++)
394 stored_player[i].action = 0;
396 ClearJoystickState();
397 ClearPlayerMouseAction();
400 void SetPlayerMouseAction(int mx, int my, int button)
402 int lx = getLevelFromScreenX(mx);
403 int ly = getLevelFromScreenY(my);
404 int new_button = (!local_player->mouse_action.button && button);
406 if (local_player->mouse_action.button_hint)
407 button = local_player->mouse_action.button_hint;
409 ClearPlayerMouseAction();
411 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
414 local_player->mouse_action.lx = lx;
415 local_player->mouse_action.ly = ly;
416 local_player->mouse_action.button = button;
418 if (tape.recording && tape.pausing && tape.use_mouse)
420 /* un-pause a paused game only if mouse button was newly pressed down */
422 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
425 SetTileCursorXY(lx, ly);
428 void SleepWhileUnmapped()
430 boolean window_unmapped = TRUE;
432 KeyboardAutoRepeatOn();
434 while (window_unmapped)
438 if (!WaitValidEvent(&event))
443 case EVENT_BUTTONRELEASE:
444 button_status = MB_RELEASED;
447 case EVENT_KEYRELEASE:
448 key_joystick_mapping = 0;
451 #if defined(TARGET_SDL2)
452 case SDL_CONTROLLERBUTTONUP:
453 HandleJoystickEvent(&event);
454 key_joystick_mapping = 0;
458 case EVENT_MAPNOTIFY:
459 window_unmapped = FALSE;
462 case EVENT_UNMAPNOTIFY:
463 /* this is only to surely prevent the 'should not happen' case
464 * of recursively looping between 'SleepWhileUnmapped()' and
465 * 'HandleOtherEvents()' which usually calls this funtion.
470 HandleOtherEvents(&event);
475 if (game_status == GAME_MODE_PLAYING)
476 KeyboardAutoRepeatOffUnlessAutoplay();
479 void HandleExposeEvent(ExposeEvent *event)
483 void HandleButtonEvent(ButtonEvent *event)
485 #if DEBUG_EVENTS_BUTTON
486 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
488 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
492 // for any mouse button event, disable playfield tile cursor
493 SetTileCursorEnabled(FALSE);
495 #if defined(HAS_SCREEN_KEYBOARD)
496 if (video.shifted_up)
497 event->y += video.shifted_up_pos;
500 motion_status = FALSE;
502 if (event->type == EVENT_BUTTONPRESS)
503 button_status = event->button;
505 button_status = MB_RELEASED;
507 HandleButton(event->x, event->y, button_status, event->button);
510 void HandleMotionEvent(MotionEvent *event)
512 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
515 motion_status = TRUE;
517 #if DEBUG_EVENTS_MOTION
518 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
519 button_status, event->x, event->y);
522 HandleButton(event->x, event->y, button_status, button_status);
525 #if defined(TARGET_SDL2)
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 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 // for any touch input event, enable overlay buttons (if activated)
724 SetOverlayEnabled(TRUE);
726 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
727 getKeyNameFromKey(key), key_status_name, event->fingerId);
729 if (key_status == KEY_PRESSED)
730 overlay.grid_button_action |= grid_button_action;
732 overlay.grid_button_action &= ~grid_button_action;
734 // check if we already know this touch event's finger id
735 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
737 if (touch_info[i].touched &&
738 touch_info[i].finger_id == event->fingerId)
740 // Error(ERR_DEBUG, "MARK 1: %d", i);
746 if (i >= NUM_TOUCH_FINGERS)
748 if (key_status == KEY_PRESSED)
750 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
752 // unknown finger id -- get new, empty slot, if available
753 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
755 if (touch_info[i].counter < oldest_counter)
758 oldest_counter = touch_info[i].counter;
760 // Error(ERR_DEBUG, "MARK 2: %d", i);
763 if (!touch_info[i].touched)
765 // Error(ERR_DEBUG, "MARK 3: %d", i);
771 if (i >= NUM_TOUCH_FINGERS)
773 // all slots allocated -- use oldest slot
776 // Error(ERR_DEBUG, "MARK 4: %d", i);
781 // release of previously unknown key (should not happen)
783 if (key != KSYM_UNDEFINED)
785 HandleKey(key, KEY_RELEASED);
787 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
788 getKeyNameFromKey(key), "KEY_RELEASED", i);
793 if (i < NUM_TOUCH_FINGERS)
795 if (key_status == KEY_PRESSED)
797 if (touch_info[i].key != key)
799 if (touch_info[i].key != KSYM_UNDEFINED)
801 HandleKey(touch_info[i].key, KEY_RELEASED);
803 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
804 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
807 if (key != KSYM_UNDEFINED)
809 HandleKey(key, KEY_PRESSED);
811 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
812 getKeyNameFromKey(key), "KEY_PRESSED", i);
816 touch_info[i].touched = TRUE;
817 touch_info[i].finger_id = event->fingerId;
818 touch_info[i].counter = Counter();
819 touch_info[i].key = key;
823 if (touch_info[i].key != KSYM_UNDEFINED)
825 HandleKey(touch_info[i].key, KEY_RELEASED);
827 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
828 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
831 touch_info[i].touched = FALSE;
832 touch_info[i].finger_id = 0;
833 touch_info[i].counter = 0;
834 touch_info[i].key = 0;
839 void HandleFingerEvent_WipeGestures(FingerEvent *event)
841 static Key motion_key_x = KSYM_UNDEFINED;
842 static Key motion_key_y = KSYM_UNDEFINED;
843 static Key button_key = KSYM_UNDEFINED;
844 static float motion_x1, motion_y1;
845 static float button_x1, button_y1;
846 static SDL_FingerID motion_id = -1;
847 static SDL_FingerID button_id = -1;
848 int move_trigger_distance_percent = setup.touch.move_distance;
849 int drop_trigger_distance_percent = setup.touch.drop_distance;
850 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
851 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
852 float event_x = event->x;
853 float event_y = event->y;
855 if (event->type == EVENT_FINGERPRESS)
857 if (event_x > 1.0 / 3.0)
861 motion_id = event->fingerId;
866 motion_key_x = KSYM_UNDEFINED;
867 motion_key_y = KSYM_UNDEFINED;
869 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
875 button_id = event->fingerId;
880 button_key = setup.input[0].key.snap;
882 HandleKey(button_key, KEY_PRESSED);
884 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
887 else if (event->type == EVENT_FINGERRELEASE)
889 if (event->fingerId == motion_id)
893 if (motion_key_x != KSYM_UNDEFINED)
894 HandleKey(motion_key_x, KEY_RELEASED);
895 if (motion_key_y != KSYM_UNDEFINED)
896 HandleKey(motion_key_y, KEY_RELEASED);
898 motion_key_x = KSYM_UNDEFINED;
899 motion_key_y = KSYM_UNDEFINED;
901 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
903 else if (event->fingerId == button_id)
907 if (button_key != KSYM_UNDEFINED)
908 HandleKey(button_key, KEY_RELEASED);
910 button_key = KSYM_UNDEFINED;
912 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
915 else if (event->type == EVENT_FINGERMOTION)
917 if (event->fingerId == motion_id)
919 float distance_x = ABS(event_x - motion_x1);
920 float distance_y = ABS(event_y - motion_y1);
921 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
922 event_x > motion_x1 ? setup.input[0].key.right :
924 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
925 event_y > motion_y1 ? setup.input[0].key.down :
928 if (distance_x < move_trigger_distance / 2 ||
929 distance_x < distance_y)
930 new_motion_key_x = KSYM_UNDEFINED;
932 if (distance_y < move_trigger_distance / 2 ||
933 distance_y < distance_x)
934 new_motion_key_y = KSYM_UNDEFINED;
936 if (distance_x > move_trigger_distance ||
937 distance_y > move_trigger_distance)
939 if (new_motion_key_x != motion_key_x)
941 if (motion_key_x != KSYM_UNDEFINED)
942 HandleKey(motion_key_x, KEY_RELEASED);
943 if (new_motion_key_x != KSYM_UNDEFINED)
944 HandleKey(new_motion_key_x, KEY_PRESSED);
947 if (new_motion_key_y != motion_key_y)
949 if (motion_key_y != KSYM_UNDEFINED)
950 HandleKey(motion_key_y, KEY_RELEASED);
951 if (new_motion_key_y != KSYM_UNDEFINED)
952 HandleKey(new_motion_key_y, KEY_PRESSED);
958 motion_key_x = new_motion_key_x;
959 motion_key_y = new_motion_key_y;
961 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
964 else if (event->fingerId == button_id)
966 float distance_x = ABS(event_x - button_x1);
967 float distance_y = ABS(event_y - button_y1);
969 if (distance_x < drop_trigger_distance / 2 &&
970 distance_y > drop_trigger_distance)
972 if (button_key == setup.input[0].key.snap)
973 HandleKey(button_key, KEY_RELEASED);
978 button_key = setup.input[0].key.drop;
980 HandleKey(button_key, KEY_PRESSED);
982 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
988 void HandleFingerEvent(FingerEvent *event)
990 #if DEBUG_EVENTS_FINGER
991 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
992 event->type == EVENT_FINGERPRESS ? "pressed" :
993 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
997 event->dx, event->dy,
1001 if (game_status != GAME_MODE_PLAYING)
1004 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1006 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1007 local_player->mouse_action.button_hint =
1008 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1009 event->x < 0.5 ? MB_LEFTBUTTON :
1010 event->x > 0.5 ? MB_RIGHTBUTTON :
1016 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1017 HandleFingerEvent_VirtualButtons(event);
1018 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1019 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);
1376 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1377 HandleButtonOrFinger_FollowFinger(mx, my, button);
1381 #if defined(TARGET_SDL2)
1383 static boolean checkTextInputKeyModState()
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)
1433 void HandleKeyEvent(KeyEvent *event)
1435 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1436 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1437 Key key = GetEventKey(event, with_modifiers);
1438 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1440 #if DEBUG_EVENTS_KEY
1441 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1442 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1443 event->keysym.scancode,
1448 getKeyNameFromKey(key));
1451 #if defined(PLATFORM_ANDROID)
1452 if (key == KSYM_Back)
1454 // always map the "back" button to the "escape" key on Android devices
1459 // for any key event other than "back" button, disable overlay buttons
1460 SetOverlayEnabled(FALSE);
1464 HandleKeyModState(keymod, key_status);
1466 #if defined(TARGET_SDL2)
1467 // only handle raw key input without text modifier keys pressed
1468 if (!checkTextInputKeyModState())
1469 HandleKey(key, key_status);
1471 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.
1505 if (game_status == GAME_MODE_PLAYING)
1508 KeyboardAutoRepeatOffUnlessAutoplay();
1511 if (old_joystick_status != -1)
1512 joystick.status = old_joystick_status;
1516 void HandleClientMessageEvent(ClientMessageEvent *event)
1518 if (CheckCloseWindowEvent(event))
1522 void HandleWindowManagerEvent(Event *event)
1524 #if defined(TARGET_SDL)
1525 SDLHandleWindowManagerEvent(event);
1529 void HandleButton(int mx, int my, int button, int button_nr)
1531 static int old_mx = 0, old_my = 0;
1532 boolean button_hold = FALSE;
1533 boolean handle_gadgets = TRUE;
1539 button_nr = -button_nr;
1548 #if defined(PLATFORM_ANDROID)
1549 // when playing, only handle gadgets when using "follow finger" controls
1550 // or when using touch controls in combination with the MM game engine
1552 (game_status != GAME_MODE_PLAYING ||
1553 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1554 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1557 if (handle_gadgets && HandleGadgets(mx, my, button))
1559 /* do not handle this button event anymore */
1560 mx = my = -32; /* force mouse event to be outside screen tiles */
1563 if (HandleGlobalAnimClicks(mx, my, button))
1565 /* do not handle this button event anymore */
1566 return; /* force mouse event not to be handled at all */
1569 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1572 /* do not use scroll wheel button events for anything other than gadgets */
1573 if (IS_WHEEL_BUTTON(button_nr))
1576 switch (game_status)
1578 case GAME_MODE_TITLE:
1579 HandleTitleScreen(mx, my, 0, 0, button);
1582 case GAME_MODE_MAIN:
1583 HandleMainMenu(mx, my, 0, 0, button);
1586 case GAME_MODE_PSEUDO_TYPENAME:
1587 HandleTypeName(0, KSYM_Return);
1590 case GAME_MODE_LEVELS:
1591 HandleChooseLevelSet(mx, my, 0, 0, button);
1594 case GAME_MODE_LEVELNR:
1595 HandleChooseLevelNr(mx, my, 0, 0, button);
1598 case GAME_MODE_SCORES:
1599 HandleHallOfFame(0, 0, 0, 0, button);
1602 case GAME_MODE_EDITOR:
1603 HandleLevelEditorIdle();
1606 case GAME_MODE_INFO:
1607 HandleInfoScreen(mx, my, 0, 0, button);
1610 case GAME_MODE_SETUP:
1611 HandleSetupScreen(mx, my, 0, 0, button);
1614 case GAME_MODE_PLAYING:
1615 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1616 HandleButtonOrFinger(mx, my, button);
1618 SetPlayerMouseAction(mx, my, button);
1621 if (button == MB_PRESSED && !motion_status && !button_hold &&
1622 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1623 DumpTileFromScreen(mx, my);
1633 static boolean is_string_suffix(char *string, char *suffix)
1635 int string_len = strlen(string);
1636 int suffix_len = strlen(suffix);
1638 if (suffix_len > string_len)
1641 return (strEqual(&string[string_len - suffix_len], suffix));
1644 #define MAX_CHEAT_INPUT_LEN 32
1646 static void HandleKeysSpecial(Key key)
1648 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1649 char letter = getCharFromKey(key);
1650 int cheat_input_len = strlen(cheat_input);
1656 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1658 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1659 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1661 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1664 cheat_input[cheat_input_len++] = letter;
1665 cheat_input[cheat_input_len] = '\0';
1667 #if DEBUG_EVENTS_KEY
1668 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1671 if (game_status == GAME_MODE_MAIN)
1673 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1674 is_string_suffix(cheat_input, ":ist"))
1676 InsertSolutionTape();
1678 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1679 is_string_suffix(cheat_input, ":pst"))
1683 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1684 is_string_suffix(cheat_input, ":rg"))
1686 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1689 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1690 is_string_suffix(cheat_input, ":rs"))
1692 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1695 else if (is_string_suffix(cheat_input, ":reload-music") ||
1696 is_string_suffix(cheat_input, ":rm"))
1698 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1701 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1702 is_string_suffix(cheat_input, ":ra"))
1704 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1705 1 << ARTWORK_TYPE_SOUNDS |
1706 1 << ARTWORK_TYPE_MUSIC);
1709 else if (is_string_suffix(cheat_input, ":dump-level") ||
1710 is_string_suffix(cheat_input, ":dl"))
1714 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1715 is_string_suffix(cheat_input, ":dt"))
1719 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1720 is_string_suffix(cheat_input, ":ft"))
1722 /* fix single-player tapes that contain player input for more than one
1723 player (due to a bug in 3.3.1.2 and earlier versions), which results
1724 in playing levels with more than one player in multi-player mode,
1725 even though the tape was originally recorded in single-player mode */
1727 /* remove player input actions for all players but the first one */
1728 for (i = 1; i < MAX_PLAYERS; i++)
1729 tape.player_participates[i] = FALSE;
1731 tape.changed = TRUE;
1733 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1734 is_string_suffix(cheat_input, ":snl"))
1736 SaveNativeLevel(&level);
1738 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1739 is_string_suffix(cheat_input, ":fps"))
1741 global.show_frames_per_second = !global.show_frames_per_second;
1744 else if (game_status == GAME_MODE_PLAYING)
1747 if (is_string_suffix(cheat_input, ".q"))
1748 DEBUG_SetMaximumDynamite();
1751 else if (game_status == GAME_MODE_EDITOR)
1753 if (is_string_suffix(cheat_input, ":dump-brush") ||
1754 is_string_suffix(cheat_input, ":DB"))
1758 else if (is_string_suffix(cheat_input, ":DDB"))
1765 void HandleKeysDebug(Key key)
1770 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1772 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1774 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1776 if (key == setup.debug.frame_delay_key[i] &&
1777 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1779 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1780 setup.debug.frame_delay[i] : setup.game_frame_delay);
1782 if (!setup.debug.frame_delay_game_only)
1783 MenuFrameDelay = GameFrameDelay;
1785 SetVideoFrameDelay(GameFrameDelay);
1787 if (GameFrameDelay > ONE_SECOND_DELAY)
1788 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1789 else if (GameFrameDelay != 0)
1790 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1791 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1792 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1794 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1801 if (game_status == GAME_MODE_PLAYING)
1805 options.debug = !options.debug;
1807 Error(ERR_DEBUG, "debug mode %s",
1808 (options.debug ? "enabled" : "disabled"));
1810 else if (key == KSYM_v)
1812 Error(ERR_DEBUG, "currently using game engine version %d",
1813 game.engine_version);
1819 void HandleKey(Key key, int key_status)
1821 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1822 static boolean ignore_repeated_key = FALSE;
1823 static struct SetupKeyboardInfo ski;
1824 static struct SetupShortcutInfo ssi;
1833 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1834 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1835 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1836 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1837 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1838 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1843 #if defined(TARGET_SDL2)
1844 /* map special keys (media keys / remote control buttons) to default keys */
1845 if (key == KSYM_PlayPause)
1847 else if (key == KSYM_Select)
1851 HandleSpecialGameControllerKeys(key, key_status);
1853 if (game_status == GAME_MODE_PLAYING)
1855 /* only needed for single-step tape recording mode */
1856 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1859 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1861 byte key_action = 0;
1863 if (setup.input[pnr].use_joystick)
1866 ski = setup.input[pnr].key;
1868 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1869 if (key == *key_info[i].key_custom)
1870 key_action |= key_info[i].action;
1872 /* use combined snap+direction keys for the first player only */
1875 ssi = setup.shortcut;
1877 for (i = 0; i < NUM_DIRECTIONS; i++)
1878 if (key == *key_info[i].key_snap)
1879 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1882 if (key_status == KEY_PRESSED)
1883 stored_player[pnr].action |= key_action;
1885 stored_player[pnr].action &= ~key_action;
1887 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
1889 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1891 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1893 /* if snap key already pressed, keep pause mode when releasing */
1894 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1895 has_snapped[pnr] = TRUE;
1897 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1899 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1901 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1902 getRedDiskReleaseFlag_SP() == 0)
1904 /* add a single inactive frame before dropping starts */
1905 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1906 stored_player[pnr].force_dropping = TRUE;
1909 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1911 /* if snap key was pressed without direction, leave pause mode */
1912 if (!has_snapped[pnr])
1913 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1915 has_snapped[pnr] = FALSE;
1918 else if (tape.recording && tape.pausing && !tape.use_mouse)
1920 /* prevent key release events from un-pausing a paused game */
1921 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1922 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1925 // for MM style levels, handle in-game keyboard input in HandleJoystick()
1926 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1932 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1933 if (key == key_info[i].key_default)
1934 joy |= key_info[i].action;
1939 if (key_status == KEY_PRESSED)
1940 key_joystick_mapping |= joy;
1942 key_joystick_mapping &= ~joy;
1947 if (game_status != GAME_MODE_PLAYING)
1948 key_joystick_mapping = 0;
1950 if (key_status == KEY_RELEASED)
1952 // reset flag to ignore repeated "key pressed" events after key release
1953 ignore_repeated_key = FALSE;
1958 if ((key == KSYM_F11 ||
1959 ((key == KSYM_Return ||
1960 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1961 video.fullscreen_available &&
1962 !ignore_repeated_key)
1964 setup.fullscreen = !setup.fullscreen;
1966 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1968 if (game_status == GAME_MODE_SETUP)
1969 RedrawSetupScreenAfterFullscreenToggle();
1971 // set flag to ignore repeated "key pressed" events
1972 ignore_repeated_key = TRUE;
1977 if ((key == KSYM_0 || key == KSYM_KP_0 ||
1978 key == KSYM_minus || key == KSYM_KP_Subtract ||
1979 key == KSYM_plus || key == KSYM_KP_Add ||
1980 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
1981 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1982 video.window_scaling_available &&
1983 !video.fullscreen_enabled)
1985 if (key == KSYM_0 || key == KSYM_KP_0)
1986 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1987 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1988 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1990 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1992 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1993 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1994 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1995 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1997 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1999 if (game_status == GAME_MODE_SETUP)
2000 RedrawSetupScreenAfterFullscreenToggle();
2005 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2006 key == KSYM_Return ||
2007 key == KSYM_Escape)))
2009 /* do not handle this key event anymore */
2010 if (key != KSYM_Escape) /* always allow ESC key to be handled */
2014 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
2015 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2022 if (game_status == GAME_MODE_MAIN &&
2023 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2025 StartGameActions(options.network, setup.autorecord, level.random_seed);
2030 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2032 if (key == setup.shortcut.save_game)
2034 else if (key == setup.shortcut.load_game)
2036 else if (key == setup.shortcut.toggle_pause)
2037 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2039 HandleTapeButtonKeys(key);
2040 HandleSoundButtonKeys(key);
2043 if (game_status == GAME_MODE_PLAYING && !network_playing)
2045 int centered_player_nr_next = -999;
2047 if (key == setup.shortcut.focus_player_all)
2048 centered_player_nr_next = -1;
2050 for (i = 0; i < MAX_PLAYERS; i++)
2051 if (key == setup.shortcut.focus_player[i])
2052 centered_player_nr_next = i;
2054 if (centered_player_nr_next != -999)
2056 game.centered_player_nr_next = centered_player_nr_next;
2057 game.set_centered_player = TRUE;
2061 tape.centered_player_nr_next = game.centered_player_nr_next;
2062 tape.set_centered_player = TRUE;
2067 HandleKeysSpecial(key);
2069 if (HandleGadgetsKeyInput(key))
2071 if (key != KSYM_Escape) /* always allow ESC key to be handled */
2072 key = KSYM_UNDEFINED;
2075 switch (game_status)
2077 case GAME_MODE_PSEUDO_TYPENAME:
2078 HandleTypeName(0, key);
2081 case GAME_MODE_TITLE:
2082 case GAME_MODE_MAIN:
2083 case GAME_MODE_LEVELS:
2084 case GAME_MODE_LEVELNR:
2085 case GAME_MODE_SETUP:
2086 case GAME_MODE_INFO:
2087 case GAME_MODE_SCORES:
2092 if (game_status == GAME_MODE_TITLE)
2093 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2094 else if (game_status == GAME_MODE_MAIN)
2095 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2096 else if (game_status == GAME_MODE_LEVELS)
2097 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2098 else if (game_status == GAME_MODE_LEVELNR)
2099 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2100 else if (game_status == GAME_MODE_SETUP)
2101 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2102 else if (game_status == GAME_MODE_INFO)
2103 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2104 else if (game_status == GAME_MODE_SCORES)
2105 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2109 if (game_status != GAME_MODE_MAIN)
2110 FadeSkipNextFadeIn();
2112 if (game_status == GAME_MODE_TITLE)
2113 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2114 else if (game_status == GAME_MODE_LEVELS)
2115 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2116 else if (game_status == GAME_MODE_LEVELNR)
2117 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2118 else if (game_status == GAME_MODE_SETUP)
2119 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2120 else if (game_status == GAME_MODE_INFO)
2121 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2122 else if (game_status == GAME_MODE_SCORES)
2123 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2127 if (game_status == GAME_MODE_LEVELS)
2128 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2129 else if (game_status == GAME_MODE_LEVELNR)
2130 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2131 else if (game_status == GAME_MODE_SETUP)
2132 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2133 else if (game_status == GAME_MODE_INFO)
2134 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2135 else if (game_status == GAME_MODE_SCORES)
2136 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2139 case KSYM_Page_Down:
2140 if (game_status == GAME_MODE_LEVELS)
2141 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2142 else if (game_status == GAME_MODE_LEVELNR)
2143 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2144 else if (game_status == GAME_MODE_SETUP)
2145 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2146 else if (game_status == GAME_MODE_INFO)
2147 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2148 else if (game_status == GAME_MODE_SCORES)
2149 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2157 case GAME_MODE_EDITOR:
2158 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2159 HandleLevelEditorKeyInput(key);
2162 case GAME_MODE_PLAYING:
2167 RequestQuitGame(setup.ask_on_escape);
2177 if (key == KSYM_Escape)
2179 SetGameStatus(GAME_MODE_MAIN);
2187 HandleKeysDebug(key);
2190 void HandleNoEvent()
2192 HandleMouseCursor();
2194 switch (game_status)
2196 case GAME_MODE_PLAYING:
2197 HandleButtonOrFinger(-1, -1, -1);
2202 void HandleEventActions()
2204 // if (button_status && game_status != GAME_MODE_PLAYING)
2205 if (button_status && (game_status != GAME_MODE_PLAYING ||
2207 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2209 HandleButton(0, 0, button_status, -button_status);
2216 #if defined(NETWORK_AVALIABLE)
2217 if (options.network)
2221 switch (game_status)
2223 case GAME_MODE_MAIN:
2224 DrawPreviewLevelAnimation();
2227 case GAME_MODE_EDITOR:
2228 HandleLevelEditorIdle();
2236 static void HandleTileCursor(int dx, int dy, int button)
2239 ClearPlayerMouseAction();
2246 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2247 (dx < 0 ? MB_LEFTBUTTON :
2248 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2250 else if (!tile_cursor.moving)
2252 int old_xpos = tile_cursor.xpos;
2253 int old_ypos = tile_cursor.ypos;
2254 int new_xpos = old_xpos;
2255 int new_ypos = old_ypos;
2257 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2258 new_xpos = old_xpos + dx;
2260 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2261 new_ypos = old_ypos + dy;
2263 SetTileCursorTargetXY(new_xpos, new_ypos);
2267 static int HandleJoystickForAllPlayers()
2271 boolean no_joysticks_configured = TRUE;
2272 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2273 static byte joy_action_last[MAX_PLAYERS];
2275 for (i = 0; i < MAX_PLAYERS; i++)
2276 if (setup.input[i].use_joystick)
2277 no_joysticks_configured = FALSE;
2279 /* if no joysticks configured, map connected joysticks to players */
2280 if (no_joysticks_configured)
2281 use_as_joystick_nr = TRUE;
2283 for (i = 0; i < MAX_PLAYERS; i++)
2285 byte joy_action = 0;
2287 joy_action = JoystickExt(i, use_as_joystick_nr);
2288 result |= joy_action;
2290 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2291 joy_action != joy_action_last[i])
2292 stored_player[i].action = joy_action;
2294 joy_action_last[i] = joy_action;
2300 void HandleJoystick()
2302 static unsigned int joytest_delay = 0;
2303 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2304 static int joytest_last = 0;
2305 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2306 int delay_value = GADGET_FRAME_DELAY;
2307 int joystick = HandleJoystickForAllPlayers();
2308 int keyboard = key_joystick_mapping;
2309 int joy = (joystick | keyboard);
2310 int joytest = joystick;
2311 int left = joy & JOY_LEFT;
2312 int right = joy & JOY_RIGHT;
2313 int up = joy & JOY_UP;
2314 int down = joy & JOY_DOWN;
2315 int button = joy & JOY_BUTTON;
2316 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2317 int dx = (left ? -1 : right ? 1 : 0);
2318 int dy = (up ? -1 : down ? 1 : 0);
2319 boolean use_delay_value_first = (joytest != joytest_last);
2321 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2323 /* do not handle this button event anymore */
2327 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2329 if (game_status == GAME_MODE_PLAYING)
2331 // when playing MM style levels, also use delay for keyboard events
2332 joytest |= keyboard;
2334 // only use first delay value for new events, but not for changed events
2335 use_delay_value_first = (!joytest != !joytest_last);
2337 // only use delay after the initial keyboard event
2341 // for any joystick or keyboard event, enable playfield tile cursor
2342 if (dx || dy || button)
2343 SetTileCursorEnabled(TRUE);
2346 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2348 /* delay joystick/keyboard actions if axes/keys continually pressed */
2349 newbutton = dx = dy = 0;
2353 /* first start with longer delay, then continue with shorter delay */
2354 joytest_delay_value =
2355 (use_delay_value_first ? delay_value_first : delay_value);
2358 joytest_last = joytest;
2360 switch (game_status)
2362 case GAME_MODE_TITLE:
2363 case GAME_MODE_MAIN:
2364 case GAME_MODE_LEVELS:
2365 case GAME_MODE_LEVELNR:
2366 case GAME_MODE_SETUP:
2367 case GAME_MODE_INFO:
2368 case GAME_MODE_SCORES:
2370 if (game_status == GAME_MODE_TITLE)
2371 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2372 else if (game_status == GAME_MODE_MAIN)
2373 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2374 else if (game_status == GAME_MODE_LEVELS)
2375 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2376 else if (game_status == GAME_MODE_LEVELNR)
2377 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2378 else if (game_status == GAME_MODE_SETUP)
2379 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2380 else if (game_status == GAME_MODE_INFO)
2381 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2382 else if (game_status == GAME_MODE_SCORES)
2383 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2388 case GAME_MODE_PLAYING:
2390 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2391 if (tape.playing || keyboard)
2392 newbutton = ((joy & JOY_BUTTON) != 0);
2395 if (newbutton && AllPlayersGone)
2402 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2404 if (joystick & JOY_ACTION)
2405 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2407 else if (tape.recording && tape.pausing && !tape.use_mouse)
2409 if (joystick & JOY_ACTION)
2410 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2413 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2414 HandleTileCursor(dx, dy, button);
2423 void HandleSpecialGameControllerButtons(Event *event)
2425 #if defined(TARGET_SDL2)
2429 switch (event->type)
2431 case SDL_CONTROLLERBUTTONDOWN:
2432 key_status = KEY_PRESSED;
2435 case SDL_CONTROLLERBUTTONUP:
2436 key_status = KEY_RELEASED;
2443 switch (event->cbutton.button)
2445 case SDL_CONTROLLER_BUTTON_START:
2449 case SDL_CONTROLLER_BUTTON_BACK:
2457 HandleKey(key, key_status);
2461 void HandleSpecialGameControllerKeys(Key key, int key_status)
2463 #if defined(TARGET_SDL2)
2464 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2465 int button = SDL_CONTROLLER_BUTTON_INVALID;
2467 /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2468 if (key == KSYM_Rewind)
2469 button = SDL_CONTROLLER_BUTTON_A;
2470 else if (key == KSYM_FastForward || key == KSYM_Menu)
2471 button = SDL_CONTROLLER_BUTTON_B;
2473 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2477 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2478 SDL_CONTROLLERBUTTONUP);
2480 event.cbutton.which = 0; /* first joystick (Amazon Fire TV remote) */
2481 event.cbutton.button = button;
2482 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2485 HandleJoystickEvent(&event);