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;
42 /* event filter especially needed for SDL event filtering due to
43 delay problems with lots of mouse motion events when mouse button
44 not pressed (X11 can handle this with 'PointerMotionHintMask') */
46 /* event filter addition for SDL2: as SDL2 does not have a function to enable
47 or disable keyboard auto-repeat, filter repeated keyboard events instead */
49 static int FilterEvents(const Event *event)
53 #if defined(TARGET_SDL2)
54 /* skip repeated key press events if keyboard auto-repeat is disabled */
55 if (event->type == EVENT_KEYPRESS &&
61 if (event->type == EVENT_BUTTONPRESS ||
62 event->type == EVENT_BUTTONRELEASE)
64 ((ButtonEvent *)event)->x -= video.screen_xoffset;
65 ((ButtonEvent *)event)->y -= video.screen_yoffset;
67 else if (event->type == EVENT_MOTIONNOTIFY)
69 ((MotionEvent *)event)->x -= video.screen_xoffset;
70 ((MotionEvent *)event)->y -= video.screen_yoffset;
73 /* non-motion events are directly passed to event handler functions */
74 if (event->type != EVENT_MOTIONNOTIFY)
77 motion = (MotionEvent *)event;
78 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
79 motion->y >= SY && motion->y < SY + SYSIZE);
81 /* do no reset mouse cursor before all pending events have been processed */
82 if (gfx.cursor_mode == cursor_mode_last &&
83 ((game_status == GAME_MODE_TITLE &&
84 gfx.cursor_mode == CURSOR_NONE) ||
85 (game_status == GAME_MODE_PLAYING &&
86 gfx.cursor_mode == CURSOR_PLAYFIELD)))
88 SetMouseCursor(CURSOR_DEFAULT);
90 DelayReached(&special_cursor_delay, 0);
92 cursor_mode_last = CURSOR_DEFAULT;
95 /* skip mouse motion events without pressed button outside level editor */
96 if (button_status == MB_RELEASED &&
97 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
103 /* to prevent delay problems, skip mouse motion events if the very next
104 event is also a mouse motion event (and therefore effectively only
105 handling the last of a row of mouse motion events in the event queue) */
107 static boolean SkipPressedMouseMotionEvent(const Event *event)
109 /* nothing to do if the current event is not a mouse motion event */
110 if (event->type != EVENT_MOTIONNOTIFY)
113 /* only skip motion events with pressed button outside the game */
114 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
121 PeekEvent(&next_event);
123 /* if next event is also a mouse motion event, skip the current one */
124 if (next_event.type == EVENT_MOTIONNOTIFY)
131 /* this is especially needed for event modifications for the Android target:
132 if mouse coordinates should be modified in the event filter function,
133 using a properly installed SDL event filter does not work, because in
134 the event filter, mouse coordinates in the event structure are still
135 physical pixel positions, not logical (scaled) screen positions, so this
136 has to be handled at a later stage in the event processing functions
137 (when device pixel positions are already converted to screen positions) */
139 boolean NextValidEvent(Event *event)
141 while (PendingEvent())
143 boolean handle_this_event = FALSE;
147 if (FilterEvents(event))
148 handle_this_event = TRUE;
150 if (SkipPressedMouseMotionEvent(event))
151 handle_this_event = FALSE;
153 if (handle_this_event)
163 unsigned int event_frame_delay = 0;
164 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
166 ResetDelayCounter(&event_frame_delay);
168 while (NextValidEvent(&event))
172 case EVENT_BUTTONPRESS:
173 case EVENT_BUTTONRELEASE:
174 HandleButtonEvent((ButtonEvent *) &event);
177 case EVENT_MOTIONNOTIFY:
178 HandleMotionEvent((MotionEvent *) &event);
181 #if defined(TARGET_SDL2)
182 case EVENT_WHEELMOTION:
183 HandleWheelEvent((WheelEvent *) &event);
186 case SDL_WINDOWEVENT:
187 HandleWindowEvent((WindowEvent *) &event);
190 case EVENT_FINGERPRESS:
191 case EVENT_FINGERRELEASE:
192 case EVENT_FINGERMOTION:
193 HandleFingerEvent((FingerEvent *) &event);
196 case EVENT_TEXTINPUT:
197 HandleTextEvent((TextEvent *) &event);
200 case SDL_APP_WILLENTERBACKGROUND:
201 case SDL_APP_DIDENTERBACKGROUND:
202 case SDL_APP_WILLENTERFOREGROUND:
203 case SDL_APP_DIDENTERFOREGROUND:
204 HandlePauseResumeEvent((PauseResumeEvent *) &event);
209 case EVENT_KEYRELEASE:
210 HandleKeyEvent((KeyEvent *) &event);
214 HandleOtherEvents(&event);
218 // do not handle events for longer than standard frame delay period
219 if (DelayReached(&event_frame_delay, event_frame_delay_value))
224 void HandleOtherEvents(Event *event)
229 HandleExposeEvent((ExposeEvent *) event);
232 case EVENT_UNMAPNOTIFY:
234 /* This causes the game to stop not only when iconified, but also
235 when on another virtual desktop, which might be not desired. */
236 SleepWhileUnmapped();
242 HandleFocusEvent((FocusChangeEvent *) event);
245 case EVENT_CLIENTMESSAGE:
246 HandleClientMessageEvent((ClientMessageEvent *) event);
249 #if defined(TARGET_SDL)
250 #if defined(TARGET_SDL2)
251 case SDL_CONTROLLERBUTTONDOWN:
252 case SDL_CONTROLLERBUTTONUP:
253 // for any game controller button event, disable overlay buttons
254 SetOverlayEnabled(FALSE);
256 HandleSpecialGameControllerButtons(event);
259 case SDL_CONTROLLERDEVICEADDED:
260 case SDL_CONTROLLERDEVICEREMOVED:
261 case SDL_CONTROLLERAXISMOTION:
263 case SDL_JOYAXISMOTION:
264 case SDL_JOYBUTTONDOWN:
265 case SDL_JOYBUTTONUP:
266 HandleJoystickEvent(event);
270 HandleWindowManagerEvent(event);
279 void HandleMouseCursor()
281 if (game_status == GAME_MODE_TITLE)
283 /* when showing title screens, hide mouse pointer (if not moved) */
285 if (gfx.cursor_mode != CURSOR_NONE &&
286 DelayReached(&special_cursor_delay, special_cursor_delay_value))
288 SetMouseCursor(CURSOR_NONE);
291 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
294 /* when playing, display a special mouse pointer inside the playfield */
296 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
297 cursor_inside_playfield &&
298 DelayReached(&special_cursor_delay, special_cursor_delay_value))
300 SetMouseCursor(CURSOR_PLAYFIELD);
303 else if (gfx.cursor_mode != CURSOR_DEFAULT)
305 SetMouseCursor(CURSOR_DEFAULT);
308 /* this is set after all pending events have been processed */
309 cursor_mode_last = gfx.cursor_mode;
321 /* also execute after pending events have been processed before */
324 /* don't use all CPU time when idle; the main loop while playing
325 has its own synchronization and is CPU friendly, too */
327 if (game_status == GAME_MODE_PLAYING)
330 /* always copy backbuffer to visible screen for every video frame */
333 /* reset video frame delay to default (may change again while playing) */
334 SetVideoFrameDelay(MenuFrameDelay);
336 if (game_status == GAME_MODE_QUIT)
341 void ClearEventQueue()
343 while (PendingEvent())
351 case EVENT_BUTTONRELEASE:
352 button_status = MB_RELEASED;
355 case EVENT_KEYRELEASE:
359 #if defined(TARGET_SDL2)
360 case SDL_CONTROLLERBUTTONUP:
361 HandleJoystickEvent(&event);
367 HandleOtherEvents(&event);
373 void ClearPlayerAction()
377 /* simulate key release events for still pressed keys */
378 key_joystick_mapping = 0;
379 for (i = 0; i < MAX_PLAYERS; i++)
380 stored_player[i].action = 0;
382 ClearJoystickState();
385 void SleepWhileUnmapped()
387 boolean window_unmapped = TRUE;
389 KeyboardAutoRepeatOn();
391 while (window_unmapped)
399 case EVENT_BUTTONRELEASE:
400 button_status = MB_RELEASED;
403 case EVENT_KEYRELEASE:
404 key_joystick_mapping = 0;
407 #if defined(TARGET_SDL2)
408 case SDL_CONTROLLERBUTTONUP:
409 HandleJoystickEvent(&event);
410 key_joystick_mapping = 0;
414 case EVENT_MAPNOTIFY:
415 window_unmapped = FALSE;
418 case EVENT_UNMAPNOTIFY:
419 /* this is only to surely prevent the 'should not happen' case
420 * of recursively looping between 'SleepWhileUnmapped()' and
421 * 'HandleOtherEvents()' which usually calls this funtion.
426 HandleOtherEvents(&event);
431 if (game_status == GAME_MODE_PLAYING)
432 KeyboardAutoRepeatOffUnlessAutoplay();
435 void HandleExposeEvent(ExposeEvent *event)
439 void HandleButtonEvent(ButtonEvent *event)
441 #if DEBUG_EVENTS_BUTTON
442 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
444 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
448 #if defined(HAS_SCREEN_KEYBOARD)
449 if (video.shifted_up)
450 event->y += video.shifted_up_pos;
453 motion_status = FALSE;
455 if (event->type == EVENT_BUTTONPRESS)
456 button_status = event->button;
458 button_status = MB_RELEASED;
460 HandleButton(event->x, event->y, button_status, event->button);
463 void HandleMotionEvent(MotionEvent *event)
465 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
468 motion_status = TRUE;
470 #if DEBUG_EVENTS_MOTION
471 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
472 button_status, event->x, event->y);
475 HandleButton(event->x, event->y, button_status, button_status);
478 #if defined(TARGET_SDL2)
480 void HandleWheelEvent(WheelEvent *event)
484 #if DEBUG_EVENTS_WHEEL
486 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
487 event->which, event->x, event->y);
489 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
490 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
491 event->which, event->x, event->y,
492 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
493 "SDL_MOUSEWHEEL_FLIPPED"));
497 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
498 event->x > 0 ? MB_WHEEL_RIGHT :
499 event->y < 0 ? MB_WHEEL_DOWN :
500 event->y > 0 ? MB_WHEEL_UP : 0);
502 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
503 // accelerated mouse wheel available on Mac and Windows
504 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
506 // no accelerated mouse wheel available on Unix/Linux
507 wheel_steps = DEFAULT_WHEEL_STEPS;
510 motion_status = FALSE;
512 button_status = button_nr;
513 HandleButton(0, 0, button_status, -button_nr);
515 button_status = MB_RELEASED;
516 HandleButton(0, 0, button_status, -button_nr);
519 void HandleWindowEvent(WindowEvent *event)
521 #if DEBUG_EVENTS_WINDOW
522 int subtype = event->event;
525 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
526 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
527 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
528 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
529 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
530 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
531 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
532 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
533 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
534 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
535 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
536 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
537 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
538 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
541 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
542 event_name, event->data1, event->data2);
546 // (not needed, as the screen gets redrawn every 20 ms anyway)
547 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
548 event->event == SDL_WINDOWEVENT_RESIZED ||
549 event->event == SDL_WINDOWEVENT_EXPOSED)
553 if (event->event == SDL_WINDOWEVENT_RESIZED)
555 if (!video.fullscreen_enabled)
557 int new_window_width = event->data1;
558 int new_window_height = event->data2;
560 // if window size has changed after resizing, calculate new scaling factor
561 if (new_window_width != video.window_width ||
562 new_window_height != video.window_height)
564 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
565 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
567 // (extreme window scaling allowed, but cannot be saved permanently)
568 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
569 setup.window_scaling_percent =
570 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
571 MAX_WINDOW_SCALING_PERCENT);
573 video.window_width = new_window_width;
574 video.window_height = new_window_height;
576 if (game_status == GAME_MODE_SETUP)
577 RedrawSetupScreenAfterFullscreenToggle();
582 #if defined(PLATFORM_ANDROID)
585 int new_display_width = event->data1;
586 int new_display_height = event->data2;
588 // if fullscreen display size has changed, device has been rotated
589 if (new_display_width != video.display_width ||
590 new_display_height != video.display_height)
592 video.display_width = new_display_width;
593 video.display_height = new_display_height;
595 SDLSetScreenProperties();
602 #define NUM_TOUCH_FINGERS 3
607 SDL_FingerID finger_id;
610 } touch_info[NUM_TOUCH_FINGERS];
612 void HandleFingerEvent(FingerEvent *event)
614 static Key motion_key_x = KSYM_UNDEFINED;
615 static Key motion_key_y = KSYM_UNDEFINED;
616 static Key button_key = KSYM_UNDEFINED;
617 static float motion_x1, motion_y1;
618 static float button_x1, button_y1;
619 static SDL_FingerID motion_id = -1;
620 static SDL_FingerID button_id = -1;
621 int move_trigger_distance_percent = setup.touch.move_distance;
622 int drop_trigger_distance_percent = setup.touch.drop_distance;
623 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
624 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
625 float event_x = event->x;
626 float event_y = event->y;
628 #if DEBUG_EVENTS_FINGER
629 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
630 event->type == EVENT_FINGERPRESS ? "pressed" :
631 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
635 event->dx, event->dy,
639 if (game_status != GAME_MODE_PLAYING)
642 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
645 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
647 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
649 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
651 event_y = (event_y - ypos) / (1 - ypos);
653 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
654 event_y > 2.0 / 3.0 && event_y < 1 ?
655 setup.input[0].key.snap :
656 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
657 event_y > 2.0 / 3.0 && event_y < 1 ?
658 setup.input[0].key.drop :
659 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
660 event_y > 0 && event_y < 1.0 / 3.0 ?
661 setup.input[0].key.up :
662 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
663 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
664 setup.input[0].key.left :
665 event_x > 8.0 / 9.0 && event_x < 1 &&
666 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
667 setup.input[0].key.right :
668 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
669 event_y > 2.0 / 3.0 && event_y < 1 ?
670 setup.input[0].key.down :
673 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
677 // for any touch input event, enable overlay buttons (if activated)
678 SetOverlayEnabled(TRUE);
680 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
681 getKeyNameFromKey(key), key_status_name, event->fingerId);
683 // check if we already know this touch event's finger id
684 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
686 if (touch_info[i].touched &&
687 touch_info[i].finger_id == event->fingerId)
689 // Error(ERR_DEBUG, "MARK 1: %d", i);
695 if (i >= NUM_TOUCH_FINGERS)
697 if (key_status == KEY_PRESSED)
699 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
701 // unknown finger id -- get new, empty slot, if available
702 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
704 if (touch_info[i].counter < oldest_counter)
707 oldest_counter = touch_info[i].counter;
709 // Error(ERR_DEBUG, "MARK 2: %d", i);
712 if (!touch_info[i].touched)
714 // Error(ERR_DEBUG, "MARK 3: %d", i);
720 if (i >= NUM_TOUCH_FINGERS)
722 // all slots allocated -- use oldest slot
725 // Error(ERR_DEBUG, "MARK 4: %d", i);
730 // release of previously unknown key (should not happen)
732 if (key != KSYM_UNDEFINED)
734 HandleKey(key, KEY_RELEASED);
736 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
737 getKeyNameFromKey(key), "KEY_RELEASED", i);
742 if (i < NUM_TOUCH_FINGERS)
744 if (key_status == KEY_PRESSED)
746 if (touch_info[i].key != key)
748 if (touch_info[i].key != KSYM_UNDEFINED)
750 HandleKey(touch_info[i].key, KEY_RELEASED);
752 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
753 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
756 if (key != KSYM_UNDEFINED)
758 HandleKey(key, KEY_PRESSED);
760 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
761 getKeyNameFromKey(key), "KEY_PRESSED", i);
765 touch_info[i].touched = TRUE;
766 touch_info[i].finger_id = event->fingerId;
767 touch_info[i].counter = Counter();
768 touch_info[i].key = key;
772 if (touch_info[i].key != KSYM_UNDEFINED)
774 HandleKey(touch_info[i].key, KEY_RELEASED);
776 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
777 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
780 touch_info[i].touched = FALSE;
781 touch_info[i].finger_id = 0;
782 touch_info[i].counter = 0;
783 touch_info[i].key = 0;
790 // use touch direction control
792 if (event->type == EVENT_FINGERPRESS)
794 if (event_x > 1.0 / 3.0)
798 motion_id = event->fingerId;
803 motion_key_x = KSYM_UNDEFINED;
804 motion_key_y = KSYM_UNDEFINED;
806 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
812 button_id = event->fingerId;
817 button_key = setup.input[0].key.snap;
819 HandleKey(button_key, KEY_PRESSED);
821 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
824 else if (event->type == EVENT_FINGERRELEASE)
826 if (event->fingerId == motion_id)
830 if (motion_key_x != KSYM_UNDEFINED)
831 HandleKey(motion_key_x, KEY_RELEASED);
832 if (motion_key_y != KSYM_UNDEFINED)
833 HandleKey(motion_key_y, KEY_RELEASED);
835 motion_key_x = KSYM_UNDEFINED;
836 motion_key_y = KSYM_UNDEFINED;
838 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
840 else if (event->fingerId == button_id)
844 if (button_key != KSYM_UNDEFINED)
845 HandleKey(button_key, KEY_RELEASED);
847 button_key = KSYM_UNDEFINED;
849 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
852 else if (event->type == EVENT_FINGERMOTION)
854 if (event->fingerId == motion_id)
856 float distance_x = ABS(event_x - motion_x1);
857 float distance_y = ABS(event_y - motion_y1);
858 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
859 event_x > motion_x1 ? setup.input[0].key.right :
861 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
862 event_y > motion_y1 ? setup.input[0].key.down :
865 if (distance_x < move_trigger_distance / 2 ||
866 distance_x < distance_y)
867 new_motion_key_x = KSYM_UNDEFINED;
869 if (distance_y < move_trigger_distance / 2 ||
870 distance_y < distance_x)
871 new_motion_key_y = KSYM_UNDEFINED;
873 if (distance_x > move_trigger_distance ||
874 distance_y > move_trigger_distance)
876 if (new_motion_key_x != motion_key_x)
878 if (motion_key_x != KSYM_UNDEFINED)
879 HandleKey(motion_key_x, KEY_RELEASED);
880 if (new_motion_key_x != KSYM_UNDEFINED)
881 HandleKey(new_motion_key_x, KEY_PRESSED);
884 if (new_motion_key_y != motion_key_y)
886 if (motion_key_y != KSYM_UNDEFINED)
887 HandleKey(motion_key_y, KEY_RELEASED);
888 if (new_motion_key_y != KSYM_UNDEFINED)
889 HandleKey(new_motion_key_y, KEY_PRESSED);
895 motion_key_x = new_motion_key_x;
896 motion_key_y = new_motion_key_y;
898 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
901 else if (event->fingerId == button_id)
903 float distance_x = ABS(event_x - button_x1);
904 float distance_y = ABS(event_y - button_y1);
906 if (distance_x < drop_trigger_distance / 2 &&
907 distance_y > drop_trigger_distance)
909 if (button_key == setup.input[0].key.snap)
910 HandleKey(button_key, KEY_RELEASED);
915 button_key = setup.input[0].key.drop;
917 HandleKey(button_key, KEY_PRESSED);
919 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
925 static void HandleFollowFinger(int mx, int my, int button)
927 static int old_mx = 0, old_my = 0;
928 static Key motion_key_x = KSYM_UNDEFINED;
929 static Key motion_key_y = KSYM_UNDEFINED;
930 static boolean started_on_player = FALSE;
931 static boolean player_is_dropping = FALSE;
932 static int player_drop_count = 0;
933 static int last_player_x = -1;
934 static int last_player_y = -1;
936 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
939 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
941 touch_info[0].touched = TRUE;
942 touch_info[0].key = 0;
949 started_on_player = FALSE;
950 player_is_dropping = FALSE;
951 player_drop_count = 0;
955 motion_key_x = KSYM_UNDEFINED;
956 motion_key_y = KSYM_UNDEFINED;
958 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
961 else if (button == MB_RELEASED && touch_info[0].touched)
963 touch_info[0].touched = FALSE;
964 touch_info[0].key = 0;
969 if (motion_key_x != KSYM_UNDEFINED)
970 HandleKey(motion_key_x, KEY_RELEASED);
971 if (motion_key_y != KSYM_UNDEFINED)
972 HandleKey(motion_key_y, KEY_RELEASED);
974 if (started_on_player)
976 if (player_is_dropping)
978 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
980 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
984 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
986 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
990 motion_key_x = KSYM_UNDEFINED;
991 motion_key_y = KSYM_UNDEFINED;
993 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
996 if (touch_info[0].touched)
998 int src_x = local_player->jx;
999 int src_y = local_player->jy;
1000 int dst_x = getLevelFromScreenX(old_mx);
1001 int dst_y = getLevelFromScreenY(old_my);
1002 int dx = dst_x - src_x;
1003 int dy = dst_y - src_y;
1004 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1005 dx > 0 ? setup.input[0].key.right :
1007 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1008 dy > 0 ? setup.input[0].key.down :
1011 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1012 (last_player_x != local_player->jx ||
1013 last_player_y != local_player->jy))
1015 // in case of asymmetric diagonal movement, use "preferred" direction
1017 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1019 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1020 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1022 local_player->last_move_dir = last_move_dir;
1024 // (required to prevent accidentally forcing direction for next movement)
1025 last_player_x = local_player->jx;
1026 last_player_y = local_player->jy;
1029 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1031 started_on_player = TRUE;
1032 player_drop_count = getPlayerInventorySize(0);
1033 player_is_dropping = (player_drop_count > 0);
1035 if (player_is_dropping)
1037 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1039 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1043 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1045 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1048 else if (dx != 0 || dy != 0)
1050 if (player_is_dropping &&
1051 player_drop_count == getPlayerInventorySize(0))
1053 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1055 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1056 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1058 player_is_dropping = FALSE;
1062 if (new_motion_key_x != motion_key_x)
1064 Error(ERR_DEBUG, "---------- %s %s ----------",
1065 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1066 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1068 if (motion_key_x != KSYM_UNDEFINED)
1069 HandleKey(motion_key_x, KEY_RELEASED);
1070 if (new_motion_key_x != KSYM_UNDEFINED)
1071 HandleKey(new_motion_key_x, KEY_PRESSED);
1074 if (new_motion_key_y != motion_key_y)
1076 Error(ERR_DEBUG, "---------- %s %s ----------",
1077 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1078 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1080 if (motion_key_y != KSYM_UNDEFINED)
1081 HandleKey(motion_key_y, KEY_RELEASED);
1082 if (new_motion_key_y != KSYM_UNDEFINED)
1083 HandleKey(new_motion_key_y, KEY_PRESSED);
1086 motion_key_x = new_motion_key_x;
1087 motion_key_y = new_motion_key_y;
1091 static boolean checkTextInputKeyModState()
1093 // when playing, only handle raw key events and ignore text input
1094 if (game_status == GAME_MODE_PLAYING)
1097 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1100 void HandleTextEvent(TextEvent *event)
1102 char *text = event->text;
1103 Key key = getKeyFromKeyName(text);
1105 #if DEBUG_EVENTS_TEXT
1106 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1109 text[0], (int)(text[0]),
1111 getKeyNameFromKey(key),
1115 #if !defined(HAS_SCREEN_KEYBOARD)
1116 // non-mobile devices: only handle key input with modifier keys pressed here
1117 // (every other key input is handled directly as physical key input event)
1118 if (!checkTextInputKeyModState())
1122 // process text input as "classic" (with uppercase etc.) key input event
1123 HandleKey(key, KEY_PRESSED);
1124 HandleKey(key, KEY_RELEASED);
1127 void HandlePauseResumeEvent(PauseResumeEvent *event)
1129 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1133 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1141 void HandleKeyEvent(KeyEvent *event)
1143 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1144 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1145 Key key = GetEventKey(event, with_modifiers);
1146 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1148 #if DEBUG_EVENTS_KEY
1149 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1150 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1151 event->keysym.scancode,
1156 getKeyNameFromKey(key));
1159 #if defined(PLATFORM_ANDROID)
1160 if (key == KSYM_Back)
1162 // always map the "back" button to the "escape" key on Android devices
1167 // for any key event other than "back" button, disable overlay buttons
1168 SetOverlayEnabled(FALSE);
1172 HandleKeyModState(keymod, key_status);
1174 #if defined(TARGET_SDL2)
1175 // only handle raw key input without text modifier keys pressed
1176 if (!checkTextInputKeyModState())
1177 HandleKey(key, key_status);
1179 HandleKey(key, key_status);
1183 void HandleFocusEvent(FocusChangeEvent *event)
1185 static int old_joystick_status = -1;
1187 if (event->type == EVENT_FOCUSOUT)
1189 KeyboardAutoRepeatOn();
1190 old_joystick_status = joystick.status;
1191 joystick.status = JOYSTICK_NOT_AVAILABLE;
1193 ClearPlayerAction();
1195 else if (event->type == EVENT_FOCUSIN)
1197 /* When there are two Rocks'n'Diamonds windows which overlap and
1198 the player moves the pointer from one game window to the other,
1199 a 'FocusOut' event is generated for the window the pointer is
1200 leaving and a 'FocusIn' event is generated for the window the
1201 pointer is entering. In some cases, it can happen that the
1202 'FocusIn' event is handled by the one game process before the
1203 'FocusOut' event by the other game process. In this case the
1204 X11 environment would end up with activated keyboard auto repeat,
1205 because unfortunately this is a global setting and not (which
1206 would be far better) set for each X11 window individually.
1207 The effect would be keyboard auto repeat while playing the game
1208 (game_status == GAME_MODE_PLAYING), which is not desired.
1209 To avoid this special case, we just wait 1/10 second before
1210 processing the 'FocusIn' event.
1213 if (game_status == GAME_MODE_PLAYING)
1216 KeyboardAutoRepeatOffUnlessAutoplay();
1219 if (old_joystick_status != -1)
1220 joystick.status = old_joystick_status;
1224 void HandleClientMessageEvent(ClientMessageEvent *event)
1226 if (CheckCloseWindowEvent(event))
1230 void HandleWindowManagerEvent(Event *event)
1232 #if defined(TARGET_SDL)
1233 SDLHandleWindowManagerEvent(event);
1237 void HandleButton(int mx, int my, int button, int button_nr)
1239 static int old_mx = 0, old_my = 0;
1240 boolean button_hold = FALSE;
1246 button_nr = -button_nr;
1255 #if defined(PLATFORM_ANDROID)
1256 // when playing, only handle gadgets when using "follow finger" controls
1257 boolean handle_gadgets =
1258 (game_status != GAME_MODE_PLAYING ||
1259 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1261 if (handle_gadgets &&
1262 HandleGadgets(mx, my, button))
1264 /* do not handle this button event anymore */
1265 mx = my = -32; /* force mouse event to be outside screen tiles */
1268 if (HandleGadgets(mx, my, button))
1270 /* do not handle this button event anymore */
1271 mx = my = -32; /* force mouse event to be outside screen tiles */
1275 if (HandleGlobalAnimClicks(mx, my, button))
1277 /* do not handle this button event anymore */
1278 mx = my = -32; /* force mouse event to be outside screen tiles */
1281 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1284 /* do not use scroll wheel button events for anything other than gadgets */
1285 if (IS_WHEEL_BUTTON(button_nr))
1288 switch (game_status)
1290 case GAME_MODE_TITLE:
1291 HandleTitleScreen(mx, my, 0, 0, button);
1294 case GAME_MODE_MAIN:
1295 HandleMainMenu(mx, my, 0, 0, button);
1298 case GAME_MODE_PSEUDO_TYPENAME:
1299 HandleTypeName(0, KSYM_Return);
1302 case GAME_MODE_LEVELS:
1303 HandleChooseLevelSet(mx, my, 0, 0, button);
1306 case GAME_MODE_LEVELNR:
1307 HandleChooseLevelNr(mx, my, 0, 0, button);
1310 case GAME_MODE_SCORES:
1311 HandleHallOfFame(0, 0, 0, 0, button);
1314 case GAME_MODE_EDITOR:
1315 HandleLevelEditorIdle();
1318 case GAME_MODE_INFO:
1319 HandleInfoScreen(mx, my, 0, 0, button);
1322 case GAME_MODE_SETUP:
1323 HandleSetupScreen(mx, my, 0, 0, button);
1326 #if defined(TARGET_SDL2)
1327 case GAME_MODE_PLAYING:
1328 HandleFollowFinger(mx, my, button);
1332 if (button == MB_PRESSED && !motion_status && IN_GFX_FIELD_PLAY(mx, my) &&
1333 GetKeyModState() & KMOD_Control)
1334 DumpTileFromScreen(mx, my);
1344 static boolean is_string_suffix(char *string, char *suffix)
1346 int string_len = strlen(string);
1347 int suffix_len = strlen(suffix);
1349 if (suffix_len > string_len)
1352 return (strEqual(&string[string_len - suffix_len], suffix));
1355 #define MAX_CHEAT_INPUT_LEN 32
1357 static void HandleKeysSpecial(Key key)
1359 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1360 char letter = getCharFromKey(key);
1361 int cheat_input_len = strlen(cheat_input);
1367 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1369 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1370 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1372 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1375 cheat_input[cheat_input_len++] = letter;
1376 cheat_input[cheat_input_len] = '\0';
1378 #if DEBUG_EVENTS_KEY
1379 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1382 if (game_status == GAME_MODE_MAIN)
1384 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1385 is_string_suffix(cheat_input, ":ist"))
1387 InsertSolutionTape();
1389 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1390 is_string_suffix(cheat_input, ":rg"))
1392 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1395 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1396 is_string_suffix(cheat_input, ":rs"))
1398 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1401 else if (is_string_suffix(cheat_input, ":reload-music") ||
1402 is_string_suffix(cheat_input, ":rm"))
1404 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1407 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1408 is_string_suffix(cheat_input, ":ra"))
1410 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1411 1 << ARTWORK_TYPE_SOUNDS |
1412 1 << ARTWORK_TYPE_MUSIC);
1415 else if (is_string_suffix(cheat_input, ":dump-level") ||
1416 is_string_suffix(cheat_input, ":dl"))
1420 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1421 is_string_suffix(cheat_input, ":dt"))
1425 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1426 is_string_suffix(cheat_input, ":ft"))
1428 /* fix single-player tapes that contain player input for more than one
1429 player (due to a bug in 3.3.1.2 and earlier versions), which results
1430 in playing levels with more than one player in multi-player mode,
1431 even though the tape was originally recorded in single-player mode */
1433 /* remove player input actions for all players but the first one */
1434 for (i = 1; i < MAX_PLAYERS; i++)
1435 tape.player_participates[i] = FALSE;
1437 tape.changed = TRUE;
1439 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1440 is_string_suffix(cheat_input, ":snl"))
1442 SaveNativeLevel(&level);
1445 else if (game_status == GAME_MODE_PLAYING)
1448 if (is_string_suffix(cheat_input, ".q"))
1449 DEBUG_SetMaximumDynamite();
1452 else if (game_status == GAME_MODE_EDITOR)
1454 if (is_string_suffix(cheat_input, ":dump-brush") ||
1455 is_string_suffix(cheat_input, ":DB"))
1459 else if (is_string_suffix(cheat_input, ":DDB"))
1466 void HandleKeysDebug(Key key)
1471 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1473 boolean mod_key_pressed = (GetKeyModState() != KMOD_None);
1475 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1477 if (key == setup.debug.frame_delay_key[i] &&
1478 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1480 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1481 setup.debug.frame_delay[i] : setup.game_frame_delay);
1483 if (!setup.debug.frame_delay_game_only)
1484 MenuFrameDelay = GameFrameDelay;
1486 SetVideoFrameDelay(GameFrameDelay);
1488 if (GameFrameDelay > ONE_SECOND_DELAY)
1489 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1490 else if (GameFrameDelay != 0)
1491 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1492 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1493 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1495 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1502 if (game_status == GAME_MODE_PLAYING)
1506 options.debug = !options.debug;
1508 Error(ERR_DEBUG, "debug mode %s",
1509 (options.debug ? "enabled" : "disabled"));
1511 else if (key == KSYM_v)
1513 Error(ERR_DEBUG, "currently using game engine version %d",
1514 game.engine_version);
1520 void HandleKey(Key key, int key_status)
1522 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1523 static boolean ignore_repeated_key = FALSE;
1524 static struct SetupKeyboardInfo ski;
1525 static struct SetupShortcutInfo ssi;
1534 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1535 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1536 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1537 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1538 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1539 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1544 #if defined(TARGET_SDL2)
1545 /* map special keys (media keys / remote control buttons) to default keys */
1546 if (key == KSYM_PlayPause)
1548 else if (key == KSYM_Select)
1552 HandleSpecialGameControllerKeys(key, key_status);
1554 if (game_status == GAME_MODE_PLAYING)
1556 /* only needed for single-step tape recording mode */
1557 static boolean clear_snap_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1558 static boolean clear_drop_button[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1559 static boolean element_snapped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1560 static boolean element_dropped[MAX_PLAYERS] = { FALSE,FALSE,FALSE,FALSE };
1563 /* initialize unifying snap and drop buttons (EM engine) */
1564 game_em.use_single_button = game_em.use_single_button_initial;
1566 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1568 byte key_action = 0;
1570 if (setup.input[pnr].use_joystick)
1573 ski = setup.input[pnr].key;
1575 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1576 if (key == *key_info[i].key_custom)
1577 key_action |= key_info[i].action;
1579 /* use combined snap+direction keys for the first player only */
1582 ssi = setup.shortcut;
1584 for (i = 0; i < NUM_DIRECTIONS; i++)
1585 if (key == *key_info[i].key_snap)
1586 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1589 /* clear delayed snap and drop actions in single step mode (see below) */
1590 if (tape.single_step)
1592 if (clear_snap_button[pnr])
1594 stored_player[pnr].action &= ~KEY_BUTTON_SNAP;
1595 clear_snap_button[pnr] = FALSE;
1598 if (clear_drop_button[pnr])
1600 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1601 clear_drop_button[pnr] = FALSE;
1605 if (key_status == KEY_PRESSED)
1606 stored_player[pnr].action |= key_action;
1608 stored_player[pnr].action &= ~key_action;
1610 if (tape.single_step && tape.recording && tape.pausing)
1612 /* do not unify snap and drop buttons in single-step mode (EM engine) */
1613 game_em.use_single_button = FALSE;
1615 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1617 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1619 /* if snap key already pressed, don't snap when releasing (below) */
1620 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1621 element_snapped[pnr] = TRUE;
1623 /* if drop key already pressed, don't drop when releasing (below) */
1624 if (stored_player[pnr].action & KEY_BUTTON_DROP)
1625 element_dropped[pnr] = TRUE;
1627 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1629 if (level.game_engine_type == GAME_ENGINE_TYPE_EM ||
1630 level.game_engine_type == GAME_ENGINE_TYPE_SP)
1633 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1634 getRedDiskReleaseFlag_SP() == 0)
1635 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1637 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1640 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON)
1642 if (key_action & KEY_BUTTON_SNAP)
1644 /* if snap key was released without moving (see above), snap now */
1645 if (!element_snapped[pnr])
1647 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1649 stored_player[pnr].action |= KEY_BUTTON_SNAP;
1651 /* clear delayed snap button on next event */
1652 clear_snap_button[pnr] = TRUE;
1655 element_snapped[pnr] = FALSE;
1658 if (key_action & KEY_BUTTON_DROP &&
1659 level.game_engine_type == GAME_ENGINE_TYPE_RND)
1661 /* if drop key was released without moving (see above), drop now */
1662 if (!element_dropped[pnr])
1664 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1666 if (level.game_engine_type != GAME_ENGINE_TYPE_SP ||
1667 getRedDiskReleaseFlag_SP() != 0)
1668 stored_player[pnr].action |= KEY_BUTTON_DROP;
1670 /* clear delayed drop button on next event */
1671 clear_drop_button[pnr] = TRUE;
1674 element_dropped[pnr] = FALSE;
1678 else if (tape.recording && tape.pausing)
1680 /* prevent key release events from un-pausing a paused game */
1681 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1682 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1688 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1689 if (key == key_info[i].key_default)
1690 joy |= key_info[i].action;
1695 if (key_status == KEY_PRESSED)
1696 key_joystick_mapping |= joy;
1698 key_joystick_mapping &= ~joy;
1703 if (game_status != GAME_MODE_PLAYING)
1704 key_joystick_mapping = 0;
1706 if (key_status == KEY_RELEASED)
1708 // reset flag to ignore repeated "key pressed" events after key release
1709 ignore_repeated_key = FALSE;
1714 if ((key == KSYM_F11 ||
1715 ((key == KSYM_Return ||
1716 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1717 video.fullscreen_available &&
1718 !ignore_repeated_key)
1720 setup.fullscreen = !setup.fullscreen;
1722 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1724 if (game_status == GAME_MODE_SETUP)
1725 RedrawSetupScreenAfterFullscreenToggle();
1727 // set flag to ignore repeated "key pressed" events
1728 ignore_repeated_key = TRUE;
1733 if ((key == KSYM_0 || key == KSYM_KP_0 ||
1734 key == KSYM_minus || key == KSYM_KP_Subtract ||
1735 key == KSYM_plus || key == KSYM_KP_Add ||
1736 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
1737 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1738 video.window_scaling_available &&
1739 !video.fullscreen_enabled)
1741 if (key == KSYM_0 || key == KSYM_KP_0)
1742 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1743 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1744 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1746 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1748 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1749 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1750 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1751 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1753 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1755 if (game_status == GAME_MODE_SETUP)
1756 RedrawSetupScreenAfterFullscreenToggle();
1761 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
1762 key == KSYM_Return ||
1763 key == KSYM_Escape)))
1765 /* do not handle this key event anymore */
1766 if (key != KSYM_Escape) /* always allow ESC key to be handled */
1770 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1771 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1778 if (game_status == GAME_MODE_MAIN &&
1779 (key == setup.shortcut.toggle_pause || key == KSYM_space))
1781 StartGameActions(options.network, setup.autorecord, level.random_seed);
1786 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1788 if (key == setup.shortcut.save_game)
1790 else if (key == setup.shortcut.load_game)
1792 else if (key == setup.shortcut.toggle_pause)
1793 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
1795 HandleTapeButtonKeys(key);
1796 HandleSoundButtonKeys(key);
1799 if (game_status == GAME_MODE_PLAYING && !network_playing)
1801 int centered_player_nr_next = -999;
1803 if (key == setup.shortcut.focus_player_all)
1804 centered_player_nr_next = -1;
1806 for (i = 0; i < MAX_PLAYERS; i++)
1807 if (key == setup.shortcut.focus_player[i])
1808 centered_player_nr_next = i;
1810 if (centered_player_nr_next != -999)
1812 game.centered_player_nr_next = centered_player_nr_next;
1813 game.set_centered_player = TRUE;
1817 tape.centered_player_nr_next = game.centered_player_nr_next;
1818 tape.set_centered_player = TRUE;
1823 HandleKeysSpecial(key);
1825 if (HandleGadgetsKeyInput(key))
1827 if (key != KSYM_Escape) /* always allow ESC key to be handled */
1828 key = KSYM_UNDEFINED;
1831 switch (game_status)
1833 case GAME_MODE_PSEUDO_TYPENAME:
1834 HandleTypeName(0, key);
1837 case GAME_MODE_TITLE:
1838 case GAME_MODE_MAIN:
1839 case GAME_MODE_LEVELS:
1840 case GAME_MODE_LEVELNR:
1841 case GAME_MODE_SETUP:
1842 case GAME_MODE_INFO:
1843 case GAME_MODE_SCORES:
1848 if (game_status == GAME_MODE_TITLE)
1849 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1850 else if (game_status == GAME_MODE_MAIN)
1851 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
1852 else if (game_status == GAME_MODE_LEVELS)
1853 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
1854 else if (game_status == GAME_MODE_LEVELNR)
1855 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
1856 else if (game_status == GAME_MODE_SETUP)
1857 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1858 else if (game_status == GAME_MODE_INFO)
1859 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1860 else if (game_status == GAME_MODE_SCORES)
1861 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
1865 if (game_status != GAME_MODE_MAIN)
1866 FadeSkipNextFadeIn();
1868 if (game_status == GAME_MODE_TITLE)
1869 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1870 else if (game_status == GAME_MODE_LEVELS)
1871 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
1872 else if (game_status == GAME_MODE_LEVELNR)
1873 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
1874 else if (game_status == GAME_MODE_SETUP)
1875 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1876 else if (game_status == GAME_MODE_INFO)
1877 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1878 else if (game_status == GAME_MODE_SCORES)
1879 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
1883 if (game_status == GAME_MODE_LEVELS)
1884 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1885 else if (game_status == GAME_MODE_LEVELNR)
1886 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1887 else if (game_status == GAME_MODE_SETUP)
1888 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1889 else if (game_status == GAME_MODE_INFO)
1890 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1891 else if (game_status == GAME_MODE_SCORES)
1892 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1895 case KSYM_Page_Down:
1896 if (game_status == GAME_MODE_LEVELS)
1897 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1898 else if (game_status == GAME_MODE_LEVELNR)
1899 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1900 else if (game_status == GAME_MODE_SETUP)
1901 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1902 else if (game_status == GAME_MODE_INFO)
1903 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1904 else if (game_status == GAME_MODE_SCORES)
1905 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1913 case GAME_MODE_EDITOR:
1914 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
1915 HandleLevelEditorKeyInput(key);
1918 case GAME_MODE_PLAYING:
1923 RequestQuitGame(setup.ask_on_escape);
1933 if (key == KSYM_Escape)
1935 SetGameStatus(GAME_MODE_MAIN);
1943 HandleKeysDebug(key);
1946 void HandleNoEvent()
1948 // if (button_status && game_status != GAME_MODE_PLAYING)
1949 if (button_status && (game_status != GAME_MODE_PLAYING || tape.pausing))
1951 HandleButton(0, 0, button_status, -button_status);
1958 #if defined(NETWORK_AVALIABLE)
1959 if (options.network)
1963 switch (game_status)
1965 case GAME_MODE_MAIN:
1966 DrawPreviewLevelAnimation();
1969 case GAME_MODE_EDITOR:
1970 HandleLevelEditorIdle();
1973 #if defined(TARGET_SDL2)
1974 case GAME_MODE_PLAYING:
1975 HandleFollowFinger(-1, -1, -1);
1984 static int HandleJoystickForAllPlayers()
1988 boolean no_joysticks_configured = TRUE;
1989 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
1990 static byte joy_action_last[MAX_PLAYERS];
1992 for (i = 0; i < MAX_PLAYERS; i++)
1993 if (setup.input[i].use_joystick)
1994 no_joysticks_configured = FALSE;
1996 /* if no joysticks configured, map connected joysticks to players */
1997 if (no_joysticks_configured)
1998 use_as_joystick_nr = TRUE;
2000 for (i = 0; i < MAX_PLAYERS; i++)
2002 byte joy_action = 0;
2004 joy_action = JoystickExt(i, use_as_joystick_nr);
2005 result |= joy_action;
2007 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2008 joy_action != joy_action_last[i])
2009 stored_player[i].action = joy_action;
2011 joy_action_last[i] = joy_action;
2017 void HandleJoystick()
2019 int joystick = HandleJoystickForAllPlayers();
2020 int keyboard = key_joystick_mapping;
2021 int joy = (joystick | keyboard);
2022 int left = joy & JOY_LEFT;
2023 int right = joy & JOY_RIGHT;
2024 int up = joy & JOY_UP;
2025 int down = joy & JOY_DOWN;
2026 int button = joy & JOY_BUTTON;
2027 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2028 int dx = (left ? -1 : right ? 1 : 0);
2029 int dy = (up ? -1 : down ? 1 : 0);
2031 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2033 /* do not handle this button event anymore */
2037 switch (game_status)
2039 case GAME_MODE_TITLE:
2040 case GAME_MODE_MAIN:
2041 case GAME_MODE_LEVELS:
2042 case GAME_MODE_LEVELNR:
2043 case GAME_MODE_SETUP:
2044 case GAME_MODE_INFO:
2046 static unsigned int joystickmove_delay = 0;
2047 static unsigned int joystickmove_delay_value = GADGET_FRAME_DELAY;
2048 static int joystick_last = 0;
2050 if (joystick && !button &&
2051 !DelayReached(&joystickmove_delay, joystickmove_delay_value))
2053 /* delay joystick actions if buttons/axes continually pressed */
2054 newbutton = dx = dy = 0;
2058 /* start with longer delay, then continue with shorter delay */
2059 if (joystick != joystick_last)
2060 joystickmove_delay_value = GADGET_FRAME_DELAY_FIRST;
2062 joystickmove_delay_value = GADGET_FRAME_DELAY;
2065 if (game_status == GAME_MODE_TITLE)
2066 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2067 else if (game_status == GAME_MODE_MAIN)
2068 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2069 else if (game_status == GAME_MODE_LEVELS)
2070 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2071 else if (game_status == GAME_MODE_LEVELNR)
2072 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2073 else if (game_status == GAME_MODE_SETUP)
2074 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2075 else if (game_status == GAME_MODE_INFO)
2076 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2078 joystick_last = joystick;
2083 case GAME_MODE_SCORES:
2084 HandleHallOfFame(0, 0, dx, dy, !newbutton);
2087 case GAME_MODE_PLAYING:
2088 if (tape.playing || keyboard)
2089 newbutton = ((joy & JOY_BUTTON) != 0);
2091 if (newbutton && AllPlayersGone)
2098 if (tape.recording && tape.pausing)
2100 if (joystick & JOY_ACTION)
2101 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2111 void HandleSpecialGameControllerButtons(Event *event)
2113 #if defined(TARGET_SDL2)
2114 switch (event->type)
2116 case SDL_CONTROLLERBUTTONDOWN:
2117 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2118 HandleKey(KSYM_space, KEY_PRESSED);
2119 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2120 HandleKey(KSYM_Escape, KEY_PRESSED);
2124 case SDL_CONTROLLERBUTTONUP:
2125 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2126 HandleKey(KSYM_space, KEY_RELEASED);
2127 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2128 HandleKey(KSYM_Escape, KEY_RELEASED);
2135 void HandleSpecialGameControllerKeys(Key key, int key_status)
2137 #if defined(TARGET_SDL2)
2138 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2139 int button = SDL_CONTROLLER_BUTTON_INVALID;
2141 /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2142 if (key == KSYM_Rewind)
2143 button = SDL_CONTROLLER_BUTTON_A;
2144 else if (key == KSYM_FastForward || key == KSYM_Menu)
2145 button = SDL_CONTROLLER_BUTTON_B;
2147 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2151 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2152 SDL_CONTROLLERBUTTONUP);
2154 event.cbutton.which = 0; /* first joystick (Amazon Fire TV remote) */
2155 event.cbutton.button = button;
2156 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2159 HandleJoystickEvent(&event);