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);
405 ClearPlayerMouseAction();
407 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
410 local_player->mouse_action.lx = lx;
411 local_player->mouse_action.ly = ly;
412 local_player->mouse_action.button = button;
414 if (tape.recording && tape.pausing && tape.use_mouse)
416 /* prevent button release or motion events from un-pausing a paused game */
417 if (button && !motion_status)
418 TapeTogglePause(TAPE_TOGGLE_MANUAL);
422 void SleepWhileUnmapped()
424 boolean window_unmapped = TRUE;
426 KeyboardAutoRepeatOn();
428 while (window_unmapped)
432 if (!WaitValidEvent(&event))
437 case EVENT_BUTTONRELEASE:
438 button_status = MB_RELEASED;
441 case EVENT_KEYRELEASE:
442 key_joystick_mapping = 0;
445 #if defined(TARGET_SDL2)
446 case SDL_CONTROLLERBUTTONUP:
447 HandleJoystickEvent(&event);
448 key_joystick_mapping = 0;
452 case EVENT_MAPNOTIFY:
453 window_unmapped = FALSE;
456 case EVENT_UNMAPNOTIFY:
457 /* this is only to surely prevent the 'should not happen' case
458 * of recursively looping between 'SleepWhileUnmapped()' and
459 * 'HandleOtherEvents()' which usually calls this funtion.
464 HandleOtherEvents(&event);
469 if (game_status == GAME_MODE_PLAYING)
470 KeyboardAutoRepeatOffUnlessAutoplay();
473 void HandleExposeEvent(ExposeEvent *event)
477 void HandleButtonEvent(ButtonEvent *event)
479 #if DEBUG_EVENTS_BUTTON
480 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
482 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
486 // for any mouse button event, disable playfield tile cursor
487 SetTileCursorEnabled(FALSE);
489 #if defined(HAS_SCREEN_KEYBOARD)
490 if (video.shifted_up)
491 event->y += video.shifted_up_pos;
494 motion_status = FALSE;
496 if (event->type == EVENT_BUTTONPRESS)
497 button_status = event->button;
499 button_status = MB_RELEASED;
501 HandleButton(event->x, event->y, button_status, event->button);
504 void HandleMotionEvent(MotionEvent *event)
506 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
509 motion_status = TRUE;
511 #if DEBUG_EVENTS_MOTION
512 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
513 button_status, event->x, event->y);
516 HandleButton(event->x, event->y, button_status, button_status);
519 #if defined(TARGET_SDL2)
521 void HandleWheelEvent(WheelEvent *event)
525 #if DEBUG_EVENTS_WHEEL
527 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
528 event->which, event->x, event->y);
530 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
531 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
532 event->which, event->x, event->y,
533 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
534 "SDL_MOUSEWHEEL_FLIPPED"));
538 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
539 event->x > 0 ? MB_WHEEL_RIGHT :
540 event->y < 0 ? MB_WHEEL_DOWN :
541 event->y > 0 ? MB_WHEEL_UP : 0);
543 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
544 // accelerated mouse wheel available on Mac and Windows
545 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
547 // no accelerated mouse wheel available on Unix/Linux
548 wheel_steps = DEFAULT_WHEEL_STEPS;
551 motion_status = FALSE;
553 button_status = button_nr;
554 HandleButton(0, 0, button_status, -button_nr);
556 button_status = MB_RELEASED;
557 HandleButton(0, 0, button_status, -button_nr);
560 void HandleWindowEvent(WindowEvent *event)
562 #if DEBUG_EVENTS_WINDOW
563 int subtype = event->event;
566 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
567 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
568 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
569 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
570 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
571 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
572 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
573 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
574 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
575 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
576 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
577 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
578 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
579 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
582 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
583 event_name, event->data1, event->data2);
587 // (not needed, as the screen gets redrawn every 20 ms anyway)
588 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
589 event->event == SDL_WINDOWEVENT_RESIZED ||
590 event->event == SDL_WINDOWEVENT_EXPOSED)
594 if (event->event == SDL_WINDOWEVENT_RESIZED)
596 if (!video.fullscreen_enabled)
598 int new_window_width = event->data1;
599 int new_window_height = event->data2;
601 // if window size has changed after resizing, calculate new scaling factor
602 if (new_window_width != video.window_width ||
603 new_window_height != video.window_height)
605 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
606 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
608 // (extreme window scaling allowed, but cannot be saved permanently)
609 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
610 setup.window_scaling_percent =
611 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
612 MAX_WINDOW_SCALING_PERCENT);
614 video.window_width = new_window_width;
615 video.window_height = new_window_height;
617 if (game_status == GAME_MODE_SETUP)
618 RedrawSetupScreenAfterFullscreenToggle();
623 #if defined(PLATFORM_ANDROID)
626 int new_display_width = event->data1;
627 int new_display_height = event->data2;
629 // if fullscreen display size has changed, device has been rotated
630 if (new_display_width != video.display_width ||
631 new_display_height != video.display_height)
633 video.display_width = new_display_width;
634 video.display_height = new_display_height;
636 SDLSetScreenProperties();
643 #define NUM_TOUCH_FINGERS 3
648 SDL_FingerID finger_id;
651 } touch_info[NUM_TOUCH_FINGERS];
653 void HandleFingerEvent_VirtualButtons(FingerEvent *event)
655 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
656 float event_x = (event->x);
657 float event_y = (event->y - ypos) / (1 - ypos);
658 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
659 event_y > 2.0 / 3.0 && event_y < 1 ?
660 setup.input[0].key.snap :
661 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
662 event_y > 2.0 / 3.0 && event_y < 1 ?
663 setup.input[0].key.drop :
664 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
665 event_y > 0 && event_y < 1.0 / 3.0 ?
666 setup.input[0].key.up :
667 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
668 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
669 setup.input[0].key.left :
670 event_x > 8.0 / 9.0 && event_x < 1 &&
671 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
672 setup.input[0].key.right :
673 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
674 event_y > 2.0 / 3.0 && event_y < 1 ?
675 setup.input[0].key.down :
677 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
679 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
683 // for any touch input event, enable overlay buttons (if activated)
684 SetOverlayEnabled(TRUE);
686 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
687 getKeyNameFromKey(key), key_status_name, event->fingerId);
689 // check if we already know this touch event's finger id
690 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
692 if (touch_info[i].touched &&
693 touch_info[i].finger_id == event->fingerId)
695 // Error(ERR_DEBUG, "MARK 1: %d", i);
701 if (i >= NUM_TOUCH_FINGERS)
703 if (key_status == KEY_PRESSED)
705 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
707 // unknown finger id -- get new, empty slot, if available
708 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
710 if (touch_info[i].counter < oldest_counter)
713 oldest_counter = touch_info[i].counter;
715 // Error(ERR_DEBUG, "MARK 2: %d", i);
718 if (!touch_info[i].touched)
720 // Error(ERR_DEBUG, "MARK 3: %d", i);
726 if (i >= NUM_TOUCH_FINGERS)
728 // all slots allocated -- use oldest slot
731 // Error(ERR_DEBUG, "MARK 4: %d", i);
736 // release of previously unknown key (should not happen)
738 if (key != KSYM_UNDEFINED)
740 HandleKey(key, KEY_RELEASED);
742 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
743 getKeyNameFromKey(key), "KEY_RELEASED", i);
748 if (i < NUM_TOUCH_FINGERS)
750 if (key_status == KEY_PRESSED)
752 if (touch_info[i].key != key)
754 if (touch_info[i].key != KSYM_UNDEFINED)
756 HandleKey(touch_info[i].key, KEY_RELEASED);
758 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
759 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
762 if (key != KSYM_UNDEFINED)
764 HandleKey(key, KEY_PRESSED);
766 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
767 getKeyNameFromKey(key), "KEY_PRESSED", i);
771 touch_info[i].touched = TRUE;
772 touch_info[i].finger_id = event->fingerId;
773 touch_info[i].counter = Counter();
774 touch_info[i].key = key;
778 if (touch_info[i].key != KSYM_UNDEFINED)
780 HandleKey(touch_info[i].key, KEY_RELEASED);
782 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
783 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
786 touch_info[i].touched = FALSE;
787 touch_info[i].finger_id = 0;
788 touch_info[i].counter = 0;
789 touch_info[i].key = 0;
794 void HandleFingerEvent_WipeGestures(FingerEvent *event)
796 static Key motion_key_x = KSYM_UNDEFINED;
797 static Key motion_key_y = KSYM_UNDEFINED;
798 static Key button_key = KSYM_UNDEFINED;
799 static float motion_x1, motion_y1;
800 static float button_x1, button_y1;
801 static SDL_FingerID motion_id = -1;
802 static SDL_FingerID button_id = -1;
803 int move_trigger_distance_percent = setup.touch.move_distance;
804 int drop_trigger_distance_percent = setup.touch.drop_distance;
805 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
806 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
807 float event_x = event->x;
808 float event_y = event->y;
810 if (event->type == EVENT_FINGERPRESS)
812 if (event_x > 1.0 / 3.0)
816 motion_id = event->fingerId;
821 motion_key_x = KSYM_UNDEFINED;
822 motion_key_y = KSYM_UNDEFINED;
824 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
830 button_id = event->fingerId;
835 button_key = setup.input[0].key.snap;
837 HandleKey(button_key, KEY_PRESSED);
839 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
842 else if (event->type == EVENT_FINGERRELEASE)
844 if (event->fingerId == motion_id)
848 if (motion_key_x != KSYM_UNDEFINED)
849 HandleKey(motion_key_x, KEY_RELEASED);
850 if (motion_key_y != KSYM_UNDEFINED)
851 HandleKey(motion_key_y, KEY_RELEASED);
853 motion_key_x = KSYM_UNDEFINED;
854 motion_key_y = KSYM_UNDEFINED;
856 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
858 else if (event->fingerId == button_id)
862 if (button_key != KSYM_UNDEFINED)
863 HandleKey(button_key, KEY_RELEASED);
865 button_key = KSYM_UNDEFINED;
867 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
870 else if (event->type == EVENT_FINGERMOTION)
872 if (event->fingerId == motion_id)
874 float distance_x = ABS(event_x - motion_x1);
875 float distance_y = ABS(event_y - motion_y1);
876 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
877 event_x > motion_x1 ? setup.input[0].key.right :
879 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
880 event_y > motion_y1 ? setup.input[0].key.down :
883 if (distance_x < move_trigger_distance / 2 ||
884 distance_x < distance_y)
885 new_motion_key_x = KSYM_UNDEFINED;
887 if (distance_y < move_trigger_distance / 2 ||
888 distance_y < distance_x)
889 new_motion_key_y = KSYM_UNDEFINED;
891 if (distance_x > move_trigger_distance ||
892 distance_y > move_trigger_distance)
894 if (new_motion_key_x != motion_key_x)
896 if (motion_key_x != KSYM_UNDEFINED)
897 HandleKey(motion_key_x, KEY_RELEASED);
898 if (new_motion_key_x != KSYM_UNDEFINED)
899 HandleKey(new_motion_key_x, KEY_PRESSED);
902 if (new_motion_key_y != motion_key_y)
904 if (motion_key_y != KSYM_UNDEFINED)
905 HandleKey(motion_key_y, KEY_RELEASED);
906 if (new_motion_key_y != KSYM_UNDEFINED)
907 HandleKey(new_motion_key_y, KEY_PRESSED);
913 motion_key_x = new_motion_key_x;
914 motion_key_y = new_motion_key_y;
916 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
919 else if (event->fingerId == button_id)
921 float distance_x = ABS(event_x - button_x1);
922 float distance_y = ABS(event_y - button_y1);
924 if (distance_x < drop_trigger_distance / 2 &&
925 distance_y > drop_trigger_distance)
927 if (button_key == setup.input[0].key.snap)
928 HandleKey(button_key, KEY_RELEASED);
933 button_key = setup.input[0].key.drop;
935 HandleKey(button_key, KEY_PRESSED);
937 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
943 void HandleFingerEvent(FingerEvent *event)
945 #if DEBUG_EVENTS_FINGER
946 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
947 event->type == EVENT_FINGERPRESS ? "pressed" :
948 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
952 event->dx, event->dy,
956 if (game_status != GAME_MODE_PLAYING)
959 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
962 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
963 HandleFingerEvent_VirtualButtons(event);
964 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
965 HandleFingerEvent_WipeGestures(event);
970 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
972 static int old_mx = 0, old_my = 0;
973 static int last_button = MB_LEFTBUTTON;
974 static boolean touched = FALSE;
975 static boolean tapped = FALSE;
977 // screen tile was tapped (but finger not touching the screen anymore)
978 // (this point will also be reached without receiving a touch event)
979 if (tapped && !touched)
981 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
986 // stop here if this function was not triggered by a touch event
990 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
992 // finger started touching the screen
1002 ClearPlayerMouseAction();
1004 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1007 else if (button == MB_RELEASED && touched)
1009 // finger stopped touching the screen
1014 SetPlayerMouseAction(old_mx, old_my, last_button);
1016 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1018 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1023 // finger moved while touching the screen
1025 int old_x = getLevelFromScreenX(old_mx);
1026 int old_y = getLevelFromScreenY(old_my);
1027 int new_x = getLevelFromScreenX(mx);
1028 int new_y = getLevelFromScreenY(my);
1030 if (new_x != old_x || new_y != old_y)
1035 // finger moved left or right from (horizontal) starting position
1037 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1039 SetPlayerMouseAction(old_mx, old_my, button_nr);
1041 last_button = button_nr;
1043 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1047 // finger stays at or returned to (horizontal) starting position
1049 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1051 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1056 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1058 static int old_mx = 0, old_my = 0;
1059 static int last_button = MB_LEFTBUTTON;
1060 static boolean touched = FALSE;
1061 static boolean tapped = FALSE;
1063 // screen tile was tapped (but finger not touching the screen anymore)
1064 // (this point will also be reached without receiving a touch event)
1065 if (tapped && !touched)
1067 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1072 // stop here if this function was not triggered by a touch event
1076 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1078 // finger started touching the screen
1088 ClearPlayerMouseAction();
1090 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1093 else if (button == MB_RELEASED && touched)
1095 // finger stopped touching the screen
1100 SetPlayerMouseAction(old_mx, old_my, last_button);
1102 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1104 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1109 // finger moved while touching the screen
1111 int old_x = getLevelFromScreenX(old_mx);
1112 int old_y = getLevelFromScreenY(old_my);
1113 int new_x = getLevelFromScreenX(mx);
1114 int new_y = getLevelFromScreenY(my);
1116 if (new_x != old_x || new_y != old_y)
1118 // finger moved away from starting position
1120 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1122 // quickly alternate between clicking and releasing for maximum speed
1123 if (FrameCounter % 2 == 0)
1124 button_nr = MB_RELEASED;
1126 SetPlayerMouseAction(old_mx, old_my, button_nr);
1129 last_button = button_nr;
1133 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1137 // finger stays at or returned to starting position
1139 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1141 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1146 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1148 static int old_mx = 0, old_my = 0;
1149 static Key motion_key_x = KSYM_UNDEFINED;
1150 static Key motion_key_y = KSYM_UNDEFINED;
1151 static boolean touched = FALSE;
1152 static boolean started_on_player = FALSE;
1153 static boolean player_is_dropping = FALSE;
1154 static int player_drop_count = 0;
1155 static int last_player_x = -1;
1156 static int last_player_y = -1;
1158 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1167 started_on_player = FALSE;
1168 player_is_dropping = FALSE;
1169 player_drop_count = 0;
1173 motion_key_x = KSYM_UNDEFINED;
1174 motion_key_y = KSYM_UNDEFINED;
1176 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1179 else if (button == MB_RELEASED && touched)
1186 if (motion_key_x != KSYM_UNDEFINED)
1187 HandleKey(motion_key_x, KEY_RELEASED);
1188 if (motion_key_y != KSYM_UNDEFINED)
1189 HandleKey(motion_key_y, KEY_RELEASED);
1191 if (started_on_player)
1193 if (player_is_dropping)
1195 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1197 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1201 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1203 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1207 motion_key_x = KSYM_UNDEFINED;
1208 motion_key_y = KSYM_UNDEFINED;
1210 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1215 int src_x = local_player->jx;
1216 int src_y = local_player->jy;
1217 int dst_x = getLevelFromScreenX(old_mx);
1218 int dst_y = getLevelFromScreenY(old_my);
1219 int dx = dst_x - src_x;
1220 int dy = dst_y - src_y;
1221 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1222 dx > 0 ? setup.input[0].key.right :
1224 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1225 dy > 0 ? setup.input[0].key.down :
1228 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1229 (last_player_x != local_player->jx ||
1230 last_player_y != local_player->jy))
1232 // in case of asymmetric diagonal movement, use "preferred" direction
1234 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1236 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1237 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1239 local_player->last_move_dir = last_move_dir;
1241 // (required to prevent accidentally forcing direction for next movement)
1242 last_player_x = local_player->jx;
1243 last_player_y = local_player->jy;
1246 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1248 started_on_player = TRUE;
1249 player_drop_count = getPlayerInventorySize(0);
1250 player_is_dropping = (player_drop_count > 0);
1252 if (player_is_dropping)
1254 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1256 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1260 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1262 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1265 else if (dx != 0 || dy != 0)
1267 if (player_is_dropping &&
1268 player_drop_count == getPlayerInventorySize(0))
1270 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1272 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1273 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1275 player_is_dropping = FALSE;
1279 if (new_motion_key_x != motion_key_x)
1281 Error(ERR_DEBUG, "---------- %s %s ----------",
1282 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1283 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1285 if (motion_key_x != KSYM_UNDEFINED)
1286 HandleKey(motion_key_x, KEY_RELEASED);
1287 if (new_motion_key_x != KSYM_UNDEFINED)
1288 HandleKey(new_motion_key_x, KEY_PRESSED);
1291 if (new_motion_key_y != motion_key_y)
1293 Error(ERR_DEBUG, "---------- %s %s ----------",
1294 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1295 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1297 if (motion_key_y != KSYM_UNDEFINED)
1298 HandleKey(motion_key_y, KEY_RELEASED);
1299 if (new_motion_key_y != KSYM_UNDEFINED)
1300 HandleKey(new_motion_key_y, KEY_PRESSED);
1303 motion_key_x = new_motion_key_x;
1304 motion_key_y = new_motion_key_y;
1308 static void HandleButtonOrFinger(int mx, int my, int button)
1310 if (game_status != GAME_MODE_PLAYING)
1313 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1315 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1316 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1317 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1318 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1322 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1323 HandleButtonOrFinger_FollowFinger(mx, my, button);
1327 #if defined(TARGET_SDL2)
1329 static boolean checkTextInputKeyModState()
1331 // when playing, only handle raw key events and ignore text input
1332 if (game_status == GAME_MODE_PLAYING)
1335 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1338 void HandleTextEvent(TextEvent *event)
1340 char *text = event->text;
1341 Key key = getKeyFromKeyName(text);
1343 #if DEBUG_EVENTS_TEXT
1344 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1347 text[0], (int)(text[0]),
1349 getKeyNameFromKey(key),
1353 #if !defined(HAS_SCREEN_KEYBOARD)
1354 // non-mobile devices: only handle key input with modifier keys pressed here
1355 // (every other key input is handled directly as physical key input event)
1356 if (!checkTextInputKeyModState())
1360 // process text input as "classic" (with uppercase etc.) key input event
1361 HandleKey(key, KEY_PRESSED);
1362 HandleKey(key, KEY_RELEASED);
1365 void HandlePauseResumeEvent(PauseResumeEvent *event)
1367 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1371 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1379 void HandleKeyEvent(KeyEvent *event)
1381 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1382 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1383 Key key = GetEventKey(event, with_modifiers);
1384 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1386 #if DEBUG_EVENTS_KEY
1387 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1388 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1389 event->keysym.scancode,
1394 getKeyNameFromKey(key));
1397 #if defined(PLATFORM_ANDROID)
1398 if (key == KSYM_Back)
1400 // always map the "back" button to the "escape" key on Android devices
1405 // for any key event other than "back" button, disable overlay buttons
1406 SetOverlayEnabled(FALSE);
1410 HandleKeyModState(keymod, key_status);
1412 #if defined(TARGET_SDL2)
1413 // only handle raw key input without text modifier keys pressed
1414 if (!checkTextInputKeyModState())
1415 HandleKey(key, key_status);
1417 HandleKey(key, key_status);
1421 void HandleFocusEvent(FocusChangeEvent *event)
1423 static int old_joystick_status = -1;
1425 if (event->type == EVENT_FOCUSOUT)
1427 KeyboardAutoRepeatOn();
1428 old_joystick_status = joystick.status;
1429 joystick.status = JOYSTICK_NOT_AVAILABLE;
1431 ClearPlayerAction();
1433 else if (event->type == EVENT_FOCUSIN)
1435 /* When there are two Rocks'n'Diamonds windows which overlap and
1436 the player moves the pointer from one game window to the other,
1437 a 'FocusOut' event is generated for the window the pointer is
1438 leaving and a 'FocusIn' event is generated for the window the
1439 pointer is entering. In some cases, it can happen that the
1440 'FocusIn' event is handled by the one game process before the
1441 'FocusOut' event by the other game process. In this case the
1442 X11 environment would end up with activated keyboard auto repeat,
1443 because unfortunately this is a global setting and not (which
1444 would be far better) set for each X11 window individually.
1445 The effect would be keyboard auto repeat while playing the game
1446 (game_status == GAME_MODE_PLAYING), which is not desired.
1447 To avoid this special case, we just wait 1/10 second before
1448 processing the 'FocusIn' event.
1451 if (game_status == GAME_MODE_PLAYING)
1454 KeyboardAutoRepeatOffUnlessAutoplay();
1457 if (old_joystick_status != -1)
1458 joystick.status = old_joystick_status;
1462 void HandleClientMessageEvent(ClientMessageEvent *event)
1464 if (CheckCloseWindowEvent(event))
1468 void HandleWindowManagerEvent(Event *event)
1470 #if defined(TARGET_SDL)
1471 SDLHandleWindowManagerEvent(event);
1475 void HandleButton(int mx, int my, int button, int button_nr)
1477 static int old_mx = 0, old_my = 0;
1478 boolean button_hold = FALSE;
1479 boolean handle_gadgets = TRUE;
1485 button_nr = -button_nr;
1494 #if defined(PLATFORM_ANDROID)
1495 // when playing, only handle gadgets when using "follow finger" controls
1496 // or when using touch controls in combination with the MM game engine
1498 (game_status != GAME_MODE_PLAYING ||
1499 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1500 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1503 if (handle_gadgets && HandleGadgets(mx, my, button))
1505 /* do not handle this button event anymore */
1506 mx = my = -32; /* force mouse event to be outside screen tiles */
1509 if (HandleGlobalAnimClicks(mx, my, button))
1511 /* do not handle this button event anymore */
1512 return; /* force mouse event not to be handled at all */
1515 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1518 /* do not use scroll wheel button events for anything other than gadgets */
1519 if (IS_WHEEL_BUTTON(button_nr))
1522 switch (game_status)
1524 case GAME_MODE_TITLE:
1525 HandleTitleScreen(mx, my, 0, 0, button);
1528 case GAME_MODE_MAIN:
1529 HandleMainMenu(mx, my, 0, 0, button);
1532 case GAME_MODE_PSEUDO_TYPENAME:
1533 HandleTypeName(0, KSYM_Return);
1536 case GAME_MODE_LEVELS:
1537 HandleChooseLevelSet(mx, my, 0, 0, button);
1540 case GAME_MODE_LEVELNR:
1541 HandleChooseLevelNr(mx, my, 0, 0, button);
1544 case GAME_MODE_SCORES:
1545 HandleHallOfFame(0, 0, 0, 0, button);
1548 case GAME_MODE_EDITOR:
1549 HandleLevelEditorIdle();
1552 case GAME_MODE_INFO:
1553 HandleInfoScreen(mx, my, 0, 0, button);
1556 case GAME_MODE_SETUP:
1557 HandleSetupScreen(mx, my, 0, 0, button);
1560 case GAME_MODE_PLAYING:
1561 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1562 HandleButtonOrFinger(mx, my, button);
1564 SetPlayerMouseAction(mx, my, button);
1567 if (button == MB_PRESSED && !motion_status && !button_hold &&
1568 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1569 DumpTileFromScreen(mx, my);
1579 static boolean is_string_suffix(char *string, char *suffix)
1581 int string_len = strlen(string);
1582 int suffix_len = strlen(suffix);
1584 if (suffix_len > string_len)
1587 return (strEqual(&string[string_len - suffix_len], suffix));
1590 #define MAX_CHEAT_INPUT_LEN 32
1592 static void HandleKeysSpecial(Key key)
1594 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1595 char letter = getCharFromKey(key);
1596 int cheat_input_len = strlen(cheat_input);
1602 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1604 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1605 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1607 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1610 cheat_input[cheat_input_len++] = letter;
1611 cheat_input[cheat_input_len] = '\0';
1613 #if DEBUG_EVENTS_KEY
1614 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1617 if (game_status == GAME_MODE_MAIN)
1619 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1620 is_string_suffix(cheat_input, ":ist"))
1622 InsertSolutionTape();
1624 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1625 is_string_suffix(cheat_input, ":rg"))
1627 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1630 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1631 is_string_suffix(cheat_input, ":rs"))
1633 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1636 else if (is_string_suffix(cheat_input, ":reload-music") ||
1637 is_string_suffix(cheat_input, ":rm"))
1639 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1642 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1643 is_string_suffix(cheat_input, ":ra"))
1645 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1646 1 << ARTWORK_TYPE_SOUNDS |
1647 1 << ARTWORK_TYPE_MUSIC);
1650 else if (is_string_suffix(cheat_input, ":dump-level") ||
1651 is_string_suffix(cheat_input, ":dl"))
1655 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1656 is_string_suffix(cheat_input, ":dt"))
1660 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1661 is_string_suffix(cheat_input, ":ft"))
1663 /* fix single-player tapes that contain player input for more than one
1664 player (due to a bug in 3.3.1.2 and earlier versions), which results
1665 in playing levels with more than one player in multi-player mode,
1666 even though the tape was originally recorded in single-player mode */
1668 /* remove player input actions for all players but the first one */
1669 for (i = 1; i < MAX_PLAYERS; i++)
1670 tape.player_participates[i] = FALSE;
1672 tape.changed = TRUE;
1674 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1675 is_string_suffix(cheat_input, ":snl"))
1677 SaveNativeLevel(&level);
1679 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1680 is_string_suffix(cheat_input, ":fps"))
1682 global.show_frames_per_second = !global.show_frames_per_second;
1685 else if (game_status == GAME_MODE_PLAYING)
1688 if (is_string_suffix(cheat_input, ".q"))
1689 DEBUG_SetMaximumDynamite();
1692 else if (game_status == GAME_MODE_EDITOR)
1694 if (is_string_suffix(cheat_input, ":dump-brush") ||
1695 is_string_suffix(cheat_input, ":DB"))
1699 else if (is_string_suffix(cheat_input, ":DDB"))
1706 void HandleKeysDebug(Key key)
1711 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1713 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1715 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1717 if (key == setup.debug.frame_delay_key[i] &&
1718 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1720 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1721 setup.debug.frame_delay[i] : setup.game_frame_delay);
1723 if (!setup.debug.frame_delay_game_only)
1724 MenuFrameDelay = GameFrameDelay;
1726 SetVideoFrameDelay(GameFrameDelay);
1728 if (GameFrameDelay > ONE_SECOND_DELAY)
1729 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1730 else if (GameFrameDelay != 0)
1731 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1732 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1733 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1735 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1742 if (game_status == GAME_MODE_PLAYING)
1746 options.debug = !options.debug;
1748 Error(ERR_DEBUG, "debug mode %s",
1749 (options.debug ? "enabled" : "disabled"));
1751 else if (key == KSYM_v)
1753 Error(ERR_DEBUG, "currently using game engine version %d",
1754 game.engine_version);
1760 void HandleKey(Key key, int key_status)
1762 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1763 static boolean ignore_repeated_key = FALSE;
1764 static struct SetupKeyboardInfo ski;
1765 static struct SetupShortcutInfo ssi;
1774 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1775 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1776 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1777 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1778 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1779 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1784 #if defined(TARGET_SDL2)
1785 /* map special keys (media keys / remote control buttons) to default keys */
1786 if (key == KSYM_PlayPause)
1788 else if (key == KSYM_Select)
1792 HandleSpecialGameControllerKeys(key, key_status);
1794 if (game_status == GAME_MODE_PLAYING)
1796 /* only needed for single-step tape recording mode */
1797 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1800 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1802 byte key_action = 0;
1804 if (setup.input[pnr].use_joystick)
1807 ski = setup.input[pnr].key;
1809 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1810 if (key == *key_info[i].key_custom)
1811 key_action |= key_info[i].action;
1813 /* use combined snap+direction keys for the first player only */
1816 ssi = setup.shortcut;
1818 for (i = 0; i < NUM_DIRECTIONS; i++)
1819 if (key == *key_info[i].key_snap)
1820 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1823 if (key_status == KEY_PRESSED)
1824 stored_player[pnr].action |= key_action;
1826 stored_player[pnr].action &= ~key_action;
1828 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
1830 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1832 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1834 /* if snap key already pressed, keep pause mode when releasing */
1835 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1836 has_snapped[pnr] = TRUE;
1838 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1840 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1842 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1843 getRedDiskReleaseFlag_SP() == 0)
1845 /* add a single inactive frame before dropping starts */
1846 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1847 stored_player[pnr].force_dropping = TRUE;
1850 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1852 /* if snap key was pressed without direction, leave pause mode */
1853 if (!has_snapped[pnr])
1854 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1856 has_snapped[pnr] = FALSE;
1859 else if (tape.recording && tape.pausing && !tape.use_mouse)
1861 /* prevent key release events from un-pausing a paused game */
1862 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1863 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1866 // for MM style levels, handle in-game keyboard input in HandleJoystick()
1867 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1873 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1874 if (key == key_info[i].key_default)
1875 joy |= key_info[i].action;
1880 if (key_status == KEY_PRESSED)
1881 key_joystick_mapping |= joy;
1883 key_joystick_mapping &= ~joy;
1888 if (game_status != GAME_MODE_PLAYING)
1889 key_joystick_mapping = 0;
1891 if (key_status == KEY_RELEASED)
1893 // reset flag to ignore repeated "key pressed" events after key release
1894 ignore_repeated_key = FALSE;
1899 if ((key == KSYM_F11 ||
1900 ((key == KSYM_Return ||
1901 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1902 video.fullscreen_available &&
1903 !ignore_repeated_key)
1905 setup.fullscreen = !setup.fullscreen;
1907 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1909 if (game_status == GAME_MODE_SETUP)
1910 RedrawSetupScreenAfterFullscreenToggle();
1912 // set flag to ignore repeated "key pressed" events
1913 ignore_repeated_key = TRUE;
1918 if ((key == KSYM_0 || key == KSYM_KP_0 ||
1919 key == KSYM_minus || key == KSYM_KP_Subtract ||
1920 key == KSYM_plus || key == KSYM_KP_Add ||
1921 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
1922 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1923 video.window_scaling_available &&
1924 !video.fullscreen_enabled)
1926 if (key == KSYM_0 || key == KSYM_KP_0)
1927 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1928 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1929 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1931 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1933 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1934 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1935 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1936 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1938 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1940 if (game_status == GAME_MODE_SETUP)
1941 RedrawSetupScreenAfterFullscreenToggle();
1946 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
1947 key == KSYM_Return ||
1948 key == KSYM_Escape)))
1950 /* do not handle this key event anymore */
1951 if (key != KSYM_Escape) /* always allow ESC key to be handled */
1955 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1956 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1963 if (game_status == GAME_MODE_MAIN &&
1964 (key == setup.shortcut.toggle_pause || key == KSYM_space))
1966 StartGameActions(options.network, setup.autorecord, level.random_seed);
1971 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1973 if (key == setup.shortcut.save_game)
1975 else if (key == setup.shortcut.load_game)
1977 else if (key == setup.shortcut.toggle_pause)
1978 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
1980 HandleTapeButtonKeys(key);
1981 HandleSoundButtonKeys(key);
1984 if (game_status == GAME_MODE_PLAYING && !network_playing)
1986 int centered_player_nr_next = -999;
1988 if (key == setup.shortcut.focus_player_all)
1989 centered_player_nr_next = -1;
1991 for (i = 0; i < MAX_PLAYERS; i++)
1992 if (key == setup.shortcut.focus_player[i])
1993 centered_player_nr_next = i;
1995 if (centered_player_nr_next != -999)
1997 game.centered_player_nr_next = centered_player_nr_next;
1998 game.set_centered_player = TRUE;
2002 tape.centered_player_nr_next = game.centered_player_nr_next;
2003 tape.set_centered_player = TRUE;
2008 HandleKeysSpecial(key);
2010 if (HandleGadgetsKeyInput(key))
2012 if (key != KSYM_Escape) /* always allow ESC key to be handled */
2013 key = KSYM_UNDEFINED;
2016 switch (game_status)
2018 case GAME_MODE_PSEUDO_TYPENAME:
2019 HandleTypeName(0, key);
2022 case GAME_MODE_TITLE:
2023 case GAME_MODE_MAIN:
2024 case GAME_MODE_LEVELS:
2025 case GAME_MODE_LEVELNR:
2026 case GAME_MODE_SETUP:
2027 case GAME_MODE_INFO:
2028 case GAME_MODE_SCORES:
2033 if (game_status == GAME_MODE_TITLE)
2034 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2035 else if (game_status == GAME_MODE_MAIN)
2036 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2037 else if (game_status == GAME_MODE_LEVELS)
2038 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2039 else if (game_status == GAME_MODE_LEVELNR)
2040 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2041 else if (game_status == GAME_MODE_SETUP)
2042 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2043 else if (game_status == GAME_MODE_INFO)
2044 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2045 else if (game_status == GAME_MODE_SCORES)
2046 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2050 if (game_status != GAME_MODE_MAIN)
2051 FadeSkipNextFadeIn();
2053 if (game_status == GAME_MODE_TITLE)
2054 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2055 else if (game_status == GAME_MODE_LEVELS)
2056 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2057 else if (game_status == GAME_MODE_LEVELNR)
2058 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2059 else if (game_status == GAME_MODE_SETUP)
2060 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2061 else if (game_status == GAME_MODE_INFO)
2062 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2063 else if (game_status == GAME_MODE_SCORES)
2064 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2068 if (game_status == GAME_MODE_LEVELS)
2069 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2070 else if (game_status == GAME_MODE_LEVELNR)
2071 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2072 else if (game_status == GAME_MODE_SETUP)
2073 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2074 else if (game_status == GAME_MODE_INFO)
2075 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2076 else if (game_status == GAME_MODE_SCORES)
2077 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2080 case KSYM_Page_Down:
2081 if (game_status == GAME_MODE_LEVELS)
2082 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2083 else if (game_status == GAME_MODE_LEVELNR)
2084 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2085 else if (game_status == GAME_MODE_SETUP)
2086 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2087 else if (game_status == GAME_MODE_INFO)
2088 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2089 else if (game_status == GAME_MODE_SCORES)
2090 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2098 case GAME_MODE_EDITOR:
2099 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2100 HandleLevelEditorKeyInput(key);
2103 case GAME_MODE_PLAYING:
2108 RequestQuitGame(setup.ask_on_escape);
2118 if (key == KSYM_Escape)
2120 SetGameStatus(GAME_MODE_MAIN);
2128 HandleKeysDebug(key);
2131 void HandleNoEvent()
2133 HandleMouseCursor();
2135 switch (game_status)
2137 case GAME_MODE_PLAYING:
2138 HandleButtonOrFinger(-1, -1, -1);
2143 void HandleEventActions()
2145 // if (button_status && game_status != GAME_MODE_PLAYING)
2146 if (button_status && (game_status != GAME_MODE_PLAYING ||
2148 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2150 HandleButton(0, 0, button_status, -button_status);
2157 #if defined(NETWORK_AVALIABLE)
2158 if (options.network)
2162 switch (game_status)
2164 case GAME_MODE_MAIN:
2165 DrawPreviewLevelAnimation();
2168 case GAME_MODE_EDITOR:
2169 HandleLevelEditorIdle();
2177 static void HandleTileCursor(int dx, int dy, int button)
2180 ClearPlayerMouseAction();
2187 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2188 (dx < 0 ? MB_LEFTBUTTON :
2189 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2191 else if (!tile_cursor.moving)
2193 int old_xpos = tile_cursor.xpos;
2194 int old_ypos = tile_cursor.ypos;
2195 int new_xpos = old_xpos;
2196 int new_ypos = old_ypos;
2198 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2199 new_xpos = old_xpos + dx;
2201 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2202 new_ypos = old_ypos + dy;
2204 SetTileCursorTargetXY(new_xpos, new_ypos);
2208 static int HandleJoystickForAllPlayers()
2212 boolean no_joysticks_configured = TRUE;
2213 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2214 static byte joy_action_last[MAX_PLAYERS];
2216 for (i = 0; i < MAX_PLAYERS; i++)
2217 if (setup.input[i].use_joystick)
2218 no_joysticks_configured = FALSE;
2220 /* if no joysticks configured, map connected joysticks to players */
2221 if (no_joysticks_configured)
2222 use_as_joystick_nr = TRUE;
2224 for (i = 0; i < MAX_PLAYERS; i++)
2226 byte joy_action = 0;
2228 joy_action = JoystickExt(i, use_as_joystick_nr);
2229 result |= joy_action;
2231 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2232 joy_action != joy_action_last[i])
2233 stored_player[i].action = joy_action;
2235 joy_action_last[i] = joy_action;
2241 void HandleJoystick()
2243 static unsigned int joytest_delay = 0;
2244 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2245 static int joytest_last = 0;
2246 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2247 int delay_value = GADGET_FRAME_DELAY;
2248 int joystick = HandleJoystickForAllPlayers();
2249 int keyboard = key_joystick_mapping;
2250 int joy = (joystick | keyboard);
2251 int joytest = joystick;
2252 int left = joy & JOY_LEFT;
2253 int right = joy & JOY_RIGHT;
2254 int up = joy & JOY_UP;
2255 int down = joy & JOY_DOWN;
2256 int button = joy & JOY_BUTTON;
2257 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2258 int dx = (left ? -1 : right ? 1 : 0);
2259 int dy = (up ? -1 : down ? 1 : 0);
2260 boolean use_delay_value_first = (joytest != joytest_last);
2262 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2264 /* do not handle this button event anymore */
2268 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2270 if (game_status == GAME_MODE_PLAYING)
2272 // when playing MM style levels, also use delay for keyboard events
2273 joytest |= keyboard;
2275 // only use first delay value for new events, but not for changed events
2276 use_delay_value_first = (!joytest != !joytest_last);
2278 // only use delay after the initial keyboard event
2282 // for any joystick or keyboard event, enable playfield tile cursor
2283 if (dx || dy || button)
2284 SetTileCursorEnabled(TRUE);
2287 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2289 /* delay joystick/keyboard actions if axes/keys continually pressed */
2290 newbutton = dx = dy = 0;
2294 /* first start with longer delay, then continue with shorter delay */
2295 joytest_delay_value =
2296 (use_delay_value_first ? delay_value_first : delay_value);
2299 joytest_last = joytest;
2301 switch (game_status)
2303 case GAME_MODE_TITLE:
2304 case GAME_MODE_MAIN:
2305 case GAME_MODE_LEVELS:
2306 case GAME_MODE_LEVELNR:
2307 case GAME_MODE_SETUP:
2308 case GAME_MODE_INFO:
2309 case GAME_MODE_SCORES:
2311 if (game_status == GAME_MODE_TITLE)
2312 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2313 else if (game_status == GAME_MODE_MAIN)
2314 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2315 else if (game_status == GAME_MODE_LEVELS)
2316 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2317 else if (game_status == GAME_MODE_LEVELNR)
2318 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2319 else if (game_status == GAME_MODE_SETUP)
2320 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2321 else if (game_status == GAME_MODE_INFO)
2322 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2323 else if (game_status == GAME_MODE_SCORES)
2324 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2329 case GAME_MODE_PLAYING:
2331 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2332 if (tape.playing || keyboard)
2333 newbutton = ((joy & JOY_BUTTON) != 0);
2336 if (newbutton && AllPlayersGone)
2343 if (tape.recording && tape.pausing && !tape.use_mouse)
2345 if (joystick & JOY_ACTION)
2346 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2349 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2350 HandleTileCursor(dx, dy, button);
2359 void HandleSpecialGameControllerButtons(Event *event)
2361 #if defined(TARGET_SDL2)
2362 switch (event->type)
2364 case SDL_CONTROLLERBUTTONDOWN:
2365 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2366 HandleKey(KSYM_space, KEY_PRESSED);
2367 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2368 HandleKey(KSYM_Escape, KEY_PRESSED);
2372 case SDL_CONTROLLERBUTTONUP:
2373 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2374 HandleKey(KSYM_space, KEY_RELEASED);
2375 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2376 HandleKey(KSYM_Escape, KEY_RELEASED);
2383 void HandleSpecialGameControllerKeys(Key key, int key_status)
2385 #if defined(TARGET_SDL2)
2386 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2387 int button = SDL_CONTROLLER_BUTTON_INVALID;
2389 /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2390 if (key == KSYM_Rewind)
2391 button = SDL_CONTROLLER_BUTTON_A;
2392 else if (key == KSYM_FastForward || key == KSYM_Menu)
2393 button = SDL_CONTROLLER_BUTTON_B;
2395 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2399 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2400 SDL_CONTROLLERBUTTONUP);
2402 event.cbutton.which = 0; /* first joystick (Amazon Fire TV remote) */
2403 event.cbutton.button = button;
2404 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2407 HandleJoystickEvent(&event);