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 static boolean WaitValidEvent(Event *event)
135 if (!FilterEvents(event))
138 if (SkipPressedMouseMotionEvent(event))
144 /* this is especially needed for event modifications for the Android target:
145 if mouse coordinates should be modified in the event filter function,
146 using a properly installed SDL event filter does not work, because in
147 the event filter, mouse coordinates in the event structure are still
148 physical pixel positions, not logical (scaled) screen positions, so this
149 has to be handled at a later stage in the event processing functions
150 (when device pixel positions are already converted to screen positions) */
152 boolean NextValidEvent(Event *event)
154 while (PendingEvent())
155 if (WaitValidEvent(event))
164 unsigned int event_frame_delay = 0;
165 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
167 ResetDelayCounter(&event_frame_delay);
169 while (NextValidEvent(&event))
173 case EVENT_BUTTONPRESS:
174 case EVENT_BUTTONRELEASE:
175 HandleButtonEvent((ButtonEvent *) &event);
178 case EVENT_MOTIONNOTIFY:
179 HandleMotionEvent((MotionEvent *) &event);
182 #if defined(TARGET_SDL2)
183 case EVENT_WHEELMOTION:
184 HandleWheelEvent((WheelEvent *) &event);
187 case SDL_WINDOWEVENT:
188 HandleWindowEvent((WindowEvent *) &event);
191 case EVENT_FINGERPRESS:
192 case EVENT_FINGERRELEASE:
193 case EVENT_FINGERMOTION:
194 HandleFingerEvent((FingerEvent *) &event);
197 case EVENT_TEXTINPUT:
198 HandleTextEvent((TextEvent *) &event);
201 case SDL_APP_WILLENTERBACKGROUND:
202 case SDL_APP_DIDENTERBACKGROUND:
203 case SDL_APP_WILLENTERFOREGROUND:
204 case SDL_APP_DIDENTERFOREGROUND:
205 HandlePauseResumeEvent((PauseResumeEvent *) &event);
210 case EVENT_KEYRELEASE:
211 HandleKeyEvent((KeyEvent *) &event);
215 HandleOtherEvents(&event);
219 // do not handle events for longer than standard frame delay period
220 if (DelayReached(&event_frame_delay, event_frame_delay_value))
225 void HandleOtherEvents(Event *event)
230 HandleExposeEvent((ExposeEvent *) event);
233 case EVENT_UNMAPNOTIFY:
235 /* This causes the game to stop not only when iconified, but also
236 when on another virtual desktop, which might be not desired. */
237 SleepWhileUnmapped();
243 HandleFocusEvent((FocusChangeEvent *) event);
246 case EVENT_CLIENTMESSAGE:
247 HandleClientMessageEvent((ClientMessageEvent *) event);
250 #if defined(TARGET_SDL)
251 #if defined(TARGET_SDL2)
252 case SDL_CONTROLLERBUTTONDOWN:
253 case SDL_CONTROLLERBUTTONUP:
254 // for any game controller button event, disable overlay buttons
255 SetOverlayEnabled(FALSE);
257 HandleSpecialGameControllerButtons(event);
260 case SDL_CONTROLLERDEVICEADDED:
261 case SDL_CONTROLLERDEVICEREMOVED:
262 case SDL_CONTROLLERAXISMOTION:
264 case SDL_JOYAXISMOTION:
265 case SDL_JOYBUTTONDOWN:
266 case SDL_JOYBUTTONUP:
267 HandleJoystickEvent(event);
271 HandleWindowManagerEvent(event);
280 void HandleMouseCursor()
282 if (game_status == GAME_MODE_TITLE)
284 /* when showing title screens, hide mouse pointer (if not moved) */
286 if (gfx.cursor_mode != CURSOR_NONE &&
287 DelayReached(&special_cursor_delay, special_cursor_delay_value))
289 SetMouseCursor(CURSOR_NONE);
292 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
295 /* when playing, display a special mouse pointer inside the playfield */
297 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
298 cursor_inside_playfield &&
299 DelayReached(&special_cursor_delay, special_cursor_delay_value))
301 if (level.game_engine_type != GAME_ENGINE_TYPE_MM)
302 SetMouseCursor(CURSOR_PLAYFIELD);
305 else if (gfx.cursor_mode != CURSOR_DEFAULT)
307 SetMouseCursor(CURSOR_DEFAULT);
310 /* this is set after all pending events have been processed */
311 cursor_mode_last = gfx.cursor_mode;
323 /* also execute after pending events have been processed before */
326 /* don't use all CPU time when idle; the main loop while playing
327 has its own synchronization and is CPU friendly, too */
329 if (game_status == GAME_MODE_PLAYING)
332 /* always copy backbuffer to visible screen for every video frame */
335 /* reset video frame delay to default (may change again while playing) */
336 SetVideoFrameDelay(MenuFrameDelay);
338 if (game_status == GAME_MODE_QUIT)
343 void ClearEventQueue()
347 while (NextValidEvent(&event))
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 ClearPlayerMouseAction()
375 local_player->mouse_action.lx = 0;
376 local_player->mouse_action.ly = 0;
377 local_player->mouse_action.button = 0;
380 void ClearPlayerAction()
384 /* simulate key release events for still pressed keys */
385 key_joystick_mapping = 0;
386 for (i = 0; i < MAX_PLAYERS; i++)
387 stored_player[i].action = 0;
389 ClearJoystickState();
390 ClearPlayerMouseAction();
393 void SetPlayerMouseAction(int mx, int my, int button)
395 int lx = getLevelFromScreenX(mx);
396 int ly = getLevelFromScreenY(my);
398 ClearPlayerMouseAction();
400 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
403 local_player->mouse_action.lx = lx;
404 local_player->mouse_action.ly = ly;
405 local_player->mouse_action.button = button;
407 if (tape.recording && tape.pausing && tape.use_mouse)
409 /* prevent button release or motion events from un-pausing a paused game */
410 if (button && !motion_status)
411 TapeTogglePause(TAPE_TOGGLE_MANUAL);
415 void SleepWhileUnmapped()
417 boolean window_unmapped = TRUE;
419 KeyboardAutoRepeatOn();
421 while (window_unmapped)
425 if (!WaitValidEvent(&event))
430 case EVENT_BUTTONRELEASE:
431 button_status = MB_RELEASED;
434 case EVENT_KEYRELEASE:
435 key_joystick_mapping = 0;
438 #if defined(TARGET_SDL2)
439 case SDL_CONTROLLERBUTTONUP:
440 HandleJoystickEvent(&event);
441 key_joystick_mapping = 0;
445 case EVENT_MAPNOTIFY:
446 window_unmapped = FALSE;
449 case EVENT_UNMAPNOTIFY:
450 /* this is only to surely prevent the 'should not happen' case
451 * of recursively looping between 'SleepWhileUnmapped()' and
452 * 'HandleOtherEvents()' which usually calls this funtion.
457 HandleOtherEvents(&event);
462 if (game_status == GAME_MODE_PLAYING)
463 KeyboardAutoRepeatOffUnlessAutoplay();
466 void HandleExposeEvent(ExposeEvent *event)
470 void HandleButtonEvent(ButtonEvent *event)
472 #if DEBUG_EVENTS_BUTTON
473 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
475 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
479 #if defined(HAS_SCREEN_KEYBOARD)
480 if (video.shifted_up)
481 event->y += video.shifted_up_pos;
484 motion_status = FALSE;
486 if (event->type == EVENT_BUTTONPRESS)
487 button_status = event->button;
489 button_status = MB_RELEASED;
491 HandleButton(event->x, event->y, button_status, event->button);
494 void HandleMotionEvent(MotionEvent *event)
496 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
499 motion_status = TRUE;
501 #if DEBUG_EVENTS_MOTION
502 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
503 button_status, event->x, event->y);
506 HandleButton(event->x, event->y, button_status, button_status);
509 #if defined(TARGET_SDL2)
511 void HandleWheelEvent(WheelEvent *event)
515 #if DEBUG_EVENTS_WHEEL
517 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
518 event->which, event->x, event->y);
520 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
521 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
522 event->which, event->x, event->y,
523 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
524 "SDL_MOUSEWHEEL_FLIPPED"));
528 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
529 event->x > 0 ? MB_WHEEL_RIGHT :
530 event->y < 0 ? MB_WHEEL_DOWN :
531 event->y > 0 ? MB_WHEEL_UP : 0);
533 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
534 // accelerated mouse wheel available on Mac and Windows
535 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
537 // no accelerated mouse wheel available on Unix/Linux
538 wheel_steps = DEFAULT_WHEEL_STEPS;
541 motion_status = FALSE;
543 button_status = button_nr;
544 HandleButton(0, 0, button_status, -button_nr);
546 button_status = MB_RELEASED;
547 HandleButton(0, 0, button_status, -button_nr);
550 void HandleWindowEvent(WindowEvent *event)
552 #if DEBUG_EVENTS_WINDOW
553 int subtype = event->event;
556 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
557 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
558 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
559 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
560 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
561 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
562 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
563 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
564 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
565 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
566 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
567 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
568 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
569 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
572 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
573 event_name, event->data1, event->data2);
577 // (not needed, as the screen gets redrawn every 20 ms anyway)
578 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
579 event->event == SDL_WINDOWEVENT_RESIZED ||
580 event->event == SDL_WINDOWEVENT_EXPOSED)
584 if (event->event == SDL_WINDOWEVENT_RESIZED)
586 if (!video.fullscreen_enabled)
588 int new_window_width = event->data1;
589 int new_window_height = event->data2;
591 // if window size has changed after resizing, calculate new scaling factor
592 if (new_window_width != video.window_width ||
593 new_window_height != video.window_height)
595 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
596 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
598 // (extreme window scaling allowed, but cannot be saved permanently)
599 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
600 setup.window_scaling_percent =
601 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
602 MAX_WINDOW_SCALING_PERCENT);
604 video.window_width = new_window_width;
605 video.window_height = new_window_height;
607 if (game_status == GAME_MODE_SETUP)
608 RedrawSetupScreenAfterFullscreenToggle();
613 #if defined(PLATFORM_ANDROID)
616 int new_display_width = event->data1;
617 int new_display_height = event->data2;
619 // if fullscreen display size has changed, device has been rotated
620 if (new_display_width != video.display_width ||
621 new_display_height != video.display_height)
623 video.display_width = new_display_width;
624 video.display_height = new_display_height;
626 SDLSetScreenProperties();
633 #define NUM_TOUCH_FINGERS 3
638 SDL_FingerID finger_id;
641 } touch_info[NUM_TOUCH_FINGERS];
643 void HandleFingerEvent(FingerEvent *event)
645 static Key motion_key_x = KSYM_UNDEFINED;
646 static Key motion_key_y = KSYM_UNDEFINED;
647 static Key button_key = KSYM_UNDEFINED;
648 static float motion_x1, motion_y1;
649 static float button_x1, button_y1;
650 static SDL_FingerID motion_id = -1;
651 static SDL_FingerID button_id = -1;
652 int move_trigger_distance_percent = setup.touch.move_distance;
653 int drop_trigger_distance_percent = setup.touch.drop_distance;
654 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
655 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
656 float event_x = event->x;
657 float event_y = event->y;
659 #if DEBUG_EVENTS_FINGER
660 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
661 event->type == EVENT_FINGERPRESS ? "pressed" :
662 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
666 event->dx, event->dy,
670 if (game_status != GAME_MODE_PLAYING)
673 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
676 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
678 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
680 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
682 event_y = (event_y - ypos) / (1 - ypos);
684 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
685 event_y > 2.0 / 3.0 && event_y < 1 ?
686 setup.input[0].key.snap :
687 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
688 event_y > 2.0 / 3.0 && event_y < 1 ?
689 setup.input[0].key.drop :
690 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
691 event_y > 0 && event_y < 1.0 / 3.0 ?
692 setup.input[0].key.up :
693 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
694 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
695 setup.input[0].key.left :
696 event_x > 8.0 / 9.0 && event_x < 1 &&
697 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
698 setup.input[0].key.right :
699 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
700 event_y > 2.0 / 3.0 && event_y < 1 ?
701 setup.input[0].key.down :
704 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
708 // for any touch input event, enable overlay buttons (if activated)
709 SetOverlayEnabled(TRUE);
711 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
712 getKeyNameFromKey(key), key_status_name, event->fingerId);
714 // check if we already know this touch event's finger id
715 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
717 if (touch_info[i].touched &&
718 touch_info[i].finger_id == event->fingerId)
720 // Error(ERR_DEBUG, "MARK 1: %d", i);
726 if (i >= NUM_TOUCH_FINGERS)
728 if (key_status == KEY_PRESSED)
730 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
732 // unknown finger id -- get new, empty slot, if available
733 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
735 if (touch_info[i].counter < oldest_counter)
738 oldest_counter = touch_info[i].counter;
740 // Error(ERR_DEBUG, "MARK 2: %d", i);
743 if (!touch_info[i].touched)
745 // Error(ERR_DEBUG, "MARK 3: %d", i);
751 if (i >= NUM_TOUCH_FINGERS)
753 // all slots allocated -- use oldest slot
756 // Error(ERR_DEBUG, "MARK 4: %d", i);
761 // release of previously unknown key (should not happen)
763 if (key != KSYM_UNDEFINED)
765 HandleKey(key, KEY_RELEASED);
767 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
768 getKeyNameFromKey(key), "KEY_RELEASED", i);
773 if (i < NUM_TOUCH_FINGERS)
775 if (key_status == KEY_PRESSED)
777 if (touch_info[i].key != key)
779 if (touch_info[i].key != KSYM_UNDEFINED)
781 HandleKey(touch_info[i].key, KEY_RELEASED);
783 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
784 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
787 if (key != KSYM_UNDEFINED)
789 HandleKey(key, KEY_PRESSED);
791 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
792 getKeyNameFromKey(key), "KEY_PRESSED", i);
796 touch_info[i].touched = TRUE;
797 touch_info[i].finger_id = event->fingerId;
798 touch_info[i].counter = Counter();
799 touch_info[i].key = key;
803 if (touch_info[i].key != KSYM_UNDEFINED)
805 HandleKey(touch_info[i].key, KEY_RELEASED);
807 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
808 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
811 touch_info[i].touched = FALSE;
812 touch_info[i].finger_id = 0;
813 touch_info[i].counter = 0;
814 touch_info[i].key = 0;
821 // use touch direction control
823 if (event->type == EVENT_FINGERPRESS)
825 if (event_x > 1.0 / 3.0)
829 motion_id = event->fingerId;
834 motion_key_x = KSYM_UNDEFINED;
835 motion_key_y = KSYM_UNDEFINED;
837 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
843 button_id = event->fingerId;
848 button_key = setup.input[0].key.snap;
850 HandleKey(button_key, KEY_PRESSED);
852 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
855 else if (event->type == EVENT_FINGERRELEASE)
857 if (event->fingerId == motion_id)
861 if (motion_key_x != KSYM_UNDEFINED)
862 HandleKey(motion_key_x, KEY_RELEASED);
863 if (motion_key_y != KSYM_UNDEFINED)
864 HandleKey(motion_key_y, KEY_RELEASED);
866 motion_key_x = KSYM_UNDEFINED;
867 motion_key_y = KSYM_UNDEFINED;
869 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
871 else if (event->fingerId == button_id)
875 if (button_key != KSYM_UNDEFINED)
876 HandleKey(button_key, KEY_RELEASED);
878 button_key = KSYM_UNDEFINED;
880 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
883 else if (event->type == EVENT_FINGERMOTION)
885 if (event->fingerId == motion_id)
887 float distance_x = ABS(event_x - motion_x1);
888 float distance_y = ABS(event_y - motion_y1);
889 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
890 event_x > motion_x1 ? setup.input[0].key.right :
892 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
893 event_y > motion_y1 ? setup.input[0].key.down :
896 if (distance_x < move_trigger_distance / 2 ||
897 distance_x < distance_y)
898 new_motion_key_x = KSYM_UNDEFINED;
900 if (distance_y < move_trigger_distance / 2 ||
901 distance_y < distance_x)
902 new_motion_key_y = KSYM_UNDEFINED;
904 if (distance_x > move_trigger_distance ||
905 distance_y > move_trigger_distance)
907 if (new_motion_key_x != motion_key_x)
909 if (motion_key_x != KSYM_UNDEFINED)
910 HandleKey(motion_key_x, KEY_RELEASED);
911 if (new_motion_key_x != KSYM_UNDEFINED)
912 HandleKey(new_motion_key_x, KEY_PRESSED);
915 if (new_motion_key_y != motion_key_y)
917 if (motion_key_y != KSYM_UNDEFINED)
918 HandleKey(motion_key_y, KEY_RELEASED);
919 if (new_motion_key_y != KSYM_UNDEFINED)
920 HandleKey(new_motion_key_y, KEY_PRESSED);
926 motion_key_x = new_motion_key_x;
927 motion_key_y = new_motion_key_y;
929 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
932 else if (event->fingerId == button_id)
934 float distance_x = ABS(event_x - button_x1);
935 float distance_y = ABS(event_y - button_y1);
937 if (distance_x < drop_trigger_distance / 2 &&
938 distance_y > drop_trigger_distance)
940 if (button_key == setup.input[0].key.snap)
941 HandleKey(button_key, KEY_RELEASED);
946 button_key = setup.input[0].key.drop;
948 HandleKey(button_key, KEY_PRESSED);
950 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
956 static void HandleFollowFinger(int mx, int my, int button)
958 static int old_mx = 0, old_my = 0;
959 static Key motion_key_x = KSYM_UNDEFINED;
960 static Key motion_key_y = KSYM_UNDEFINED;
961 static boolean started_on_player = FALSE;
962 static boolean player_is_dropping = FALSE;
963 static int player_drop_count = 0;
964 static int last_player_x = -1;
965 static int last_player_y = -1;
967 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
970 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
972 touch_info[0].touched = TRUE;
973 touch_info[0].key = 0;
980 started_on_player = FALSE;
981 player_is_dropping = FALSE;
982 player_drop_count = 0;
986 motion_key_x = KSYM_UNDEFINED;
987 motion_key_y = KSYM_UNDEFINED;
989 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
992 else if (button == MB_RELEASED && touch_info[0].touched)
994 touch_info[0].touched = FALSE;
995 touch_info[0].key = 0;
1000 if (motion_key_x != KSYM_UNDEFINED)
1001 HandleKey(motion_key_x, KEY_RELEASED);
1002 if (motion_key_y != KSYM_UNDEFINED)
1003 HandleKey(motion_key_y, KEY_RELEASED);
1005 if (started_on_player)
1007 if (player_is_dropping)
1009 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1011 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1015 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1017 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1021 motion_key_x = KSYM_UNDEFINED;
1022 motion_key_y = KSYM_UNDEFINED;
1024 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1027 if (touch_info[0].touched)
1029 int src_x = local_player->jx;
1030 int src_y = local_player->jy;
1031 int dst_x = getLevelFromScreenX(old_mx);
1032 int dst_y = getLevelFromScreenY(old_my);
1033 int dx = dst_x - src_x;
1034 int dy = dst_y - src_y;
1035 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1036 dx > 0 ? setup.input[0].key.right :
1038 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1039 dy > 0 ? setup.input[0].key.down :
1042 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1043 (last_player_x != local_player->jx ||
1044 last_player_y != local_player->jy))
1046 // in case of asymmetric diagonal movement, use "preferred" direction
1048 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1050 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1051 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1053 local_player->last_move_dir = last_move_dir;
1055 // (required to prevent accidentally forcing direction for next movement)
1056 last_player_x = local_player->jx;
1057 last_player_y = local_player->jy;
1060 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1062 started_on_player = TRUE;
1063 player_drop_count = getPlayerInventorySize(0);
1064 player_is_dropping = (player_drop_count > 0);
1066 if (player_is_dropping)
1068 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1070 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1074 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1076 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1079 else if (dx != 0 || dy != 0)
1081 if (player_is_dropping &&
1082 player_drop_count == getPlayerInventorySize(0))
1084 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1086 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1087 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1089 player_is_dropping = FALSE;
1093 if (new_motion_key_x != motion_key_x)
1095 Error(ERR_DEBUG, "---------- %s %s ----------",
1096 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1097 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1099 if (motion_key_x != KSYM_UNDEFINED)
1100 HandleKey(motion_key_x, KEY_RELEASED);
1101 if (new_motion_key_x != KSYM_UNDEFINED)
1102 HandleKey(new_motion_key_x, KEY_PRESSED);
1105 if (new_motion_key_y != motion_key_y)
1107 Error(ERR_DEBUG, "---------- %s %s ----------",
1108 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1109 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1111 if (motion_key_y != KSYM_UNDEFINED)
1112 HandleKey(motion_key_y, KEY_RELEASED);
1113 if (new_motion_key_y != KSYM_UNDEFINED)
1114 HandleKey(new_motion_key_y, KEY_PRESSED);
1117 motion_key_x = new_motion_key_x;
1118 motion_key_y = new_motion_key_y;
1122 static boolean checkTextInputKeyModState()
1124 // when playing, only handle raw key events and ignore text input
1125 if (game_status == GAME_MODE_PLAYING)
1128 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1131 void HandleTextEvent(TextEvent *event)
1133 char *text = event->text;
1134 Key key = getKeyFromKeyName(text);
1136 #if DEBUG_EVENTS_TEXT
1137 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1140 text[0], (int)(text[0]),
1142 getKeyNameFromKey(key),
1146 #if !defined(HAS_SCREEN_KEYBOARD)
1147 // non-mobile devices: only handle key input with modifier keys pressed here
1148 // (every other key input is handled directly as physical key input event)
1149 if (!checkTextInputKeyModState())
1153 // process text input as "classic" (with uppercase etc.) key input event
1154 HandleKey(key, KEY_PRESSED);
1155 HandleKey(key, KEY_RELEASED);
1158 void HandlePauseResumeEvent(PauseResumeEvent *event)
1160 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1164 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1172 void HandleKeyEvent(KeyEvent *event)
1174 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1175 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1176 Key key = GetEventKey(event, with_modifiers);
1177 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1179 #if DEBUG_EVENTS_KEY
1180 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1181 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1182 event->keysym.scancode,
1187 getKeyNameFromKey(key));
1190 #if defined(PLATFORM_ANDROID)
1191 if (key == KSYM_Back)
1193 // always map the "back" button to the "escape" key on Android devices
1198 // for any key event other than "back" button, disable overlay buttons
1199 SetOverlayEnabled(FALSE);
1203 HandleKeyModState(keymod, key_status);
1205 #if defined(TARGET_SDL2)
1206 // only handle raw key input without text modifier keys pressed
1207 if (!checkTextInputKeyModState())
1208 HandleKey(key, key_status);
1210 HandleKey(key, key_status);
1214 void HandleFocusEvent(FocusChangeEvent *event)
1216 static int old_joystick_status = -1;
1218 if (event->type == EVENT_FOCUSOUT)
1220 KeyboardAutoRepeatOn();
1221 old_joystick_status = joystick.status;
1222 joystick.status = JOYSTICK_NOT_AVAILABLE;
1224 ClearPlayerAction();
1226 else if (event->type == EVENT_FOCUSIN)
1228 /* When there are two Rocks'n'Diamonds windows which overlap and
1229 the player moves the pointer from one game window to the other,
1230 a 'FocusOut' event is generated for the window the pointer is
1231 leaving and a 'FocusIn' event is generated for the window the
1232 pointer is entering. In some cases, it can happen that the
1233 'FocusIn' event is handled by the one game process before the
1234 'FocusOut' event by the other game process. In this case the
1235 X11 environment would end up with activated keyboard auto repeat,
1236 because unfortunately this is a global setting and not (which
1237 would be far better) set for each X11 window individually.
1238 The effect would be keyboard auto repeat while playing the game
1239 (game_status == GAME_MODE_PLAYING), which is not desired.
1240 To avoid this special case, we just wait 1/10 second before
1241 processing the 'FocusIn' event.
1244 if (game_status == GAME_MODE_PLAYING)
1247 KeyboardAutoRepeatOffUnlessAutoplay();
1250 if (old_joystick_status != -1)
1251 joystick.status = old_joystick_status;
1255 void HandleClientMessageEvent(ClientMessageEvent *event)
1257 if (CheckCloseWindowEvent(event))
1261 void HandleWindowManagerEvent(Event *event)
1263 #if defined(TARGET_SDL)
1264 SDLHandleWindowManagerEvent(event);
1268 void HandleButton(int mx, int my, int button, int button_nr)
1270 static int old_mx = 0, old_my = 0;
1271 boolean button_hold = FALSE;
1277 button_nr = -button_nr;
1286 #if defined(PLATFORM_ANDROID)
1287 // when playing, only handle gadgets when using "follow finger" controls
1288 boolean handle_gadgets =
1289 (game_status != GAME_MODE_PLAYING ||
1290 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1292 if (handle_gadgets &&
1293 HandleGadgets(mx, my, button))
1295 /* do not handle this button event anymore */
1296 mx = my = -32; /* force mouse event to be outside screen tiles */
1299 if (HandleGadgets(mx, my, button))
1301 /* do not handle this button event anymore */
1302 mx = my = -32; /* force mouse event to be outside screen tiles */
1306 if (HandleGlobalAnimClicks(mx, my, button))
1308 /* do not handle this button event anymore */
1309 return; /* force mouse event not to be handled at all */
1312 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1315 /* do not use scroll wheel button events for anything other than gadgets */
1316 if (IS_WHEEL_BUTTON(button_nr))
1319 switch (game_status)
1321 case GAME_MODE_TITLE:
1322 HandleTitleScreen(mx, my, 0, 0, button);
1325 case GAME_MODE_MAIN:
1326 HandleMainMenu(mx, my, 0, 0, button);
1329 case GAME_MODE_PSEUDO_TYPENAME:
1330 HandleTypeName(0, KSYM_Return);
1333 case GAME_MODE_LEVELS:
1334 HandleChooseLevelSet(mx, my, 0, 0, button);
1337 case GAME_MODE_LEVELNR:
1338 HandleChooseLevelNr(mx, my, 0, 0, button);
1341 case GAME_MODE_SCORES:
1342 HandleHallOfFame(0, 0, 0, 0, button);
1345 case GAME_MODE_EDITOR:
1346 HandleLevelEditorIdle();
1349 case GAME_MODE_INFO:
1350 HandleInfoScreen(mx, my, 0, 0, button);
1353 case GAME_MODE_SETUP:
1354 HandleSetupScreen(mx, my, 0, 0, button);
1357 case GAME_MODE_PLAYING:
1358 SetPlayerMouseAction(mx, my, button);
1360 #if defined(TARGET_SDL2)
1361 HandleFollowFinger(mx, my, button);
1365 if (button == MB_PRESSED && !motion_status && !button_hold &&
1366 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1367 DumpTileFromScreen(mx, my);
1377 static boolean is_string_suffix(char *string, char *suffix)
1379 int string_len = strlen(string);
1380 int suffix_len = strlen(suffix);
1382 if (suffix_len > string_len)
1385 return (strEqual(&string[string_len - suffix_len], suffix));
1388 #define MAX_CHEAT_INPUT_LEN 32
1390 static void HandleKeysSpecial(Key key)
1392 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1393 char letter = getCharFromKey(key);
1394 int cheat_input_len = strlen(cheat_input);
1400 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1402 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1403 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1405 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1408 cheat_input[cheat_input_len++] = letter;
1409 cheat_input[cheat_input_len] = '\0';
1411 #if DEBUG_EVENTS_KEY
1412 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1415 if (game_status == GAME_MODE_MAIN)
1417 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1418 is_string_suffix(cheat_input, ":ist"))
1420 InsertSolutionTape();
1422 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1423 is_string_suffix(cheat_input, ":rg"))
1425 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1428 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1429 is_string_suffix(cheat_input, ":rs"))
1431 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1434 else if (is_string_suffix(cheat_input, ":reload-music") ||
1435 is_string_suffix(cheat_input, ":rm"))
1437 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1440 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1441 is_string_suffix(cheat_input, ":ra"))
1443 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1444 1 << ARTWORK_TYPE_SOUNDS |
1445 1 << ARTWORK_TYPE_MUSIC);
1448 else if (is_string_suffix(cheat_input, ":dump-level") ||
1449 is_string_suffix(cheat_input, ":dl"))
1453 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1454 is_string_suffix(cheat_input, ":dt"))
1458 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1459 is_string_suffix(cheat_input, ":ft"))
1461 /* fix single-player tapes that contain player input for more than one
1462 player (due to a bug in 3.3.1.2 and earlier versions), which results
1463 in playing levels with more than one player in multi-player mode,
1464 even though the tape was originally recorded in single-player mode */
1466 /* remove player input actions for all players but the first one */
1467 for (i = 1; i < MAX_PLAYERS; i++)
1468 tape.player_participates[i] = FALSE;
1470 tape.changed = TRUE;
1472 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1473 is_string_suffix(cheat_input, ":snl"))
1475 SaveNativeLevel(&level);
1477 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1478 is_string_suffix(cheat_input, ":fps"))
1480 global.show_frames_per_second = !global.show_frames_per_second;
1483 else if (game_status == GAME_MODE_PLAYING)
1486 if (is_string_suffix(cheat_input, ".q"))
1487 DEBUG_SetMaximumDynamite();
1490 else if (game_status == GAME_MODE_EDITOR)
1492 if (is_string_suffix(cheat_input, ":dump-brush") ||
1493 is_string_suffix(cheat_input, ":DB"))
1497 else if (is_string_suffix(cheat_input, ":DDB"))
1504 void HandleKeysDebug(Key key)
1509 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1511 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1513 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1515 if (key == setup.debug.frame_delay_key[i] &&
1516 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1518 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1519 setup.debug.frame_delay[i] : setup.game_frame_delay);
1521 if (!setup.debug.frame_delay_game_only)
1522 MenuFrameDelay = GameFrameDelay;
1524 SetVideoFrameDelay(GameFrameDelay);
1526 if (GameFrameDelay > ONE_SECOND_DELAY)
1527 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1528 else if (GameFrameDelay != 0)
1529 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1530 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1531 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1533 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1540 if (game_status == GAME_MODE_PLAYING)
1544 options.debug = !options.debug;
1546 Error(ERR_DEBUG, "debug mode %s",
1547 (options.debug ? "enabled" : "disabled"));
1549 else if (key == KSYM_v)
1551 Error(ERR_DEBUG, "currently using game engine version %d",
1552 game.engine_version);
1558 void HandleKey(Key key, int key_status)
1560 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1561 static boolean ignore_repeated_key = FALSE;
1562 static struct SetupKeyboardInfo ski;
1563 static struct SetupShortcutInfo ssi;
1572 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1573 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1574 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1575 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1576 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1577 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1582 #if defined(TARGET_SDL2)
1583 /* map special keys (media keys / remote control buttons) to default keys */
1584 if (key == KSYM_PlayPause)
1586 else if (key == KSYM_Select)
1590 HandleSpecialGameControllerKeys(key, key_status);
1592 if (game_status == GAME_MODE_PLAYING)
1594 /* only needed for single-step tape recording mode */
1595 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1598 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1600 byte key_action = 0;
1602 if (setup.input[pnr].use_joystick)
1605 ski = setup.input[pnr].key;
1607 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1608 if (key == *key_info[i].key_custom)
1609 key_action |= key_info[i].action;
1611 /* use combined snap+direction keys for the first player only */
1614 ssi = setup.shortcut;
1616 for (i = 0; i < NUM_DIRECTIONS; i++)
1617 if (key == *key_info[i].key_snap)
1618 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1621 if (key_status == KEY_PRESSED)
1622 stored_player[pnr].action |= key_action;
1624 stored_player[pnr].action &= ~key_action;
1626 if (tape.single_step && tape.recording && tape.pausing)
1628 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1630 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1632 /* if snap key already pressed, keep pause mode when releasing */
1633 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1634 has_snapped[pnr] = TRUE;
1636 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1638 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1640 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1641 getRedDiskReleaseFlag_SP() == 0)
1643 /* add a single inactive frame before dropping starts */
1644 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1645 stored_player[pnr].force_dropping = TRUE;
1648 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1650 /* if snap key was pressed without direction, leave pause mode */
1651 if (!has_snapped[pnr])
1652 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1654 has_snapped[pnr] = FALSE;
1657 else if (tape.recording && tape.pausing && !tape.use_mouse)
1659 /* prevent key release events from un-pausing a paused game */
1660 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1661 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1667 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1668 if (key == key_info[i].key_default)
1669 joy |= key_info[i].action;
1674 if (key_status == KEY_PRESSED)
1675 key_joystick_mapping |= joy;
1677 key_joystick_mapping &= ~joy;
1682 if (game_status != GAME_MODE_PLAYING)
1683 key_joystick_mapping = 0;
1685 if (key_status == KEY_RELEASED)
1687 // reset flag to ignore repeated "key pressed" events after key release
1688 ignore_repeated_key = FALSE;
1693 if ((key == KSYM_F11 ||
1694 ((key == KSYM_Return ||
1695 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1696 video.fullscreen_available &&
1697 !ignore_repeated_key)
1699 setup.fullscreen = !setup.fullscreen;
1701 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1703 if (game_status == GAME_MODE_SETUP)
1704 RedrawSetupScreenAfterFullscreenToggle();
1706 // set flag to ignore repeated "key pressed" events
1707 ignore_repeated_key = TRUE;
1712 if ((key == KSYM_0 || key == KSYM_KP_0 ||
1713 key == KSYM_minus || key == KSYM_KP_Subtract ||
1714 key == KSYM_plus || key == KSYM_KP_Add ||
1715 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
1716 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1717 video.window_scaling_available &&
1718 !video.fullscreen_enabled)
1720 if (key == KSYM_0 || key == KSYM_KP_0)
1721 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1722 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1723 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1725 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1727 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1728 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1729 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1730 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1732 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1734 if (game_status == GAME_MODE_SETUP)
1735 RedrawSetupScreenAfterFullscreenToggle();
1740 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
1741 key == KSYM_Return ||
1742 key == KSYM_Escape)))
1744 /* do not handle this key event anymore */
1745 if (key != KSYM_Escape) /* always allow ESC key to be handled */
1749 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1750 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1757 if (game_status == GAME_MODE_MAIN &&
1758 (key == setup.shortcut.toggle_pause || key == KSYM_space))
1760 StartGameActions(options.network, setup.autorecord, level.random_seed);
1765 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1767 if (key == setup.shortcut.save_game)
1769 else if (key == setup.shortcut.load_game)
1771 else if (key == setup.shortcut.toggle_pause)
1772 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
1774 HandleTapeButtonKeys(key);
1775 HandleSoundButtonKeys(key);
1778 if (game_status == GAME_MODE_PLAYING && !network_playing)
1780 int centered_player_nr_next = -999;
1782 if (key == setup.shortcut.focus_player_all)
1783 centered_player_nr_next = -1;
1785 for (i = 0; i < MAX_PLAYERS; i++)
1786 if (key == setup.shortcut.focus_player[i])
1787 centered_player_nr_next = i;
1789 if (centered_player_nr_next != -999)
1791 game.centered_player_nr_next = centered_player_nr_next;
1792 game.set_centered_player = TRUE;
1796 tape.centered_player_nr_next = game.centered_player_nr_next;
1797 tape.set_centered_player = TRUE;
1802 HandleKeysSpecial(key);
1804 if (HandleGadgetsKeyInput(key))
1806 if (key != KSYM_Escape) /* always allow ESC key to be handled */
1807 key = KSYM_UNDEFINED;
1810 switch (game_status)
1812 case GAME_MODE_PSEUDO_TYPENAME:
1813 HandleTypeName(0, key);
1816 case GAME_MODE_TITLE:
1817 case GAME_MODE_MAIN:
1818 case GAME_MODE_LEVELS:
1819 case GAME_MODE_LEVELNR:
1820 case GAME_MODE_SETUP:
1821 case GAME_MODE_INFO:
1822 case GAME_MODE_SCORES:
1827 if (game_status == GAME_MODE_TITLE)
1828 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1829 else if (game_status == GAME_MODE_MAIN)
1830 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
1831 else if (game_status == GAME_MODE_LEVELS)
1832 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
1833 else if (game_status == GAME_MODE_LEVELNR)
1834 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
1835 else if (game_status == GAME_MODE_SETUP)
1836 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1837 else if (game_status == GAME_MODE_INFO)
1838 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1839 else if (game_status == GAME_MODE_SCORES)
1840 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
1844 if (game_status != GAME_MODE_MAIN)
1845 FadeSkipNextFadeIn();
1847 if (game_status == GAME_MODE_TITLE)
1848 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1849 else if (game_status == GAME_MODE_LEVELS)
1850 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
1851 else if (game_status == GAME_MODE_LEVELNR)
1852 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
1853 else if (game_status == GAME_MODE_SETUP)
1854 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1855 else if (game_status == GAME_MODE_INFO)
1856 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1857 else if (game_status == GAME_MODE_SCORES)
1858 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
1862 if (game_status == GAME_MODE_LEVELS)
1863 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1864 else if (game_status == GAME_MODE_LEVELNR)
1865 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1866 else if (game_status == GAME_MODE_SETUP)
1867 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1868 else if (game_status == GAME_MODE_INFO)
1869 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1870 else if (game_status == GAME_MODE_SCORES)
1871 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1874 case KSYM_Page_Down:
1875 if (game_status == GAME_MODE_LEVELS)
1876 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1877 else if (game_status == GAME_MODE_LEVELNR)
1878 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1879 else if (game_status == GAME_MODE_SETUP)
1880 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1881 else if (game_status == GAME_MODE_INFO)
1882 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1883 else if (game_status == GAME_MODE_SCORES)
1884 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1892 case GAME_MODE_EDITOR:
1893 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
1894 HandleLevelEditorKeyInput(key);
1897 case GAME_MODE_PLAYING:
1902 RequestQuitGame(setup.ask_on_escape);
1912 if (key == KSYM_Escape)
1914 SetGameStatus(GAME_MODE_MAIN);
1922 HandleKeysDebug(key);
1925 void HandleNoEvent()
1927 // if (button_status && game_status != GAME_MODE_PLAYING)
1928 if (button_status && (game_status != GAME_MODE_PLAYING ||
1930 level.game_engine_type == GAME_ENGINE_TYPE_MM))
1932 HandleButton(0, 0, button_status, -button_status);
1939 #if defined(NETWORK_AVALIABLE)
1940 if (options.network)
1944 switch (game_status)
1946 case GAME_MODE_MAIN:
1947 DrawPreviewLevelAnimation();
1950 case GAME_MODE_EDITOR:
1951 HandleLevelEditorIdle();
1954 #if defined(TARGET_SDL2)
1955 case GAME_MODE_PLAYING:
1956 HandleFollowFinger(-1, -1, -1);
1965 static int HandleJoystickForAllPlayers()
1969 boolean no_joysticks_configured = TRUE;
1970 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
1971 static byte joy_action_last[MAX_PLAYERS];
1973 for (i = 0; i < MAX_PLAYERS; i++)
1974 if (setup.input[i].use_joystick)
1975 no_joysticks_configured = FALSE;
1977 /* if no joysticks configured, map connected joysticks to players */
1978 if (no_joysticks_configured)
1979 use_as_joystick_nr = TRUE;
1981 for (i = 0; i < MAX_PLAYERS; i++)
1983 byte joy_action = 0;
1985 joy_action = JoystickExt(i, use_as_joystick_nr);
1986 result |= joy_action;
1988 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
1989 joy_action != joy_action_last[i])
1990 stored_player[i].action = joy_action;
1992 joy_action_last[i] = joy_action;
1998 void HandleJoystick()
2000 int joystick = HandleJoystickForAllPlayers();
2001 int keyboard = key_joystick_mapping;
2002 int joy = (joystick | keyboard);
2003 int left = joy & JOY_LEFT;
2004 int right = joy & JOY_RIGHT;
2005 int up = joy & JOY_UP;
2006 int down = joy & JOY_DOWN;
2007 int button = joy & JOY_BUTTON;
2008 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2009 int dx = (left ? -1 : right ? 1 : 0);
2010 int dy = (up ? -1 : down ? 1 : 0);
2012 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2014 /* do not handle this button event anymore */
2018 switch (game_status)
2020 case GAME_MODE_TITLE:
2021 case GAME_MODE_MAIN:
2022 case GAME_MODE_LEVELS:
2023 case GAME_MODE_LEVELNR:
2024 case GAME_MODE_SETUP:
2025 case GAME_MODE_INFO:
2027 static unsigned int joystickmove_delay = 0;
2028 static unsigned int joystickmove_delay_value = GADGET_FRAME_DELAY;
2029 static int joystick_last = 0;
2031 if (joystick && !button &&
2032 !DelayReached(&joystickmove_delay, joystickmove_delay_value))
2034 /* delay joystick actions if buttons/axes continually pressed */
2035 newbutton = dx = dy = 0;
2039 /* start with longer delay, then continue with shorter delay */
2040 if (joystick != joystick_last)
2041 joystickmove_delay_value = GADGET_FRAME_DELAY_FIRST;
2043 joystickmove_delay_value = GADGET_FRAME_DELAY;
2046 if (game_status == GAME_MODE_TITLE)
2047 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2048 else if (game_status == GAME_MODE_MAIN)
2049 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2050 else if (game_status == GAME_MODE_LEVELS)
2051 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2052 else if (game_status == GAME_MODE_LEVELNR)
2053 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2054 else if (game_status == GAME_MODE_SETUP)
2055 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2056 else if (game_status == GAME_MODE_INFO)
2057 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2059 joystick_last = joystick;
2064 case GAME_MODE_SCORES:
2065 HandleHallOfFame(0, 0, dx, dy, !newbutton);
2068 case GAME_MODE_PLAYING:
2069 if (tape.playing || keyboard)
2070 newbutton = ((joy & JOY_BUTTON) != 0);
2072 if (newbutton && AllPlayersGone)
2079 if (tape.recording && tape.pausing)
2081 if (joystick & JOY_ACTION)
2082 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2092 void HandleSpecialGameControllerButtons(Event *event)
2094 #if defined(TARGET_SDL2)
2095 switch (event->type)
2097 case SDL_CONTROLLERBUTTONDOWN:
2098 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2099 HandleKey(KSYM_space, KEY_PRESSED);
2100 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2101 HandleKey(KSYM_Escape, KEY_PRESSED);
2105 case SDL_CONTROLLERBUTTONUP:
2106 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2107 HandleKey(KSYM_space, KEY_RELEASED);
2108 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2109 HandleKey(KSYM_Escape, KEY_RELEASED);
2116 void HandleSpecialGameControllerKeys(Key key, int key_status)
2118 #if defined(TARGET_SDL2)
2119 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2120 int button = SDL_CONTROLLER_BUTTON_INVALID;
2122 /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2123 if (key == KSYM_Rewind)
2124 button = SDL_CONTROLLER_BUTTON_A;
2125 else if (key == KSYM_FastForward || key == KSYM_Menu)
2126 button = SDL_CONTROLLER_BUTTON_B;
2128 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2132 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2133 SDL_CONTROLLERBUTTONUP);
2135 event.cbutton.which = 0; /* first joystick (Amazon Fire TV remote) */
2136 event.cbutton.button = button;
2137 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2140 HandleJoystickEvent(&event);