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);
421 SetTileCursorXY(lx, ly);
424 void SleepWhileUnmapped()
426 boolean window_unmapped = TRUE;
428 KeyboardAutoRepeatOn();
430 while (window_unmapped)
434 if (!WaitValidEvent(&event))
439 case EVENT_BUTTONRELEASE:
440 button_status = MB_RELEASED;
443 case EVENT_KEYRELEASE:
444 key_joystick_mapping = 0;
447 #if defined(TARGET_SDL2)
448 case SDL_CONTROLLERBUTTONUP:
449 HandleJoystickEvent(&event);
450 key_joystick_mapping = 0;
454 case EVENT_MAPNOTIFY:
455 window_unmapped = FALSE;
458 case EVENT_UNMAPNOTIFY:
459 /* this is only to surely prevent the 'should not happen' case
460 * of recursively looping between 'SleepWhileUnmapped()' and
461 * 'HandleOtherEvents()' which usually calls this funtion.
466 HandleOtherEvents(&event);
471 if (game_status == GAME_MODE_PLAYING)
472 KeyboardAutoRepeatOffUnlessAutoplay();
475 void HandleExposeEvent(ExposeEvent *event)
479 void HandleButtonEvent(ButtonEvent *event)
481 #if DEBUG_EVENTS_BUTTON
482 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
484 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
488 // for any mouse button event, disable playfield tile cursor
489 SetTileCursorEnabled(FALSE);
491 #if defined(HAS_SCREEN_KEYBOARD)
492 if (video.shifted_up)
493 event->y += video.shifted_up_pos;
496 motion_status = FALSE;
498 if (event->type == EVENT_BUTTONPRESS)
499 button_status = event->button;
501 button_status = MB_RELEASED;
503 HandleButton(event->x, event->y, button_status, event->button);
506 void HandleMotionEvent(MotionEvent *event)
508 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
511 motion_status = TRUE;
513 #if DEBUG_EVENTS_MOTION
514 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
515 button_status, event->x, event->y);
518 HandleButton(event->x, event->y, button_status, button_status);
521 #if defined(TARGET_SDL2)
523 void HandleWheelEvent(WheelEvent *event)
527 #if DEBUG_EVENTS_WHEEL
529 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
530 event->which, event->x, event->y);
532 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
533 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
534 event->which, event->x, event->y,
535 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
536 "SDL_MOUSEWHEEL_FLIPPED"));
540 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
541 event->x > 0 ? MB_WHEEL_RIGHT :
542 event->y < 0 ? MB_WHEEL_DOWN :
543 event->y > 0 ? MB_WHEEL_UP : 0);
545 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
546 // accelerated mouse wheel available on Mac and Windows
547 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
549 // no accelerated mouse wheel available on Unix/Linux
550 wheel_steps = DEFAULT_WHEEL_STEPS;
553 motion_status = FALSE;
555 button_status = button_nr;
556 HandleButton(0, 0, button_status, -button_nr);
558 button_status = MB_RELEASED;
559 HandleButton(0, 0, button_status, -button_nr);
562 void HandleWindowEvent(WindowEvent *event)
564 #if DEBUG_EVENTS_WINDOW
565 int subtype = event->event;
568 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
569 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
570 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
571 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
572 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
573 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
574 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
575 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
576 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
577 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
578 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
579 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
580 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
581 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
584 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
585 event_name, event->data1, event->data2);
589 // (not needed, as the screen gets redrawn every 20 ms anyway)
590 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
591 event->event == SDL_WINDOWEVENT_RESIZED ||
592 event->event == SDL_WINDOWEVENT_EXPOSED)
596 if (event->event == SDL_WINDOWEVENT_RESIZED)
598 if (!video.fullscreen_enabled)
600 int new_window_width = event->data1;
601 int new_window_height = event->data2;
603 // if window size has changed after resizing, calculate new scaling factor
604 if (new_window_width != video.window_width ||
605 new_window_height != video.window_height)
607 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
608 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
610 // (extreme window scaling allowed, but cannot be saved permanently)
611 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
612 setup.window_scaling_percent =
613 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
614 MAX_WINDOW_SCALING_PERCENT);
616 video.window_width = new_window_width;
617 video.window_height = new_window_height;
619 if (game_status == GAME_MODE_SETUP)
620 RedrawSetupScreenAfterFullscreenToggle();
625 #if defined(PLATFORM_ANDROID)
628 int new_display_width = event->data1;
629 int new_display_height = event->data2;
631 // if fullscreen display size has changed, device has been rotated
632 if (new_display_width != video.display_width ||
633 new_display_height != video.display_height)
635 video.display_width = new_display_width;
636 video.display_height = new_display_height;
638 SDLSetScreenProperties();
645 #define NUM_TOUCH_FINGERS 3
650 SDL_FingerID finger_id;
653 } touch_info[NUM_TOUCH_FINGERS];
655 void HandleFingerEvent_VirtualButtons(FingerEvent *event)
657 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
658 float event_x = (event->x);
659 float event_y = (event->y - ypos) / (1 - ypos);
660 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
661 event_y > 2.0 / 3.0 && event_y < 1 ?
662 setup.input[0].key.snap :
663 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
664 event_y > 2.0 / 3.0 && event_y < 1 ?
665 setup.input[0].key.drop :
666 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
667 event_y > 0 && event_y < 1.0 / 3.0 ?
668 setup.input[0].key.up :
669 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
670 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
671 setup.input[0].key.left :
672 event_x > 8.0 / 9.0 && event_x < 1 &&
673 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
674 setup.input[0].key.right :
675 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
676 event_y > 2.0 / 3.0 && event_y < 1 ?
677 setup.input[0].key.down :
679 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
681 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
685 // for any touch input event, enable overlay buttons (if activated)
686 SetOverlayEnabled(TRUE);
688 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
689 getKeyNameFromKey(key), key_status_name, event->fingerId);
691 // check if we already know this touch event's finger id
692 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
694 if (touch_info[i].touched &&
695 touch_info[i].finger_id == event->fingerId)
697 // Error(ERR_DEBUG, "MARK 1: %d", i);
703 if (i >= NUM_TOUCH_FINGERS)
705 if (key_status == KEY_PRESSED)
707 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
709 // unknown finger id -- get new, empty slot, if available
710 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
712 if (touch_info[i].counter < oldest_counter)
715 oldest_counter = touch_info[i].counter;
717 // Error(ERR_DEBUG, "MARK 2: %d", i);
720 if (!touch_info[i].touched)
722 // Error(ERR_DEBUG, "MARK 3: %d", i);
728 if (i >= NUM_TOUCH_FINGERS)
730 // all slots allocated -- use oldest slot
733 // Error(ERR_DEBUG, "MARK 4: %d", i);
738 // release of previously unknown key (should not happen)
740 if (key != KSYM_UNDEFINED)
742 HandleKey(key, KEY_RELEASED);
744 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
745 getKeyNameFromKey(key), "KEY_RELEASED", i);
750 if (i < NUM_TOUCH_FINGERS)
752 if (key_status == KEY_PRESSED)
754 if (touch_info[i].key != key)
756 if (touch_info[i].key != KSYM_UNDEFINED)
758 HandleKey(touch_info[i].key, KEY_RELEASED);
760 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
761 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
764 if (key != KSYM_UNDEFINED)
766 HandleKey(key, KEY_PRESSED);
768 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
769 getKeyNameFromKey(key), "KEY_PRESSED", i);
773 touch_info[i].touched = TRUE;
774 touch_info[i].finger_id = event->fingerId;
775 touch_info[i].counter = Counter();
776 touch_info[i].key = key;
780 if (touch_info[i].key != KSYM_UNDEFINED)
782 HandleKey(touch_info[i].key, KEY_RELEASED);
784 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
785 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
788 touch_info[i].touched = FALSE;
789 touch_info[i].finger_id = 0;
790 touch_info[i].counter = 0;
791 touch_info[i].key = 0;
796 void HandleFingerEvent_WipeGestures(FingerEvent *event)
798 static Key motion_key_x = KSYM_UNDEFINED;
799 static Key motion_key_y = KSYM_UNDEFINED;
800 static Key button_key = KSYM_UNDEFINED;
801 static float motion_x1, motion_y1;
802 static float button_x1, button_y1;
803 static SDL_FingerID motion_id = -1;
804 static SDL_FingerID button_id = -1;
805 int move_trigger_distance_percent = setup.touch.move_distance;
806 int drop_trigger_distance_percent = setup.touch.drop_distance;
807 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
808 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
809 float event_x = event->x;
810 float event_y = event->y;
812 if (event->type == EVENT_FINGERPRESS)
814 if (event_x > 1.0 / 3.0)
818 motion_id = event->fingerId;
823 motion_key_x = KSYM_UNDEFINED;
824 motion_key_y = KSYM_UNDEFINED;
826 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
832 button_id = event->fingerId;
837 button_key = setup.input[0].key.snap;
839 HandleKey(button_key, KEY_PRESSED);
841 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
844 else if (event->type == EVENT_FINGERRELEASE)
846 if (event->fingerId == motion_id)
850 if (motion_key_x != KSYM_UNDEFINED)
851 HandleKey(motion_key_x, KEY_RELEASED);
852 if (motion_key_y != KSYM_UNDEFINED)
853 HandleKey(motion_key_y, KEY_RELEASED);
855 motion_key_x = KSYM_UNDEFINED;
856 motion_key_y = KSYM_UNDEFINED;
858 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
860 else if (event->fingerId == button_id)
864 if (button_key != KSYM_UNDEFINED)
865 HandleKey(button_key, KEY_RELEASED);
867 button_key = KSYM_UNDEFINED;
869 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
872 else if (event->type == EVENT_FINGERMOTION)
874 if (event->fingerId == motion_id)
876 float distance_x = ABS(event_x - motion_x1);
877 float distance_y = ABS(event_y - motion_y1);
878 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
879 event_x > motion_x1 ? setup.input[0].key.right :
881 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
882 event_y > motion_y1 ? setup.input[0].key.down :
885 if (distance_x < move_trigger_distance / 2 ||
886 distance_x < distance_y)
887 new_motion_key_x = KSYM_UNDEFINED;
889 if (distance_y < move_trigger_distance / 2 ||
890 distance_y < distance_x)
891 new_motion_key_y = KSYM_UNDEFINED;
893 if (distance_x > move_trigger_distance ||
894 distance_y > move_trigger_distance)
896 if (new_motion_key_x != motion_key_x)
898 if (motion_key_x != KSYM_UNDEFINED)
899 HandleKey(motion_key_x, KEY_RELEASED);
900 if (new_motion_key_x != KSYM_UNDEFINED)
901 HandleKey(new_motion_key_x, KEY_PRESSED);
904 if (new_motion_key_y != motion_key_y)
906 if (motion_key_y != KSYM_UNDEFINED)
907 HandleKey(motion_key_y, KEY_RELEASED);
908 if (new_motion_key_y != KSYM_UNDEFINED)
909 HandleKey(new_motion_key_y, KEY_PRESSED);
915 motion_key_x = new_motion_key_x;
916 motion_key_y = new_motion_key_y;
918 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
921 else if (event->fingerId == button_id)
923 float distance_x = ABS(event_x - button_x1);
924 float distance_y = ABS(event_y - button_y1);
926 if (distance_x < drop_trigger_distance / 2 &&
927 distance_y > drop_trigger_distance)
929 if (button_key == setup.input[0].key.snap)
930 HandleKey(button_key, KEY_RELEASED);
935 button_key = setup.input[0].key.drop;
937 HandleKey(button_key, KEY_PRESSED);
939 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
945 void HandleFingerEvent(FingerEvent *event)
947 #if DEBUG_EVENTS_FINGER
948 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
949 event->type == EVENT_FINGERPRESS ? "pressed" :
950 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
954 event->dx, event->dy,
958 if (game_status != GAME_MODE_PLAYING)
961 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
964 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
965 HandleFingerEvent_VirtualButtons(event);
966 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
967 HandleFingerEvent_WipeGestures(event);
972 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
974 static int old_mx = 0, old_my = 0;
975 static int last_button = MB_LEFTBUTTON;
976 static boolean touched = FALSE;
977 static boolean tapped = FALSE;
979 // screen tile was tapped (but finger not touching the screen anymore)
980 // (this point will also be reached without receiving a touch event)
981 if (tapped && !touched)
983 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
988 // stop here if this function was not triggered by a touch event
992 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
994 // finger started touching the screen
1004 ClearPlayerMouseAction();
1006 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1009 else if (button == MB_RELEASED && touched)
1011 // finger stopped touching the screen
1016 SetPlayerMouseAction(old_mx, old_my, last_button);
1018 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1020 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1025 // finger moved while touching the screen
1027 int old_x = getLevelFromScreenX(old_mx);
1028 int old_y = getLevelFromScreenY(old_my);
1029 int new_x = getLevelFromScreenX(mx);
1030 int new_y = getLevelFromScreenY(my);
1032 if (new_x != old_x || new_y != old_y)
1037 // finger moved left or right from (horizontal) starting position
1039 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1041 SetPlayerMouseAction(old_mx, old_my, button_nr);
1043 last_button = button_nr;
1045 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1049 // finger stays at or returned to (horizontal) starting position
1051 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1053 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1058 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1060 static int old_mx = 0, old_my = 0;
1061 static int last_button = MB_LEFTBUTTON;
1062 static boolean touched = FALSE;
1063 static boolean tapped = FALSE;
1065 // screen tile was tapped (but finger not touching the screen anymore)
1066 // (this point will also be reached without receiving a touch event)
1067 if (tapped && !touched)
1069 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1074 // stop here if this function was not triggered by a touch event
1078 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1080 // finger started touching the screen
1090 ClearPlayerMouseAction();
1092 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1095 else if (button == MB_RELEASED && touched)
1097 // finger stopped touching the screen
1102 SetPlayerMouseAction(old_mx, old_my, last_button);
1104 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1106 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1111 // finger moved while touching the screen
1113 int old_x = getLevelFromScreenX(old_mx);
1114 int old_y = getLevelFromScreenY(old_my);
1115 int new_x = getLevelFromScreenX(mx);
1116 int new_y = getLevelFromScreenY(my);
1118 if (new_x != old_x || new_y != old_y)
1120 // finger moved away from starting position
1122 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1124 // quickly alternate between clicking and releasing for maximum speed
1125 if (FrameCounter % 2 == 0)
1126 button_nr = MB_RELEASED;
1128 SetPlayerMouseAction(old_mx, old_my, button_nr);
1131 last_button = button_nr;
1135 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1139 // finger stays at or returned to starting position
1141 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1143 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1148 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1150 static int old_mx = 0, old_my = 0;
1151 static Key motion_key_x = KSYM_UNDEFINED;
1152 static Key motion_key_y = KSYM_UNDEFINED;
1153 static boolean touched = FALSE;
1154 static boolean started_on_player = FALSE;
1155 static boolean player_is_dropping = FALSE;
1156 static int player_drop_count = 0;
1157 static int last_player_x = -1;
1158 static int last_player_y = -1;
1160 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1169 started_on_player = FALSE;
1170 player_is_dropping = FALSE;
1171 player_drop_count = 0;
1175 motion_key_x = KSYM_UNDEFINED;
1176 motion_key_y = KSYM_UNDEFINED;
1178 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1181 else if (button == MB_RELEASED && touched)
1188 if (motion_key_x != KSYM_UNDEFINED)
1189 HandleKey(motion_key_x, KEY_RELEASED);
1190 if (motion_key_y != KSYM_UNDEFINED)
1191 HandleKey(motion_key_y, KEY_RELEASED);
1193 if (started_on_player)
1195 if (player_is_dropping)
1197 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1199 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1203 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1205 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1209 motion_key_x = KSYM_UNDEFINED;
1210 motion_key_y = KSYM_UNDEFINED;
1212 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1217 int src_x = local_player->jx;
1218 int src_y = local_player->jy;
1219 int dst_x = getLevelFromScreenX(old_mx);
1220 int dst_y = getLevelFromScreenY(old_my);
1221 int dx = dst_x - src_x;
1222 int dy = dst_y - src_y;
1223 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1224 dx > 0 ? setup.input[0].key.right :
1226 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1227 dy > 0 ? setup.input[0].key.down :
1230 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1231 (last_player_x != local_player->jx ||
1232 last_player_y != local_player->jy))
1234 // in case of asymmetric diagonal movement, use "preferred" direction
1236 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1238 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1239 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1241 local_player->last_move_dir = last_move_dir;
1243 // (required to prevent accidentally forcing direction for next movement)
1244 last_player_x = local_player->jx;
1245 last_player_y = local_player->jy;
1248 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1250 started_on_player = TRUE;
1251 player_drop_count = getPlayerInventorySize(0);
1252 player_is_dropping = (player_drop_count > 0);
1254 if (player_is_dropping)
1256 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1258 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1262 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1264 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1267 else if (dx != 0 || dy != 0)
1269 if (player_is_dropping &&
1270 player_drop_count == getPlayerInventorySize(0))
1272 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1274 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1275 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1277 player_is_dropping = FALSE;
1281 if (new_motion_key_x != motion_key_x)
1283 Error(ERR_DEBUG, "---------- %s %s ----------",
1284 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1285 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1287 if (motion_key_x != KSYM_UNDEFINED)
1288 HandleKey(motion_key_x, KEY_RELEASED);
1289 if (new_motion_key_x != KSYM_UNDEFINED)
1290 HandleKey(new_motion_key_x, KEY_PRESSED);
1293 if (new_motion_key_y != motion_key_y)
1295 Error(ERR_DEBUG, "---------- %s %s ----------",
1296 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1297 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1299 if (motion_key_y != KSYM_UNDEFINED)
1300 HandleKey(motion_key_y, KEY_RELEASED);
1301 if (new_motion_key_y != KSYM_UNDEFINED)
1302 HandleKey(new_motion_key_y, KEY_PRESSED);
1305 motion_key_x = new_motion_key_x;
1306 motion_key_y = new_motion_key_y;
1310 static void HandleButtonOrFinger(int mx, int my, int button)
1312 if (game_status != GAME_MODE_PLAYING)
1315 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1317 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1318 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1319 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1320 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1324 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1325 HandleButtonOrFinger_FollowFinger(mx, my, button);
1329 #if defined(TARGET_SDL2)
1331 static boolean checkTextInputKeyModState()
1333 // when playing, only handle raw key events and ignore text input
1334 if (game_status == GAME_MODE_PLAYING)
1337 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1340 void HandleTextEvent(TextEvent *event)
1342 char *text = event->text;
1343 Key key = getKeyFromKeyName(text);
1345 #if DEBUG_EVENTS_TEXT
1346 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1349 text[0], (int)(text[0]),
1351 getKeyNameFromKey(key),
1355 #if !defined(HAS_SCREEN_KEYBOARD)
1356 // non-mobile devices: only handle key input with modifier keys pressed here
1357 // (every other key input is handled directly as physical key input event)
1358 if (!checkTextInputKeyModState())
1362 // process text input as "classic" (with uppercase etc.) key input event
1363 HandleKey(key, KEY_PRESSED);
1364 HandleKey(key, KEY_RELEASED);
1367 void HandlePauseResumeEvent(PauseResumeEvent *event)
1369 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1373 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1381 void HandleKeyEvent(KeyEvent *event)
1383 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1384 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1385 Key key = GetEventKey(event, with_modifiers);
1386 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1388 #if DEBUG_EVENTS_KEY
1389 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1390 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1391 event->keysym.scancode,
1396 getKeyNameFromKey(key));
1399 #if defined(PLATFORM_ANDROID)
1400 if (key == KSYM_Back)
1402 // always map the "back" button to the "escape" key on Android devices
1407 // for any key event other than "back" button, disable overlay buttons
1408 SetOverlayEnabled(FALSE);
1412 HandleKeyModState(keymod, key_status);
1414 #if defined(TARGET_SDL2)
1415 // only handle raw key input without text modifier keys pressed
1416 if (!checkTextInputKeyModState())
1417 HandleKey(key, key_status);
1419 HandleKey(key, key_status);
1423 void HandleFocusEvent(FocusChangeEvent *event)
1425 static int old_joystick_status = -1;
1427 if (event->type == EVENT_FOCUSOUT)
1429 KeyboardAutoRepeatOn();
1430 old_joystick_status = joystick.status;
1431 joystick.status = JOYSTICK_NOT_AVAILABLE;
1433 ClearPlayerAction();
1435 else if (event->type == EVENT_FOCUSIN)
1437 /* When there are two Rocks'n'Diamonds windows which overlap and
1438 the player moves the pointer from one game window to the other,
1439 a 'FocusOut' event is generated for the window the pointer is
1440 leaving and a 'FocusIn' event is generated for the window the
1441 pointer is entering. In some cases, it can happen that the
1442 'FocusIn' event is handled by the one game process before the
1443 'FocusOut' event by the other game process. In this case the
1444 X11 environment would end up with activated keyboard auto repeat,
1445 because unfortunately this is a global setting and not (which
1446 would be far better) set for each X11 window individually.
1447 The effect would be keyboard auto repeat while playing the game
1448 (game_status == GAME_MODE_PLAYING), which is not desired.
1449 To avoid this special case, we just wait 1/10 second before
1450 processing the 'FocusIn' event.
1453 if (game_status == GAME_MODE_PLAYING)
1456 KeyboardAutoRepeatOffUnlessAutoplay();
1459 if (old_joystick_status != -1)
1460 joystick.status = old_joystick_status;
1464 void HandleClientMessageEvent(ClientMessageEvent *event)
1466 if (CheckCloseWindowEvent(event))
1470 void HandleWindowManagerEvent(Event *event)
1472 #if defined(TARGET_SDL)
1473 SDLHandleWindowManagerEvent(event);
1477 void HandleButton(int mx, int my, int button, int button_nr)
1479 static int old_mx = 0, old_my = 0;
1480 boolean button_hold = FALSE;
1481 boolean handle_gadgets = TRUE;
1487 button_nr = -button_nr;
1496 #if defined(PLATFORM_ANDROID)
1497 // when playing, only handle gadgets when using "follow finger" controls
1498 // or when using touch controls in combination with the MM game engine
1500 (game_status != GAME_MODE_PLAYING ||
1501 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1502 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1505 if (handle_gadgets && HandleGadgets(mx, my, button))
1507 /* do not handle this button event anymore */
1508 mx = my = -32; /* force mouse event to be outside screen tiles */
1511 if (HandleGlobalAnimClicks(mx, my, button))
1513 /* do not handle this button event anymore */
1514 return; /* force mouse event not to be handled at all */
1517 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1520 /* do not use scroll wheel button events for anything other than gadgets */
1521 if (IS_WHEEL_BUTTON(button_nr))
1524 switch (game_status)
1526 case GAME_MODE_TITLE:
1527 HandleTitleScreen(mx, my, 0, 0, button);
1530 case GAME_MODE_MAIN:
1531 HandleMainMenu(mx, my, 0, 0, button);
1534 case GAME_MODE_PSEUDO_TYPENAME:
1535 HandleTypeName(0, KSYM_Return);
1538 case GAME_MODE_LEVELS:
1539 HandleChooseLevelSet(mx, my, 0, 0, button);
1542 case GAME_MODE_LEVELNR:
1543 HandleChooseLevelNr(mx, my, 0, 0, button);
1546 case GAME_MODE_SCORES:
1547 HandleHallOfFame(0, 0, 0, 0, button);
1550 case GAME_MODE_EDITOR:
1551 HandleLevelEditorIdle();
1554 case GAME_MODE_INFO:
1555 HandleInfoScreen(mx, my, 0, 0, button);
1558 case GAME_MODE_SETUP:
1559 HandleSetupScreen(mx, my, 0, 0, button);
1562 case GAME_MODE_PLAYING:
1563 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1564 HandleButtonOrFinger(mx, my, button);
1566 SetPlayerMouseAction(mx, my, button);
1569 if (button == MB_PRESSED && !motion_status && !button_hold &&
1570 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1571 DumpTileFromScreen(mx, my);
1581 static boolean is_string_suffix(char *string, char *suffix)
1583 int string_len = strlen(string);
1584 int suffix_len = strlen(suffix);
1586 if (suffix_len > string_len)
1589 return (strEqual(&string[string_len - suffix_len], suffix));
1592 #define MAX_CHEAT_INPUT_LEN 32
1594 static void HandleKeysSpecial(Key key)
1596 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1597 char letter = getCharFromKey(key);
1598 int cheat_input_len = strlen(cheat_input);
1604 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1606 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1607 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1609 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1612 cheat_input[cheat_input_len++] = letter;
1613 cheat_input[cheat_input_len] = '\0';
1615 #if DEBUG_EVENTS_KEY
1616 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1619 if (game_status == GAME_MODE_MAIN)
1621 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1622 is_string_suffix(cheat_input, ":ist"))
1624 InsertSolutionTape();
1626 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1627 is_string_suffix(cheat_input, ":rg"))
1629 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1632 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1633 is_string_suffix(cheat_input, ":rs"))
1635 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1638 else if (is_string_suffix(cheat_input, ":reload-music") ||
1639 is_string_suffix(cheat_input, ":rm"))
1641 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1644 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1645 is_string_suffix(cheat_input, ":ra"))
1647 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1648 1 << ARTWORK_TYPE_SOUNDS |
1649 1 << ARTWORK_TYPE_MUSIC);
1652 else if (is_string_suffix(cheat_input, ":dump-level") ||
1653 is_string_suffix(cheat_input, ":dl"))
1657 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1658 is_string_suffix(cheat_input, ":dt"))
1662 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1663 is_string_suffix(cheat_input, ":ft"))
1665 /* fix single-player tapes that contain player input for more than one
1666 player (due to a bug in 3.3.1.2 and earlier versions), which results
1667 in playing levels with more than one player in multi-player mode,
1668 even though the tape was originally recorded in single-player mode */
1670 /* remove player input actions for all players but the first one */
1671 for (i = 1; i < MAX_PLAYERS; i++)
1672 tape.player_participates[i] = FALSE;
1674 tape.changed = TRUE;
1676 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1677 is_string_suffix(cheat_input, ":snl"))
1679 SaveNativeLevel(&level);
1681 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1682 is_string_suffix(cheat_input, ":fps"))
1684 global.show_frames_per_second = !global.show_frames_per_second;
1687 else if (game_status == GAME_MODE_PLAYING)
1690 if (is_string_suffix(cheat_input, ".q"))
1691 DEBUG_SetMaximumDynamite();
1694 else if (game_status == GAME_MODE_EDITOR)
1696 if (is_string_suffix(cheat_input, ":dump-brush") ||
1697 is_string_suffix(cheat_input, ":DB"))
1701 else if (is_string_suffix(cheat_input, ":DDB"))
1708 void HandleKeysDebug(Key key)
1713 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1715 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1717 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1719 if (key == setup.debug.frame_delay_key[i] &&
1720 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1722 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1723 setup.debug.frame_delay[i] : setup.game_frame_delay);
1725 if (!setup.debug.frame_delay_game_only)
1726 MenuFrameDelay = GameFrameDelay;
1728 SetVideoFrameDelay(GameFrameDelay);
1730 if (GameFrameDelay > ONE_SECOND_DELAY)
1731 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1732 else if (GameFrameDelay != 0)
1733 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1734 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1735 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1737 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1744 if (game_status == GAME_MODE_PLAYING)
1748 options.debug = !options.debug;
1750 Error(ERR_DEBUG, "debug mode %s",
1751 (options.debug ? "enabled" : "disabled"));
1753 else if (key == KSYM_v)
1755 Error(ERR_DEBUG, "currently using game engine version %d",
1756 game.engine_version);
1762 void HandleKey(Key key, int key_status)
1764 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1765 static boolean ignore_repeated_key = FALSE;
1766 static struct SetupKeyboardInfo ski;
1767 static struct SetupShortcutInfo ssi;
1776 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1777 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1778 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1779 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1780 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1781 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1786 #if defined(TARGET_SDL2)
1787 /* map special keys (media keys / remote control buttons) to default keys */
1788 if (key == KSYM_PlayPause)
1790 else if (key == KSYM_Select)
1794 HandleSpecialGameControllerKeys(key, key_status);
1796 if (game_status == GAME_MODE_PLAYING)
1798 /* only needed for single-step tape recording mode */
1799 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1802 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1804 byte key_action = 0;
1806 if (setup.input[pnr].use_joystick)
1809 ski = setup.input[pnr].key;
1811 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1812 if (key == *key_info[i].key_custom)
1813 key_action |= key_info[i].action;
1815 /* use combined snap+direction keys for the first player only */
1818 ssi = setup.shortcut;
1820 for (i = 0; i < NUM_DIRECTIONS; i++)
1821 if (key == *key_info[i].key_snap)
1822 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1825 if (key_status == KEY_PRESSED)
1826 stored_player[pnr].action |= key_action;
1828 stored_player[pnr].action &= ~key_action;
1830 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
1832 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1834 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1836 /* if snap key already pressed, keep pause mode when releasing */
1837 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1838 has_snapped[pnr] = TRUE;
1840 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1842 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1844 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1845 getRedDiskReleaseFlag_SP() == 0)
1847 /* add a single inactive frame before dropping starts */
1848 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1849 stored_player[pnr].force_dropping = TRUE;
1852 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1854 /* if snap key was pressed without direction, leave pause mode */
1855 if (!has_snapped[pnr])
1856 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1858 has_snapped[pnr] = FALSE;
1861 else if (tape.recording && tape.pausing && !tape.use_mouse)
1863 /* prevent key release events from un-pausing a paused game */
1864 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1865 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1868 // for MM style levels, handle in-game keyboard input in HandleJoystick()
1869 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1875 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1876 if (key == key_info[i].key_default)
1877 joy |= key_info[i].action;
1882 if (key_status == KEY_PRESSED)
1883 key_joystick_mapping |= joy;
1885 key_joystick_mapping &= ~joy;
1890 if (game_status != GAME_MODE_PLAYING)
1891 key_joystick_mapping = 0;
1893 if (key_status == KEY_RELEASED)
1895 // reset flag to ignore repeated "key pressed" events after key release
1896 ignore_repeated_key = FALSE;
1901 if ((key == KSYM_F11 ||
1902 ((key == KSYM_Return ||
1903 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1904 video.fullscreen_available &&
1905 !ignore_repeated_key)
1907 setup.fullscreen = !setup.fullscreen;
1909 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1911 if (game_status == GAME_MODE_SETUP)
1912 RedrawSetupScreenAfterFullscreenToggle();
1914 // set flag to ignore repeated "key pressed" events
1915 ignore_repeated_key = TRUE;
1920 if ((key == KSYM_0 || key == KSYM_KP_0 ||
1921 key == KSYM_minus || key == KSYM_KP_Subtract ||
1922 key == KSYM_plus || key == KSYM_KP_Add ||
1923 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
1924 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1925 video.window_scaling_available &&
1926 !video.fullscreen_enabled)
1928 if (key == KSYM_0 || key == KSYM_KP_0)
1929 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1930 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1931 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1933 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1935 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1936 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1937 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1938 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1940 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1942 if (game_status == GAME_MODE_SETUP)
1943 RedrawSetupScreenAfterFullscreenToggle();
1948 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
1949 key == KSYM_Return ||
1950 key == KSYM_Escape)))
1952 /* do not handle this key event anymore */
1953 if (key != KSYM_Escape) /* always allow ESC key to be handled */
1957 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1958 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1965 if (game_status == GAME_MODE_MAIN &&
1966 (key == setup.shortcut.toggle_pause || key == KSYM_space))
1968 StartGameActions(options.network, setup.autorecord, level.random_seed);
1973 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1975 if (key == setup.shortcut.save_game)
1977 else if (key == setup.shortcut.load_game)
1979 else if (key == setup.shortcut.toggle_pause)
1980 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
1982 HandleTapeButtonKeys(key);
1983 HandleSoundButtonKeys(key);
1986 if (game_status == GAME_MODE_PLAYING && !network_playing)
1988 int centered_player_nr_next = -999;
1990 if (key == setup.shortcut.focus_player_all)
1991 centered_player_nr_next = -1;
1993 for (i = 0; i < MAX_PLAYERS; i++)
1994 if (key == setup.shortcut.focus_player[i])
1995 centered_player_nr_next = i;
1997 if (centered_player_nr_next != -999)
1999 game.centered_player_nr_next = centered_player_nr_next;
2000 game.set_centered_player = TRUE;
2004 tape.centered_player_nr_next = game.centered_player_nr_next;
2005 tape.set_centered_player = TRUE;
2010 HandleKeysSpecial(key);
2012 if (HandleGadgetsKeyInput(key))
2014 if (key != KSYM_Escape) /* always allow ESC key to be handled */
2015 key = KSYM_UNDEFINED;
2018 switch (game_status)
2020 case GAME_MODE_PSEUDO_TYPENAME:
2021 HandleTypeName(0, key);
2024 case GAME_MODE_TITLE:
2025 case GAME_MODE_MAIN:
2026 case GAME_MODE_LEVELS:
2027 case GAME_MODE_LEVELNR:
2028 case GAME_MODE_SETUP:
2029 case GAME_MODE_INFO:
2030 case GAME_MODE_SCORES:
2035 if (game_status == GAME_MODE_TITLE)
2036 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2037 else if (game_status == GAME_MODE_MAIN)
2038 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2039 else if (game_status == GAME_MODE_LEVELS)
2040 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2041 else if (game_status == GAME_MODE_LEVELNR)
2042 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2043 else if (game_status == GAME_MODE_SETUP)
2044 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2045 else if (game_status == GAME_MODE_INFO)
2046 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2047 else if (game_status == GAME_MODE_SCORES)
2048 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2052 if (game_status != GAME_MODE_MAIN)
2053 FadeSkipNextFadeIn();
2055 if (game_status == GAME_MODE_TITLE)
2056 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2057 else if (game_status == GAME_MODE_LEVELS)
2058 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2059 else if (game_status == GAME_MODE_LEVELNR)
2060 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2061 else if (game_status == GAME_MODE_SETUP)
2062 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2063 else if (game_status == GAME_MODE_INFO)
2064 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2065 else if (game_status == GAME_MODE_SCORES)
2066 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2070 if (game_status == GAME_MODE_LEVELS)
2071 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2072 else if (game_status == GAME_MODE_LEVELNR)
2073 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2074 else if (game_status == GAME_MODE_SETUP)
2075 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2076 else if (game_status == GAME_MODE_INFO)
2077 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2078 else if (game_status == GAME_MODE_SCORES)
2079 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2082 case KSYM_Page_Down:
2083 if (game_status == GAME_MODE_LEVELS)
2084 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2085 else if (game_status == GAME_MODE_LEVELNR)
2086 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2087 else if (game_status == GAME_MODE_SETUP)
2088 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2089 else if (game_status == GAME_MODE_INFO)
2090 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2091 else if (game_status == GAME_MODE_SCORES)
2092 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2100 case GAME_MODE_EDITOR:
2101 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2102 HandleLevelEditorKeyInput(key);
2105 case GAME_MODE_PLAYING:
2110 RequestQuitGame(setup.ask_on_escape);
2120 if (key == KSYM_Escape)
2122 SetGameStatus(GAME_MODE_MAIN);
2130 HandleKeysDebug(key);
2133 void HandleNoEvent()
2135 HandleMouseCursor();
2137 switch (game_status)
2139 case GAME_MODE_PLAYING:
2140 HandleButtonOrFinger(-1, -1, -1);
2145 void HandleEventActions()
2147 // if (button_status && game_status != GAME_MODE_PLAYING)
2148 if (button_status && (game_status != GAME_MODE_PLAYING ||
2150 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2152 HandleButton(0, 0, button_status, -button_status);
2159 #if defined(NETWORK_AVALIABLE)
2160 if (options.network)
2164 switch (game_status)
2166 case GAME_MODE_MAIN:
2167 DrawPreviewLevelAnimation();
2170 case GAME_MODE_EDITOR:
2171 HandleLevelEditorIdle();
2179 static void HandleTileCursor(int dx, int dy, int button)
2182 ClearPlayerMouseAction();
2189 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2190 (dx < 0 ? MB_LEFTBUTTON :
2191 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2193 else if (!tile_cursor.moving)
2195 int old_xpos = tile_cursor.xpos;
2196 int old_ypos = tile_cursor.ypos;
2197 int new_xpos = old_xpos;
2198 int new_ypos = old_ypos;
2200 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2201 new_xpos = old_xpos + dx;
2203 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2204 new_ypos = old_ypos + dy;
2206 SetTileCursorTargetXY(new_xpos, new_ypos);
2210 static int HandleJoystickForAllPlayers()
2214 boolean no_joysticks_configured = TRUE;
2215 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2216 static byte joy_action_last[MAX_PLAYERS];
2218 for (i = 0; i < MAX_PLAYERS; i++)
2219 if (setup.input[i].use_joystick)
2220 no_joysticks_configured = FALSE;
2222 /* if no joysticks configured, map connected joysticks to players */
2223 if (no_joysticks_configured)
2224 use_as_joystick_nr = TRUE;
2226 for (i = 0; i < MAX_PLAYERS; i++)
2228 byte joy_action = 0;
2230 joy_action = JoystickExt(i, use_as_joystick_nr);
2231 result |= joy_action;
2233 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2234 joy_action != joy_action_last[i])
2235 stored_player[i].action = joy_action;
2237 joy_action_last[i] = joy_action;
2243 void HandleJoystick()
2245 static unsigned int joytest_delay = 0;
2246 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2247 static int joytest_last = 0;
2248 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2249 int delay_value = GADGET_FRAME_DELAY;
2250 int joystick = HandleJoystickForAllPlayers();
2251 int keyboard = key_joystick_mapping;
2252 int joy = (joystick | keyboard);
2253 int joytest = joystick;
2254 int left = joy & JOY_LEFT;
2255 int right = joy & JOY_RIGHT;
2256 int up = joy & JOY_UP;
2257 int down = joy & JOY_DOWN;
2258 int button = joy & JOY_BUTTON;
2259 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2260 int dx = (left ? -1 : right ? 1 : 0);
2261 int dy = (up ? -1 : down ? 1 : 0);
2262 boolean use_delay_value_first = (joytest != joytest_last);
2264 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2266 /* do not handle this button event anymore */
2270 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2272 if (game_status == GAME_MODE_PLAYING)
2274 // when playing MM style levels, also use delay for keyboard events
2275 joytest |= keyboard;
2277 // only use first delay value for new events, but not for changed events
2278 use_delay_value_first = (!joytest != !joytest_last);
2280 // only use delay after the initial keyboard event
2284 // for any joystick or keyboard event, enable playfield tile cursor
2285 if (dx || dy || button)
2286 SetTileCursorEnabled(TRUE);
2289 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2291 /* delay joystick/keyboard actions if axes/keys continually pressed */
2292 newbutton = dx = dy = 0;
2296 /* first start with longer delay, then continue with shorter delay */
2297 joytest_delay_value =
2298 (use_delay_value_first ? delay_value_first : delay_value);
2301 joytest_last = joytest;
2303 switch (game_status)
2305 case GAME_MODE_TITLE:
2306 case GAME_MODE_MAIN:
2307 case GAME_MODE_LEVELS:
2308 case GAME_MODE_LEVELNR:
2309 case GAME_MODE_SETUP:
2310 case GAME_MODE_INFO:
2311 case GAME_MODE_SCORES:
2313 if (game_status == GAME_MODE_TITLE)
2314 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2315 else if (game_status == GAME_MODE_MAIN)
2316 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2317 else if (game_status == GAME_MODE_LEVELS)
2318 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2319 else if (game_status == GAME_MODE_LEVELNR)
2320 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2321 else if (game_status == GAME_MODE_SETUP)
2322 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2323 else if (game_status == GAME_MODE_INFO)
2324 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2325 else if (game_status == GAME_MODE_SCORES)
2326 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2331 case GAME_MODE_PLAYING:
2333 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2334 if (tape.playing || keyboard)
2335 newbutton = ((joy & JOY_BUTTON) != 0);
2338 if (newbutton && AllPlayersGone)
2345 if (tape.recording && tape.pausing && !tape.use_mouse)
2347 if (joystick & JOY_ACTION)
2348 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2351 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2352 HandleTileCursor(dx, dy, button);
2361 void HandleSpecialGameControllerButtons(Event *event)
2363 #if defined(TARGET_SDL2)
2364 switch (event->type)
2366 case SDL_CONTROLLERBUTTONDOWN:
2367 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2368 HandleKey(KSYM_space, KEY_PRESSED);
2369 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2370 HandleKey(KSYM_Escape, KEY_PRESSED);
2374 case SDL_CONTROLLERBUTTONUP:
2375 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2376 HandleKey(KSYM_space, KEY_RELEASED);
2377 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2378 HandleKey(KSYM_Escape, KEY_RELEASED);
2385 void HandleSpecialGameControllerKeys(Key key, int key_status)
2387 #if defined(TARGET_SDL2)
2388 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2389 int button = SDL_CONTROLLER_BUTTON_INVALID;
2391 /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2392 if (key == KSYM_Rewind)
2393 button = SDL_CONTROLLER_BUTTON_A;
2394 else if (key == KSYM_FastForward || key == KSYM_Menu)
2395 button = SDL_CONTROLLER_BUTTON_B;
2397 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2401 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2402 SDL_CONTROLLERBUTTONUP);
2404 event.cbutton.which = 0; /* first joystick (Amazon Fire TV remote) */
2405 event.cbutton.button = button;
2406 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2409 HandleJoystickEvent(&event);