1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_EVENTS 0
28 #define DEBUG_EVENTS_BUTTON (DEBUG_EVENTS * 0)
29 #define DEBUG_EVENTS_MOTION (DEBUG_EVENTS * 0)
30 #define DEBUG_EVENTS_WHEEL (DEBUG_EVENTS * 1)
31 #define DEBUG_EVENTS_WINDOW (DEBUG_EVENTS * 0)
32 #define DEBUG_EVENTS_FINGER (DEBUG_EVENTS * 0)
33 #define DEBUG_EVENTS_TEXT (DEBUG_EVENTS * 1)
34 #define DEBUG_EVENTS_KEY (DEBUG_EVENTS * 1)
37 static boolean cursor_inside_playfield = FALSE;
38 static int cursor_mode_last = CURSOR_DEFAULT;
39 static unsigned int special_cursor_delay = 0;
40 static unsigned int special_cursor_delay_value = 1000;
43 /* forward declarations for internal use */
44 static void HandleNoEvent(void);
45 static void HandleEventActions(void);
48 /* event filter especially needed for SDL event filtering due to
49 delay problems with lots of mouse motion events when mouse button
50 not pressed (X11 can handle this with 'PointerMotionHintMask') */
52 /* event filter addition for SDL2: as SDL2 does not have a function to enable
53 or disable keyboard auto-repeat, filter repeated keyboard events instead */
55 static int FilterEvents(const Event *event)
59 #if defined(TARGET_SDL2)
60 /* skip repeated key press events if keyboard auto-repeat is disabled */
61 if (event->type == EVENT_KEYPRESS &&
67 if (event->type == EVENT_BUTTONPRESS ||
68 event->type == EVENT_BUTTONRELEASE)
70 ((ButtonEvent *)event)->x -= video.screen_xoffset;
71 ((ButtonEvent *)event)->y -= video.screen_yoffset;
73 else if (event->type == EVENT_MOTIONNOTIFY)
75 ((MotionEvent *)event)->x -= video.screen_xoffset;
76 ((MotionEvent *)event)->y -= video.screen_yoffset;
79 /* non-motion events are directly passed to event handler functions */
80 if (event->type != EVENT_MOTIONNOTIFY)
83 motion = (MotionEvent *)event;
84 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
85 motion->y >= SY && motion->y < SY + SYSIZE);
87 /* do no reset mouse cursor before all pending events have been processed */
88 if (gfx.cursor_mode == cursor_mode_last &&
89 ((game_status == GAME_MODE_TITLE &&
90 gfx.cursor_mode == CURSOR_NONE) ||
91 (game_status == GAME_MODE_PLAYING &&
92 gfx.cursor_mode == CURSOR_PLAYFIELD)))
94 SetMouseCursor(CURSOR_DEFAULT);
96 DelayReached(&special_cursor_delay, 0);
98 cursor_mode_last = CURSOR_DEFAULT;
101 /* skip mouse motion events without pressed button outside level editor */
102 if (button_status == MB_RELEASED &&
103 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
109 /* to prevent delay problems, skip mouse motion events if the very next
110 event is also a mouse motion event (and therefore effectively only
111 handling the last of a row of mouse motion events in the event queue) */
113 static boolean SkipPressedMouseMotionEvent(const Event *event)
115 /* nothing to do if the current event is not a mouse motion event */
116 if (event->type != EVENT_MOTIONNOTIFY)
119 /* only skip motion events with pressed button outside the game */
120 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
127 PeekEvent(&next_event);
129 /* if next event is also a mouse motion event, skip the current one */
130 if (next_event.type == EVENT_MOTIONNOTIFY)
137 static boolean WaitValidEvent(Event *event)
141 if (!FilterEvents(event))
144 if (SkipPressedMouseMotionEvent(event))
150 /* this is especially needed for event modifications for the Android target:
151 if mouse coordinates should be modified in the event filter function,
152 using a properly installed SDL event filter does not work, because in
153 the event filter, mouse coordinates in the event structure are still
154 physical pixel positions, not logical (scaled) screen positions, so this
155 has to be handled at a later stage in the event processing functions
156 (when device pixel positions are already converted to screen positions) */
158 boolean NextValidEvent(Event *event)
160 while (PendingEvent())
161 if (WaitValidEvent(event))
170 unsigned int event_frame_delay = 0;
171 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
173 ResetDelayCounter(&event_frame_delay);
175 while (NextValidEvent(&event))
179 case EVENT_BUTTONPRESS:
180 case EVENT_BUTTONRELEASE:
181 HandleButtonEvent((ButtonEvent *) &event);
184 case EVENT_MOTIONNOTIFY:
185 HandleMotionEvent((MotionEvent *) &event);
188 #if defined(TARGET_SDL2)
189 case EVENT_WHEELMOTION:
190 HandleWheelEvent((WheelEvent *) &event);
193 case SDL_WINDOWEVENT:
194 HandleWindowEvent((WindowEvent *) &event);
197 case EVENT_FINGERPRESS:
198 case EVENT_FINGERRELEASE:
199 case EVENT_FINGERMOTION:
200 HandleFingerEvent((FingerEvent *) &event);
203 case EVENT_TEXTINPUT:
204 HandleTextEvent((TextEvent *) &event);
207 case SDL_APP_WILLENTERBACKGROUND:
208 case SDL_APP_DIDENTERBACKGROUND:
209 case SDL_APP_WILLENTERFOREGROUND:
210 case SDL_APP_DIDENTERFOREGROUND:
211 HandlePauseResumeEvent((PauseResumeEvent *) &event);
216 case EVENT_KEYRELEASE:
217 HandleKeyEvent((KeyEvent *) &event);
221 HandleOtherEvents(&event);
225 // do not handle events for longer than standard frame delay period
226 if (DelayReached(&event_frame_delay, event_frame_delay_value))
231 void HandleOtherEvents(Event *event)
236 HandleExposeEvent((ExposeEvent *) event);
239 case EVENT_UNMAPNOTIFY:
241 /* This causes the game to stop not only when iconified, but also
242 when on another virtual desktop, which might be not desired. */
243 SleepWhileUnmapped();
249 HandleFocusEvent((FocusChangeEvent *) event);
252 case EVENT_CLIENTMESSAGE:
253 HandleClientMessageEvent((ClientMessageEvent *) event);
256 #if defined(TARGET_SDL)
257 #if defined(TARGET_SDL2)
258 case SDL_CONTROLLERBUTTONDOWN:
259 case SDL_CONTROLLERBUTTONUP:
260 // for any game controller button event, disable overlay buttons
261 SetOverlayEnabled(FALSE);
263 HandleSpecialGameControllerButtons(event);
266 case SDL_CONTROLLERDEVICEADDED:
267 case SDL_CONTROLLERDEVICEREMOVED:
268 case SDL_CONTROLLERAXISMOTION:
270 case SDL_JOYAXISMOTION:
271 case SDL_JOYBUTTONDOWN:
272 case SDL_JOYBUTTONUP:
273 HandleJoystickEvent(event);
277 HandleWindowManagerEvent(event);
286 void HandleMouseCursor()
288 if (game_status == GAME_MODE_TITLE)
290 /* when showing title screens, hide mouse pointer (if not moved) */
292 if (gfx.cursor_mode != CURSOR_NONE &&
293 DelayReached(&special_cursor_delay, special_cursor_delay_value))
295 SetMouseCursor(CURSOR_NONE);
298 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
301 /* when playing, display a special mouse pointer inside the playfield */
303 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
304 cursor_inside_playfield &&
305 DelayReached(&special_cursor_delay, special_cursor_delay_value))
307 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
309 SetMouseCursor(CURSOR_PLAYFIELD);
312 else if (gfx.cursor_mode != CURSOR_DEFAULT)
314 SetMouseCursor(CURSOR_DEFAULT);
317 /* this is set after all pending events have been processed */
318 cursor_mode_last = gfx.cursor_mode;
330 /* execute event related actions after pending events have been processed */
331 HandleEventActions();
333 /* don't use all CPU time when idle; the main loop while playing
334 has its own synchronization and is CPU friendly, too */
336 if (game_status == GAME_MODE_PLAYING)
339 /* always copy backbuffer to visible screen for every video frame */
342 /* reset video frame delay to default (may change again while playing) */
343 SetVideoFrameDelay(MenuFrameDelay);
345 if (game_status == GAME_MODE_QUIT)
350 void ClearEventQueue()
354 while (NextValidEvent(&event))
358 case EVENT_BUTTONRELEASE:
359 button_status = MB_RELEASED;
362 case EVENT_KEYRELEASE:
366 #if defined(TARGET_SDL2)
367 case SDL_CONTROLLERBUTTONUP:
368 HandleJoystickEvent(&event);
374 HandleOtherEvents(&event);
380 void ClearPlayerMouseAction()
382 local_player->mouse_action.lx = 0;
383 local_player->mouse_action.ly = 0;
384 local_player->mouse_action.button = 0;
387 void ClearPlayerAction()
391 /* simulate key release events for still pressed keys */
392 key_joystick_mapping = 0;
393 for (i = 0; i < MAX_PLAYERS; i++)
394 stored_player[i].action = 0;
396 ClearJoystickState();
397 ClearPlayerMouseAction();
400 void SetPlayerMouseAction(int mx, int my, int button)
402 int lx = getLevelFromScreenX(mx);
403 int ly = getLevelFromScreenY(my);
404 int new_button = (!local_player->mouse_action.button && button);
406 ClearPlayerMouseAction();
408 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
411 local_player->mouse_action.lx = lx;
412 local_player->mouse_action.ly = ly;
413 local_player->mouse_action.button = button;
415 if (tape.recording && tape.pausing && tape.use_mouse)
417 /* un-pause a paused game only if mouse button was newly pressed down */
419 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
422 SetTileCursorXY(lx, ly);
425 void SleepWhileUnmapped()
427 boolean window_unmapped = TRUE;
429 KeyboardAutoRepeatOn();
431 while (window_unmapped)
435 if (!WaitValidEvent(&event))
440 case EVENT_BUTTONRELEASE:
441 button_status = MB_RELEASED;
444 case EVENT_KEYRELEASE:
445 key_joystick_mapping = 0;
448 #if defined(TARGET_SDL2)
449 case SDL_CONTROLLERBUTTONUP:
450 HandleJoystickEvent(&event);
451 key_joystick_mapping = 0;
455 case EVENT_MAPNOTIFY:
456 window_unmapped = FALSE;
459 case EVENT_UNMAPNOTIFY:
460 /* this is only to surely prevent the 'should not happen' case
461 * of recursively looping between 'SleepWhileUnmapped()' and
462 * 'HandleOtherEvents()' which usually calls this funtion.
467 HandleOtherEvents(&event);
472 if (game_status == GAME_MODE_PLAYING)
473 KeyboardAutoRepeatOffUnlessAutoplay();
476 void HandleExposeEvent(ExposeEvent *event)
480 void HandleButtonEvent(ButtonEvent *event)
482 #if DEBUG_EVENTS_BUTTON
483 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
485 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
489 // for any mouse button event, disable playfield tile cursor
490 SetTileCursorEnabled(FALSE);
492 #if defined(HAS_SCREEN_KEYBOARD)
493 if (video.shifted_up)
494 event->y += video.shifted_up_pos;
497 motion_status = FALSE;
499 if (event->type == EVENT_BUTTONPRESS)
500 button_status = event->button;
502 button_status = MB_RELEASED;
504 HandleButton(event->x, event->y, button_status, event->button);
507 void HandleMotionEvent(MotionEvent *event)
509 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
512 motion_status = TRUE;
514 #if DEBUG_EVENTS_MOTION
515 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
516 button_status, event->x, event->y);
519 HandleButton(event->x, event->y, button_status, button_status);
522 #if defined(TARGET_SDL2)
524 void HandleWheelEvent(WheelEvent *event)
528 #if DEBUG_EVENTS_WHEEL
530 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
531 event->which, event->x, event->y);
533 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
534 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
535 event->which, event->x, event->y,
536 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
537 "SDL_MOUSEWHEEL_FLIPPED"));
541 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
542 event->x > 0 ? MB_WHEEL_RIGHT :
543 event->y < 0 ? MB_WHEEL_DOWN :
544 event->y > 0 ? MB_WHEEL_UP : 0);
546 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
547 // accelerated mouse wheel available on Mac and Windows
548 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
550 // no accelerated mouse wheel available on Unix/Linux
551 wheel_steps = DEFAULT_WHEEL_STEPS;
554 motion_status = FALSE;
556 button_status = button_nr;
557 HandleButton(0, 0, button_status, -button_nr);
559 button_status = MB_RELEASED;
560 HandleButton(0, 0, button_status, -button_nr);
563 void HandleWindowEvent(WindowEvent *event)
565 #if DEBUG_EVENTS_WINDOW
566 int subtype = event->event;
569 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
570 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
571 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
572 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
573 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
574 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
575 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
576 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
577 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
578 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
579 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
580 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
581 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
582 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
585 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
586 event_name, event->data1, event->data2);
590 // (not needed, as the screen gets redrawn every 20 ms anyway)
591 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
592 event->event == SDL_WINDOWEVENT_RESIZED ||
593 event->event == SDL_WINDOWEVENT_EXPOSED)
597 if (event->event == SDL_WINDOWEVENT_RESIZED)
599 if (!video.fullscreen_enabled)
601 int new_window_width = event->data1;
602 int new_window_height = event->data2;
604 // if window size has changed after resizing, calculate new scaling factor
605 if (new_window_width != video.window_width ||
606 new_window_height != video.window_height)
608 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
609 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
611 // (extreme window scaling allowed, but cannot be saved permanently)
612 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
613 setup.window_scaling_percent =
614 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
615 MAX_WINDOW_SCALING_PERCENT);
617 video.window_width = new_window_width;
618 video.window_height = new_window_height;
620 if (game_status == GAME_MODE_SETUP)
621 RedrawSetupScreenAfterFullscreenToggle();
626 #if defined(PLATFORM_ANDROID)
629 int new_display_width = event->data1;
630 int new_display_height = event->data2;
632 // if fullscreen display size has changed, device has been rotated
633 if (new_display_width != video.display_width ||
634 new_display_height != video.display_height)
636 video.display_width = new_display_width;
637 video.display_height = new_display_height;
639 SDLSetScreenProperties();
646 #define NUM_TOUCH_FINGERS 3
651 SDL_FingerID finger_id;
654 } touch_info[NUM_TOUCH_FINGERS];
656 void HandleFingerEvent_VirtualButtons(FingerEvent *event)
658 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
659 float event_x = (event->x);
660 float event_y = (event->y - ypos) / (1 - ypos);
661 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
662 event_y > 2.0 / 3.0 && event_y < 1 ?
663 setup.input[0].key.snap :
664 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
665 event_y > 2.0 / 3.0 && event_y < 1 ?
666 setup.input[0].key.drop :
667 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
668 event_y > 0 && event_y < 1.0 / 3.0 ?
669 setup.input[0].key.up :
670 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
671 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
672 setup.input[0].key.left :
673 event_x > 8.0 / 9.0 && event_x < 1 &&
674 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
675 setup.input[0].key.right :
676 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
677 event_y > 2.0 / 3.0 && event_y < 1 ?
678 setup.input[0].key.down :
680 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
682 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
686 // for any touch input event, enable overlay buttons (if activated)
687 SetOverlayEnabled(TRUE);
689 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
690 getKeyNameFromKey(key), key_status_name, event->fingerId);
692 // check if we already know this touch event's finger id
693 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
695 if (touch_info[i].touched &&
696 touch_info[i].finger_id == event->fingerId)
698 // Error(ERR_DEBUG, "MARK 1: %d", i);
704 if (i >= NUM_TOUCH_FINGERS)
706 if (key_status == KEY_PRESSED)
708 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
710 // unknown finger id -- get new, empty slot, if available
711 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
713 if (touch_info[i].counter < oldest_counter)
716 oldest_counter = touch_info[i].counter;
718 // Error(ERR_DEBUG, "MARK 2: %d", i);
721 if (!touch_info[i].touched)
723 // Error(ERR_DEBUG, "MARK 3: %d", i);
729 if (i >= NUM_TOUCH_FINGERS)
731 // all slots allocated -- use oldest slot
734 // Error(ERR_DEBUG, "MARK 4: %d", i);
739 // release of previously unknown key (should not happen)
741 if (key != KSYM_UNDEFINED)
743 HandleKey(key, KEY_RELEASED);
745 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
746 getKeyNameFromKey(key), "KEY_RELEASED", i);
751 if (i < NUM_TOUCH_FINGERS)
753 if (key_status == KEY_PRESSED)
755 if (touch_info[i].key != key)
757 if (touch_info[i].key != KSYM_UNDEFINED)
759 HandleKey(touch_info[i].key, KEY_RELEASED);
761 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
762 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
765 if (key != KSYM_UNDEFINED)
767 HandleKey(key, KEY_PRESSED);
769 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
770 getKeyNameFromKey(key), "KEY_PRESSED", i);
774 touch_info[i].touched = TRUE;
775 touch_info[i].finger_id = event->fingerId;
776 touch_info[i].counter = Counter();
777 touch_info[i].key = key;
781 if (touch_info[i].key != KSYM_UNDEFINED)
783 HandleKey(touch_info[i].key, KEY_RELEASED);
785 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
786 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
789 touch_info[i].touched = FALSE;
790 touch_info[i].finger_id = 0;
791 touch_info[i].counter = 0;
792 touch_info[i].key = 0;
797 void HandleFingerEvent_WipeGestures(FingerEvent *event)
799 static Key motion_key_x = KSYM_UNDEFINED;
800 static Key motion_key_y = KSYM_UNDEFINED;
801 static Key button_key = KSYM_UNDEFINED;
802 static float motion_x1, motion_y1;
803 static float button_x1, button_y1;
804 static SDL_FingerID motion_id = -1;
805 static SDL_FingerID button_id = -1;
806 int move_trigger_distance_percent = setup.touch.move_distance;
807 int drop_trigger_distance_percent = setup.touch.drop_distance;
808 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
809 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
810 float event_x = event->x;
811 float event_y = event->y;
813 if (event->type == EVENT_FINGERPRESS)
815 if (event_x > 1.0 / 3.0)
819 motion_id = event->fingerId;
824 motion_key_x = KSYM_UNDEFINED;
825 motion_key_y = KSYM_UNDEFINED;
827 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
833 button_id = event->fingerId;
838 button_key = setup.input[0].key.snap;
840 HandleKey(button_key, KEY_PRESSED);
842 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
845 else if (event->type == EVENT_FINGERRELEASE)
847 if (event->fingerId == motion_id)
851 if (motion_key_x != KSYM_UNDEFINED)
852 HandleKey(motion_key_x, KEY_RELEASED);
853 if (motion_key_y != KSYM_UNDEFINED)
854 HandleKey(motion_key_y, KEY_RELEASED);
856 motion_key_x = KSYM_UNDEFINED;
857 motion_key_y = KSYM_UNDEFINED;
859 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
861 else if (event->fingerId == button_id)
865 if (button_key != KSYM_UNDEFINED)
866 HandleKey(button_key, KEY_RELEASED);
868 button_key = KSYM_UNDEFINED;
870 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
873 else if (event->type == EVENT_FINGERMOTION)
875 if (event->fingerId == motion_id)
877 float distance_x = ABS(event_x - motion_x1);
878 float distance_y = ABS(event_y - motion_y1);
879 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
880 event_x > motion_x1 ? setup.input[0].key.right :
882 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
883 event_y > motion_y1 ? setup.input[0].key.down :
886 if (distance_x < move_trigger_distance / 2 ||
887 distance_x < distance_y)
888 new_motion_key_x = KSYM_UNDEFINED;
890 if (distance_y < move_trigger_distance / 2 ||
891 distance_y < distance_x)
892 new_motion_key_y = KSYM_UNDEFINED;
894 if (distance_x > move_trigger_distance ||
895 distance_y > move_trigger_distance)
897 if (new_motion_key_x != motion_key_x)
899 if (motion_key_x != KSYM_UNDEFINED)
900 HandleKey(motion_key_x, KEY_RELEASED);
901 if (new_motion_key_x != KSYM_UNDEFINED)
902 HandleKey(new_motion_key_x, KEY_PRESSED);
905 if (new_motion_key_y != motion_key_y)
907 if (motion_key_y != KSYM_UNDEFINED)
908 HandleKey(motion_key_y, KEY_RELEASED);
909 if (new_motion_key_y != KSYM_UNDEFINED)
910 HandleKey(new_motion_key_y, KEY_PRESSED);
916 motion_key_x = new_motion_key_x;
917 motion_key_y = new_motion_key_y;
919 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
922 else if (event->fingerId == button_id)
924 float distance_x = ABS(event_x - button_x1);
925 float distance_y = ABS(event_y - button_y1);
927 if (distance_x < drop_trigger_distance / 2 &&
928 distance_y > drop_trigger_distance)
930 if (button_key == setup.input[0].key.snap)
931 HandleKey(button_key, KEY_RELEASED);
936 button_key = setup.input[0].key.drop;
938 HandleKey(button_key, KEY_PRESSED);
940 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
946 void HandleFingerEvent(FingerEvent *event)
948 #if DEBUG_EVENTS_FINGER
949 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
950 event->type == EVENT_FINGERPRESS ? "pressed" :
951 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
955 event->dx, event->dy,
959 if (game_status != GAME_MODE_PLAYING)
962 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
965 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
966 HandleFingerEvent_VirtualButtons(event);
967 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
968 HandleFingerEvent_WipeGestures(event);
973 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
975 static int old_mx = 0, old_my = 0;
976 static int last_button = MB_LEFTBUTTON;
977 static boolean touched = FALSE;
978 static boolean tapped = FALSE;
980 // screen tile was tapped (but finger not touching the screen anymore)
981 // (this point will also be reached without receiving a touch event)
982 if (tapped && !touched)
984 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
989 // stop here if this function was not triggered by a touch event
993 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
995 // finger started touching the screen
1005 ClearPlayerMouseAction();
1007 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1010 else if (button == MB_RELEASED && touched)
1012 // finger stopped touching the screen
1017 SetPlayerMouseAction(old_mx, old_my, last_button);
1019 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1021 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1026 // finger moved while touching the screen
1028 int old_x = getLevelFromScreenX(old_mx);
1029 int old_y = getLevelFromScreenY(old_my);
1030 int new_x = getLevelFromScreenX(mx);
1031 int new_y = getLevelFromScreenY(my);
1033 if (new_x != old_x || new_y != old_y)
1038 // finger moved left or right from (horizontal) starting position
1040 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1042 SetPlayerMouseAction(old_mx, old_my, button_nr);
1044 last_button = button_nr;
1046 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1050 // finger stays at or returned to (horizontal) starting position
1052 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1054 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1059 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1061 static int old_mx = 0, old_my = 0;
1062 static int last_button = MB_LEFTBUTTON;
1063 static boolean touched = FALSE;
1064 static boolean tapped = FALSE;
1066 // screen tile was tapped (but finger not touching the screen anymore)
1067 // (this point will also be reached without receiving a touch event)
1068 if (tapped && !touched)
1070 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1075 // stop here if this function was not triggered by a touch event
1079 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1081 // finger started touching the screen
1091 ClearPlayerMouseAction();
1093 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1096 else if (button == MB_RELEASED && touched)
1098 // finger stopped touching the screen
1103 SetPlayerMouseAction(old_mx, old_my, last_button);
1105 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1107 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1112 // finger moved while touching the screen
1114 int old_x = getLevelFromScreenX(old_mx);
1115 int old_y = getLevelFromScreenY(old_my);
1116 int new_x = getLevelFromScreenX(mx);
1117 int new_y = getLevelFromScreenY(my);
1119 if (new_x != old_x || new_y != old_y)
1121 // finger moved away from starting position
1123 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1125 // quickly alternate between clicking and releasing for maximum speed
1126 if (FrameCounter % 2 == 0)
1127 button_nr = MB_RELEASED;
1129 SetPlayerMouseAction(old_mx, old_my, button_nr);
1132 last_button = button_nr;
1136 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1140 // finger stays at or returned to starting position
1142 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1144 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1149 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1151 static int old_mx = 0, old_my = 0;
1152 static Key motion_key_x = KSYM_UNDEFINED;
1153 static Key motion_key_y = KSYM_UNDEFINED;
1154 static boolean touched = FALSE;
1155 static boolean started_on_player = FALSE;
1156 static boolean player_is_dropping = FALSE;
1157 static int player_drop_count = 0;
1158 static int last_player_x = -1;
1159 static int last_player_y = -1;
1161 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1170 started_on_player = FALSE;
1171 player_is_dropping = FALSE;
1172 player_drop_count = 0;
1176 motion_key_x = KSYM_UNDEFINED;
1177 motion_key_y = KSYM_UNDEFINED;
1179 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1182 else if (button == MB_RELEASED && touched)
1189 if (motion_key_x != KSYM_UNDEFINED)
1190 HandleKey(motion_key_x, KEY_RELEASED);
1191 if (motion_key_y != KSYM_UNDEFINED)
1192 HandleKey(motion_key_y, KEY_RELEASED);
1194 if (started_on_player)
1196 if (player_is_dropping)
1198 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1200 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1204 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1206 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1210 motion_key_x = KSYM_UNDEFINED;
1211 motion_key_y = KSYM_UNDEFINED;
1213 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1218 int src_x = local_player->jx;
1219 int src_y = local_player->jy;
1220 int dst_x = getLevelFromScreenX(old_mx);
1221 int dst_y = getLevelFromScreenY(old_my);
1222 int dx = dst_x - src_x;
1223 int dy = dst_y - src_y;
1224 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1225 dx > 0 ? setup.input[0].key.right :
1227 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1228 dy > 0 ? setup.input[0].key.down :
1231 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1232 (last_player_x != local_player->jx ||
1233 last_player_y != local_player->jy))
1235 // in case of asymmetric diagonal movement, use "preferred" direction
1237 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1239 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1240 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1242 local_player->last_move_dir = last_move_dir;
1244 // (required to prevent accidentally forcing direction for next movement)
1245 last_player_x = local_player->jx;
1246 last_player_y = local_player->jy;
1249 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1251 started_on_player = TRUE;
1252 player_drop_count = getPlayerInventorySize(0);
1253 player_is_dropping = (player_drop_count > 0);
1255 if (player_is_dropping)
1257 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1259 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1263 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1265 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1268 else if (dx != 0 || dy != 0)
1270 if (player_is_dropping &&
1271 player_drop_count == getPlayerInventorySize(0))
1273 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1275 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1276 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1278 player_is_dropping = FALSE;
1282 if (new_motion_key_x != motion_key_x)
1284 Error(ERR_DEBUG, "---------- %s %s ----------",
1285 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1286 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1288 if (motion_key_x != KSYM_UNDEFINED)
1289 HandleKey(motion_key_x, KEY_RELEASED);
1290 if (new_motion_key_x != KSYM_UNDEFINED)
1291 HandleKey(new_motion_key_x, KEY_PRESSED);
1294 if (new_motion_key_y != motion_key_y)
1296 Error(ERR_DEBUG, "---------- %s %s ----------",
1297 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1298 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1300 if (motion_key_y != KSYM_UNDEFINED)
1301 HandleKey(motion_key_y, KEY_RELEASED);
1302 if (new_motion_key_y != KSYM_UNDEFINED)
1303 HandleKey(new_motion_key_y, KEY_PRESSED);
1306 motion_key_x = new_motion_key_x;
1307 motion_key_y = new_motion_key_y;
1311 static void HandleButtonOrFinger(int mx, int my, int button)
1313 if (game_status != GAME_MODE_PLAYING)
1316 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1318 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1319 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1320 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1321 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1325 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1326 HandleButtonOrFinger_FollowFinger(mx, my, button);
1330 #if defined(TARGET_SDL2)
1332 static boolean checkTextInputKeyModState()
1334 // when playing, only handle raw key events and ignore text input
1335 if (game_status == GAME_MODE_PLAYING)
1338 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1341 void HandleTextEvent(TextEvent *event)
1343 char *text = event->text;
1344 Key key = getKeyFromKeyName(text);
1346 #if DEBUG_EVENTS_TEXT
1347 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1350 text[0], (int)(text[0]),
1352 getKeyNameFromKey(key),
1356 #if !defined(HAS_SCREEN_KEYBOARD)
1357 // non-mobile devices: only handle key input with modifier keys pressed here
1358 // (every other key input is handled directly as physical key input event)
1359 if (!checkTextInputKeyModState())
1363 // process text input as "classic" (with uppercase etc.) key input event
1364 HandleKey(key, KEY_PRESSED);
1365 HandleKey(key, KEY_RELEASED);
1368 void HandlePauseResumeEvent(PauseResumeEvent *event)
1370 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1374 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1382 void HandleKeyEvent(KeyEvent *event)
1384 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1385 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1386 Key key = GetEventKey(event, with_modifiers);
1387 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1389 #if DEBUG_EVENTS_KEY
1390 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1391 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1392 event->keysym.scancode,
1397 getKeyNameFromKey(key));
1400 #if defined(PLATFORM_ANDROID)
1401 if (key == KSYM_Back)
1403 // always map the "back" button to the "escape" key on Android devices
1408 // for any key event other than "back" button, disable overlay buttons
1409 SetOverlayEnabled(FALSE);
1413 HandleKeyModState(keymod, key_status);
1415 #if defined(TARGET_SDL2)
1416 // only handle raw key input without text modifier keys pressed
1417 if (!checkTextInputKeyModState())
1418 HandleKey(key, key_status);
1420 HandleKey(key, key_status);
1424 void HandleFocusEvent(FocusChangeEvent *event)
1426 static int old_joystick_status = -1;
1428 if (event->type == EVENT_FOCUSOUT)
1430 KeyboardAutoRepeatOn();
1431 old_joystick_status = joystick.status;
1432 joystick.status = JOYSTICK_NOT_AVAILABLE;
1434 ClearPlayerAction();
1436 else if (event->type == EVENT_FOCUSIN)
1438 /* When there are two Rocks'n'Diamonds windows which overlap and
1439 the player moves the pointer from one game window to the other,
1440 a 'FocusOut' event is generated for the window the pointer is
1441 leaving and a 'FocusIn' event is generated for the window the
1442 pointer is entering. In some cases, it can happen that the
1443 'FocusIn' event is handled by the one game process before the
1444 'FocusOut' event by the other game process. In this case the
1445 X11 environment would end up with activated keyboard auto repeat,
1446 because unfortunately this is a global setting and not (which
1447 would be far better) set for each X11 window individually.
1448 The effect would be keyboard auto repeat while playing the game
1449 (game_status == GAME_MODE_PLAYING), which is not desired.
1450 To avoid this special case, we just wait 1/10 second before
1451 processing the 'FocusIn' event.
1454 if (game_status == GAME_MODE_PLAYING)
1457 KeyboardAutoRepeatOffUnlessAutoplay();
1460 if (old_joystick_status != -1)
1461 joystick.status = old_joystick_status;
1465 void HandleClientMessageEvent(ClientMessageEvent *event)
1467 if (CheckCloseWindowEvent(event))
1471 void HandleWindowManagerEvent(Event *event)
1473 #if defined(TARGET_SDL)
1474 SDLHandleWindowManagerEvent(event);
1478 void HandleButton(int mx, int my, int button, int button_nr)
1480 static int old_mx = 0, old_my = 0;
1481 boolean button_hold = FALSE;
1482 boolean handle_gadgets = TRUE;
1488 button_nr = -button_nr;
1497 #if defined(PLATFORM_ANDROID)
1498 // when playing, only handle gadgets when using "follow finger" controls
1499 // or when using touch controls in combination with the MM game engine
1501 (game_status != GAME_MODE_PLAYING ||
1502 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1503 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1506 if (handle_gadgets && HandleGadgets(mx, my, button))
1508 /* do not handle this button event anymore */
1509 mx = my = -32; /* force mouse event to be outside screen tiles */
1512 if (HandleGlobalAnimClicks(mx, my, button))
1514 /* do not handle this button event anymore */
1515 return; /* force mouse event not to be handled at all */
1518 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1521 /* do not use scroll wheel button events for anything other than gadgets */
1522 if (IS_WHEEL_BUTTON(button_nr))
1525 switch (game_status)
1527 case GAME_MODE_TITLE:
1528 HandleTitleScreen(mx, my, 0, 0, button);
1531 case GAME_MODE_MAIN:
1532 HandleMainMenu(mx, my, 0, 0, button);
1535 case GAME_MODE_PSEUDO_TYPENAME:
1536 HandleTypeName(0, KSYM_Return);
1539 case GAME_MODE_LEVELS:
1540 HandleChooseLevelSet(mx, my, 0, 0, button);
1543 case GAME_MODE_LEVELNR:
1544 HandleChooseLevelNr(mx, my, 0, 0, button);
1547 case GAME_MODE_SCORES:
1548 HandleHallOfFame(0, 0, 0, 0, button);
1551 case GAME_MODE_EDITOR:
1552 HandleLevelEditorIdle();
1555 case GAME_MODE_INFO:
1556 HandleInfoScreen(mx, my, 0, 0, button);
1559 case GAME_MODE_SETUP:
1560 HandleSetupScreen(mx, my, 0, 0, button);
1563 case GAME_MODE_PLAYING:
1564 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1565 HandleButtonOrFinger(mx, my, button);
1567 SetPlayerMouseAction(mx, my, button);
1570 if (button == MB_PRESSED && !motion_status && !button_hold &&
1571 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1572 DumpTileFromScreen(mx, my);
1582 static boolean is_string_suffix(char *string, char *suffix)
1584 int string_len = strlen(string);
1585 int suffix_len = strlen(suffix);
1587 if (suffix_len > string_len)
1590 return (strEqual(&string[string_len - suffix_len], suffix));
1593 #define MAX_CHEAT_INPUT_LEN 32
1595 static void HandleKeysSpecial(Key key)
1597 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1598 char letter = getCharFromKey(key);
1599 int cheat_input_len = strlen(cheat_input);
1605 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1607 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1608 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1610 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1613 cheat_input[cheat_input_len++] = letter;
1614 cheat_input[cheat_input_len] = '\0';
1616 #if DEBUG_EVENTS_KEY
1617 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1620 if (game_status == GAME_MODE_MAIN)
1622 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1623 is_string_suffix(cheat_input, ":ist"))
1625 InsertSolutionTape();
1627 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1628 is_string_suffix(cheat_input, ":rg"))
1630 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1633 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1634 is_string_suffix(cheat_input, ":rs"))
1636 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1639 else if (is_string_suffix(cheat_input, ":reload-music") ||
1640 is_string_suffix(cheat_input, ":rm"))
1642 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1645 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1646 is_string_suffix(cheat_input, ":ra"))
1648 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1649 1 << ARTWORK_TYPE_SOUNDS |
1650 1 << ARTWORK_TYPE_MUSIC);
1653 else if (is_string_suffix(cheat_input, ":dump-level") ||
1654 is_string_suffix(cheat_input, ":dl"))
1658 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1659 is_string_suffix(cheat_input, ":dt"))
1663 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1664 is_string_suffix(cheat_input, ":ft"))
1666 /* fix single-player tapes that contain player input for more than one
1667 player (due to a bug in 3.3.1.2 and earlier versions), which results
1668 in playing levels with more than one player in multi-player mode,
1669 even though the tape was originally recorded in single-player mode */
1671 /* remove player input actions for all players but the first one */
1672 for (i = 1; i < MAX_PLAYERS; i++)
1673 tape.player_participates[i] = FALSE;
1675 tape.changed = TRUE;
1677 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1678 is_string_suffix(cheat_input, ":snl"))
1680 SaveNativeLevel(&level);
1682 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1683 is_string_suffix(cheat_input, ":fps"))
1685 global.show_frames_per_second = !global.show_frames_per_second;
1688 else if (game_status == GAME_MODE_PLAYING)
1691 if (is_string_suffix(cheat_input, ".q"))
1692 DEBUG_SetMaximumDynamite();
1695 else if (game_status == GAME_MODE_EDITOR)
1697 if (is_string_suffix(cheat_input, ":dump-brush") ||
1698 is_string_suffix(cheat_input, ":DB"))
1702 else if (is_string_suffix(cheat_input, ":DDB"))
1709 void HandleKeysDebug(Key key)
1714 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1716 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1718 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1720 if (key == setup.debug.frame_delay_key[i] &&
1721 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1723 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1724 setup.debug.frame_delay[i] : setup.game_frame_delay);
1726 if (!setup.debug.frame_delay_game_only)
1727 MenuFrameDelay = GameFrameDelay;
1729 SetVideoFrameDelay(GameFrameDelay);
1731 if (GameFrameDelay > ONE_SECOND_DELAY)
1732 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1733 else if (GameFrameDelay != 0)
1734 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1735 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1736 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1738 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1745 if (game_status == GAME_MODE_PLAYING)
1749 options.debug = !options.debug;
1751 Error(ERR_DEBUG, "debug mode %s",
1752 (options.debug ? "enabled" : "disabled"));
1754 else if (key == KSYM_v)
1756 Error(ERR_DEBUG, "currently using game engine version %d",
1757 game.engine_version);
1763 void HandleKey(Key key, int key_status)
1765 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1766 static boolean ignore_repeated_key = FALSE;
1767 static struct SetupKeyboardInfo ski;
1768 static struct SetupShortcutInfo ssi;
1777 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1778 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1779 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1780 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1781 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1782 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1787 #if defined(TARGET_SDL2)
1788 /* map special keys (media keys / remote control buttons) to default keys */
1789 if (key == KSYM_PlayPause)
1791 else if (key == KSYM_Select)
1795 HandleSpecialGameControllerKeys(key, key_status);
1797 if (game_status == GAME_MODE_PLAYING)
1799 /* only needed for single-step tape recording mode */
1800 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1803 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1805 byte key_action = 0;
1807 if (setup.input[pnr].use_joystick)
1810 ski = setup.input[pnr].key;
1812 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1813 if (key == *key_info[i].key_custom)
1814 key_action |= key_info[i].action;
1816 /* use combined snap+direction keys for the first player only */
1819 ssi = setup.shortcut;
1821 for (i = 0; i < NUM_DIRECTIONS; i++)
1822 if (key == *key_info[i].key_snap)
1823 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1826 if (key_status == KEY_PRESSED)
1827 stored_player[pnr].action |= key_action;
1829 stored_player[pnr].action &= ~key_action;
1831 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
1833 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1835 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1837 /* if snap key already pressed, keep pause mode when releasing */
1838 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1839 has_snapped[pnr] = TRUE;
1841 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1843 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1845 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1846 getRedDiskReleaseFlag_SP() == 0)
1848 /* add a single inactive frame before dropping starts */
1849 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1850 stored_player[pnr].force_dropping = TRUE;
1853 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1855 /* if snap key was pressed without direction, leave pause mode */
1856 if (!has_snapped[pnr])
1857 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1859 has_snapped[pnr] = FALSE;
1862 else if (tape.recording && tape.pausing && !tape.use_mouse)
1864 /* prevent key release events from un-pausing a paused game */
1865 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1866 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1869 // for MM style levels, handle in-game keyboard input in HandleJoystick()
1870 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1876 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1877 if (key == key_info[i].key_default)
1878 joy |= key_info[i].action;
1883 if (key_status == KEY_PRESSED)
1884 key_joystick_mapping |= joy;
1886 key_joystick_mapping &= ~joy;
1891 if (game_status != GAME_MODE_PLAYING)
1892 key_joystick_mapping = 0;
1894 if (key_status == KEY_RELEASED)
1896 // reset flag to ignore repeated "key pressed" events after key release
1897 ignore_repeated_key = FALSE;
1902 if ((key == KSYM_F11 ||
1903 ((key == KSYM_Return ||
1904 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1905 video.fullscreen_available &&
1906 !ignore_repeated_key)
1908 setup.fullscreen = !setup.fullscreen;
1910 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1912 if (game_status == GAME_MODE_SETUP)
1913 RedrawSetupScreenAfterFullscreenToggle();
1915 // set flag to ignore repeated "key pressed" events
1916 ignore_repeated_key = TRUE;
1921 if ((key == KSYM_0 || key == KSYM_KP_0 ||
1922 key == KSYM_minus || key == KSYM_KP_Subtract ||
1923 key == KSYM_plus || key == KSYM_KP_Add ||
1924 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
1925 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1926 video.window_scaling_available &&
1927 !video.fullscreen_enabled)
1929 if (key == KSYM_0 || key == KSYM_KP_0)
1930 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1931 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1932 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1934 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1936 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1937 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1938 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1939 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1941 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1943 if (game_status == GAME_MODE_SETUP)
1944 RedrawSetupScreenAfterFullscreenToggle();
1949 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
1950 key == KSYM_Return ||
1951 key == KSYM_Escape)))
1953 /* do not handle this key event anymore */
1954 if (key != KSYM_Escape) /* always allow ESC key to be handled */
1958 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1959 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1966 if (game_status == GAME_MODE_MAIN &&
1967 (key == setup.shortcut.toggle_pause || key == KSYM_space))
1969 StartGameActions(options.network, setup.autorecord, level.random_seed);
1974 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1976 if (key == setup.shortcut.save_game)
1978 else if (key == setup.shortcut.load_game)
1980 else if (key == setup.shortcut.toggle_pause)
1981 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
1983 HandleTapeButtonKeys(key);
1984 HandleSoundButtonKeys(key);
1987 if (game_status == GAME_MODE_PLAYING && !network_playing)
1989 int centered_player_nr_next = -999;
1991 if (key == setup.shortcut.focus_player_all)
1992 centered_player_nr_next = -1;
1994 for (i = 0; i < MAX_PLAYERS; i++)
1995 if (key == setup.shortcut.focus_player[i])
1996 centered_player_nr_next = i;
1998 if (centered_player_nr_next != -999)
2000 game.centered_player_nr_next = centered_player_nr_next;
2001 game.set_centered_player = TRUE;
2005 tape.centered_player_nr_next = game.centered_player_nr_next;
2006 tape.set_centered_player = TRUE;
2011 HandleKeysSpecial(key);
2013 if (HandleGadgetsKeyInput(key))
2015 if (key != KSYM_Escape) /* always allow ESC key to be handled */
2016 key = KSYM_UNDEFINED;
2019 switch (game_status)
2021 case GAME_MODE_PSEUDO_TYPENAME:
2022 HandleTypeName(0, key);
2025 case GAME_MODE_TITLE:
2026 case GAME_MODE_MAIN:
2027 case GAME_MODE_LEVELS:
2028 case GAME_MODE_LEVELNR:
2029 case GAME_MODE_SETUP:
2030 case GAME_MODE_INFO:
2031 case GAME_MODE_SCORES:
2036 if (game_status == GAME_MODE_TITLE)
2037 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2038 else if (game_status == GAME_MODE_MAIN)
2039 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2040 else if (game_status == GAME_MODE_LEVELS)
2041 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2042 else if (game_status == GAME_MODE_LEVELNR)
2043 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2044 else if (game_status == GAME_MODE_SETUP)
2045 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2046 else if (game_status == GAME_MODE_INFO)
2047 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2048 else if (game_status == GAME_MODE_SCORES)
2049 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2053 if (game_status != GAME_MODE_MAIN)
2054 FadeSkipNextFadeIn();
2056 if (game_status == GAME_MODE_TITLE)
2057 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2058 else if (game_status == GAME_MODE_LEVELS)
2059 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2060 else if (game_status == GAME_MODE_LEVELNR)
2061 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2062 else if (game_status == GAME_MODE_SETUP)
2063 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2064 else if (game_status == GAME_MODE_INFO)
2065 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2066 else if (game_status == GAME_MODE_SCORES)
2067 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2071 if (game_status == GAME_MODE_LEVELS)
2072 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2073 else if (game_status == GAME_MODE_LEVELNR)
2074 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2075 else if (game_status == GAME_MODE_SETUP)
2076 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2077 else if (game_status == GAME_MODE_INFO)
2078 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2079 else if (game_status == GAME_MODE_SCORES)
2080 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2083 case KSYM_Page_Down:
2084 if (game_status == GAME_MODE_LEVELS)
2085 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2086 else if (game_status == GAME_MODE_LEVELNR)
2087 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2088 else if (game_status == GAME_MODE_SETUP)
2089 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2090 else if (game_status == GAME_MODE_INFO)
2091 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2092 else if (game_status == GAME_MODE_SCORES)
2093 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2101 case GAME_MODE_EDITOR:
2102 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2103 HandleLevelEditorKeyInput(key);
2106 case GAME_MODE_PLAYING:
2111 RequestQuitGame(setup.ask_on_escape);
2121 if (key == KSYM_Escape)
2123 SetGameStatus(GAME_MODE_MAIN);
2131 HandleKeysDebug(key);
2134 void HandleNoEvent()
2136 HandleMouseCursor();
2138 switch (game_status)
2140 case GAME_MODE_PLAYING:
2141 HandleButtonOrFinger(-1, -1, -1);
2146 void HandleEventActions()
2148 // if (button_status && game_status != GAME_MODE_PLAYING)
2149 if (button_status && (game_status != GAME_MODE_PLAYING ||
2151 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2153 HandleButton(0, 0, button_status, -button_status);
2160 #if defined(NETWORK_AVALIABLE)
2161 if (options.network)
2165 switch (game_status)
2167 case GAME_MODE_MAIN:
2168 DrawPreviewLevelAnimation();
2171 case GAME_MODE_EDITOR:
2172 HandleLevelEditorIdle();
2180 static void HandleTileCursor(int dx, int dy, int button)
2183 ClearPlayerMouseAction();
2190 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2191 (dx < 0 ? MB_LEFTBUTTON :
2192 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2194 else if (!tile_cursor.moving)
2196 int old_xpos = tile_cursor.xpos;
2197 int old_ypos = tile_cursor.ypos;
2198 int new_xpos = old_xpos;
2199 int new_ypos = old_ypos;
2201 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2202 new_xpos = old_xpos + dx;
2204 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2205 new_ypos = old_ypos + dy;
2207 SetTileCursorTargetXY(new_xpos, new_ypos);
2211 static int HandleJoystickForAllPlayers()
2215 boolean no_joysticks_configured = TRUE;
2216 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2217 static byte joy_action_last[MAX_PLAYERS];
2219 for (i = 0; i < MAX_PLAYERS; i++)
2220 if (setup.input[i].use_joystick)
2221 no_joysticks_configured = FALSE;
2223 /* if no joysticks configured, map connected joysticks to players */
2224 if (no_joysticks_configured)
2225 use_as_joystick_nr = TRUE;
2227 for (i = 0; i < MAX_PLAYERS; i++)
2229 byte joy_action = 0;
2231 joy_action = JoystickExt(i, use_as_joystick_nr);
2232 result |= joy_action;
2234 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2235 joy_action != joy_action_last[i])
2236 stored_player[i].action = joy_action;
2238 joy_action_last[i] = joy_action;
2244 void HandleJoystick()
2246 static unsigned int joytest_delay = 0;
2247 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2248 static int joytest_last = 0;
2249 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2250 int delay_value = GADGET_FRAME_DELAY;
2251 int joystick = HandleJoystickForAllPlayers();
2252 int keyboard = key_joystick_mapping;
2253 int joy = (joystick | keyboard);
2254 int joytest = joystick;
2255 int left = joy & JOY_LEFT;
2256 int right = joy & JOY_RIGHT;
2257 int up = joy & JOY_UP;
2258 int down = joy & JOY_DOWN;
2259 int button = joy & JOY_BUTTON;
2260 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2261 int dx = (left ? -1 : right ? 1 : 0);
2262 int dy = (up ? -1 : down ? 1 : 0);
2263 boolean use_delay_value_first = (joytest != joytest_last);
2265 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2267 /* do not handle this button event anymore */
2271 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2273 if (game_status == GAME_MODE_PLAYING)
2275 // when playing MM style levels, also use delay for keyboard events
2276 joytest |= keyboard;
2278 // only use first delay value for new events, but not for changed events
2279 use_delay_value_first = (!joytest != !joytest_last);
2281 // only use delay after the initial keyboard event
2285 // for any joystick or keyboard event, enable playfield tile cursor
2286 if (dx || dy || button)
2287 SetTileCursorEnabled(TRUE);
2290 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2292 /* delay joystick/keyboard actions if axes/keys continually pressed */
2293 newbutton = dx = dy = 0;
2297 /* first start with longer delay, then continue with shorter delay */
2298 joytest_delay_value =
2299 (use_delay_value_first ? delay_value_first : delay_value);
2302 joytest_last = joytest;
2304 switch (game_status)
2306 case GAME_MODE_TITLE:
2307 case GAME_MODE_MAIN:
2308 case GAME_MODE_LEVELS:
2309 case GAME_MODE_LEVELNR:
2310 case GAME_MODE_SETUP:
2311 case GAME_MODE_INFO:
2312 case GAME_MODE_SCORES:
2314 if (game_status == GAME_MODE_TITLE)
2315 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2316 else if (game_status == GAME_MODE_MAIN)
2317 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2318 else if (game_status == GAME_MODE_LEVELS)
2319 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2320 else if (game_status == GAME_MODE_LEVELNR)
2321 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2322 else if (game_status == GAME_MODE_SETUP)
2323 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2324 else if (game_status == GAME_MODE_INFO)
2325 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2326 else if (game_status == GAME_MODE_SCORES)
2327 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2332 case GAME_MODE_PLAYING:
2334 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2335 if (tape.playing || keyboard)
2336 newbutton = ((joy & JOY_BUTTON) != 0);
2339 if (newbutton && AllPlayersGone)
2346 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2348 if (joystick & JOY_ACTION)
2349 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2351 else if (tape.recording && tape.pausing && !tape.use_mouse)
2353 if (joystick & JOY_ACTION)
2354 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2357 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2358 HandleTileCursor(dx, dy, button);
2367 void HandleSpecialGameControllerButtons(Event *event)
2369 #if defined(TARGET_SDL2)
2370 switch (event->type)
2372 case SDL_CONTROLLERBUTTONDOWN:
2373 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2374 HandleKey(KSYM_space, KEY_PRESSED);
2375 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2376 HandleKey(KSYM_Escape, KEY_PRESSED);
2380 case SDL_CONTROLLERBUTTONUP:
2381 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2382 HandleKey(KSYM_space, KEY_RELEASED);
2383 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2384 HandleKey(KSYM_Escape, KEY_RELEASED);
2391 void HandleSpecialGameControllerKeys(Key key, int key_status)
2393 #if defined(TARGET_SDL2)
2394 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2395 int button = SDL_CONTROLLER_BUTTON_INVALID;
2397 /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2398 if (key == KSYM_Rewind)
2399 button = SDL_CONTROLLER_BUTTON_A;
2400 else if (key == KSYM_FastForward || key == KSYM_Menu)
2401 button = SDL_CONTROLLER_BUTTON_B;
2403 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2407 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2408 SDL_CONTROLLERBUTTONUP);
2410 event.cbutton.which = 0; /* first joystick (Amazon Fire TV remote) */
2411 event.cbutton.button = button;
2412 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2415 HandleJoystickEvent(&event);