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 #if defined(HAS_SCREEN_KEYBOARD)
486 if (video.shifted_up)
487 event->y += video.shifted_up_pos;
490 motion_status = FALSE;
492 if (event->type == EVENT_BUTTONPRESS)
493 button_status = event->button;
495 button_status = MB_RELEASED;
497 HandleButton(event->x, event->y, button_status, event->button);
500 void HandleMotionEvent(MotionEvent *event)
502 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
505 motion_status = TRUE;
507 #if DEBUG_EVENTS_MOTION
508 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
509 button_status, event->x, event->y);
512 HandleButton(event->x, event->y, button_status, button_status);
515 #if defined(TARGET_SDL2)
517 void HandleWheelEvent(WheelEvent *event)
521 #if DEBUG_EVENTS_WHEEL
523 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
524 event->which, event->x, event->y);
526 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
527 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
528 event->which, event->x, event->y,
529 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
530 "SDL_MOUSEWHEEL_FLIPPED"));
534 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
535 event->x > 0 ? MB_WHEEL_RIGHT :
536 event->y < 0 ? MB_WHEEL_DOWN :
537 event->y > 0 ? MB_WHEEL_UP : 0);
539 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
540 // accelerated mouse wheel available on Mac and Windows
541 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
543 // no accelerated mouse wheel available on Unix/Linux
544 wheel_steps = DEFAULT_WHEEL_STEPS;
547 motion_status = FALSE;
549 button_status = button_nr;
550 HandleButton(0, 0, button_status, -button_nr);
552 button_status = MB_RELEASED;
553 HandleButton(0, 0, button_status, -button_nr);
556 void HandleWindowEvent(WindowEvent *event)
558 #if DEBUG_EVENTS_WINDOW
559 int subtype = event->event;
562 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
563 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
564 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
565 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
566 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
567 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
568 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
569 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
570 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
571 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
572 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
573 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
574 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
575 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
578 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
579 event_name, event->data1, event->data2);
583 // (not needed, as the screen gets redrawn every 20 ms anyway)
584 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
585 event->event == SDL_WINDOWEVENT_RESIZED ||
586 event->event == SDL_WINDOWEVENT_EXPOSED)
590 if (event->event == SDL_WINDOWEVENT_RESIZED)
592 if (!video.fullscreen_enabled)
594 int new_window_width = event->data1;
595 int new_window_height = event->data2;
597 // if window size has changed after resizing, calculate new scaling factor
598 if (new_window_width != video.window_width ||
599 new_window_height != video.window_height)
601 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
602 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
604 // (extreme window scaling allowed, but cannot be saved permanently)
605 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
606 setup.window_scaling_percent =
607 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
608 MAX_WINDOW_SCALING_PERCENT);
610 video.window_width = new_window_width;
611 video.window_height = new_window_height;
613 if (game_status == GAME_MODE_SETUP)
614 RedrawSetupScreenAfterFullscreenToggle();
619 #if defined(PLATFORM_ANDROID)
622 int new_display_width = event->data1;
623 int new_display_height = event->data2;
625 // if fullscreen display size has changed, device has been rotated
626 if (new_display_width != video.display_width ||
627 new_display_height != video.display_height)
629 video.display_width = new_display_width;
630 video.display_height = new_display_height;
632 SDLSetScreenProperties();
639 #define NUM_TOUCH_FINGERS 3
644 SDL_FingerID finger_id;
647 } touch_info[NUM_TOUCH_FINGERS];
649 void HandleFingerEvent_VirtualButtons(FingerEvent *event)
651 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
652 float event_x = (event->x);
653 float event_y = (event->y - ypos) / (1 - ypos);
654 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
655 event_y > 2.0 / 3.0 && event_y < 1 ?
656 setup.input[0].key.snap :
657 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
658 event_y > 2.0 / 3.0 && event_y < 1 ?
659 setup.input[0].key.drop :
660 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
661 event_y > 0 && event_y < 1.0 / 3.0 ?
662 setup.input[0].key.up :
663 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
664 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
665 setup.input[0].key.left :
666 event_x > 8.0 / 9.0 && event_x < 1 &&
667 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
668 setup.input[0].key.right :
669 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
670 event_y > 2.0 / 3.0 && event_y < 1 ?
671 setup.input[0].key.down :
673 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
675 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
679 // for any touch input event, enable overlay buttons (if activated)
680 SetOverlayEnabled(TRUE);
682 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
683 getKeyNameFromKey(key), key_status_name, event->fingerId);
685 // check if we already know this touch event's finger id
686 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
688 if (touch_info[i].touched &&
689 touch_info[i].finger_id == event->fingerId)
691 // Error(ERR_DEBUG, "MARK 1: %d", i);
697 if (i >= NUM_TOUCH_FINGERS)
699 if (key_status == KEY_PRESSED)
701 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
703 // unknown finger id -- get new, empty slot, if available
704 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
706 if (touch_info[i].counter < oldest_counter)
709 oldest_counter = touch_info[i].counter;
711 // Error(ERR_DEBUG, "MARK 2: %d", i);
714 if (!touch_info[i].touched)
716 // Error(ERR_DEBUG, "MARK 3: %d", i);
722 if (i >= NUM_TOUCH_FINGERS)
724 // all slots allocated -- use oldest slot
727 // Error(ERR_DEBUG, "MARK 4: %d", i);
732 // release of previously unknown key (should not happen)
734 if (key != KSYM_UNDEFINED)
736 HandleKey(key, KEY_RELEASED);
738 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
739 getKeyNameFromKey(key), "KEY_RELEASED", i);
744 if (i < NUM_TOUCH_FINGERS)
746 if (key_status == KEY_PRESSED)
748 if (touch_info[i].key != key)
750 if (touch_info[i].key != KSYM_UNDEFINED)
752 HandleKey(touch_info[i].key, KEY_RELEASED);
754 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
755 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
758 if (key != KSYM_UNDEFINED)
760 HandleKey(key, KEY_PRESSED);
762 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
763 getKeyNameFromKey(key), "KEY_PRESSED", i);
767 touch_info[i].touched = TRUE;
768 touch_info[i].finger_id = event->fingerId;
769 touch_info[i].counter = Counter();
770 touch_info[i].key = key;
774 if (touch_info[i].key != KSYM_UNDEFINED)
776 HandleKey(touch_info[i].key, KEY_RELEASED);
778 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
779 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
782 touch_info[i].touched = FALSE;
783 touch_info[i].finger_id = 0;
784 touch_info[i].counter = 0;
785 touch_info[i].key = 0;
790 void HandleFingerEvent_WipeGestures(FingerEvent *event)
792 static Key motion_key_x = KSYM_UNDEFINED;
793 static Key motion_key_y = KSYM_UNDEFINED;
794 static Key button_key = KSYM_UNDEFINED;
795 static float motion_x1, motion_y1;
796 static float button_x1, button_y1;
797 static SDL_FingerID motion_id = -1;
798 static SDL_FingerID button_id = -1;
799 int move_trigger_distance_percent = setup.touch.move_distance;
800 int drop_trigger_distance_percent = setup.touch.drop_distance;
801 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
802 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
803 float event_x = event->x;
804 float event_y = event->y;
806 if (event->type == EVENT_FINGERPRESS)
808 if (event_x > 1.0 / 3.0)
812 motion_id = event->fingerId;
817 motion_key_x = KSYM_UNDEFINED;
818 motion_key_y = KSYM_UNDEFINED;
820 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
826 button_id = event->fingerId;
831 button_key = setup.input[0].key.snap;
833 HandleKey(button_key, KEY_PRESSED);
835 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
838 else if (event->type == EVENT_FINGERRELEASE)
840 if (event->fingerId == motion_id)
844 if (motion_key_x != KSYM_UNDEFINED)
845 HandleKey(motion_key_x, KEY_RELEASED);
846 if (motion_key_y != KSYM_UNDEFINED)
847 HandleKey(motion_key_y, KEY_RELEASED);
849 motion_key_x = KSYM_UNDEFINED;
850 motion_key_y = KSYM_UNDEFINED;
852 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
854 else if (event->fingerId == button_id)
858 if (button_key != KSYM_UNDEFINED)
859 HandleKey(button_key, KEY_RELEASED);
861 button_key = KSYM_UNDEFINED;
863 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
866 else if (event->type == EVENT_FINGERMOTION)
868 if (event->fingerId == motion_id)
870 float distance_x = ABS(event_x - motion_x1);
871 float distance_y = ABS(event_y - motion_y1);
872 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
873 event_x > motion_x1 ? setup.input[0].key.right :
875 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
876 event_y > motion_y1 ? setup.input[0].key.down :
879 if (distance_x < move_trigger_distance / 2 ||
880 distance_x < distance_y)
881 new_motion_key_x = KSYM_UNDEFINED;
883 if (distance_y < move_trigger_distance / 2 ||
884 distance_y < distance_x)
885 new_motion_key_y = KSYM_UNDEFINED;
887 if (distance_x > move_trigger_distance ||
888 distance_y > move_trigger_distance)
890 if (new_motion_key_x != motion_key_x)
892 if (motion_key_x != KSYM_UNDEFINED)
893 HandleKey(motion_key_x, KEY_RELEASED);
894 if (new_motion_key_x != KSYM_UNDEFINED)
895 HandleKey(new_motion_key_x, KEY_PRESSED);
898 if (new_motion_key_y != motion_key_y)
900 if (motion_key_y != KSYM_UNDEFINED)
901 HandleKey(motion_key_y, KEY_RELEASED);
902 if (new_motion_key_y != KSYM_UNDEFINED)
903 HandleKey(new_motion_key_y, KEY_PRESSED);
909 motion_key_x = new_motion_key_x;
910 motion_key_y = new_motion_key_y;
912 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
915 else if (event->fingerId == button_id)
917 float distance_x = ABS(event_x - button_x1);
918 float distance_y = ABS(event_y - button_y1);
920 if (distance_x < drop_trigger_distance / 2 &&
921 distance_y > drop_trigger_distance)
923 if (button_key == setup.input[0].key.snap)
924 HandleKey(button_key, KEY_RELEASED);
929 button_key = setup.input[0].key.drop;
931 HandleKey(button_key, KEY_PRESSED);
933 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
939 void HandleFingerEvent(FingerEvent *event)
941 #if DEBUG_EVENTS_FINGER
942 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
943 event->type == EVENT_FINGERPRESS ? "pressed" :
944 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
948 event->dx, event->dy,
952 if (game_status != GAME_MODE_PLAYING)
955 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
958 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
959 HandleFingerEvent_VirtualButtons(event);
960 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
961 HandleFingerEvent_WipeGestures(event);
966 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
968 static int old_mx = 0, old_my = 0;
969 static int last_button = MB_LEFTBUTTON;
970 static boolean touched = FALSE;
971 static boolean tapped = FALSE;
973 // screen tile was tapped (but finger not touching the screen anymore)
974 // (this point will also be reached without receiving a touch event)
975 if (tapped && !touched)
977 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
982 // stop here if this function was not triggered by a touch event
986 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
988 // finger started touching the screen
998 ClearPlayerMouseAction();
1000 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1003 else if (button == MB_RELEASED && touched)
1005 // finger stopped touching the screen
1010 SetPlayerMouseAction(old_mx, old_my, last_button);
1012 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1014 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1019 // finger moved while touching the screen
1021 int old_x = getLevelFromScreenX(old_mx);
1022 int old_y = getLevelFromScreenY(old_my);
1023 int new_x = getLevelFromScreenX(mx);
1024 int new_y = getLevelFromScreenY(my);
1026 if (new_x != old_x || new_y != old_y)
1031 // finger moved left or right from (horizontal) starting position
1033 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1035 SetPlayerMouseAction(old_mx, old_my, button_nr);
1037 last_button = button_nr;
1039 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1043 // finger stays at or returned to (horizontal) starting position
1045 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1047 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1052 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1054 static int old_mx = 0, old_my = 0;
1055 static int last_button = MB_LEFTBUTTON;
1056 static boolean touched = FALSE;
1057 static boolean tapped = FALSE;
1059 // screen tile was tapped (but finger not touching the screen anymore)
1060 // (this point will also be reached without receiving a touch event)
1061 if (tapped && !touched)
1063 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1068 // stop here if this function was not triggered by a touch event
1072 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1074 // finger started touching the screen
1084 ClearPlayerMouseAction();
1086 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1089 else if (button == MB_RELEASED && touched)
1091 // finger stopped touching the screen
1096 SetPlayerMouseAction(old_mx, old_my, last_button);
1098 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1100 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1105 // finger moved while touching the screen
1107 int old_x = getLevelFromScreenX(old_mx);
1108 int old_y = getLevelFromScreenY(old_my);
1109 int new_x = getLevelFromScreenX(mx);
1110 int new_y = getLevelFromScreenY(my);
1112 if (new_x != old_x || new_y != old_y)
1114 // finger moved away from starting position
1116 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1118 // quickly alternate between clicking and releasing for maximum speed
1119 if (FrameCounter % 2 == 0)
1120 button_nr = MB_RELEASED;
1122 SetPlayerMouseAction(old_mx, old_my, button_nr);
1125 last_button = button_nr;
1129 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1133 // finger stays at or returned to starting position
1135 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1137 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1142 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1144 static int old_mx = 0, old_my = 0;
1145 static Key motion_key_x = KSYM_UNDEFINED;
1146 static Key motion_key_y = KSYM_UNDEFINED;
1147 static boolean touched = FALSE;
1148 static boolean started_on_player = FALSE;
1149 static boolean player_is_dropping = FALSE;
1150 static int player_drop_count = 0;
1151 static int last_player_x = -1;
1152 static int last_player_y = -1;
1154 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1163 started_on_player = FALSE;
1164 player_is_dropping = FALSE;
1165 player_drop_count = 0;
1169 motion_key_x = KSYM_UNDEFINED;
1170 motion_key_y = KSYM_UNDEFINED;
1172 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1175 else if (button == MB_RELEASED && touched)
1182 if (motion_key_x != KSYM_UNDEFINED)
1183 HandleKey(motion_key_x, KEY_RELEASED);
1184 if (motion_key_y != KSYM_UNDEFINED)
1185 HandleKey(motion_key_y, KEY_RELEASED);
1187 if (started_on_player)
1189 if (player_is_dropping)
1191 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1193 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1197 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1199 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1203 motion_key_x = KSYM_UNDEFINED;
1204 motion_key_y = KSYM_UNDEFINED;
1206 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1211 int src_x = local_player->jx;
1212 int src_y = local_player->jy;
1213 int dst_x = getLevelFromScreenX(old_mx);
1214 int dst_y = getLevelFromScreenY(old_my);
1215 int dx = dst_x - src_x;
1216 int dy = dst_y - src_y;
1217 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1218 dx > 0 ? setup.input[0].key.right :
1220 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1221 dy > 0 ? setup.input[0].key.down :
1224 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1225 (last_player_x != local_player->jx ||
1226 last_player_y != local_player->jy))
1228 // in case of asymmetric diagonal movement, use "preferred" direction
1230 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1232 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1233 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1235 local_player->last_move_dir = last_move_dir;
1237 // (required to prevent accidentally forcing direction for next movement)
1238 last_player_x = local_player->jx;
1239 last_player_y = local_player->jy;
1242 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1244 started_on_player = TRUE;
1245 player_drop_count = getPlayerInventorySize(0);
1246 player_is_dropping = (player_drop_count > 0);
1248 if (player_is_dropping)
1250 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1252 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1256 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1258 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1261 else if (dx != 0 || dy != 0)
1263 if (player_is_dropping &&
1264 player_drop_count == getPlayerInventorySize(0))
1266 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1268 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1269 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1271 player_is_dropping = FALSE;
1275 if (new_motion_key_x != motion_key_x)
1277 Error(ERR_DEBUG, "---------- %s %s ----------",
1278 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1279 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1281 if (motion_key_x != KSYM_UNDEFINED)
1282 HandleKey(motion_key_x, KEY_RELEASED);
1283 if (new_motion_key_x != KSYM_UNDEFINED)
1284 HandleKey(new_motion_key_x, KEY_PRESSED);
1287 if (new_motion_key_y != motion_key_y)
1289 Error(ERR_DEBUG, "---------- %s %s ----------",
1290 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1291 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1293 if (motion_key_y != KSYM_UNDEFINED)
1294 HandleKey(motion_key_y, KEY_RELEASED);
1295 if (new_motion_key_y != KSYM_UNDEFINED)
1296 HandleKey(new_motion_key_y, KEY_PRESSED);
1299 motion_key_x = new_motion_key_x;
1300 motion_key_y = new_motion_key_y;
1304 static void HandleButtonOrFinger(int mx, int my, int button)
1306 if (game_status != GAME_MODE_PLAYING)
1309 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1311 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1312 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1313 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1314 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1318 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1319 HandleButtonOrFinger_FollowFinger(mx, my, button);
1323 #if defined(TARGET_SDL2)
1325 static boolean checkTextInputKeyModState()
1327 // when playing, only handle raw key events and ignore text input
1328 if (game_status == GAME_MODE_PLAYING)
1331 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1334 void HandleTextEvent(TextEvent *event)
1336 char *text = event->text;
1337 Key key = getKeyFromKeyName(text);
1339 #if DEBUG_EVENTS_TEXT
1340 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1343 text[0], (int)(text[0]),
1345 getKeyNameFromKey(key),
1349 #if !defined(HAS_SCREEN_KEYBOARD)
1350 // non-mobile devices: only handle key input with modifier keys pressed here
1351 // (every other key input is handled directly as physical key input event)
1352 if (!checkTextInputKeyModState())
1356 // process text input as "classic" (with uppercase etc.) key input event
1357 HandleKey(key, KEY_PRESSED);
1358 HandleKey(key, KEY_RELEASED);
1361 void HandlePauseResumeEvent(PauseResumeEvent *event)
1363 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1367 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1375 void HandleKeyEvent(KeyEvent *event)
1377 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1378 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1379 Key key = GetEventKey(event, with_modifiers);
1380 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1382 #if DEBUG_EVENTS_KEY
1383 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1384 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1385 event->keysym.scancode,
1390 getKeyNameFromKey(key));
1393 #if defined(PLATFORM_ANDROID)
1394 if (key == KSYM_Back)
1396 // always map the "back" button to the "escape" key on Android devices
1401 // for any key event other than "back" button, disable overlay buttons
1402 SetOverlayEnabled(FALSE);
1406 HandleKeyModState(keymod, key_status);
1408 #if defined(TARGET_SDL2)
1409 // only handle raw key input without text modifier keys pressed
1410 if (!checkTextInputKeyModState())
1411 HandleKey(key, key_status);
1413 HandleKey(key, key_status);
1417 void HandleFocusEvent(FocusChangeEvent *event)
1419 static int old_joystick_status = -1;
1421 if (event->type == EVENT_FOCUSOUT)
1423 KeyboardAutoRepeatOn();
1424 old_joystick_status = joystick.status;
1425 joystick.status = JOYSTICK_NOT_AVAILABLE;
1427 ClearPlayerAction();
1429 else if (event->type == EVENT_FOCUSIN)
1431 /* When there are two Rocks'n'Diamonds windows which overlap and
1432 the player moves the pointer from one game window to the other,
1433 a 'FocusOut' event is generated for the window the pointer is
1434 leaving and a 'FocusIn' event is generated for the window the
1435 pointer is entering. In some cases, it can happen that the
1436 'FocusIn' event is handled by the one game process before the
1437 'FocusOut' event by the other game process. In this case the
1438 X11 environment would end up with activated keyboard auto repeat,
1439 because unfortunately this is a global setting and not (which
1440 would be far better) set for each X11 window individually.
1441 The effect would be keyboard auto repeat while playing the game
1442 (game_status == GAME_MODE_PLAYING), which is not desired.
1443 To avoid this special case, we just wait 1/10 second before
1444 processing the 'FocusIn' event.
1447 if (game_status == GAME_MODE_PLAYING)
1450 KeyboardAutoRepeatOffUnlessAutoplay();
1453 if (old_joystick_status != -1)
1454 joystick.status = old_joystick_status;
1458 void HandleClientMessageEvent(ClientMessageEvent *event)
1460 if (CheckCloseWindowEvent(event))
1464 void HandleWindowManagerEvent(Event *event)
1466 #if defined(TARGET_SDL)
1467 SDLHandleWindowManagerEvent(event);
1471 void HandleButton(int mx, int my, int button, int button_nr)
1473 static int old_mx = 0, old_my = 0;
1474 boolean button_hold = FALSE;
1475 boolean handle_gadgets = TRUE;
1481 button_nr = -button_nr;
1490 #if defined(PLATFORM_ANDROID)
1491 // when playing, only handle gadgets when using "follow finger" controls
1492 // or when using touch controls in combination with the MM game engine
1494 (game_status != GAME_MODE_PLAYING ||
1495 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1496 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1499 if (handle_gadgets && HandleGadgets(mx, my, button))
1501 /* do not handle this button event anymore */
1502 mx = my = -32; /* force mouse event to be outside screen tiles */
1505 if (HandleGlobalAnimClicks(mx, my, button))
1507 /* do not handle this button event anymore */
1508 return; /* force mouse event not to be handled at all */
1511 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1514 /* do not use scroll wheel button events for anything other than gadgets */
1515 if (IS_WHEEL_BUTTON(button_nr))
1518 switch (game_status)
1520 case GAME_MODE_TITLE:
1521 HandleTitleScreen(mx, my, 0, 0, button);
1524 case GAME_MODE_MAIN:
1525 HandleMainMenu(mx, my, 0, 0, button);
1528 case GAME_MODE_PSEUDO_TYPENAME:
1529 HandleTypeName(0, KSYM_Return);
1532 case GAME_MODE_LEVELS:
1533 HandleChooseLevelSet(mx, my, 0, 0, button);
1536 case GAME_MODE_LEVELNR:
1537 HandleChooseLevelNr(mx, my, 0, 0, button);
1540 case GAME_MODE_SCORES:
1541 HandleHallOfFame(0, 0, 0, 0, button);
1544 case GAME_MODE_EDITOR:
1545 HandleLevelEditorIdle();
1548 case GAME_MODE_INFO:
1549 HandleInfoScreen(mx, my, 0, 0, button);
1552 case GAME_MODE_SETUP:
1553 HandleSetupScreen(mx, my, 0, 0, button);
1556 case GAME_MODE_PLAYING:
1557 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1558 HandleButtonOrFinger(mx, my, button);
1560 SetPlayerMouseAction(mx, my, button);
1563 if (button == MB_PRESSED && !motion_status && !button_hold &&
1564 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1565 DumpTileFromScreen(mx, my);
1575 static boolean is_string_suffix(char *string, char *suffix)
1577 int string_len = strlen(string);
1578 int suffix_len = strlen(suffix);
1580 if (suffix_len > string_len)
1583 return (strEqual(&string[string_len - suffix_len], suffix));
1586 #define MAX_CHEAT_INPUT_LEN 32
1588 static void HandleKeysSpecial(Key key)
1590 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1591 char letter = getCharFromKey(key);
1592 int cheat_input_len = strlen(cheat_input);
1598 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1600 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1601 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1603 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1606 cheat_input[cheat_input_len++] = letter;
1607 cheat_input[cheat_input_len] = '\0';
1609 #if DEBUG_EVENTS_KEY
1610 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1613 if (game_status == GAME_MODE_MAIN)
1615 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1616 is_string_suffix(cheat_input, ":ist"))
1618 InsertSolutionTape();
1620 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1621 is_string_suffix(cheat_input, ":rg"))
1623 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1626 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1627 is_string_suffix(cheat_input, ":rs"))
1629 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1632 else if (is_string_suffix(cheat_input, ":reload-music") ||
1633 is_string_suffix(cheat_input, ":rm"))
1635 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1638 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1639 is_string_suffix(cheat_input, ":ra"))
1641 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1642 1 << ARTWORK_TYPE_SOUNDS |
1643 1 << ARTWORK_TYPE_MUSIC);
1646 else if (is_string_suffix(cheat_input, ":dump-level") ||
1647 is_string_suffix(cheat_input, ":dl"))
1651 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1652 is_string_suffix(cheat_input, ":dt"))
1656 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1657 is_string_suffix(cheat_input, ":ft"))
1659 /* fix single-player tapes that contain player input for more than one
1660 player (due to a bug in 3.3.1.2 and earlier versions), which results
1661 in playing levels with more than one player in multi-player mode,
1662 even though the tape was originally recorded in single-player mode */
1664 /* remove player input actions for all players but the first one */
1665 for (i = 1; i < MAX_PLAYERS; i++)
1666 tape.player_participates[i] = FALSE;
1668 tape.changed = TRUE;
1670 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1671 is_string_suffix(cheat_input, ":snl"))
1673 SaveNativeLevel(&level);
1675 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1676 is_string_suffix(cheat_input, ":fps"))
1678 global.show_frames_per_second = !global.show_frames_per_second;
1681 else if (game_status == GAME_MODE_PLAYING)
1684 if (is_string_suffix(cheat_input, ".q"))
1685 DEBUG_SetMaximumDynamite();
1688 else if (game_status == GAME_MODE_EDITOR)
1690 if (is_string_suffix(cheat_input, ":dump-brush") ||
1691 is_string_suffix(cheat_input, ":DB"))
1695 else if (is_string_suffix(cheat_input, ":DDB"))
1702 void HandleKeysDebug(Key key)
1707 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1709 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1711 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1713 if (key == setup.debug.frame_delay_key[i] &&
1714 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1716 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1717 setup.debug.frame_delay[i] : setup.game_frame_delay);
1719 if (!setup.debug.frame_delay_game_only)
1720 MenuFrameDelay = GameFrameDelay;
1722 SetVideoFrameDelay(GameFrameDelay);
1724 if (GameFrameDelay > ONE_SECOND_DELAY)
1725 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1726 else if (GameFrameDelay != 0)
1727 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1728 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1729 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1731 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1738 if (game_status == GAME_MODE_PLAYING)
1742 options.debug = !options.debug;
1744 Error(ERR_DEBUG, "debug mode %s",
1745 (options.debug ? "enabled" : "disabled"));
1747 else if (key == KSYM_v)
1749 Error(ERR_DEBUG, "currently using game engine version %d",
1750 game.engine_version);
1756 void HandleKey(Key key, int key_status)
1758 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1759 static boolean ignore_repeated_key = FALSE;
1760 static struct SetupKeyboardInfo ski;
1761 static struct SetupShortcutInfo ssi;
1770 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1771 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1772 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1773 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1774 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1775 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1780 #if defined(TARGET_SDL2)
1781 /* map special keys (media keys / remote control buttons) to default keys */
1782 if (key == KSYM_PlayPause)
1784 else if (key == KSYM_Select)
1788 HandleSpecialGameControllerKeys(key, key_status);
1790 if (game_status == GAME_MODE_PLAYING)
1792 /* only needed for single-step tape recording mode */
1793 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1796 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1798 byte key_action = 0;
1800 if (setup.input[pnr].use_joystick)
1803 ski = setup.input[pnr].key;
1805 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1806 if (key == *key_info[i].key_custom)
1807 key_action |= key_info[i].action;
1809 /* use combined snap+direction keys for the first player only */
1812 ssi = setup.shortcut;
1814 for (i = 0; i < NUM_DIRECTIONS; i++)
1815 if (key == *key_info[i].key_snap)
1816 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1819 if (key_status == KEY_PRESSED)
1820 stored_player[pnr].action |= key_action;
1822 stored_player[pnr].action &= ~key_action;
1824 if (tape.single_step && tape.recording && tape.pausing)
1826 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1828 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1830 /* if snap key already pressed, keep pause mode when releasing */
1831 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1832 has_snapped[pnr] = TRUE;
1834 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1836 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1838 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1839 getRedDiskReleaseFlag_SP() == 0)
1841 /* add a single inactive frame before dropping starts */
1842 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1843 stored_player[pnr].force_dropping = TRUE;
1846 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1848 /* if snap key was pressed without direction, leave pause mode */
1849 if (!has_snapped[pnr])
1850 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1852 has_snapped[pnr] = FALSE;
1855 else if (tape.recording && tape.pausing && !tape.use_mouse)
1857 /* prevent key release events from un-pausing a paused game */
1858 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1859 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1865 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1866 if (key == key_info[i].key_default)
1867 joy |= key_info[i].action;
1872 if (key_status == KEY_PRESSED)
1873 key_joystick_mapping |= joy;
1875 key_joystick_mapping &= ~joy;
1880 if (game_status != GAME_MODE_PLAYING)
1881 key_joystick_mapping = 0;
1883 if (key_status == KEY_RELEASED)
1885 // reset flag to ignore repeated "key pressed" events after key release
1886 ignore_repeated_key = FALSE;
1891 if ((key == KSYM_F11 ||
1892 ((key == KSYM_Return ||
1893 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1894 video.fullscreen_available &&
1895 !ignore_repeated_key)
1897 setup.fullscreen = !setup.fullscreen;
1899 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1901 if (game_status == GAME_MODE_SETUP)
1902 RedrawSetupScreenAfterFullscreenToggle();
1904 // set flag to ignore repeated "key pressed" events
1905 ignore_repeated_key = TRUE;
1910 if ((key == KSYM_0 || key == KSYM_KP_0 ||
1911 key == KSYM_minus || key == KSYM_KP_Subtract ||
1912 key == KSYM_plus || key == KSYM_KP_Add ||
1913 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
1914 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1915 video.window_scaling_available &&
1916 !video.fullscreen_enabled)
1918 if (key == KSYM_0 || key == KSYM_KP_0)
1919 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1920 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1921 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1923 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1925 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1926 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1927 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1928 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1930 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1932 if (game_status == GAME_MODE_SETUP)
1933 RedrawSetupScreenAfterFullscreenToggle();
1938 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
1939 key == KSYM_Return ||
1940 key == KSYM_Escape)))
1942 /* do not handle this key event anymore */
1943 if (key != KSYM_Escape) /* always allow ESC key to be handled */
1947 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1948 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1955 if (game_status == GAME_MODE_MAIN &&
1956 (key == setup.shortcut.toggle_pause || key == KSYM_space))
1958 StartGameActions(options.network, setup.autorecord, level.random_seed);
1963 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1965 if (key == setup.shortcut.save_game)
1967 else if (key == setup.shortcut.load_game)
1969 else if (key == setup.shortcut.toggle_pause)
1970 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
1972 HandleTapeButtonKeys(key);
1973 HandleSoundButtonKeys(key);
1976 if (game_status == GAME_MODE_PLAYING && !network_playing)
1978 int centered_player_nr_next = -999;
1980 if (key == setup.shortcut.focus_player_all)
1981 centered_player_nr_next = -1;
1983 for (i = 0; i < MAX_PLAYERS; i++)
1984 if (key == setup.shortcut.focus_player[i])
1985 centered_player_nr_next = i;
1987 if (centered_player_nr_next != -999)
1989 game.centered_player_nr_next = centered_player_nr_next;
1990 game.set_centered_player = TRUE;
1994 tape.centered_player_nr_next = game.centered_player_nr_next;
1995 tape.set_centered_player = TRUE;
2000 HandleKeysSpecial(key);
2002 if (HandleGadgetsKeyInput(key))
2004 if (key != KSYM_Escape) /* always allow ESC key to be handled */
2005 key = KSYM_UNDEFINED;
2008 switch (game_status)
2010 case GAME_MODE_PSEUDO_TYPENAME:
2011 HandleTypeName(0, key);
2014 case GAME_MODE_TITLE:
2015 case GAME_MODE_MAIN:
2016 case GAME_MODE_LEVELS:
2017 case GAME_MODE_LEVELNR:
2018 case GAME_MODE_SETUP:
2019 case GAME_MODE_INFO:
2020 case GAME_MODE_SCORES:
2025 if (game_status == GAME_MODE_TITLE)
2026 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2027 else if (game_status == GAME_MODE_MAIN)
2028 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2029 else if (game_status == GAME_MODE_LEVELS)
2030 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2031 else if (game_status == GAME_MODE_LEVELNR)
2032 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2033 else if (game_status == GAME_MODE_SETUP)
2034 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2035 else if (game_status == GAME_MODE_INFO)
2036 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2037 else if (game_status == GAME_MODE_SCORES)
2038 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2042 if (game_status != GAME_MODE_MAIN)
2043 FadeSkipNextFadeIn();
2045 if (game_status == GAME_MODE_TITLE)
2046 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2047 else if (game_status == GAME_MODE_LEVELS)
2048 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2049 else if (game_status == GAME_MODE_LEVELNR)
2050 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2051 else if (game_status == GAME_MODE_SETUP)
2052 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2053 else if (game_status == GAME_MODE_INFO)
2054 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2055 else if (game_status == GAME_MODE_SCORES)
2056 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2060 if (game_status == GAME_MODE_LEVELS)
2061 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2062 else if (game_status == GAME_MODE_LEVELNR)
2063 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2064 else if (game_status == GAME_MODE_SETUP)
2065 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2066 else if (game_status == GAME_MODE_INFO)
2067 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2068 else if (game_status == GAME_MODE_SCORES)
2069 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2072 case KSYM_Page_Down:
2073 if (game_status == GAME_MODE_LEVELS)
2074 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2075 else if (game_status == GAME_MODE_LEVELNR)
2076 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2077 else if (game_status == GAME_MODE_SETUP)
2078 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2079 else if (game_status == GAME_MODE_INFO)
2080 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2081 else if (game_status == GAME_MODE_SCORES)
2082 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2090 case GAME_MODE_EDITOR:
2091 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2092 HandleLevelEditorKeyInput(key);
2095 case GAME_MODE_PLAYING:
2100 RequestQuitGame(setup.ask_on_escape);
2110 if (key == KSYM_Escape)
2112 SetGameStatus(GAME_MODE_MAIN);
2120 HandleKeysDebug(key);
2123 void HandleNoEvent()
2125 HandleMouseCursor();
2127 switch (game_status)
2129 case GAME_MODE_PLAYING:
2130 HandleButtonOrFinger(-1, -1, -1);
2135 void HandleEventActions()
2137 // if (button_status && game_status != GAME_MODE_PLAYING)
2138 if (button_status && (game_status != GAME_MODE_PLAYING ||
2140 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2142 HandleButton(0, 0, button_status, -button_status);
2149 #if defined(NETWORK_AVALIABLE)
2150 if (options.network)
2154 switch (game_status)
2156 case GAME_MODE_MAIN:
2157 DrawPreviewLevelAnimation();
2160 case GAME_MODE_EDITOR:
2161 HandleLevelEditorIdle();
2169 static int HandleJoystickForAllPlayers()
2173 boolean no_joysticks_configured = TRUE;
2174 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2175 static byte joy_action_last[MAX_PLAYERS];
2177 for (i = 0; i < MAX_PLAYERS; i++)
2178 if (setup.input[i].use_joystick)
2179 no_joysticks_configured = FALSE;
2181 /* if no joysticks configured, map connected joysticks to players */
2182 if (no_joysticks_configured)
2183 use_as_joystick_nr = TRUE;
2185 for (i = 0; i < MAX_PLAYERS; i++)
2187 byte joy_action = 0;
2189 joy_action = JoystickExt(i, use_as_joystick_nr);
2190 result |= joy_action;
2192 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2193 joy_action != joy_action_last[i])
2194 stored_player[i].action = joy_action;
2196 joy_action_last[i] = joy_action;
2202 void HandleJoystick()
2204 int joystick = HandleJoystickForAllPlayers();
2205 int keyboard = key_joystick_mapping;
2206 int joy = (joystick | keyboard);
2207 int left = joy & JOY_LEFT;
2208 int right = joy & JOY_RIGHT;
2209 int up = joy & JOY_UP;
2210 int down = joy & JOY_DOWN;
2211 int button = joy & JOY_BUTTON;
2212 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2213 int dx = (left ? -1 : right ? 1 : 0);
2214 int dy = (up ? -1 : down ? 1 : 0);
2216 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2218 /* do not handle this button event anymore */
2222 switch (game_status)
2224 case GAME_MODE_TITLE:
2225 case GAME_MODE_MAIN:
2226 case GAME_MODE_LEVELS:
2227 case GAME_MODE_LEVELNR:
2228 case GAME_MODE_SETUP:
2229 case GAME_MODE_INFO:
2231 static unsigned int joystickmove_delay = 0;
2232 static unsigned int joystickmove_delay_value = GADGET_FRAME_DELAY;
2233 static int joystick_last = 0;
2235 if (joystick && !button &&
2236 !DelayReached(&joystickmove_delay, joystickmove_delay_value))
2238 /* delay joystick actions if buttons/axes continually pressed */
2239 newbutton = dx = dy = 0;
2243 /* start with longer delay, then continue with shorter delay */
2244 if (joystick != joystick_last)
2245 joystickmove_delay_value = GADGET_FRAME_DELAY_FIRST;
2247 joystickmove_delay_value = GADGET_FRAME_DELAY;
2250 if (game_status == GAME_MODE_TITLE)
2251 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2252 else if (game_status == GAME_MODE_MAIN)
2253 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2254 else if (game_status == GAME_MODE_LEVELS)
2255 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2256 else if (game_status == GAME_MODE_LEVELNR)
2257 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2258 else if (game_status == GAME_MODE_SETUP)
2259 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2260 else if (game_status == GAME_MODE_INFO)
2261 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2263 joystick_last = joystick;
2268 case GAME_MODE_SCORES:
2269 HandleHallOfFame(0, 0, dx, dy, !newbutton);
2272 case GAME_MODE_PLAYING:
2273 if (tape.playing || keyboard)
2274 newbutton = ((joy & JOY_BUTTON) != 0);
2276 if (newbutton && AllPlayersGone)
2283 if (tape.recording && tape.pausing)
2285 if (joystick & JOY_ACTION)
2286 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2296 void HandleSpecialGameControllerButtons(Event *event)
2298 #if defined(TARGET_SDL2)
2299 switch (event->type)
2301 case SDL_CONTROLLERBUTTONDOWN:
2302 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2303 HandleKey(KSYM_space, KEY_PRESSED);
2304 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2305 HandleKey(KSYM_Escape, KEY_PRESSED);
2309 case SDL_CONTROLLERBUTTONUP:
2310 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2311 HandleKey(KSYM_space, KEY_RELEASED);
2312 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2313 HandleKey(KSYM_Escape, KEY_RELEASED);
2320 void HandleSpecialGameControllerKeys(Key key, int key_status)
2322 #if defined(TARGET_SDL2)
2323 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2324 int button = SDL_CONTROLLER_BUTTON_INVALID;
2326 /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2327 if (key == KSYM_Rewind)
2328 button = SDL_CONTROLLER_BUTTON_A;
2329 else if (key == KSYM_FastForward || key == KSYM_Menu)
2330 button = SDL_CONTROLLER_BUTTON_B;
2332 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2336 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2337 SDL_CONTROLLERBUTTONUP);
2339 event.cbutton.which = 0; /* first joystick (Amazon Fire TV remote) */
2340 event.cbutton.button = button;
2341 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2344 HandleJoystickEvent(&event);