1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_EVENTS 0
28 #define DEBUG_EVENTS_BUTTON (DEBUG_EVENTS * 0)
29 #define DEBUG_EVENTS_MOTION (DEBUG_EVENTS * 0)
30 #define DEBUG_EVENTS_WHEEL (DEBUG_EVENTS * 1)
31 #define DEBUG_EVENTS_WINDOW (DEBUG_EVENTS * 0)
32 #define DEBUG_EVENTS_FINGER (DEBUG_EVENTS * 0)
33 #define DEBUG_EVENTS_TEXT (DEBUG_EVENTS * 1)
34 #define DEBUG_EVENTS_KEY (DEBUG_EVENTS * 1)
37 static boolean cursor_inside_playfield = FALSE;
38 static int cursor_mode_last = CURSOR_DEFAULT;
39 static unsigned int special_cursor_delay = 0;
40 static unsigned int special_cursor_delay_value = 1000;
43 /* forward declarations for internal use */
44 static void HandleNoEvent(void);
45 static void HandleEventActions(void);
48 /* event filter especially needed for SDL event filtering due to
49 delay problems with lots of mouse motion events when mouse button
50 not pressed (X11 can handle this with 'PointerMotionHintMask') */
52 /* event filter addition for SDL2: as SDL2 does not have a function to enable
53 or disable keyboard auto-repeat, filter repeated keyboard events instead */
55 static int FilterEvents(const Event *event)
59 #if defined(TARGET_SDL2)
60 /* skip repeated key press events if keyboard auto-repeat is disabled */
61 if (event->type == EVENT_KEYPRESS &&
67 if (event->type == EVENT_BUTTONPRESS ||
68 event->type == EVENT_BUTTONRELEASE)
70 ((ButtonEvent *)event)->x -= video.screen_xoffset;
71 ((ButtonEvent *)event)->y -= video.screen_yoffset;
73 else if (event->type == EVENT_MOTIONNOTIFY)
75 ((MotionEvent *)event)->x -= video.screen_xoffset;
76 ((MotionEvent *)event)->y -= video.screen_yoffset;
79 /* non-motion events are directly passed to event handler functions */
80 if (event->type != EVENT_MOTIONNOTIFY)
83 motion = (MotionEvent *)event;
84 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
85 motion->y >= SY && motion->y < SY + SYSIZE);
87 /* do no reset mouse cursor before all pending events have been processed */
88 if (gfx.cursor_mode == cursor_mode_last &&
89 ((game_status == GAME_MODE_TITLE &&
90 gfx.cursor_mode == CURSOR_NONE) ||
91 (game_status == GAME_MODE_PLAYING &&
92 gfx.cursor_mode == CURSOR_PLAYFIELD)))
94 SetMouseCursor(CURSOR_DEFAULT);
96 DelayReached(&special_cursor_delay, 0);
98 cursor_mode_last = CURSOR_DEFAULT;
101 /* skip mouse motion events without pressed button outside level editor */
102 if (button_status == MB_RELEASED &&
103 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
109 /* to prevent delay problems, skip mouse motion events if the very next
110 event is also a mouse motion event (and therefore effectively only
111 handling the last of a row of mouse motion events in the event queue) */
113 static boolean SkipPressedMouseMotionEvent(const Event *event)
115 /* nothing to do if the current event is not a mouse motion event */
116 if (event->type != EVENT_MOTIONNOTIFY)
119 /* only skip motion events with pressed button outside the game */
120 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
127 PeekEvent(&next_event);
129 /* if next event is also a mouse motion event, skip the current one */
130 if (next_event.type == EVENT_MOTIONNOTIFY)
137 static boolean WaitValidEvent(Event *event)
141 if (!FilterEvents(event))
144 if (SkipPressedMouseMotionEvent(event))
150 /* this is especially needed for event modifications for the Android target:
151 if mouse coordinates should be modified in the event filter function,
152 using a properly installed SDL event filter does not work, because in
153 the event filter, mouse coordinates in the event structure are still
154 physical pixel positions, not logical (scaled) screen positions, so this
155 has to be handled at a later stage in the event processing functions
156 (when device pixel positions are already converted to screen positions) */
158 boolean NextValidEvent(Event *event)
160 while (PendingEvent())
161 if (WaitValidEvent(event))
170 unsigned int event_frame_delay = 0;
171 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
173 ResetDelayCounter(&event_frame_delay);
175 while (NextValidEvent(&event))
179 case EVENT_BUTTONPRESS:
180 case EVENT_BUTTONRELEASE:
181 HandleButtonEvent((ButtonEvent *) &event);
184 case EVENT_MOTIONNOTIFY:
185 HandleMotionEvent((MotionEvent *) &event);
188 #if defined(TARGET_SDL2)
189 case EVENT_WHEELMOTION:
190 HandleWheelEvent((WheelEvent *) &event);
193 case SDL_WINDOWEVENT:
194 HandleWindowEvent((WindowEvent *) &event);
197 case EVENT_FINGERPRESS:
198 case EVENT_FINGERRELEASE:
199 case EVENT_FINGERMOTION:
200 HandleFingerEvent((FingerEvent *) &event);
203 case EVENT_TEXTINPUT:
204 HandleTextEvent((TextEvent *) &event);
207 case SDL_APP_WILLENTERBACKGROUND:
208 case SDL_APP_DIDENTERBACKGROUND:
209 case SDL_APP_WILLENTERFOREGROUND:
210 case SDL_APP_DIDENTERFOREGROUND:
211 HandlePauseResumeEvent((PauseResumeEvent *) &event);
216 case EVENT_KEYRELEASE:
217 HandleKeyEvent((KeyEvent *) &event);
221 HandleOtherEvents(&event);
225 // do not handle events for longer than standard frame delay period
226 if (DelayReached(&event_frame_delay, event_frame_delay_value))
231 void HandleOtherEvents(Event *event)
236 HandleExposeEvent((ExposeEvent *) event);
239 case EVENT_UNMAPNOTIFY:
241 /* This causes the game to stop not only when iconified, but also
242 when on another virtual desktop, which might be not desired. */
243 SleepWhileUnmapped();
249 HandleFocusEvent((FocusChangeEvent *) event);
252 case EVENT_CLIENTMESSAGE:
253 HandleClientMessageEvent((ClientMessageEvent *) event);
256 #if defined(TARGET_SDL)
257 #if defined(TARGET_SDL2)
258 case SDL_CONTROLLERBUTTONDOWN:
259 case SDL_CONTROLLERBUTTONUP:
260 // for any game controller button event, disable overlay buttons
261 SetOverlayEnabled(FALSE);
263 HandleSpecialGameControllerButtons(event);
266 case SDL_CONTROLLERDEVICEADDED:
267 case SDL_CONTROLLERDEVICEREMOVED:
268 case SDL_CONTROLLERAXISMOTION:
270 case SDL_JOYAXISMOTION:
271 case SDL_JOYBUTTONDOWN:
272 case SDL_JOYBUTTONUP:
273 HandleJoystickEvent(event);
277 HandleWindowManagerEvent(event);
286 void HandleMouseCursor()
288 if (game_status == GAME_MODE_TITLE)
290 /* when showing title screens, hide mouse pointer (if not moved) */
292 if (gfx.cursor_mode != CURSOR_NONE &&
293 DelayReached(&special_cursor_delay, special_cursor_delay_value))
295 SetMouseCursor(CURSOR_NONE);
298 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
301 /* when playing, display a special mouse pointer inside the playfield */
303 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
304 cursor_inside_playfield &&
305 DelayReached(&special_cursor_delay, special_cursor_delay_value))
307 if (level.game_engine_type != GAME_ENGINE_TYPE_MM)
308 SetMouseCursor(CURSOR_PLAYFIELD);
311 else if (gfx.cursor_mode != CURSOR_DEFAULT)
313 SetMouseCursor(CURSOR_DEFAULT);
316 /* this is set after all pending events have been processed */
317 cursor_mode_last = gfx.cursor_mode;
329 /* execute event related actions after pending events have been processed */
330 HandleEventActions();
332 /* don't use all CPU time when idle; the main loop while playing
333 has its own synchronization and is CPU friendly, too */
335 if (game_status == GAME_MODE_PLAYING)
338 /* always copy backbuffer to visible screen for every video frame */
341 /* reset video frame delay to default (may change again while playing) */
342 SetVideoFrameDelay(MenuFrameDelay);
344 if (game_status == GAME_MODE_QUIT)
349 void ClearEventQueue()
353 while (NextValidEvent(&event))
357 case EVENT_BUTTONRELEASE:
358 button_status = MB_RELEASED;
361 case EVENT_KEYRELEASE:
365 #if defined(TARGET_SDL2)
366 case SDL_CONTROLLERBUTTONUP:
367 HandleJoystickEvent(&event);
373 HandleOtherEvents(&event);
379 void ClearPlayerMouseAction()
381 local_player->mouse_action.lx = 0;
382 local_player->mouse_action.ly = 0;
383 local_player->mouse_action.button = 0;
386 void ClearPlayerAction()
390 /* simulate key release events for still pressed keys */
391 key_joystick_mapping = 0;
392 for (i = 0; i < MAX_PLAYERS; i++)
393 stored_player[i].action = 0;
395 ClearJoystickState();
396 ClearPlayerMouseAction();
399 void SetPlayerMouseAction(int mx, int my, int button)
401 int lx = getLevelFromScreenX(mx);
402 int ly = getLevelFromScreenY(my);
404 ClearPlayerMouseAction();
406 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
409 local_player->mouse_action.lx = lx;
410 local_player->mouse_action.ly = ly;
411 local_player->mouse_action.button = button;
413 if (tape.recording && tape.pausing && tape.use_mouse)
415 /* prevent button release or motion events from un-pausing a paused game */
416 if (button && !motion_status)
417 TapeTogglePause(TAPE_TOGGLE_MANUAL);
421 void SleepWhileUnmapped()
423 boolean window_unmapped = TRUE;
425 KeyboardAutoRepeatOn();
427 while (window_unmapped)
431 if (!WaitValidEvent(&event))
436 case EVENT_BUTTONRELEASE:
437 button_status = MB_RELEASED;
440 case EVENT_KEYRELEASE:
441 key_joystick_mapping = 0;
444 #if defined(TARGET_SDL2)
445 case SDL_CONTROLLERBUTTONUP:
446 HandleJoystickEvent(&event);
447 key_joystick_mapping = 0;
451 case EVENT_MAPNOTIFY:
452 window_unmapped = FALSE;
455 case EVENT_UNMAPNOTIFY:
456 /* this is only to surely prevent the 'should not happen' case
457 * of recursively looping between 'SleepWhileUnmapped()' and
458 * 'HandleOtherEvents()' which usually calls this funtion.
463 HandleOtherEvents(&event);
468 if (game_status == GAME_MODE_PLAYING)
469 KeyboardAutoRepeatOffUnlessAutoplay();
472 void HandleExposeEvent(ExposeEvent *event)
476 void HandleButtonEvent(ButtonEvent *event)
478 #if DEBUG_EVENTS_BUTTON
479 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
481 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
485 #if defined(HAS_SCREEN_KEYBOARD)
486 if (video.shifted_up)
487 event->y += video.shifted_up_pos;
490 motion_status = FALSE;
492 if (event->type == EVENT_BUTTONPRESS)
493 button_status = event->button;
495 button_status = MB_RELEASED;
497 HandleButton(event->x, event->y, button_status, event->button);
500 void HandleMotionEvent(MotionEvent *event)
502 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
505 motion_status = TRUE;
507 #if DEBUG_EVENTS_MOTION
508 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
509 button_status, event->x, event->y);
512 HandleButton(event->x, event->y, button_status, button_status);
515 #if defined(TARGET_SDL2)
517 void HandleWheelEvent(WheelEvent *event)
521 #if DEBUG_EVENTS_WHEEL
523 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
524 event->which, event->x, event->y);
526 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
527 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
528 event->which, event->x, event->y,
529 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
530 "SDL_MOUSEWHEEL_FLIPPED"));
534 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
535 event->x > 0 ? MB_WHEEL_RIGHT :
536 event->y < 0 ? MB_WHEEL_DOWN :
537 event->y > 0 ? MB_WHEEL_UP : 0);
539 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
540 // accelerated mouse wheel available on Mac and Windows
541 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
543 // no accelerated mouse wheel available on Unix/Linux
544 wheel_steps = DEFAULT_WHEEL_STEPS;
547 motion_status = FALSE;
549 button_status = button_nr;
550 HandleButton(0, 0, button_status, -button_nr);
552 button_status = MB_RELEASED;
553 HandleButton(0, 0, button_status, -button_nr);
556 void HandleWindowEvent(WindowEvent *event)
558 #if DEBUG_EVENTS_WINDOW
559 int subtype = event->event;
562 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
563 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
564 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
565 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
566 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
567 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
568 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
569 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
570 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
571 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
572 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
573 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
574 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
575 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
578 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
579 event_name, event->data1, event->data2);
583 // (not needed, as the screen gets redrawn every 20 ms anyway)
584 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
585 event->event == SDL_WINDOWEVENT_RESIZED ||
586 event->event == SDL_WINDOWEVENT_EXPOSED)
590 if (event->event == SDL_WINDOWEVENT_RESIZED)
592 if (!video.fullscreen_enabled)
594 int new_window_width = event->data1;
595 int new_window_height = event->data2;
597 // if window size has changed after resizing, calculate new scaling factor
598 if (new_window_width != video.window_width ||
599 new_window_height != video.window_height)
601 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
602 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
604 // (extreme window scaling allowed, but cannot be saved permanently)
605 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
606 setup.window_scaling_percent =
607 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
608 MAX_WINDOW_SCALING_PERCENT);
610 video.window_width = new_window_width;
611 video.window_height = new_window_height;
613 if (game_status == GAME_MODE_SETUP)
614 RedrawSetupScreenAfterFullscreenToggle();
619 #if defined(PLATFORM_ANDROID)
622 int new_display_width = event->data1;
623 int new_display_height = event->data2;
625 // if fullscreen display size has changed, device has been rotated
626 if (new_display_width != video.display_width ||
627 new_display_height != video.display_height)
629 video.display_width = new_display_width;
630 video.display_height = new_display_height;
632 SDLSetScreenProperties();
639 #define NUM_TOUCH_FINGERS 3
644 SDL_FingerID finger_id;
647 } touch_info[NUM_TOUCH_FINGERS];
649 void HandleFingerEvent(FingerEvent *event)
651 static Key motion_key_x = KSYM_UNDEFINED;
652 static Key motion_key_y = KSYM_UNDEFINED;
653 static Key button_key = KSYM_UNDEFINED;
654 static float motion_x1, motion_y1;
655 static float button_x1, button_y1;
656 static SDL_FingerID motion_id = -1;
657 static SDL_FingerID button_id = -1;
658 int move_trigger_distance_percent = setup.touch.move_distance;
659 int drop_trigger_distance_percent = setup.touch.drop_distance;
660 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
661 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
662 float event_x = event->x;
663 float event_y = event->y;
665 #if DEBUG_EVENTS_FINGER
666 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
667 event->type == EVENT_FINGERPRESS ? "pressed" :
668 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
672 event->dx, event->dy,
676 if (game_status != GAME_MODE_PLAYING)
679 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
682 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
684 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
686 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
688 event_y = (event_y - ypos) / (1 - ypos);
690 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
691 event_y > 2.0 / 3.0 && event_y < 1 ?
692 setup.input[0].key.snap :
693 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
694 event_y > 2.0 / 3.0 && event_y < 1 ?
695 setup.input[0].key.drop :
696 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
697 event_y > 0 && event_y < 1.0 / 3.0 ?
698 setup.input[0].key.up :
699 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
700 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
701 setup.input[0].key.left :
702 event_x > 8.0 / 9.0 && event_x < 1 &&
703 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
704 setup.input[0].key.right :
705 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
706 event_y > 2.0 / 3.0 && event_y < 1 ?
707 setup.input[0].key.down :
710 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
714 // for any touch input event, enable overlay buttons (if activated)
715 SetOverlayEnabled(TRUE);
717 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
718 getKeyNameFromKey(key), key_status_name, event->fingerId);
720 // check if we already know this touch event's finger id
721 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
723 if (touch_info[i].touched &&
724 touch_info[i].finger_id == event->fingerId)
726 // Error(ERR_DEBUG, "MARK 1: %d", i);
732 if (i >= NUM_TOUCH_FINGERS)
734 if (key_status == KEY_PRESSED)
736 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
738 // unknown finger id -- get new, empty slot, if available
739 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
741 if (touch_info[i].counter < oldest_counter)
744 oldest_counter = touch_info[i].counter;
746 // Error(ERR_DEBUG, "MARK 2: %d", i);
749 if (!touch_info[i].touched)
751 // Error(ERR_DEBUG, "MARK 3: %d", i);
757 if (i >= NUM_TOUCH_FINGERS)
759 // all slots allocated -- use oldest slot
762 // Error(ERR_DEBUG, "MARK 4: %d", i);
767 // release of previously unknown key (should not happen)
769 if (key != KSYM_UNDEFINED)
771 HandleKey(key, KEY_RELEASED);
773 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
774 getKeyNameFromKey(key), "KEY_RELEASED", i);
779 if (i < NUM_TOUCH_FINGERS)
781 if (key_status == KEY_PRESSED)
783 if (touch_info[i].key != key)
785 if (touch_info[i].key != KSYM_UNDEFINED)
787 HandleKey(touch_info[i].key, KEY_RELEASED);
789 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
790 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
793 if (key != KSYM_UNDEFINED)
795 HandleKey(key, KEY_PRESSED);
797 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
798 getKeyNameFromKey(key), "KEY_PRESSED", i);
802 touch_info[i].touched = TRUE;
803 touch_info[i].finger_id = event->fingerId;
804 touch_info[i].counter = Counter();
805 touch_info[i].key = key;
809 if (touch_info[i].key != KSYM_UNDEFINED)
811 HandleKey(touch_info[i].key, KEY_RELEASED);
813 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
814 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
817 touch_info[i].touched = FALSE;
818 touch_info[i].finger_id = 0;
819 touch_info[i].counter = 0;
820 touch_info[i].key = 0;
827 // use touch direction control
829 if (event->type == EVENT_FINGERPRESS)
831 if (event_x > 1.0 / 3.0)
835 motion_id = event->fingerId;
840 motion_key_x = KSYM_UNDEFINED;
841 motion_key_y = KSYM_UNDEFINED;
843 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
849 button_id = event->fingerId;
854 button_key = setup.input[0].key.snap;
856 HandleKey(button_key, KEY_PRESSED);
858 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
861 else if (event->type == EVENT_FINGERRELEASE)
863 if (event->fingerId == motion_id)
867 if (motion_key_x != KSYM_UNDEFINED)
868 HandleKey(motion_key_x, KEY_RELEASED);
869 if (motion_key_y != KSYM_UNDEFINED)
870 HandleKey(motion_key_y, KEY_RELEASED);
872 motion_key_x = KSYM_UNDEFINED;
873 motion_key_y = KSYM_UNDEFINED;
875 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
877 else if (event->fingerId == button_id)
881 if (button_key != KSYM_UNDEFINED)
882 HandleKey(button_key, KEY_RELEASED);
884 button_key = KSYM_UNDEFINED;
886 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
889 else if (event->type == EVENT_FINGERMOTION)
891 if (event->fingerId == motion_id)
893 float distance_x = ABS(event_x - motion_x1);
894 float distance_y = ABS(event_y - motion_y1);
895 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
896 event_x > motion_x1 ? setup.input[0].key.right :
898 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
899 event_y > motion_y1 ? setup.input[0].key.down :
902 if (distance_x < move_trigger_distance / 2 ||
903 distance_x < distance_y)
904 new_motion_key_x = KSYM_UNDEFINED;
906 if (distance_y < move_trigger_distance / 2 ||
907 distance_y < distance_x)
908 new_motion_key_y = KSYM_UNDEFINED;
910 if (distance_x > move_trigger_distance ||
911 distance_y > move_trigger_distance)
913 if (new_motion_key_x != motion_key_x)
915 if (motion_key_x != KSYM_UNDEFINED)
916 HandleKey(motion_key_x, KEY_RELEASED);
917 if (new_motion_key_x != KSYM_UNDEFINED)
918 HandleKey(new_motion_key_x, KEY_PRESSED);
921 if (new_motion_key_y != motion_key_y)
923 if (motion_key_y != KSYM_UNDEFINED)
924 HandleKey(motion_key_y, KEY_RELEASED);
925 if (new_motion_key_y != KSYM_UNDEFINED)
926 HandleKey(new_motion_key_y, KEY_PRESSED);
932 motion_key_x = new_motion_key_x;
933 motion_key_y = new_motion_key_y;
935 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
938 else if (event->fingerId == button_id)
940 float distance_x = ABS(event_x - button_x1);
941 float distance_y = ABS(event_y - button_y1);
943 if (distance_x < drop_trigger_distance / 2 &&
944 distance_y > drop_trigger_distance)
946 if (button_key == setup.input[0].key.snap)
947 HandleKey(button_key, KEY_RELEASED);
952 button_key = setup.input[0].key.drop;
954 HandleKey(button_key, KEY_PRESSED);
956 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
962 static void HandleFollowFinger(int mx, int my, int button)
964 static int old_mx = 0, old_my = 0;
965 static Key motion_key_x = KSYM_UNDEFINED;
966 static Key motion_key_y = KSYM_UNDEFINED;
967 static boolean started_on_player = FALSE;
968 static boolean player_is_dropping = FALSE;
969 static int player_drop_count = 0;
970 static int last_player_x = -1;
971 static int last_player_y = -1;
973 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
976 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
978 touch_info[0].touched = TRUE;
979 touch_info[0].key = 0;
986 started_on_player = FALSE;
987 player_is_dropping = FALSE;
988 player_drop_count = 0;
992 motion_key_x = KSYM_UNDEFINED;
993 motion_key_y = KSYM_UNDEFINED;
995 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
998 else if (button == MB_RELEASED && touch_info[0].touched)
1000 touch_info[0].touched = FALSE;
1001 touch_info[0].key = 0;
1006 if (motion_key_x != KSYM_UNDEFINED)
1007 HandleKey(motion_key_x, KEY_RELEASED);
1008 if (motion_key_y != KSYM_UNDEFINED)
1009 HandleKey(motion_key_y, KEY_RELEASED);
1011 if (started_on_player)
1013 if (player_is_dropping)
1015 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1017 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1021 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1023 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1027 motion_key_x = KSYM_UNDEFINED;
1028 motion_key_y = KSYM_UNDEFINED;
1030 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1033 if (touch_info[0].touched)
1035 int src_x = local_player->jx;
1036 int src_y = local_player->jy;
1037 int dst_x = getLevelFromScreenX(old_mx);
1038 int dst_y = getLevelFromScreenY(old_my);
1039 int dx = dst_x - src_x;
1040 int dy = dst_y - src_y;
1041 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1042 dx > 0 ? setup.input[0].key.right :
1044 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1045 dy > 0 ? setup.input[0].key.down :
1048 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1049 (last_player_x != local_player->jx ||
1050 last_player_y != local_player->jy))
1052 // in case of asymmetric diagonal movement, use "preferred" direction
1054 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1056 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1057 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1059 local_player->last_move_dir = last_move_dir;
1061 // (required to prevent accidentally forcing direction for next movement)
1062 last_player_x = local_player->jx;
1063 last_player_y = local_player->jy;
1066 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1068 started_on_player = TRUE;
1069 player_drop_count = getPlayerInventorySize(0);
1070 player_is_dropping = (player_drop_count > 0);
1072 if (player_is_dropping)
1074 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1076 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1080 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1082 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1085 else if (dx != 0 || dy != 0)
1087 if (player_is_dropping &&
1088 player_drop_count == getPlayerInventorySize(0))
1090 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1092 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1093 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1095 player_is_dropping = FALSE;
1099 if (new_motion_key_x != motion_key_x)
1101 Error(ERR_DEBUG, "---------- %s %s ----------",
1102 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1103 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1105 if (motion_key_x != KSYM_UNDEFINED)
1106 HandleKey(motion_key_x, KEY_RELEASED);
1107 if (new_motion_key_x != KSYM_UNDEFINED)
1108 HandleKey(new_motion_key_x, KEY_PRESSED);
1111 if (new_motion_key_y != motion_key_y)
1113 Error(ERR_DEBUG, "---------- %s %s ----------",
1114 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1115 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1117 if (motion_key_y != KSYM_UNDEFINED)
1118 HandleKey(motion_key_y, KEY_RELEASED);
1119 if (new_motion_key_y != KSYM_UNDEFINED)
1120 HandleKey(new_motion_key_y, KEY_PRESSED);
1123 motion_key_x = new_motion_key_x;
1124 motion_key_y = new_motion_key_y;
1128 static boolean checkTextInputKeyModState()
1130 // when playing, only handle raw key events and ignore text input
1131 if (game_status == GAME_MODE_PLAYING)
1134 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1137 void HandleTextEvent(TextEvent *event)
1139 char *text = event->text;
1140 Key key = getKeyFromKeyName(text);
1142 #if DEBUG_EVENTS_TEXT
1143 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1146 text[0], (int)(text[0]),
1148 getKeyNameFromKey(key),
1152 #if !defined(HAS_SCREEN_KEYBOARD)
1153 // non-mobile devices: only handle key input with modifier keys pressed here
1154 // (every other key input is handled directly as physical key input event)
1155 if (!checkTextInputKeyModState())
1159 // process text input as "classic" (with uppercase etc.) key input event
1160 HandleKey(key, KEY_PRESSED);
1161 HandleKey(key, KEY_RELEASED);
1164 void HandlePauseResumeEvent(PauseResumeEvent *event)
1166 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1170 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1178 void HandleKeyEvent(KeyEvent *event)
1180 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1181 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1182 Key key = GetEventKey(event, with_modifiers);
1183 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1185 #if DEBUG_EVENTS_KEY
1186 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1187 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1188 event->keysym.scancode,
1193 getKeyNameFromKey(key));
1196 #if defined(PLATFORM_ANDROID)
1197 if (key == KSYM_Back)
1199 // always map the "back" button to the "escape" key on Android devices
1204 // for any key event other than "back" button, disable overlay buttons
1205 SetOverlayEnabled(FALSE);
1209 HandleKeyModState(keymod, key_status);
1211 #if defined(TARGET_SDL2)
1212 // only handle raw key input without text modifier keys pressed
1213 if (!checkTextInputKeyModState())
1214 HandleKey(key, key_status);
1216 HandleKey(key, key_status);
1220 void HandleFocusEvent(FocusChangeEvent *event)
1222 static int old_joystick_status = -1;
1224 if (event->type == EVENT_FOCUSOUT)
1226 KeyboardAutoRepeatOn();
1227 old_joystick_status = joystick.status;
1228 joystick.status = JOYSTICK_NOT_AVAILABLE;
1230 ClearPlayerAction();
1232 else if (event->type == EVENT_FOCUSIN)
1234 /* When there are two Rocks'n'Diamonds windows which overlap and
1235 the player moves the pointer from one game window to the other,
1236 a 'FocusOut' event is generated for the window the pointer is
1237 leaving and a 'FocusIn' event is generated for the window the
1238 pointer is entering. In some cases, it can happen that the
1239 'FocusIn' event is handled by the one game process before the
1240 'FocusOut' event by the other game process. In this case the
1241 X11 environment would end up with activated keyboard auto repeat,
1242 because unfortunately this is a global setting and not (which
1243 would be far better) set for each X11 window individually.
1244 The effect would be keyboard auto repeat while playing the game
1245 (game_status == GAME_MODE_PLAYING), which is not desired.
1246 To avoid this special case, we just wait 1/10 second before
1247 processing the 'FocusIn' event.
1250 if (game_status == GAME_MODE_PLAYING)
1253 KeyboardAutoRepeatOffUnlessAutoplay();
1256 if (old_joystick_status != -1)
1257 joystick.status = old_joystick_status;
1261 void HandleClientMessageEvent(ClientMessageEvent *event)
1263 if (CheckCloseWindowEvent(event))
1267 void HandleWindowManagerEvent(Event *event)
1269 #if defined(TARGET_SDL)
1270 SDLHandleWindowManagerEvent(event);
1274 void HandleButton(int mx, int my, int button, int button_nr)
1276 static int old_mx = 0, old_my = 0;
1277 boolean button_hold = FALSE;
1283 button_nr = -button_nr;
1292 #if defined(PLATFORM_ANDROID)
1293 // when playing, only handle gadgets when using "follow finger" controls
1294 boolean handle_gadgets =
1295 (game_status != GAME_MODE_PLAYING ||
1296 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1298 if (handle_gadgets &&
1299 HandleGadgets(mx, my, button))
1301 /* do not handle this button event anymore */
1302 mx = my = -32; /* force mouse event to be outside screen tiles */
1305 if (HandleGadgets(mx, my, button))
1307 /* do not handle this button event anymore */
1308 mx = my = -32; /* force mouse event to be outside screen tiles */
1312 if (HandleGlobalAnimClicks(mx, my, button))
1314 /* do not handle this button event anymore */
1315 return; /* force mouse event not to be handled at all */
1318 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1321 /* do not use scroll wheel button events for anything other than gadgets */
1322 if (IS_WHEEL_BUTTON(button_nr))
1325 switch (game_status)
1327 case GAME_MODE_TITLE:
1328 HandleTitleScreen(mx, my, 0, 0, button);
1331 case GAME_MODE_MAIN:
1332 HandleMainMenu(mx, my, 0, 0, button);
1335 case GAME_MODE_PSEUDO_TYPENAME:
1336 HandleTypeName(0, KSYM_Return);
1339 case GAME_MODE_LEVELS:
1340 HandleChooseLevelSet(mx, my, 0, 0, button);
1343 case GAME_MODE_LEVELNR:
1344 HandleChooseLevelNr(mx, my, 0, 0, button);
1347 case GAME_MODE_SCORES:
1348 HandleHallOfFame(0, 0, 0, 0, button);
1351 case GAME_MODE_EDITOR:
1352 HandleLevelEditorIdle();
1355 case GAME_MODE_INFO:
1356 HandleInfoScreen(mx, my, 0, 0, button);
1359 case GAME_MODE_SETUP:
1360 HandleSetupScreen(mx, my, 0, 0, button);
1363 case GAME_MODE_PLAYING:
1364 SetPlayerMouseAction(mx, my, button);
1366 #if defined(TARGET_SDL2)
1367 HandleFollowFinger(mx, my, button);
1371 if (button == MB_PRESSED && !motion_status && !button_hold &&
1372 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1373 DumpTileFromScreen(mx, my);
1383 static boolean is_string_suffix(char *string, char *suffix)
1385 int string_len = strlen(string);
1386 int suffix_len = strlen(suffix);
1388 if (suffix_len > string_len)
1391 return (strEqual(&string[string_len - suffix_len], suffix));
1394 #define MAX_CHEAT_INPUT_LEN 32
1396 static void HandleKeysSpecial(Key key)
1398 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1399 char letter = getCharFromKey(key);
1400 int cheat_input_len = strlen(cheat_input);
1406 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1408 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1409 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1411 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1414 cheat_input[cheat_input_len++] = letter;
1415 cheat_input[cheat_input_len] = '\0';
1417 #if DEBUG_EVENTS_KEY
1418 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1421 if (game_status == GAME_MODE_MAIN)
1423 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1424 is_string_suffix(cheat_input, ":ist"))
1426 InsertSolutionTape();
1428 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1429 is_string_suffix(cheat_input, ":rg"))
1431 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1434 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1435 is_string_suffix(cheat_input, ":rs"))
1437 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1440 else if (is_string_suffix(cheat_input, ":reload-music") ||
1441 is_string_suffix(cheat_input, ":rm"))
1443 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1446 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1447 is_string_suffix(cheat_input, ":ra"))
1449 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1450 1 << ARTWORK_TYPE_SOUNDS |
1451 1 << ARTWORK_TYPE_MUSIC);
1454 else if (is_string_suffix(cheat_input, ":dump-level") ||
1455 is_string_suffix(cheat_input, ":dl"))
1459 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1460 is_string_suffix(cheat_input, ":dt"))
1464 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1465 is_string_suffix(cheat_input, ":ft"))
1467 /* fix single-player tapes that contain player input for more than one
1468 player (due to a bug in 3.3.1.2 and earlier versions), which results
1469 in playing levels with more than one player in multi-player mode,
1470 even though the tape was originally recorded in single-player mode */
1472 /* remove player input actions for all players but the first one */
1473 for (i = 1; i < MAX_PLAYERS; i++)
1474 tape.player_participates[i] = FALSE;
1476 tape.changed = TRUE;
1478 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1479 is_string_suffix(cheat_input, ":snl"))
1481 SaveNativeLevel(&level);
1483 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1484 is_string_suffix(cheat_input, ":fps"))
1486 global.show_frames_per_second = !global.show_frames_per_second;
1489 else if (game_status == GAME_MODE_PLAYING)
1492 if (is_string_suffix(cheat_input, ".q"))
1493 DEBUG_SetMaximumDynamite();
1496 else if (game_status == GAME_MODE_EDITOR)
1498 if (is_string_suffix(cheat_input, ":dump-brush") ||
1499 is_string_suffix(cheat_input, ":DB"))
1503 else if (is_string_suffix(cheat_input, ":DDB"))
1510 void HandleKeysDebug(Key key)
1515 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1517 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1519 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1521 if (key == setup.debug.frame_delay_key[i] &&
1522 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1524 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1525 setup.debug.frame_delay[i] : setup.game_frame_delay);
1527 if (!setup.debug.frame_delay_game_only)
1528 MenuFrameDelay = GameFrameDelay;
1530 SetVideoFrameDelay(GameFrameDelay);
1532 if (GameFrameDelay > ONE_SECOND_DELAY)
1533 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1534 else if (GameFrameDelay != 0)
1535 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1536 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1537 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1539 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1546 if (game_status == GAME_MODE_PLAYING)
1550 options.debug = !options.debug;
1552 Error(ERR_DEBUG, "debug mode %s",
1553 (options.debug ? "enabled" : "disabled"));
1555 else if (key == KSYM_v)
1557 Error(ERR_DEBUG, "currently using game engine version %d",
1558 game.engine_version);
1564 void HandleKey(Key key, int key_status)
1566 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1567 static boolean ignore_repeated_key = FALSE;
1568 static struct SetupKeyboardInfo ski;
1569 static struct SetupShortcutInfo ssi;
1578 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1579 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1580 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1581 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1582 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1583 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1588 #if defined(TARGET_SDL2)
1589 /* map special keys (media keys / remote control buttons) to default keys */
1590 if (key == KSYM_PlayPause)
1592 else if (key == KSYM_Select)
1596 HandleSpecialGameControllerKeys(key, key_status);
1598 if (game_status == GAME_MODE_PLAYING)
1600 /* only needed for single-step tape recording mode */
1601 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1604 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1606 byte key_action = 0;
1608 if (setup.input[pnr].use_joystick)
1611 ski = setup.input[pnr].key;
1613 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1614 if (key == *key_info[i].key_custom)
1615 key_action |= key_info[i].action;
1617 /* use combined snap+direction keys for the first player only */
1620 ssi = setup.shortcut;
1622 for (i = 0; i < NUM_DIRECTIONS; i++)
1623 if (key == *key_info[i].key_snap)
1624 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1627 if (key_status == KEY_PRESSED)
1628 stored_player[pnr].action |= key_action;
1630 stored_player[pnr].action &= ~key_action;
1632 if (tape.single_step && tape.recording && tape.pausing)
1634 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1636 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1638 /* if snap key already pressed, keep pause mode when releasing */
1639 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1640 has_snapped[pnr] = TRUE;
1642 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1644 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1646 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1647 getRedDiskReleaseFlag_SP() == 0)
1649 /* add a single inactive frame before dropping starts */
1650 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1651 stored_player[pnr].force_dropping = TRUE;
1654 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1656 /* if snap key was pressed without direction, leave pause mode */
1657 if (!has_snapped[pnr])
1658 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1660 has_snapped[pnr] = FALSE;
1663 else if (tape.recording && tape.pausing && !tape.use_mouse)
1665 /* prevent key release events from un-pausing a paused game */
1666 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1667 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1673 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1674 if (key == key_info[i].key_default)
1675 joy |= key_info[i].action;
1680 if (key_status == KEY_PRESSED)
1681 key_joystick_mapping |= joy;
1683 key_joystick_mapping &= ~joy;
1688 if (game_status != GAME_MODE_PLAYING)
1689 key_joystick_mapping = 0;
1691 if (key_status == KEY_RELEASED)
1693 // reset flag to ignore repeated "key pressed" events after key release
1694 ignore_repeated_key = FALSE;
1699 if ((key == KSYM_F11 ||
1700 ((key == KSYM_Return ||
1701 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1702 video.fullscreen_available &&
1703 !ignore_repeated_key)
1705 setup.fullscreen = !setup.fullscreen;
1707 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1709 if (game_status == GAME_MODE_SETUP)
1710 RedrawSetupScreenAfterFullscreenToggle();
1712 // set flag to ignore repeated "key pressed" events
1713 ignore_repeated_key = TRUE;
1718 if ((key == KSYM_0 || key == KSYM_KP_0 ||
1719 key == KSYM_minus || key == KSYM_KP_Subtract ||
1720 key == KSYM_plus || key == KSYM_KP_Add ||
1721 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
1722 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1723 video.window_scaling_available &&
1724 !video.fullscreen_enabled)
1726 if (key == KSYM_0 || key == KSYM_KP_0)
1727 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1728 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1729 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1731 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1733 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1734 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1735 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1736 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1738 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1740 if (game_status == GAME_MODE_SETUP)
1741 RedrawSetupScreenAfterFullscreenToggle();
1746 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
1747 key == KSYM_Return ||
1748 key == KSYM_Escape)))
1750 /* do not handle this key event anymore */
1751 if (key != KSYM_Escape) /* always allow ESC key to be handled */
1755 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1756 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1763 if (game_status == GAME_MODE_MAIN &&
1764 (key == setup.shortcut.toggle_pause || key == KSYM_space))
1766 StartGameActions(options.network, setup.autorecord, level.random_seed);
1771 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1773 if (key == setup.shortcut.save_game)
1775 else if (key == setup.shortcut.load_game)
1777 else if (key == setup.shortcut.toggle_pause)
1778 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
1780 HandleTapeButtonKeys(key);
1781 HandleSoundButtonKeys(key);
1784 if (game_status == GAME_MODE_PLAYING && !network_playing)
1786 int centered_player_nr_next = -999;
1788 if (key == setup.shortcut.focus_player_all)
1789 centered_player_nr_next = -1;
1791 for (i = 0; i < MAX_PLAYERS; i++)
1792 if (key == setup.shortcut.focus_player[i])
1793 centered_player_nr_next = i;
1795 if (centered_player_nr_next != -999)
1797 game.centered_player_nr_next = centered_player_nr_next;
1798 game.set_centered_player = TRUE;
1802 tape.centered_player_nr_next = game.centered_player_nr_next;
1803 tape.set_centered_player = TRUE;
1808 HandleKeysSpecial(key);
1810 if (HandleGadgetsKeyInput(key))
1812 if (key != KSYM_Escape) /* always allow ESC key to be handled */
1813 key = KSYM_UNDEFINED;
1816 switch (game_status)
1818 case GAME_MODE_PSEUDO_TYPENAME:
1819 HandleTypeName(0, key);
1822 case GAME_MODE_TITLE:
1823 case GAME_MODE_MAIN:
1824 case GAME_MODE_LEVELS:
1825 case GAME_MODE_LEVELNR:
1826 case GAME_MODE_SETUP:
1827 case GAME_MODE_INFO:
1828 case GAME_MODE_SCORES:
1833 if (game_status == GAME_MODE_TITLE)
1834 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1835 else if (game_status == GAME_MODE_MAIN)
1836 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
1837 else if (game_status == GAME_MODE_LEVELS)
1838 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
1839 else if (game_status == GAME_MODE_LEVELNR)
1840 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
1841 else if (game_status == GAME_MODE_SETUP)
1842 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1843 else if (game_status == GAME_MODE_INFO)
1844 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1845 else if (game_status == GAME_MODE_SCORES)
1846 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
1850 if (game_status != GAME_MODE_MAIN)
1851 FadeSkipNextFadeIn();
1853 if (game_status == GAME_MODE_TITLE)
1854 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1855 else if (game_status == GAME_MODE_LEVELS)
1856 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
1857 else if (game_status == GAME_MODE_LEVELNR)
1858 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
1859 else if (game_status == GAME_MODE_SETUP)
1860 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1861 else if (game_status == GAME_MODE_INFO)
1862 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1863 else if (game_status == GAME_MODE_SCORES)
1864 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
1868 if (game_status == GAME_MODE_LEVELS)
1869 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1870 else if (game_status == GAME_MODE_LEVELNR)
1871 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1872 else if (game_status == GAME_MODE_SETUP)
1873 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1874 else if (game_status == GAME_MODE_INFO)
1875 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1876 else if (game_status == GAME_MODE_SCORES)
1877 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1880 case KSYM_Page_Down:
1881 if (game_status == GAME_MODE_LEVELS)
1882 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1883 else if (game_status == GAME_MODE_LEVELNR)
1884 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1885 else if (game_status == GAME_MODE_SETUP)
1886 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1887 else if (game_status == GAME_MODE_INFO)
1888 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1889 else if (game_status == GAME_MODE_SCORES)
1890 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1898 case GAME_MODE_EDITOR:
1899 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
1900 HandleLevelEditorKeyInput(key);
1903 case GAME_MODE_PLAYING:
1908 RequestQuitGame(setup.ask_on_escape);
1918 if (key == KSYM_Escape)
1920 SetGameStatus(GAME_MODE_MAIN);
1928 HandleKeysDebug(key);
1931 void HandleNoEvent()
1933 HandleMouseCursor();
1935 switch (game_status)
1937 #if defined(TARGET_SDL2)
1938 case GAME_MODE_PLAYING:
1939 HandleFollowFinger(-1, -1, -1);
1945 void HandleEventActions()
1947 // if (button_status && game_status != GAME_MODE_PLAYING)
1948 if (button_status && (game_status != GAME_MODE_PLAYING ||
1950 level.game_engine_type == GAME_ENGINE_TYPE_MM))
1952 HandleButton(0, 0, button_status, -button_status);
1959 #if defined(NETWORK_AVALIABLE)
1960 if (options.network)
1964 switch (game_status)
1966 case GAME_MODE_MAIN:
1967 DrawPreviewLevelAnimation();
1970 case GAME_MODE_EDITOR:
1971 HandleLevelEditorIdle();
1979 static int HandleJoystickForAllPlayers()
1983 boolean no_joysticks_configured = TRUE;
1984 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
1985 static byte joy_action_last[MAX_PLAYERS];
1987 for (i = 0; i < MAX_PLAYERS; i++)
1988 if (setup.input[i].use_joystick)
1989 no_joysticks_configured = FALSE;
1991 /* if no joysticks configured, map connected joysticks to players */
1992 if (no_joysticks_configured)
1993 use_as_joystick_nr = TRUE;
1995 for (i = 0; i < MAX_PLAYERS; i++)
1997 byte joy_action = 0;
1999 joy_action = JoystickExt(i, use_as_joystick_nr);
2000 result |= joy_action;
2002 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2003 joy_action != joy_action_last[i])
2004 stored_player[i].action = joy_action;
2006 joy_action_last[i] = joy_action;
2012 void HandleJoystick()
2014 int joystick = HandleJoystickForAllPlayers();
2015 int keyboard = key_joystick_mapping;
2016 int joy = (joystick | keyboard);
2017 int left = joy & JOY_LEFT;
2018 int right = joy & JOY_RIGHT;
2019 int up = joy & JOY_UP;
2020 int down = joy & JOY_DOWN;
2021 int button = joy & JOY_BUTTON;
2022 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2023 int dx = (left ? -1 : right ? 1 : 0);
2024 int dy = (up ? -1 : down ? 1 : 0);
2026 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2028 /* do not handle this button event anymore */
2032 switch (game_status)
2034 case GAME_MODE_TITLE:
2035 case GAME_MODE_MAIN:
2036 case GAME_MODE_LEVELS:
2037 case GAME_MODE_LEVELNR:
2038 case GAME_MODE_SETUP:
2039 case GAME_MODE_INFO:
2041 static unsigned int joystickmove_delay = 0;
2042 static unsigned int joystickmove_delay_value = GADGET_FRAME_DELAY;
2043 static int joystick_last = 0;
2045 if (joystick && !button &&
2046 !DelayReached(&joystickmove_delay, joystickmove_delay_value))
2048 /* delay joystick actions if buttons/axes continually pressed */
2049 newbutton = dx = dy = 0;
2053 /* start with longer delay, then continue with shorter delay */
2054 if (joystick != joystick_last)
2055 joystickmove_delay_value = GADGET_FRAME_DELAY_FIRST;
2057 joystickmove_delay_value = GADGET_FRAME_DELAY;
2060 if (game_status == GAME_MODE_TITLE)
2061 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2062 else if (game_status == GAME_MODE_MAIN)
2063 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2064 else if (game_status == GAME_MODE_LEVELS)
2065 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2066 else if (game_status == GAME_MODE_LEVELNR)
2067 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2068 else if (game_status == GAME_MODE_SETUP)
2069 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2070 else if (game_status == GAME_MODE_INFO)
2071 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2073 joystick_last = joystick;
2078 case GAME_MODE_SCORES:
2079 HandleHallOfFame(0, 0, dx, dy, !newbutton);
2082 case GAME_MODE_PLAYING:
2083 if (tape.playing || keyboard)
2084 newbutton = ((joy & JOY_BUTTON) != 0);
2086 if (newbutton && AllPlayersGone)
2093 if (tape.recording && tape.pausing)
2095 if (joystick & JOY_ACTION)
2096 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2106 void HandleSpecialGameControllerButtons(Event *event)
2108 #if defined(TARGET_SDL2)
2109 switch (event->type)
2111 case SDL_CONTROLLERBUTTONDOWN:
2112 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2113 HandleKey(KSYM_space, KEY_PRESSED);
2114 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2115 HandleKey(KSYM_Escape, KEY_PRESSED);
2119 case SDL_CONTROLLERBUTTONUP:
2120 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2121 HandleKey(KSYM_space, KEY_RELEASED);
2122 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2123 HandleKey(KSYM_Escape, KEY_RELEASED);
2130 void HandleSpecialGameControllerKeys(Key key, int key_status)
2132 #if defined(TARGET_SDL2)
2133 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2134 int button = SDL_CONTROLLER_BUTTON_INVALID;
2136 /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2137 if (key == KSYM_Rewind)
2138 button = SDL_CONTROLLER_BUTTON_A;
2139 else if (key == KSYM_FastForward || key == KSYM_Menu)
2140 button = SDL_CONTROLLER_BUTTON_B;
2142 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2146 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2147 SDL_CONTROLLERBUTTONUP);
2149 event.cbutton.which = 0; /* first joystick (Amazon Fire TV remote) */
2150 event.cbutton.button = button;
2151 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2154 HandleJoystickEvent(&event);