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)
308 SetMouseCursor(CURSOR_PLAYFIELD);
311 else if (gfx.cursor_mode != CURSOR_DEFAULT)
313 SetMouseCursor(CURSOR_DEFAULT);
316 /* this is set after all pending events have been processed */
317 cursor_mode_last = gfx.cursor_mode;
329 /* execute event related actions after pending events have been processed */
330 HandleEventActions();
332 /* don't use all CPU time when idle; the main loop while playing
333 has its own synchronization and is CPU friendly, too */
335 if (game_status == GAME_MODE_PLAYING)
338 /* always copy backbuffer to visible screen for every video frame */
341 /* reset video frame delay to default (may change again while playing) */
342 SetVideoFrameDelay(MenuFrameDelay);
344 if (game_status == GAME_MODE_QUIT)
349 void ClearEventQueue()
353 while (NextValidEvent(&event))
357 case EVENT_BUTTONRELEASE:
358 button_status = MB_RELEASED;
361 case EVENT_KEYRELEASE:
365 #if defined(TARGET_SDL2)
366 case SDL_CONTROLLERBUTTONUP:
367 HandleJoystickEvent(&event);
373 HandleOtherEvents(&event);
379 void ClearPlayerMouseAction()
381 local_player->mouse_action.lx = 0;
382 local_player->mouse_action.ly = 0;
383 local_player->mouse_action.button = 0;
386 void ClearPlayerAction()
390 /* simulate key release events for still pressed keys */
391 key_joystick_mapping = 0;
392 for (i = 0; i < MAX_PLAYERS; i++)
393 stored_player[i].action = 0;
395 ClearJoystickState();
396 ClearPlayerMouseAction();
399 void SetPlayerMouseAction(int mx, int my, int button)
401 int lx = getLevelFromScreenX(mx);
402 int ly = getLevelFromScreenY(my);
404 ClearPlayerMouseAction();
406 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
409 local_player->mouse_action.lx = lx;
410 local_player->mouse_action.ly = ly;
411 local_player->mouse_action.button = button;
413 if (tape.recording && tape.pausing && tape.use_mouse)
415 /* prevent button release or motion events from un-pausing a paused game */
416 if (button && !motion_status)
417 TapeTogglePause(TAPE_TOGGLE_MANUAL);
421 void SleepWhileUnmapped()
423 boolean window_unmapped = TRUE;
425 KeyboardAutoRepeatOn();
427 while (window_unmapped)
431 if (!WaitValidEvent(&event))
436 case EVENT_BUTTONRELEASE:
437 button_status = MB_RELEASED;
440 case EVENT_KEYRELEASE:
441 key_joystick_mapping = 0;
444 #if defined(TARGET_SDL2)
445 case SDL_CONTROLLERBUTTONUP:
446 HandleJoystickEvent(&event);
447 key_joystick_mapping = 0;
451 case EVENT_MAPNOTIFY:
452 window_unmapped = FALSE;
455 case EVENT_UNMAPNOTIFY:
456 /* this is only to surely prevent the 'should not happen' case
457 * of recursively looping between 'SleepWhileUnmapped()' and
458 * 'HandleOtherEvents()' which usually calls this funtion.
463 HandleOtherEvents(&event);
468 if (game_status == GAME_MODE_PLAYING)
469 KeyboardAutoRepeatOffUnlessAutoplay();
472 void HandleExposeEvent(ExposeEvent *event)
476 void HandleButtonEvent(ButtonEvent *event)
478 #if DEBUG_EVENTS_BUTTON
479 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
481 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
485 // for any mouse button event, disable playfield tile cursor
486 SetTileCursorEnabled(FALSE);
488 #if defined(HAS_SCREEN_KEYBOARD)
489 if (video.shifted_up)
490 event->y += video.shifted_up_pos;
493 motion_status = FALSE;
495 if (event->type == EVENT_BUTTONPRESS)
496 button_status = event->button;
498 button_status = MB_RELEASED;
500 HandleButton(event->x, event->y, button_status, event->button);
503 void HandleMotionEvent(MotionEvent *event)
505 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
508 motion_status = TRUE;
510 #if DEBUG_EVENTS_MOTION
511 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
512 button_status, event->x, event->y);
515 HandleButton(event->x, event->y, button_status, button_status);
518 #if defined(TARGET_SDL2)
520 void HandleWheelEvent(WheelEvent *event)
524 #if DEBUG_EVENTS_WHEEL
526 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
527 event->which, event->x, event->y);
529 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
530 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
531 event->which, event->x, event->y,
532 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
533 "SDL_MOUSEWHEEL_FLIPPED"));
537 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
538 event->x > 0 ? MB_WHEEL_RIGHT :
539 event->y < 0 ? MB_WHEEL_DOWN :
540 event->y > 0 ? MB_WHEEL_UP : 0);
542 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
543 // accelerated mouse wheel available on Mac and Windows
544 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
546 // no accelerated mouse wheel available on Unix/Linux
547 wheel_steps = DEFAULT_WHEEL_STEPS;
550 motion_status = FALSE;
552 button_status = button_nr;
553 HandleButton(0, 0, button_status, -button_nr);
555 button_status = MB_RELEASED;
556 HandleButton(0, 0, button_status, -button_nr);
559 void HandleWindowEvent(WindowEvent *event)
561 #if DEBUG_EVENTS_WINDOW
562 int subtype = event->event;
565 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
566 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
567 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
568 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
569 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
570 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
571 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
572 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
573 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
574 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
575 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
576 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
577 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
578 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
581 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
582 event_name, event->data1, event->data2);
586 // (not needed, as the screen gets redrawn every 20 ms anyway)
587 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
588 event->event == SDL_WINDOWEVENT_RESIZED ||
589 event->event == SDL_WINDOWEVENT_EXPOSED)
593 if (event->event == SDL_WINDOWEVENT_RESIZED)
595 if (!video.fullscreen_enabled)
597 int new_window_width = event->data1;
598 int new_window_height = event->data2;
600 // if window size has changed after resizing, calculate new scaling factor
601 if (new_window_width != video.window_width ||
602 new_window_height != video.window_height)
604 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
605 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
607 // (extreme window scaling allowed, but cannot be saved permanently)
608 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
609 setup.window_scaling_percent =
610 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
611 MAX_WINDOW_SCALING_PERCENT);
613 video.window_width = new_window_width;
614 video.window_height = new_window_height;
616 if (game_status == GAME_MODE_SETUP)
617 RedrawSetupScreenAfterFullscreenToggle();
622 #if defined(PLATFORM_ANDROID)
625 int new_display_width = event->data1;
626 int new_display_height = event->data2;
628 // if fullscreen display size has changed, device has been rotated
629 if (new_display_width != video.display_width ||
630 new_display_height != video.display_height)
632 video.display_width = new_display_width;
633 video.display_height = new_display_height;
635 SDLSetScreenProperties();
642 #define NUM_TOUCH_FINGERS 3
647 SDL_FingerID finger_id;
650 } touch_info[NUM_TOUCH_FINGERS];
652 void HandleFingerEvent_VirtualButtons(FingerEvent *event)
654 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
655 float event_x = (event->x);
656 float event_y = (event->y - ypos) / (1 - ypos);
657 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
658 event_y > 2.0 / 3.0 && event_y < 1 ?
659 setup.input[0].key.snap :
660 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
661 event_y > 2.0 / 3.0 && event_y < 1 ?
662 setup.input[0].key.drop :
663 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
664 event_y > 0 && event_y < 1.0 / 3.0 ?
665 setup.input[0].key.up :
666 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
667 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
668 setup.input[0].key.left :
669 event_x > 8.0 / 9.0 && event_x < 1 &&
670 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
671 setup.input[0].key.right :
672 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
673 event_y > 2.0 / 3.0 && event_y < 1 ?
674 setup.input[0].key.down :
676 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
678 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
682 // for any touch input event, enable overlay buttons (if activated)
683 SetOverlayEnabled(TRUE);
685 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
686 getKeyNameFromKey(key), key_status_name, event->fingerId);
688 // check if we already know this touch event's finger id
689 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
691 if (touch_info[i].touched &&
692 touch_info[i].finger_id == event->fingerId)
694 // Error(ERR_DEBUG, "MARK 1: %d", i);
700 if (i >= NUM_TOUCH_FINGERS)
702 if (key_status == KEY_PRESSED)
704 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
706 // unknown finger id -- get new, empty slot, if available
707 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
709 if (touch_info[i].counter < oldest_counter)
712 oldest_counter = touch_info[i].counter;
714 // Error(ERR_DEBUG, "MARK 2: %d", i);
717 if (!touch_info[i].touched)
719 // Error(ERR_DEBUG, "MARK 3: %d", i);
725 if (i >= NUM_TOUCH_FINGERS)
727 // all slots allocated -- use oldest slot
730 // Error(ERR_DEBUG, "MARK 4: %d", i);
735 // release of previously unknown key (should not happen)
737 if (key != KSYM_UNDEFINED)
739 HandleKey(key, KEY_RELEASED);
741 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
742 getKeyNameFromKey(key), "KEY_RELEASED", i);
747 if (i < NUM_TOUCH_FINGERS)
749 if (key_status == KEY_PRESSED)
751 if (touch_info[i].key != key)
753 if (touch_info[i].key != KSYM_UNDEFINED)
755 HandleKey(touch_info[i].key, KEY_RELEASED);
757 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
758 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
761 if (key != KSYM_UNDEFINED)
763 HandleKey(key, KEY_PRESSED);
765 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
766 getKeyNameFromKey(key), "KEY_PRESSED", i);
770 touch_info[i].touched = TRUE;
771 touch_info[i].finger_id = event->fingerId;
772 touch_info[i].counter = Counter();
773 touch_info[i].key = key;
777 if (touch_info[i].key != KSYM_UNDEFINED)
779 HandleKey(touch_info[i].key, KEY_RELEASED);
781 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
782 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
785 touch_info[i].touched = FALSE;
786 touch_info[i].finger_id = 0;
787 touch_info[i].counter = 0;
788 touch_info[i].key = 0;
793 void HandleFingerEvent_WipeGestures(FingerEvent *event)
795 static Key motion_key_x = KSYM_UNDEFINED;
796 static Key motion_key_y = KSYM_UNDEFINED;
797 static Key button_key = KSYM_UNDEFINED;
798 static float motion_x1, motion_y1;
799 static float button_x1, button_y1;
800 static SDL_FingerID motion_id = -1;
801 static SDL_FingerID button_id = -1;
802 int move_trigger_distance_percent = setup.touch.move_distance;
803 int drop_trigger_distance_percent = setup.touch.drop_distance;
804 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
805 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
806 float event_x = event->x;
807 float event_y = event->y;
809 if (event->type == EVENT_FINGERPRESS)
811 if (event_x > 1.0 / 3.0)
815 motion_id = event->fingerId;
820 motion_key_x = KSYM_UNDEFINED;
821 motion_key_y = KSYM_UNDEFINED;
823 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
829 button_id = event->fingerId;
834 button_key = setup.input[0].key.snap;
836 HandleKey(button_key, KEY_PRESSED);
838 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
841 else if (event->type == EVENT_FINGERRELEASE)
843 if (event->fingerId == motion_id)
847 if (motion_key_x != KSYM_UNDEFINED)
848 HandleKey(motion_key_x, KEY_RELEASED);
849 if (motion_key_y != KSYM_UNDEFINED)
850 HandleKey(motion_key_y, KEY_RELEASED);
852 motion_key_x = KSYM_UNDEFINED;
853 motion_key_y = KSYM_UNDEFINED;
855 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
857 else if (event->fingerId == button_id)
861 if (button_key != KSYM_UNDEFINED)
862 HandleKey(button_key, KEY_RELEASED);
864 button_key = KSYM_UNDEFINED;
866 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
869 else if (event->type == EVENT_FINGERMOTION)
871 if (event->fingerId == motion_id)
873 float distance_x = ABS(event_x - motion_x1);
874 float distance_y = ABS(event_y - motion_y1);
875 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
876 event_x > motion_x1 ? setup.input[0].key.right :
878 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
879 event_y > motion_y1 ? setup.input[0].key.down :
882 if (distance_x < move_trigger_distance / 2 ||
883 distance_x < distance_y)
884 new_motion_key_x = KSYM_UNDEFINED;
886 if (distance_y < move_trigger_distance / 2 ||
887 distance_y < distance_x)
888 new_motion_key_y = KSYM_UNDEFINED;
890 if (distance_x > move_trigger_distance ||
891 distance_y > move_trigger_distance)
893 if (new_motion_key_x != motion_key_x)
895 if (motion_key_x != KSYM_UNDEFINED)
896 HandleKey(motion_key_x, KEY_RELEASED);
897 if (new_motion_key_x != KSYM_UNDEFINED)
898 HandleKey(new_motion_key_x, KEY_PRESSED);
901 if (new_motion_key_y != motion_key_y)
903 if (motion_key_y != KSYM_UNDEFINED)
904 HandleKey(motion_key_y, KEY_RELEASED);
905 if (new_motion_key_y != KSYM_UNDEFINED)
906 HandleKey(new_motion_key_y, KEY_PRESSED);
912 motion_key_x = new_motion_key_x;
913 motion_key_y = new_motion_key_y;
915 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
918 else if (event->fingerId == button_id)
920 float distance_x = ABS(event_x - button_x1);
921 float distance_y = ABS(event_y - button_y1);
923 if (distance_x < drop_trigger_distance / 2 &&
924 distance_y > drop_trigger_distance)
926 if (button_key == setup.input[0].key.snap)
927 HandleKey(button_key, KEY_RELEASED);
932 button_key = setup.input[0].key.drop;
934 HandleKey(button_key, KEY_PRESSED);
936 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
942 void HandleFingerEvent(FingerEvent *event)
944 #if DEBUG_EVENTS_FINGER
945 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
946 event->type == EVENT_FINGERPRESS ? "pressed" :
947 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
951 event->dx, event->dy,
955 if (game_status != GAME_MODE_PLAYING)
958 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
961 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
962 HandleFingerEvent_VirtualButtons(event);
963 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
964 HandleFingerEvent_WipeGestures(event);
969 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
971 static int old_mx = 0, old_my = 0;
972 static int last_button = MB_LEFTBUTTON;
973 static boolean touched = FALSE;
974 static boolean tapped = FALSE;
976 // screen tile was tapped (but finger not touching the screen anymore)
977 // (this point will also be reached without receiving a touch event)
978 if (tapped && !touched)
980 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
985 // stop here if this function was not triggered by a touch event
989 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
991 // finger started touching the screen
1001 ClearPlayerMouseAction();
1003 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1006 else if (button == MB_RELEASED && touched)
1008 // finger stopped touching the screen
1013 SetPlayerMouseAction(old_mx, old_my, last_button);
1015 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1017 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1022 // finger moved while touching the screen
1024 int old_x = getLevelFromScreenX(old_mx);
1025 int old_y = getLevelFromScreenY(old_my);
1026 int new_x = getLevelFromScreenX(mx);
1027 int new_y = getLevelFromScreenY(my);
1029 if (new_x != old_x || new_y != old_y)
1034 // finger moved left or right from (horizontal) starting position
1036 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1038 SetPlayerMouseAction(old_mx, old_my, button_nr);
1040 last_button = button_nr;
1042 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1046 // finger stays at or returned to (horizontal) starting position
1048 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1050 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1055 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1057 static int old_mx = 0, old_my = 0;
1058 static int last_button = MB_LEFTBUTTON;
1059 static boolean touched = FALSE;
1060 static boolean tapped = FALSE;
1062 // screen tile was tapped (but finger not touching the screen anymore)
1063 // (this point will also be reached without receiving a touch event)
1064 if (tapped && !touched)
1066 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1071 // stop here if this function was not triggered by a touch event
1075 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1077 // finger started touching the screen
1087 ClearPlayerMouseAction();
1089 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1092 else if (button == MB_RELEASED && touched)
1094 // finger stopped touching the screen
1099 SetPlayerMouseAction(old_mx, old_my, last_button);
1101 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1103 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1108 // finger moved while touching the screen
1110 int old_x = getLevelFromScreenX(old_mx);
1111 int old_y = getLevelFromScreenY(old_my);
1112 int new_x = getLevelFromScreenX(mx);
1113 int new_y = getLevelFromScreenY(my);
1115 if (new_x != old_x || new_y != old_y)
1117 // finger moved away from starting position
1119 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1121 // quickly alternate between clicking and releasing for maximum speed
1122 if (FrameCounter % 2 == 0)
1123 button_nr = MB_RELEASED;
1125 SetPlayerMouseAction(old_mx, old_my, button_nr);
1128 last_button = button_nr;
1132 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1136 // finger stays at or returned to starting position
1138 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1140 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1145 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1147 static int old_mx = 0, old_my = 0;
1148 static Key motion_key_x = KSYM_UNDEFINED;
1149 static Key motion_key_y = KSYM_UNDEFINED;
1150 static boolean touched = FALSE;
1151 static boolean started_on_player = FALSE;
1152 static boolean player_is_dropping = FALSE;
1153 static int player_drop_count = 0;
1154 static int last_player_x = -1;
1155 static int last_player_y = -1;
1157 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1166 started_on_player = FALSE;
1167 player_is_dropping = FALSE;
1168 player_drop_count = 0;
1172 motion_key_x = KSYM_UNDEFINED;
1173 motion_key_y = KSYM_UNDEFINED;
1175 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1178 else if (button == MB_RELEASED && touched)
1185 if (motion_key_x != KSYM_UNDEFINED)
1186 HandleKey(motion_key_x, KEY_RELEASED);
1187 if (motion_key_y != KSYM_UNDEFINED)
1188 HandleKey(motion_key_y, KEY_RELEASED);
1190 if (started_on_player)
1192 if (player_is_dropping)
1194 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1196 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1200 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1202 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1206 motion_key_x = KSYM_UNDEFINED;
1207 motion_key_y = KSYM_UNDEFINED;
1209 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1214 int src_x = local_player->jx;
1215 int src_y = local_player->jy;
1216 int dst_x = getLevelFromScreenX(old_mx);
1217 int dst_y = getLevelFromScreenY(old_my);
1218 int dx = dst_x - src_x;
1219 int dy = dst_y - src_y;
1220 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1221 dx > 0 ? setup.input[0].key.right :
1223 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1224 dy > 0 ? setup.input[0].key.down :
1227 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1228 (last_player_x != local_player->jx ||
1229 last_player_y != local_player->jy))
1231 // in case of asymmetric diagonal movement, use "preferred" direction
1233 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1235 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1236 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1238 local_player->last_move_dir = last_move_dir;
1240 // (required to prevent accidentally forcing direction for next movement)
1241 last_player_x = local_player->jx;
1242 last_player_y = local_player->jy;
1245 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1247 started_on_player = TRUE;
1248 player_drop_count = getPlayerInventorySize(0);
1249 player_is_dropping = (player_drop_count > 0);
1251 if (player_is_dropping)
1253 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1255 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1259 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1261 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1264 else if (dx != 0 || dy != 0)
1266 if (player_is_dropping &&
1267 player_drop_count == getPlayerInventorySize(0))
1269 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1271 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1272 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1274 player_is_dropping = FALSE;
1278 if (new_motion_key_x != motion_key_x)
1280 Error(ERR_DEBUG, "---------- %s %s ----------",
1281 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1282 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1284 if (motion_key_x != KSYM_UNDEFINED)
1285 HandleKey(motion_key_x, KEY_RELEASED);
1286 if (new_motion_key_x != KSYM_UNDEFINED)
1287 HandleKey(new_motion_key_x, KEY_PRESSED);
1290 if (new_motion_key_y != motion_key_y)
1292 Error(ERR_DEBUG, "---------- %s %s ----------",
1293 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1294 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1296 if (motion_key_y != KSYM_UNDEFINED)
1297 HandleKey(motion_key_y, KEY_RELEASED);
1298 if (new_motion_key_y != KSYM_UNDEFINED)
1299 HandleKey(new_motion_key_y, KEY_PRESSED);
1302 motion_key_x = new_motion_key_x;
1303 motion_key_y = new_motion_key_y;
1307 static void HandleButtonOrFinger(int mx, int my, int button)
1309 if (game_status != GAME_MODE_PLAYING)
1312 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1314 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1315 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1316 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1317 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1321 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1322 HandleButtonOrFinger_FollowFinger(mx, my, button);
1326 #if defined(TARGET_SDL2)
1328 static boolean checkTextInputKeyModState()
1330 // when playing, only handle raw key events and ignore text input
1331 if (game_status == GAME_MODE_PLAYING)
1334 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1337 void HandleTextEvent(TextEvent *event)
1339 char *text = event->text;
1340 Key key = getKeyFromKeyName(text);
1342 #if DEBUG_EVENTS_TEXT
1343 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1346 text[0], (int)(text[0]),
1348 getKeyNameFromKey(key),
1352 #if !defined(HAS_SCREEN_KEYBOARD)
1353 // non-mobile devices: only handle key input with modifier keys pressed here
1354 // (every other key input is handled directly as physical key input event)
1355 if (!checkTextInputKeyModState())
1359 // process text input as "classic" (with uppercase etc.) key input event
1360 HandleKey(key, KEY_PRESSED);
1361 HandleKey(key, KEY_RELEASED);
1364 void HandlePauseResumeEvent(PauseResumeEvent *event)
1366 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1370 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1378 void HandleKeyEvent(KeyEvent *event)
1380 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1381 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1382 Key key = GetEventKey(event, with_modifiers);
1383 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1385 #if DEBUG_EVENTS_KEY
1386 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1387 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1388 event->keysym.scancode,
1393 getKeyNameFromKey(key));
1396 #if defined(PLATFORM_ANDROID)
1397 if (key == KSYM_Back)
1399 // always map the "back" button to the "escape" key on Android devices
1404 // for any key event other than "back" button, disable overlay buttons
1405 SetOverlayEnabled(FALSE);
1409 HandleKeyModState(keymod, key_status);
1411 #if defined(TARGET_SDL2)
1412 // only handle raw key input without text modifier keys pressed
1413 if (!checkTextInputKeyModState())
1414 HandleKey(key, key_status);
1416 HandleKey(key, key_status);
1420 void HandleFocusEvent(FocusChangeEvent *event)
1422 static int old_joystick_status = -1;
1424 if (event->type == EVENT_FOCUSOUT)
1426 KeyboardAutoRepeatOn();
1427 old_joystick_status = joystick.status;
1428 joystick.status = JOYSTICK_NOT_AVAILABLE;
1430 ClearPlayerAction();
1432 else if (event->type == EVENT_FOCUSIN)
1434 /* When there are two Rocks'n'Diamonds windows which overlap and
1435 the player moves the pointer from one game window to the other,
1436 a 'FocusOut' event is generated for the window the pointer is
1437 leaving and a 'FocusIn' event is generated for the window the
1438 pointer is entering. In some cases, it can happen that the
1439 'FocusIn' event is handled by the one game process before the
1440 'FocusOut' event by the other game process. In this case the
1441 X11 environment would end up with activated keyboard auto repeat,
1442 because unfortunately this is a global setting and not (which
1443 would be far better) set for each X11 window individually.
1444 The effect would be keyboard auto repeat while playing the game
1445 (game_status == GAME_MODE_PLAYING), which is not desired.
1446 To avoid this special case, we just wait 1/10 second before
1447 processing the 'FocusIn' event.
1450 if (game_status == GAME_MODE_PLAYING)
1453 KeyboardAutoRepeatOffUnlessAutoplay();
1456 if (old_joystick_status != -1)
1457 joystick.status = old_joystick_status;
1461 void HandleClientMessageEvent(ClientMessageEvent *event)
1463 if (CheckCloseWindowEvent(event))
1467 void HandleWindowManagerEvent(Event *event)
1469 #if defined(TARGET_SDL)
1470 SDLHandleWindowManagerEvent(event);
1474 void HandleButton(int mx, int my, int button, int button_nr)
1476 static int old_mx = 0, old_my = 0;
1477 boolean button_hold = FALSE;
1478 boolean handle_gadgets = TRUE;
1484 button_nr = -button_nr;
1493 #if defined(PLATFORM_ANDROID)
1494 // when playing, only handle gadgets when using "follow finger" controls
1495 // or when using touch controls in combination with the MM game engine
1497 (game_status != GAME_MODE_PLAYING ||
1498 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1499 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1502 if (handle_gadgets && HandleGadgets(mx, my, button))
1504 /* do not handle this button event anymore */
1505 mx = my = -32; /* force mouse event to be outside screen tiles */
1508 if (HandleGlobalAnimClicks(mx, my, button))
1510 /* do not handle this button event anymore */
1511 return; /* force mouse event not to be handled at all */
1514 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1517 /* do not use scroll wheel button events for anything other than gadgets */
1518 if (IS_WHEEL_BUTTON(button_nr))
1521 switch (game_status)
1523 case GAME_MODE_TITLE:
1524 HandleTitleScreen(mx, my, 0, 0, button);
1527 case GAME_MODE_MAIN:
1528 HandleMainMenu(mx, my, 0, 0, button);
1531 case GAME_MODE_PSEUDO_TYPENAME:
1532 HandleTypeName(0, KSYM_Return);
1535 case GAME_MODE_LEVELS:
1536 HandleChooseLevelSet(mx, my, 0, 0, button);
1539 case GAME_MODE_LEVELNR:
1540 HandleChooseLevelNr(mx, my, 0, 0, button);
1543 case GAME_MODE_SCORES:
1544 HandleHallOfFame(0, 0, 0, 0, button);
1547 case GAME_MODE_EDITOR:
1548 HandleLevelEditorIdle();
1551 case GAME_MODE_INFO:
1552 HandleInfoScreen(mx, my, 0, 0, button);
1555 case GAME_MODE_SETUP:
1556 HandleSetupScreen(mx, my, 0, 0, button);
1559 case GAME_MODE_PLAYING:
1560 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1561 HandleButtonOrFinger(mx, my, button);
1563 SetPlayerMouseAction(mx, my, button);
1566 if (button == MB_PRESSED && !motion_status && !button_hold &&
1567 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1568 DumpTileFromScreen(mx, my);
1578 static boolean is_string_suffix(char *string, char *suffix)
1580 int string_len = strlen(string);
1581 int suffix_len = strlen(suffix);
1583 if (suffix_len > string_len)
1586 return (strEqual(&string[string_len - suffix_len], suffix));
1589 #define MAX_CHEAT_INPUT_LEN 32
1591 static void HandleKeysSpecial(Key key)
1593 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1594 char letter = getCharFromKey(key);
1595 int cheat_input_len = strlen(cheat_input);
1601 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1603 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1604 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1606 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1609 cheat_input[cheat_input_len++] = letter;
1610 cheat_input[cheat_input_len] = '\0';
1612 #if DEBUG_EVENTS_KEY
1613 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1616 if (game_status == GAME_MODE_MAIN)
1618 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1619 is_string_suffix(cheat_input, ":ist"))
1621 InsertSolutionTape();
1623 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1624 is_string_suffix(cheat_input, ":rg"))
1626 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1629 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1630 is_string_suffix(cheat_input, ":rs"))
1632 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1635 else if (is_string_suffix(cheat_input, ":reload-music") ||
1636 is_string_suffix(cheat_input, ":rm"))
1638 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1641 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1642 is_string_suffix(cheat_input, ":ra"))
1644 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1645 1 << ARTWORK_TYPE_SOUNDS |
1646 1 << ARTWORK_TYPE_MUSIC);
1649 else if (is_string_suffix(cheat_input, ":dump-level") ||
1650 is_string_suffix(cheat_input, ":dl"))
1654 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1655 is_string_suffix(cheat_input, ":dt"))
1659 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1660 is_string_suffix(cheat_input, ":ft"))
1662 /* fix single-player tapes that contain player input for more than one
1663 player (due to a bug in 3.3.1.2 and earlier versions), which results
1664 in playing levels with more than one player in multi-player mode,
1665 even though the tape was originally recorded in single-player mode */
1667 /* remove player input actions for all players but the first one */
1668 for (i = 1; i < MAX_PLAYERS; i++)
1669 tape.player_participates[i] = FALSE;
1671 tape.changed = TRUE;
1673 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1674 is_string_suffix(cheat_input, ":snl"))
1676 SaveNativeLevel(&level);
1678 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1679 is_string_suffix(cheat_input, ":fps"))
1681 global.show_frames_per_second = !global.show_frames_per_second;
1684 else if (game_status == GAME_MODE_PLAYING)
1687 if (is_string_suffix(cheat_input, ".q"))
1688 DEBUG_SetMaximumDynamite();
1691 else if (game_status == GAME_MODE_EDITOR)
1693 if (is_string_suffix(cheat_input, ":dump-brush") ||
1694 is_string_suffix(cheat_input, ":DB"))
1698 else if (is_string_suffix(cheat_input, ":DDB"))
1705 void HandleKeysDebug(Key key)
1710 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1712 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1714 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1716 if (key == setup.debug.frame_delay_key[i] &&
1717 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1719 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1720 setup.debug.frame_delay[i] : setup.game_frame_delay);
1722 if (!setup.debug.frame_delay_game_only)
1723 MenuFrameDelay = GameFrameDelay;
1725 SetVideoFrameDelay(GameFrameDelay);
1727 if (GameFrameDelay > ONE_SECOND_DELAY)
1728 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1729 else if (GameFrameDelay != 0)
1730 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1731 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1732 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1734 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1741 if (game_status == GAME_MODE_PLAYING)
1745 options.debug = !options.debug;
1747 Error(ERR_DEBUG, "debug mode %s",
1748 (options.debug ? "enabled" : "disabled"));
1750 else if (key == KSYM_v)
1752 Error(ERR_DEBUG, "currently using game engine version %d",
1753 game.engine_version);
1759 void HandleKey(Key key, int key_status)
1761 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1762 static boolean ignore_repeated_key = FALSE;
1763 static struct SetupKeyboardInfo ski;
1764 static struct SetupShortcutInfo ssi;
1773 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1774 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1775 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1776 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1777 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1778 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1783 #if defined(TARGET_SDL2)
1784 /* map special keys (media keys / remote control buttons) to default keys */
1785 if (key == KSYM_PlayPause)
1787 else if (key == KSYM_Select)
1791 HandleSpecialGameControllerKeys(key, key_status);
1793 if (game_status == GAME_MODE_PLAYING)
1795 /* only needed for single-step tape recording mode */
1796 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1799 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1801 byte key_action = 0;
1803 if (setup.input[pnr].use_joystick)
1806 ski = setup.input[pnr].key;
1808 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1809 if (key == *key_info[i].key_custom)
1810 key_action |= key_info[i].action;
1812 /* use combined snap+direction keys for the first player only */
1815 ssi = setup.shortcut;
1817 for (i = 0; i < NUM_DIRECTIONS; i++)
1818 if (key == *key_info[i].key_snap)
1819 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1822 if (key_status == KEY_PRESSED)
1823 stored_player[pnr].action |= key_action;
1825 stored_player[pnr].action &= ~key_action;
1827 if (tape.single_step && tape.recording && tape.pausing)
1829 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1831 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1833 /* if snap key already pressed, keep pause mode when releasing */
1834 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1835 has_snapped[pnr] = TRUE;
1837 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1839 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1841 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1842 getRedDiskReleaseFlag_SP() == 0)
1844 /* add a single inactive frame before dropping starts */
1845 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1846 stored_player[pnr].force_dropping = TRUE;
1849 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1851 /* if snap key was pressed without direction, leave pause mode */
1852 if (!has_snapped[pnr])
1853 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1855 has_snapped[pnr] = FALSE;
1858 else if (tape.recording && tape.pausing && !tape.use_mouse)
1860 /* prevent key release events from un-pausing a paused game */
1861 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1862 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1865 // for MM style levels, handle in-game keyboard input in HandleJoystick()
1866 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1872 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1873 if (key == key_info[i].key_default)
1874 joy |= key_info[i].action;
1879 if (key_status == KEY_PRESSED)
1880 key_joystick_mapping |= joy;
1882 key_joystick_mapping &= ~joy;
1887 if (game_status != GAME_MODE_PLAYING)
1888 key_joystick_mapping = 0;
1890 if (key_status == KEY_RELEASED)
1892 // reset flag to ignore repeated "key pressed" events after key release
1893 ignore_repeated_key = FALSE;
1898 if ((key == KSYM_F11 ||
1899 ((key == KSYM_Return ||
1900 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1901 video.fullscreen_available &&
1902 !ignore_repeated_key)
1904 setup.fullscreen = !setup.fullscreen;
1906 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1908 if (game_status == GAME_MODE_SETUP)
1909 RedrawSetupScreenAfterFullscreenToggle();
1911 // set flag to ignore repeated "key pressed" events
1912 ignore_repeated_key = TRUE;
1917 if ((key == KSYM_0 || key == KSYM_KP_0 ||
1918 key == KSYM_minus || key == KSYM_KP_Subtract ||
1919 key == KSYM_plus || key == KSYM_KP_Add ||
1920 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
1921 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1922 video.window_scaling_available &&
1923 !video.fullscreen_enabled)
1925 if (key == KSYM_0 || key == KSYM_KP_0)
1926 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1927 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1928 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1930 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1932 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1933 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1934 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1935 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1937 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1939 if (game_status == GAME_MODE_SETUP)
1940 RedrawSetupScreenAfterFullscreenToggle();
1945 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
1946 key == KSYM_Return ||
1947 key == KSYM_Escape)))
1949 /* do not handle this key event anymore */
1950 if (key != KSYM_Escape) /* always allow ESC key to be handled */
1954 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1955 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1962 if (game_status == GAME_MODE_MAIN &&
1963 (key == setup.shortcut.toggle_pause || key == KSYM_space))
1965 StartGameActions(options.network, setup.autorecord, level.random_seed);
1970 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1972 if (key == setup.shortcut.save_game)
1974 else if (key == setup.shortcut.load_game)
1976 else if (key == setup.shortcut.toggle_pause)
1977 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
1979 HandleTapeButtonKeys(key);
1980 HandleSoundButtonKeys(key);
1983 if (game_status == GAME_MODE_PLAYING && !network_playing)
1985 int centered_player_nr_next = -999;
1987 if (key == setup.shortcut.focus_player_all)
1988 centered_player_nr_next = -1;
1990 for (i = 0; i < MAX_PLAYERS; i++)
1991 if (key == setup.shortcut.focus_player[i])
1992 centered_player_nr_next = i;
1994 if (centered_player_nr_next != -999)
1996 game.centered_player_nr_next = centered_player_nr_next;
1997 game.set_centered_player = TRUE;
2001 tape.centered_player_nr_next = game.centered_player_nr_next;
2002 tape.set_centered_player = TRUE;
2007 HandleKeysSpecial(key);
2009 if (HandleGadgetsKeyInput(key))
2011 if (key != KSYM_Escape) /* always allow ESC key to be handled */
2012 key = KSYM_UNDEFINED;
2015 switch (game_status)
2017 case GAME_MODE_PSEUDO_TYPENAME:
2018 HandleTypeName(0, key);
2021 case GAME_MODE_TITLE:
2022 case GAME_MODE_MAIN:
2023 case GAME_MODE_LEVELS:
2024 case GAME_MODE_LEVELNR:
2025 case GAME_MODE_SETUP:
2026 case GAME_MODE_INFO:
2027 case GAME_MODE_SCORES:
2032 if (game_status == GAME_MODE_TITLE)
2033 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2034 else if (game_status == GAME_MODE_MAIN)
2035 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2036 else if (game_status == GAME_MODE_LEVELS)
2037 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2038 else if (game_status == GAME_MODE_LEVELNR)
2039 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2040 else if (game_status == GAME_MODE_SETUP)
2041 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2042 else if (game_status == GAME_MODE_INFO)
2043 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2044 else if (game_status == GAME_MODE_SCORES)
2045 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2049 if (game_status != GAME_MODE_MAIN)
2050 FadeSkipNextFadeIn();
2052 if (game_status == GAME_MODE_TITLE)
2053 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2054 else if (game_status == GAME_MODE_LEVELS)
2055 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2056 else if (game_status == GAME_MODE_LEVELNR)
2057 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2058 else if (game_status == GAME_MODE_SETUP)
2059 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2060 else if (game_status == GAME_MODE_INFO)
2061 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2062 else if (game_status == GAME_MODE_SCORES)
2063 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2067 if (game_status == GAME_MODE_LEVELS)
2068 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2069 else if (game_status == GAME_MODE_LEVELNR)
2070 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2071 else if (game_status == GAME_MODE_SETUP)
2072 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2073 else if (game_status == GAME_MODE_INFO)
2074 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2075 else if (game_status == GAME_MODE_SCORES)
2076 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2079 case KSYM_Page_Down:
2080 if (game_status == GAME_MODE_LEVELS)
2081 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2082 else if (game_status == GAME_MODE_LEVELNR)
2083 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2084 else if (game_status == GAME_MODE_SETUP)
2085 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2086 else if (game_status == GAME_MODE_INFO)
2087 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2088 else if (game_status == GAME_MODE_SCORES)
2089 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2097 case GAME_MODE_EDITOR:
2098 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2099 HandleLevelEditorKeyInput(key);
2102 case GAME_MODE_PLAYING:
2107 RequestQuitGame(setup.ask_on_escape);
2117 if (key == KSYM_Escape)
2119 SetGameStatus(GAME_MODE_MAIN);
2127 HandleKeysDebug(key);
2130 void HandleNoEvent()
2132 HandleMouseCursor();
2134 switch (game_status)
2136 case GAME_MODE_PLAYING:
2137 HandleButtonOrFinger(-1, -1, -1);
2142 void HandleEventActions()
2144 // if (button_status && game_status != GAME_MODE_PLAYING)
2145 if (button_status && (game_status != GAME_MODE_PLAYING ||
2147 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2149 HandleButton(0, 0, button_status, -button_status);
2156 #if defined(NETWORK_AVALIABLE)
2157 if (options.network)
2161 switch (game_status)
2163 case GAME_MODE_MAIN:
2164 DrawPreviewLevelAnimation();
2167 case GAME_MODE_EDITOR:
2168 HandleLevelEditorIdle();
2176 static void HandleTileCursor(int dx, int dy, int button)
2179 ClearPlayerMouseAction();
2186 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2187 (dx < 0 ? MB_LEFTBUTTON :
2188 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2192 int old_xpos = tile_cursor.xpos;
2193 int old_ypos = tile_cursor.ypos;
2194 int new_xpos = old_xpos;
2195 int new_ypos = old_ypos;
2197 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2198 new_xpos = old_xpos + dx;
2200 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2201 new_ypos = old_ypos + dy;
2203 SetTileCursorTargetXY(new_xpos, new_ypos);
2207 static int HandleJoystickForAllPlayers()
2211 boolean no_joysticks_configured = TRUE;
2212 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2213 static byte joy_action_last[MAX_PLAYERS];
2215 for (i = 0; i < MAX_PLAYERS; i++)
2216 if (setup.input[i].use_joystick)
2217 no_joysticks_configured = FALSE;
2219 /* if no joysticks configured, map connected joysticks to players */
2220 if (no_joysticks_configured)
2221 use_as_joystick_nr = TRUE;
2223 for (i = 0; i < MAX_PLAYERS; i++)
2225 byte joy_action = 0;
2227 joy_action = JoystickExt(i, use_as_joystick_nr);
2228 result |= joy_action;
2230 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2231 joy_action != joy_action_last[i])
2232 stored_player[i].action = joy_action;
2234 joy_action_last[i] = joy_action;
2240 void HandleJoystick()
2242 static unsigned int joytest_delay = 0;
2243 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2244 static int joytest_last = 0;
2245 int joystick = HandleJoystickForAllPlayers();
2246 int keyboard = key_joystick_mapping;
2247 int joy = (joystick | keyboard);
2248 int joytest = joystick;
2249 int left = joy & JOY_LEFT;
2250 int right = joy & JOY_RIGHT;
2251 int up = joy & JOY_UP;
2252 int down = joy & JOY_DOWN;
2253 int button = joy & JOY_BUTTON;
2254 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2255 int dx = (left ? -1 : right ? 1 : 0);
2256 int dy = (up ? -1 : down ? 1 : 0);
2258 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2260 /* do not handle this button event anymore */
2264 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2266 // when playing MM style levels, also use delay for keyboard events
2267 if (game_status == GAME_MODE_PLAYING)
2268 joytest |= keyboard;
2270 // for any joystick or keyboard event, enable playfield tile cursor
2271 if (dx || dy || button)
2272 SetTileCursorEnabled(TRUE);
2275 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2277 /* delay joystick/keyboard actions if axes/keys continually pressed */
2278 newbutton = dx = dy = 0;
2282 /* first start with longer delay, then continue with shorter delay */
2283 joytest_delay_value =
2284 (joytest != joytest_last ? GADGET_FRAME_DELAY_FIRST : GADGET_FRAME_DELAY);
2287 joytest_last = joytest;
2289 switch (game_status)
2291 case GAME_MODE_TITLE:
2292 case GAME_MODE_MAIN:
2293 case GAME_MODE_LEVELS:
2294 case GAME_MODE_LEVELNR:
2295 case GAME_MODE_SETUP:
2296 case GAME_MODE_INFO:
2297 case GAME_MODE_SCORES:
2299 if (game_status == GAME_MODE_TITLE)
2300 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2301 else if (game_status == GAME_MODE_MAIN)
2302 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2303 else if (game_status == GAME_MODE_LEVELS)
2304 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2305 else if (game_status == GAME_MODE_LEVELNR)
2306 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2307 else if (game_status == GAME_MODE_SETUP)
2308 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2309 else if (game_status == GAME_MODE_INFO)
2310 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2311 else if (game_status == GAME_MODE_SCORES)
2312 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2317 case GAME_MODE_PLAYING:
2319 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2320 if (tape.playing || keyboard)
2321 newbutton = ((joy & JOY_BUTTON) != 0);
2324 if (newbutton && AllPlayersGone)
2331 if (tape.recording && tape.pausing)
2333 if (joystick & JOY_ACTION)
2334 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2337 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2338 HandleTileCursor(dx, dy, button);
2347 void HandleSpecialGameControllerButtons(Event *event)
2349 #if defined(TARGET_SDL2)
2350 switch (event->type)
2352 case SDL_CONTROLLERBUTTONDOWN:
2353 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2354 HandleKey(KSYM_space, KEY_PRESSED);
2355 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2356 HandleKey(KSYM_Escape, KEY_PRESSED);
2360 case SDL_CONTROLLERBUTTONUP:
2361 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2362 HandleKey(KSYM_space, KEY_RELEASED);
2363 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2364 HandleKey(KSYM_Escape, KEY_RELEASED);
2371 void HandleSpecialGameControllerKeys(Key key, int key_status)
2373 #if defined(TARGET_SDL2)
2374 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2375 int button = SDL_CONTROLLER_BUTTON_INVALID;
2377 /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2378 if (key == KSYM_Rewind)
2379 button = SDL_CONTROLLER_BUTTON_A;
2380 else if (key == KSYM_FastForward || key == KSYM_Menu)
2381 button = SDL_CONTROLLER_BUTTON_B;
2383 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2387 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2388 SDL_CONTROLLERBUTTONUP);
2390 event.cbutton.which = 0; /* first joystick (Amazon Fire TV remote) */
2391 event.cbutton.button = button;
2392 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2395 HandleJoystickEvent(&event);