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 static boolean virtual_button_pressed = FALSE;
45 // forward declarations for internal use
46 static void HandleNoEvent(void);
47 static void HandleEventActions(void);
50 // event filter especially needed for SDL event filtering due to
51 // delay problems with lots of mouse motion events when mouse button
52 // not pressed (X11 can handle this with 'PointerMotionHintMask')
54 // event filter addition for SDL2: as SDL2 does not have a function to enable
55 // or disable keyboard auto-repeat, filter repeated keyboard events instead
57 static int FilterEvents(const Event *event)
61 // skip repeated key press events if keyboard auto-repeat is disabled
62 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;
78 gfx.mouse_x = ((MotionEvent *)event)->x;
79 gfx.mouse_y = ((MotionEvent *)event)->y;
82 // non-motion events are directly passed to event handler functions
83 if (event->type != EVENT_MOTIONNOTIFY)
86 motion = (MotionEvent *)event;
87 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
88 motion->y >= SY && motion->y < SY + SYSIZE);
90 // do no reset mouse cursor before all pending events have been processed
91 if (gfx.cursor_mode == cursor_mode_last &&
92 ((game_status == GAME_MODE_TITLE &&
93 gfx.cursor_mode == CURSOR_NONE) ||
94 (game_status == GAME_MODE_PLAYING &&
95 gfx.cursor_mode == CURSOR_PLAYFIELD)))
97 SetMouseCursor(CURSOR_DEFAULT);
99 DelayReached(&special_cursor_delay, 0);
101 cursor_mode_last = CURSOR_DEFAULT;
104 // skip mouse motion events without pressed button outside level editor
105 if (button_status == MB_RELEASED &&
106 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
112 // to prevent delay problems, skip mouse motion events if the very next
113 // event is also a mouse motion event (and therefore effectively only
114 // handling the last of a row of mouse motion events in the event queue)
116 static boolean SkipPressedMouseMotionEvent(const Event *event)
118 // nothing to do if the current event is not a mouse motion event
119 if (event->type != EVENT_MOTIONNOTIFY)
122 // only skip motion events with pressed button outside the game
123 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
130 PeekEvent(&next_event);
132 // if next event is also a mouse motion event, skip the current one
133 if (next_event.type == EVENT_MOTIONNOTIFY)
140 static boolean WaitValidEvent(Event *event)
144 if (!FilterEvents(event))
147 if (SkipPressedMouseMotionEvent(event))
153 /* this is especially needed for event modifications for the Android target:
154 if mouse coordinates should be modified in the event filter function,
155 using a properly installed SDL event filter does not work, because in
156 the event filter, mouse coordinates in the event structure are still
157 physical pixel positions, not logical (scaled) screen positions, so this
158 has to be handled at a later stage in the event processing functions
159 (when device pixel positions are already converted to screen positions) */
161 boolean NextValidEvent(Event *event)
163 while (PendingEvent())
164 if (WaitValidEvent(event))
170 static void HandleEvents(void)
173 unsigned int event_frame_delay = 0;
174 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
176 ResetDelayCounter(&event_frame_delay);
178 while (NextValidEvent(&event))
182 case EVENT_BUTTONPRESS:
183 case EVENT_BUTTONRELEASE:
184 HandleButtonEvent((ButtonEvent *) &event);
187 case EVENT_MOTIONNOTIFY:
188 HandleMotionEvent((MotionEvent *) &event);
191 case EVENT_WHEELMOTION:
192 HandleWheelEvent((WheelEvent *) &event);
195 case SDL_WINDOWEVENT:
196 HandleWindowEvent((WindowEvent *) &event);
199 case EVENT_FINGERPRESS:
200 case EVENT_FINGERRELEASE:
201 case EVENT_FINGERMOTION:
202 HandleFingerEvent((FingerEvent *) &event);
205 case EVENT_TEXTINPUT:
206 HandleTextEvent((TextEvent *) &event);
209 case SDL_APP_WILLENTERBACKGROUND:
210 case SDL_APP_DIDENTERBACKGROUND:
211 case SDL_APP_WILLENTERFOREGROUND:
212 case SDL_APP_DIDENTERFOREGROUND:
213 HandlePauseResumeEvent((PauseResumeEvent *) &event);
217 case EVENT_KEYRELEASE:
218 HandleKeyEvent((KeyEvent *) &event);
222 HandleOtherEvents(&event);
226 // do not handle events for longer than standard frame delay period
227 if (DelayReached(&event_frame_delay, event_frame_delay_value))
232 void HandleOtherEvents(Event *event)
237 HandleExposeEvent((ExposeEvent *) event);
240 case EVENT_UNMAPNOTIFY:
242 // This causes the game to stop not only when iconified, but also
243 // when on another virtual desktop, which might be not desired.
244 SleepWhileUnmapped();
250 HandleFocusEvent((FocusChangeEvent *) event);
253 case EVENT_CLIENTMESSAGE:
254 HandleClientMessageEvent((ClientMessageEvent *) event);
257 case SDL_CONTROLLERBUTTONDOWN:
258 case SDL_CONTROLLERBUTTONUP:
259 // for any game controller button event, disable overlay buttons
260 SetOverlayEnabled(FALSE);
262 HandleSpecialGameControllerButtons(event);
265 case SDL_CONTROLLERDEVICEADDED:
266 case SDL_CONTROLLERDEVICEREMOVED:
267 case SDL_CONTROLLERAXISMOTION:
268 case SDL_JOYAXISMOTION:
269 case SDL_JOYBUTTONDOWN:
270 case SDL_JOYBUTTONUP:
271 HandleJoystickEvent(event);
275 case SDL_DROPCOMPLETE:
278 HandleDropEvent(event);
286 static void HandleMouseCursor(void)
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 ClearAutoRepeatKeyEvents(void)
352 while (PendingEvent())
356 PeekEvent(&next_event);
358 // if event is repeated key press event, remove it from event queue
359 if (next_event.type == EVENT_KEYPRESS &&
360 next_event.key.repeat)
361 WaitEvent(&next_event);
367 void ClearEventQueue(void)
371 while (NextValidEvent(&event))
375 case EVENT_BUTTONRELEASE:
376 button_status = MB_RELEASED;
379 case EVENT_KEYRELEASE:
383 case SDL_CONTROLLERBUTTONUP:
384 HandleJoystickEvent(&event);
389 HandleOtherEvents(&event);
395 static void ClearPlayerMouseAction(void)
397 local_player->mouse_action.lx = 0;
398 local_player->mouse_action.ly = 0;
399 local_player->mouse_action.button = 0;
402 void ClearPlayerAction(void)
406 // simulate key release events for still pressed keys
407 key_joystick_mapping = 0;
408 for (i = 0; i < MAX_PLAYERS; i++)
410 stored_player[i].action = 0;
411 stored_player[i].snap_action = 0;
414 ClearJoystickState();
415 ClearPlayerMouseAction();
418 static void SetPlayerMouseAction(int mx, int my, int button)
420 int lx = getLevelFromScreenX(mx);
421 int ly = getLevelFromScreenY(my);
422 int new_button = (!local_player->mouse_action.button && button);
424 if (local_player->mouse_action.button_hint)
425 button = local_player->mouse_action.button_hint;
427 ClearPlayerMouseAction();
429 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
432 local_player->mouse_action.lx = lx;
433 local_player->mouse_action.ly = ly;
434 local_player->mouse_action.button = button;
436 if (tape.recording && tape.pausing && tape.use_mouse)
438 // un-pause a paused game only if mouse button was newly pressed down
440 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
443 SetTileCursorXY(lx, ly);
446 void SleepWhileUnmapped(void)
448 boolean window_unmapped = TRUE;
450 KeyboardAutoRepeatOn();
452 while (window_unmapped)
456 if (!WaitValidEvent(&event))
461 case EVENT_BUTTONRELEASE:
462 button_status = MB_RELEASED;
465 case EVENT_KEYRELEASE:
466 key_joystick_mapping = 0;
469 case SDL_CONTROLLERBUTTONUP:
470 HandleJoystickEvent(&event);
471 key_joystick_mapping = 0;
474 case EVENT_MAPNOTIFY:
475 window_unmapped = FALSE;
478 case EVENT_UNMAPNOTIFY:
479 // this is only to surely prevent the 'should not happen' case
480 // of recursively looping between 'SleepWhileUnmapped()' and
481 // 'HandleOtherEvents()' which usually calls this funtion.
485 HandleOtherEvents(&event);
490 if (game_status == GAME_MODE_PLAYING)
491 KeyboardAutoRepeatOffUnlessAutoplay();
494 void HandleExposeEvent(ExposeEvent *event)
498 void HandleButtonEvent(ButtonEvent *event)
500 #if DEBUG_EVENTS_BUTTON
501 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
503 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
507 // for any mouse button event, disable playfield tile cursor
508 SetTileCursorEnabled(FALSE);
510 #if defined(HAS_SCREEN_KEYBOARD)
511 if (video.shifted_up)
512 event->y += video.shifted_up_pos;
515 motion_status = FALSE;
517 if (event->type == EVENT_BUTTONPRESS)
518 button_status = event->button;
520 button_status = MB_RELEASED;
522 HandleButton(event->x, event->y, button_status, event->button);
525 void HandleMotionEvent(MotionEvent *event)
527 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
530 motion_status = TRUE;
532 #if DEBUG_EVENTS_MOTION
533 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
534 button_status, event->x, event->y);
537 HandleButton(event->x, event->y, button_status, button_status);
540 void HandleWheelEvent(WheelEvent *event)
544 #if DEBUG_EVENTS_WHEEL
546 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
547 event->which, event->x, event->y);
549 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
550 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
551 event->which, event->x, event->y,
552 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
553 "SDL_MOUSEWHEEL_FLIPPED"));
557 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
558 event->x > 0 ? MB_WHEEL_RIGHT :
559 event->y < 0 ? MB_WHEEL_DOWN :
560 event->y > 0 ? MB_WHEEL_UP : 0);
562 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
563 // accelerated mouse wheel available on Mac and Windows
564 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
566 // no accelerated mouse wheel available on Unix/Linux
567 wheel_steps = DEFAULT_WHEEL_STEPS;
570 motion_status = FALSE;
572 button_status = button_nr;
573 HandleButton(0, 0, button_status, -button_nr);
575 button_status = MB_RELEASED;
576 HandleButton(0, 0, button_status, -button_nr);
579 void HandleWindowEvent(WindowEvent *event)
581 #if DEBUG_EVENTS_WINDOW
582 int subtype = event->event;
585 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
586 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
587 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
588 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
589 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
590 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
591 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
592 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
593 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
594 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
595 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
596 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
597 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
598 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
601 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
602 event_name, event->data1, event->data2);
606 // (not needed, as the screen gets redrawn every 20 ms anyway)
607 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
608 event->event == SDL_WINDOWEVENT_RESIZED ||
609 event->event == SDL_WINDOWEVENT_EXPOSED)
613 if (event->event == SDL_WINDOWEVENT_RESIZED)
615 if (!video.fullscreen_enabled)
617 int new_window_width = event->data1;
618 int new_window_height = event->data2;
620 // if window size has changed after resizing, calculate new scaling factor
621 if (new_window_width != video.window_width ||
622 new_window_height != video.window_height)
624 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
625 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
627 // (extreme window scaling allowed, but cannot be saved permanently)
628 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
629 setup.window_scaling_percent =
630 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
631 MAX_WINDOW_SCALING_PERCENT);
633 video.window_width = new_window_width;
634 video.window_height = new_window_height;
636 if (game_status == GAME_MODE_SETUP)
637 RedrawSetupScreenAfterFullscreenToggle();
642 #if defined(PLATFORM_ANDROID)
645 int new_display_width = event->data1;
646 int new_display_height = event->data2;
648 // if fullscreen display size has changed, device has been rotated
649 if (new_display_width != video.display_width ||
650 new_display_height != video.display_height)
652 int nr = GRID_ACTIVE_NR(); // previous screen orientation
654 video.display_width = new_display_width;
655 video.display_height = new_display_height;
657 SDLSetScreenProperties();
659 // check if screen orientation has changed (should always be true here)
660 if (nr != GRID_ACTIVE_NR())
664 if (game_status == GAME_MODE_SETUP)
665 RedrawSetupScreenAfterScreenRotation(nr);
667 nr = GRID_ACTIVE_NR();
669 overlay.grid_xsize = setup.touch.grid_xsize[nr];
670 overlay.grid_ysize = setup.touch.grid_ysize[nr];
672 for (x = 0; x < MAX_GRID_XSIZE; x++)
673 for (y = 0; y < MAX_GRID_YSIZE; y++)
674 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
682 #define NUM_TOUCH_FINGERS 3
687 SDL_FingerID finger_id;
691 } touch_info[NUM_TOUCH_FINGERS];
693 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
696 int x = event->x * overlay.grid_xsize;
697 int y = event->y * overlay.grid_ysize;
698 int grid_button = overlay.grid_button[x][y];
699 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
700 Key key = (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
701 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
702 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
703 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
704 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
705 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
708 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
709 float event_x = (event->x);
710 float event_y = (event->y - ypos) / (1 - ypos);
711 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
712 event_y > 2.0 / 3.0 && event_y < 1 ?
713 setup.input[0].key.snap :
714 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
715 event_y > 2.0 / 3.0 && event_y < 1 ?
716 setup.input[0].key.drop :
717 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
718 event_y > 0 && event_y < 1.0 / 3.0 ?
719 setup.input[0].key.up :
720 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
721 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
722 setup.input[0].key.left :
723 event_x > 8.0 / 9.0 && event_x < 1 &&
724 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
725 setup.input[0].key.right :
726 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
727 event_y > 2.0 / 3.0 && event_y < 1 ?
728 setup.input[0].key.down :
731 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
733 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
737 virtual_button_pressed = (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
739 // for any touch input event, enable overlay buttons (if activated)
740 SetOverlayEnabled(TRUE);
742 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
743 getKeyNameFromKey(key), key_status_name, event->fingerId);
745 if (key_status == KEY_PRESSED)
746 overlay.grid_button_action |= grid_button_action;
748 overlay.grid_button_action &= ~grid_button_action;
750 // check if we already know this touch event's finger id
751 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
753 if (touch_info[i].touched &&
754 touch_info[i].finger_id == event->fingerId)
756 // Error(ERR_DEBUG, "MARK 1: %d", i);
762 if (i >= NUM_TOUCH_FINGERS)
764 if (key_status == KEY_PRESSED)
766 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
768 // unknown finger id -- get new, empty slot, if available
769 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
771 if (touch_info[i].counter < oldest_counter)
774 oldest_counter = touch_info[i].counter;
776 // Error(ERR_DEBUG, "MARK 2: %d", i);
779 if (!touch_info[i].touched)
781 // Error(ERR_DEBUG, "MARK 3: %d", i);
787 if (i >= NUM_TOUCH_FINGERS)
789 // all slots allocated -- use oldest slot
792 // Error(ERR_DEBUG, "MARK 4: %d", i);
797 // release of previously unknown key (should not happen)
799 if (key != KSYM_UNDEFINED)
801 HandleKey(key, KEY_RELEASED);
803 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
804 getKeyNameFromKey(key), "KEY_RELEASED", i);
809 if (i < NUM_TOUCH_FINGERS)
811 if (key_status == KEY_PRESSED)
813 if (touch_info[i].key != key)
815 if (touch_info[i].key != KSYM_UNDEFINED)
817 HandleKey(touch_info[i].key, KEY_RELEASED);
819 // undraw previous grid button when moving finger away
820 overlay.grid_button_action &= ~touch_info[i].action;
822 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
823 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
826 if (key != KSYM_UNDEFINED)
828 HandleKey(key, KEY_PRESSED);
830 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
831 getKeyNameFromKey(key), "KEY_PRESSED", i);
835 touch_info[i].touched = TRUE;
836 touch_info[i].finger_id = event->fingerId;
837 touch_info[i].counter = Counter();
838 touch_info[i].key = key;
839 touch_info[i].action = grid_button_action;
843 if (touch_info[i].key != KSYM_UNDEFINED)
845 HandleKey(touch_info[i].key, KEY_RELEASED);
847 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
848 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
851 touch_info[i].touched = FALSE;
852 touch_info[i].finger_id = 0;
853 touch_info[i].counter = 0;
854 touch_info[i].key = 0;
855 touch_info[i].action = JOY_NO_ACTION;
860 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
862 static Key motion_key_x = KSYM_UNDEFINED;
863 static Key motion_key_y = KSYM_UNDEFINED;
864 static Key button_key = KSYM_UNDEFINED;
865 static float motion_x1, motion_y1;
866 static float button_x1, button_y1;
867 static SDL_FingerID motion_id = -1;
868 static SDL_FingerID button_id = -1;
869 int move_trigger_distance_percent = setup.touch.move_distance;
870 int drop_trigger_distance_percent = setup.touch.drop_distance;
871 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
872 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
873 float event_x = event->x;
874 float event_y = event->y;
876 if (event->type == EVENT_FINGERPRESS)
878 if (event_x > 1.0 / 3.0)
882 motion_id = event->fingerId;
887 motion_key_x = KSYM_UNDEFINED;
888 motion_key_y = KSYM_UNDEFINED;
890 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
896 button_id = event->fingerId;
901 button_key = setup.input[0].key.snap;
903 HandleKey(button_key, KEY_PRESSED);
905 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
908 else if (event->type == EVENT_FINGERRELEASE)
910 if (event->fingerId == motion_id)
914 if (motion_key_x != KSYM_UNDEFINED)
915 HandleKey(motion_key_x, KEY_RELEASED);
916 if (motion_key_y != KSYM_UNDEFINED)
917 HandleKey(motion_key_y, KEY_RELEASED);
919 motion_key_x = KSYM_UNDEFINED;
920 motion_key_y = KSYM_UNDEFINED;
922 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
924 else if (event->fingerId == button_id)
928 if (button_key != KSYM_UNDEFINED)
929 HandleKey(button_key, KEY_RELEASED);
931 button_key = KSYM_UNDEFINED;
933 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
936 else if (event->type == EVENT_FINGERMOTION)
938 if (event->fingerId == motion_id)
940 float distance_x = ABS(event_x - motion_x1);
941 float distance_y = ABS(event_y - motion_y1);
942 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
943 event_x > motion_x1 ? setup.input[0].key.right :
945 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
946 event_y > motion_y1 ? setup.input[0].key.down :
949 if (distance_x < move_trigger_distance / 2 ||
950 distance_x < distance_y)
951 new_motion_key_x = KSYM_UNDEFINED;
953 if (distance_y < move_trigger_distance / 2 ||
954 distance_y < distance_x)
955 new_motion_key_y = KSYM_UNDEFINED;
957 if (distance_x > move_trigger_distance ||
958 distance_y > move_trigger_distance)
960 if (new_motion_key_x != motion_key_x)
962 if (motion_key_x != KSYM_UNDEFINED)
963 HandleKey(motion_key_x, KEY_RELEASED);
964 if (new_motion_key_x != KSYM_UNDEFINED)
965 HandleKey(new_motion_key_x, KEY_PRESSED);
968 if (new_motion_key_y != motion_key_y)
970 if (motion_key_y != KSYM_UNDEFINED)
971 HandleKey(motion_key_y, KEY_RELEASED);
972 if (new_motion_key_y != KSYM_UNDEFINED)
973 HandleKey(new_motion_key_y, KEY_PRESSED);
979 motion_key_x = new_motion_key_x;
980 motion_key_y = new_motion_key_y;
982 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
985 else if (event->fingerId == button_id)
987 float distance_x = ABS(event_x - button_x1);
988 float distance_y = ABS(event_y - button_y1);
990 if (distance_x < drop_trigger_distance / 2 &&
991 distance_y > drop_trigger_distance)
993 if (button_key == setup.input[0].key.snap)
994 HandleKey(button_key, KEY_RELEASED);
999 button_key = setup.input[0].key.drop;
1001 HandleKey(button_key, KEY_PRESSED);
1003 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1009 void HandleFingerEvent(FingerEvent *event)
1011 #if DEBUG_EVENTS_FINGER
1012 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
1013 event->type == EVENT_FINGERPRESS ? "pressed" :
1014 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
1018 event->dx, event->dy,
1022 runtime.uses_touch_device = TRUE;
1024 if (game_status != GAME_MODE_PLAYING)
1027 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1029 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1030 local_player->mouse_action.button_hint =
1031 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1032 event->x < 0.5 ? MB_LEFTBUTTON :
1033 event->x > 0.5 ? MB_RIGHTBUTTON :
1039 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1040 HandleFingerEvent_VirtualButtons(event);
1041 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1042 HandleFingerEvent_WipeGestures(event);
1045 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1047 static int old_mx = 0, old_my = 0;
1048 static int last_button = MB_LEFTBUTTON;
1049 static boolean touched = FALSE;
1050 static boolean tapped = FALSE;
1052 // screen tile was tapped (but finger not touching the screen anymore)
1053 // (this point will also be reached without receiving a touch event)
1054 if (tapped && !touched)
1056 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1061 // stop here if this function was not triggered by a touch event
1065 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1067 // finger started touching the screen
1077 ClearPlayerMouseAction();
1079 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1082 else if (button == MB_RELEASED && touched)
1084 // finger stopped touching the screen
1089 SetPlayerMouseAction(old_mx, old_my, last_button);
1091 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1093 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1098 // finger moved while touching the screen
1100 int old_x = getLevelFromScreenX(old_mx);
1101 int old_y = getLevelFromScreenY(old_my);
1102 int new_x = getLevelFromScreenX(mx);
1103 int new_y = getLevelFromScreenY(my);
1105 if (new_x != old_x || new_y != old_y)
1110 // finger moved left or right from (horizontal) starting position
1112 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1114 SetPlayerMouseAction(old_mx, old_my, button_nr);
1116 last_button = button_nr;
1118 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1122 // finger stays at or returned to (horizontal) starting position
1124 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1126 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1131 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1133 static int old_mx = 0, old_my = 0;
1134 static int last_button = MB_LEFTBUTTON;
1135 static boolean touched = FALSE;
1136 static boolean tapped = FALSE;
1138 // screen tile was tapped (but finger not touching the screen anymore)
1139 // (this point will also be reached without receiving a touch event)
1140 if (tapped && !touched)
1142 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1147 // stop here if this function was not triggered by a touch event
1151 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1153 // finger started touching the screen
1163 ClearPlayerMouseAction();
1165 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1168 else if (button == MB_RELEASED && touched)
1170 // finger stopped touching the screen
1175 SetPlayerMouseAction(old_mx, old_my, last_button);
1177 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1179 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1184 // finger moved while touching the screen
1186 int old_x = getLevelFromScreenX(old_mx);
1187 int old_y = getLevelFromScreenY(old_my);
1188 int new_x = getLevelFromScreenX(mx);
1189 int new_y = getLevelFromScreenY(my);
1191 if (new_x != old_x || new_y != old_y)
1193 // finger moved away from starting position
1195 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1197 // quickly alternate between clicking and releasing for maximum speed
1198 if (FrameCounter % 2 == 0)
1199 button_nr = MB_RELEASED;
1201 SetPlayerMouseAction(old_mx, old_my, button_nr);
1204 last_button = button_nr;
1208 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1212 // finger stays at or returned to starting position
1214 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1216 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1221 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1223 static int old_mx = 0, old_my = 0;
1224 static Key motion_key_x = KSYM_UNDEFINED;
1225 static Key motion_key_y = KSYM_UNDEFINED;
1226 static boolean touched = FALSE;
1227 static boolean started_on_player = FALSE;
1228 static boolean player_is_dropping = FALSE;
1229 static int player_drop_count = 0;
1230 static int last_player_x = -1;
1231 static int last_player_y = -1;
1233 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1242 started_on_player = FALSE;
1243 player_is_dropping = FALSE;
1244 player_drop_count = 0;
1248 motion_key_x = KSYM_UNDEFINED;
1249 motion_key_y = KSYM_UNDEFINED;
1251 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1254 else if (button == MB_RELEASED && touched)
1261 if (motion_key_x != KSYM_UNDEFINED)
1262 HandleKey(motion_key_x, KEY_RELEASED);
1263 if (motion_key_y != KSYM_UNDEFINED)
1264 HandleKey(motion_key_y, KEY_RELEASED);
1266 if (started_on_player)
1268 if (player_is_dropping)
1270 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1272 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1276 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1278 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1282 motion_key_x = KSYM_UNDEFINED;
1283 motion_key_y = KSYM_UNDEFINED;
1285 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1290 int src_x = local_player->jx;
1291 int src_y = local_player->jy;
1292 int dst_x = getLevelFromScreenX(old_mx);
1293 int dst_y = getLevelFromScreenY(old_my);
1294 int dx = dst_x - src_x;
1295 int dy = dst_y - src_y;
1296 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1297 dx > 0 ? setup.input[0].key.right :
1299 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1300 dy > 0 ? setup.input[0].key.down :
1303 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1304 (last_player_x != local_player->jx ||
1305 last_player_y != local_player->jy))
1307 // in case of asymmetric diagonal movement, use "preferred" direction
1309 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1311 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1312 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1314 local_player->last_move_dir = last_move_dir;
1316 // (required to prevent accidentally forcing direction for next movement)
1317 last_player_x = local_player->jx;
1318 last_player_y = local_player->jy;
1321 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1323 started_on_player = TRUE;
1324 player_drop_count = getPlayerInventorySize(0);
1325 player_is_dropping = (player_drop_count > 0);
1327 if (player_is_dropping)
1329 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1331 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1335 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1337 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1340 else if (dx != 0 || dy != 0)
1342 if (player_is_dropping &&
1343 player_drop_count == getPlayerInventorySize(0))
1345 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1347 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1348 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1350 player_is_dropping = FALSE;
1354 if (new_motion_key_x != motion_key_x)
1356 Error(ERR_DEBUG, "---------- %s %s ----------",
1357 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1358 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1360 if (motion_key_x != KSYM_UNDEFINED)
1361 HandleKey(motion_key_x, KEY_RELEASED);
1362 if (new_motion_key_x != KSYM_UNDEFINED)
1363 HandleKey(new_motion_key_x, KEY_PRESSED);
1366 if (new_motion_key_y != motion_key_y)
1368 Error(ERR_DEBUG, "---------- %s %s ----------",
1369 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1370 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1372 if (motion_key_y != KSYM_UNDEFINED)
1373 HandleKey(motion_key_y, KEY_RELEASED);
1374 if (new_motion_key_y != KSYM_UNDEFINED)
1375 HandleKey(new_motion_key_y, KEY_PRESSED);
1378 motion_key_x = new_motion_key_x;
1379 motion_key_y = new_motion_key_y;
1383 static void HandleButtonOrFinger(int mx, int my, int button)
1385 if (game_status != GAME_MODE_PLAYING)
1388 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1390 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1391 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1392 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1393 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1394 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1395 SetPlayerMouseAction(mx, my, button); // special case
1399 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1400 HandleButtonOrFinger_FollowFinger(mx, my, button);
1404 static boolean checkTextInputKeyModState(void)
1406 // when playing, only handle raw key events and ignore text input
1407 if (game_status == GAME_MODE_PLAYING)
1410 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1413 void HandleTextEvent(TextEvent *event)
1415 char *text = event->text;
1416 Key key = getKeyFromKeyName(text);
1418 #if DEBUG_EVENTS_TEXT
1419 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1422 text[0], (int)(text[0]),
1424 getKeyNameFromKey(key),
1428 #if !defined(HAS_SCREEN_KEYBOARD)
1429 // non-mobile devices: only handle key input with modifier keys pressed here
1430 // (every other key input is handled directly as physical key input event)
1431 if (!checkTextInputKeyModState())
1435 // process text input as "classic" (with uppercase etc.) key input event
1436 HandleKey(key, KEY_PRESSED);
1437 HandleKey(key, KEY_RELEASED);
1440 void HandlePauseResumeEvent(PauseResumeEvent *event)
1442 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1446 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1452 void HandleKeyEvent(KeyEvent *event)
1454 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1455 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1456 Key key = GetEventKey(event, with_modifiers);
1457 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1459 #if DEBUG_EVENTS_KEY
1460 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1461 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1462 event->keysym.scancode,
1467 getKeyNameFromKey(key));
1470 #if defined(PLATFORM_ANDROID)
1471 if (key == KSYM_Back)
1473 // always map the "back" button to the "escape" key on Android devices
1476 else if (key == KSYM_Menu)
1478 // the "menu" button can be used to toggle displaying virtual buttons
1479 if (key_status == KEY_PRESSED)
1480 SetOverlayEnabled(!GetOverlayEnabled());
1484 // for any other "real" key event, disable virtual buttons
1485 SetOverlayEnabled(FALSE);
1489 HandleKeyModState(keymod, key_status);
1491 // only handle raw key input without text modifier keys pressed
1492 if (!checkTextInputKeyModState())
1493 HandleKey(key, key_status);
1496 void HandleFocusEvent(FocusChangeEvent *event)
1498 static int old_joystick_status = -1;
1500 if (event->type == EVENT_FOCUSOUT)
1502 KeyboardAutoRepeatOn();
1503 old_joystick_status = joystick.status;
1504 joystick.status = JOYSTICK_NOT_AVAILABLE;
1506 ClearPlayerAction();
1508 else if (event->type == EVENT_FOCUSIN)
1510 /* When there are two Rocks'n'Diamonds windows which overlap and
1511 the player moves the pointer from one game window to the other,
1512 a 'FocusOut' event is generated for the window the pointer is
1513 leaving and a 'FocusIn' event is generated for the window the
1514 pointer is entering. In some cases, it can happen that the
1515 'FocusIn' event is handled by the one game process before the
1516 'FocusOut' event by the other game process. In this case the
1517 X11 environment would end up with activated keyboard auto repeat,
1518 because unfortunately this is a global setting and not (which
1519 would be far better) set for each X11 window individually.
1520 The effect would be keyboard auto repeat while playing the game
1521 (game_status == GAME_MODE_PLAYING), which is not desired.
1522 To avoid this special case, we just wait 1/10 second before
1523 processing the 'FocusIn' event. */
1525 if (game_status == GAME_MODE_PLAYING)
1528 KeyboardAutoRepeatOffUnlessAutoplay();
1531 if (old_joystick_status != -1)
1532 joystick.status = old_joystick_status;
1536 void HandleClientMessageEvent(ClientMessageEvent *event)
1538 if (CheckCloseWindowEvent(event))
1542 static int HandleDropFileEvent(char *filename)
1544 Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
1546 // check and extract dropped zip files into correct user data directory
1547 if (!strSuffixLower(filename, ".zip"))
1549 Error(ERR_WARN, "file '%s' not supported", filename);
1551 return TREE_TYPE_UNDEFINED;
1554 TreeInfo *tree_node = NULL;
1555 int tree_type = GetZipFileTreeType(filename);
1556 char *directory = TREE_USERDIR(tree_type);
1558 if (directory == NULL)
1560 Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1562 return TREE_TYPE_UNDEFINED;
1565 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1566 game_status == GAME_MODE_LEVELS &&
1567 leveldir_current->node_parent != NULL)
1569 // extract new level set next to currently selected level set
1570 tree_node = leveldir_current;
1572 // get parent directory of currently selected level set directory
1573 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1575 // use private level directory instead of top-level package level directory
1576 if (strPrefix(directory, options.level_directory) &&
1577 strEqual(leveldir_current->node_parent->fullpath, "."))
1578 directory = getUserLevelDir(NULL);
1581 // extract level or artwork set from zip file to target directory
1582 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1584 if (top_dir == NULL)
1586 // error message already issued by "ExtractZipFileIntoDirectory()"
1588 return TREE_TYPE_UNDEFINED;
1591 // add extracted level or artwork set to tree info structure
1592 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1594 // update menu screen (and possibly change current level set)
1595 DrawScreenAfterAddingSet(top_dir, tree_type);
1600 static void HandleDropTextEvent(char *text)
1602 Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1605 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1606 int num_artwork_sets_succeeded,
1607 int num_files_failed)
1609 // only show request dialog if no other request dialog already active
1610 if (game.request_active)
1613 // this case can happen with drag-and-drop with older SDL versions
1614 if (num_level_sets_succeeded == 0 &&
1615 num_artwork_sets_succeeded == 0 &&
1616 num_files_failed == 0)
1621 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1623 char message_part1[50];
1625 sprintf(message_part1, "New %s set%s added",
1626 (num_artwork_sets_succeeded == 0 ? "level" :
1627 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1628 (num_level_sets_succeeded +
1629 num_artwork_sets_succeeded > 1 ? "s" : ""));
1631 if (num_files_failed > 0)
1632 sprintf(message, "%s, but %d dropped file%s failed!",
1633 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1635 sprintf(message, "%s!", message_part1);
1637 else if (num_files_failed > 0)
1639 sprintf(message, "Failed to process dropped file%s!",
1640 num_files_failed > 1 ? "s" : "");
1643 Request(message, REQ_CONFIRM);
1646 void HandleDropEvent(Event *event)
1648 static boolean confirm_on_drop_complete = FALSE;
1649 static int num_level_sets_succeeded = 0;
1650 static int num_artwork_sets_succeeded = 0;
1651 static int num_files_failed = 0;
1653 switch (event->type)
1657 confirm_on_drop_complete = TRUE;
1658 num_level_sets_succeeded = 0;
1659 num_artwork_sets_succeeded = 0;
1660 num_files_failed = 0;
1667 int tree_type = HandleDropFileEvent(event->drop.file);
1669 if (tree_type == TREE_TYPE_LEVEL_DIR)
1670 num_level_sets_succeeded++;
1671 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1672 tree_type == TREE_TYPE_SOUNDS_DIR ||
1673 tree_type == TREE_TYPE_MUSIC_DIR)
1674 num_artwork_sets_succeeded++;
1678 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1679 if (!confirm_on_drop_complete)
1681 // process all remaining events, including further SDL_DROPFILE events
1684 HandleDropCompleteEvent(num_level_sets_succeeded,
1685 num_artwork_sets_succeeded,
1688 num_level_sets_succeeded = 0;
1689 num_artwork_sets_succeeded = 0;
1690 num_files_failed = 0;
1698 HandleDropTextEvent(event->drop.file);
1703 case SDL_DROPCOMPLETE:
1705 HandleDropCompleteEvent(num_level_sets_succeeded,
1706 num_artwork_sets_succeeded,
1713 if (event->drop.file != NULL)
1714 SDL_free(event->drop.file);
1717 void HandleButton(int mx, int my, int button, int button_nr)
1719 static int old_mx = 0, old_my = 0;
1720 boolean button_hold = FALSE;
1721 boolean handle_gadgets = TRUE;
1727 button_nr = -button_nr;
1736 #if defined(PLATFORM_ANDROID)
1737 // when playing, only handle gadgets when using "follow finger" controls
1738 // or when using touch controls in combination with the MM game engine
1739 // or when using gadgets that do not overlap with virtual buttons
1741 (game_status != GAME_MODE_PLAYING ||
1742 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1743 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1744 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1745 !virtual_button_pressed));
1748 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1750 // do not handle this button event anymore
1751 return; // force mouse event not to be handled at all
1754 if (handle_gadgets && HandleGadgets(mx, my, button))
1756 // do not handle this button event anymore
1757 mx = my = -32; // force mouse event to be outside screen tiles
1760 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1763 // do not use scroll wheel button events for anything other than gadgets
1764 if (IS_WHEEL_BUTTON(button_nr))
1767 switch (game_status)
1769 case GAME_MODE_TITLE:
1770 HandleTitleScreen(mx, my, 0, 0, button);
1773 case GAME_MODE_MAIN:
1774 HandleMainMenu(mx, my, 0, 0, button);
1777 case GAME_MODE_PSEUDO_TYPENAME:
1778 HandleTypeName(0, KSYM_Return);
1781 case GAME_MODE_LEVELS:
1782 HandleChooseLevelSet(mx, my, 0, 0, button);
1785 case GAME_MODE_LEVELNR:
1786 HandleChooseLevelNr(mx, my, 0, 0, button);
1789 case GAME_MODE_SCORES:
1790 HandleHallOfFame(0, 0, 0, 0, button);
1793 case GAME_MODE_EDITOR:
1794 HandleLevelEditorIdle();
1797 case GAME_MODE_INFO:
1798 HandleInfoScreen(mx, my, 0, 0, button);
1801 case GAME_MODE_SETUP:
1802 HandleSetupScreen(mx, my, 0, 0, button);
1805 case GAME_MODE_PLAYING:
1806 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1807 HandleButtonOrFinger(mx, my, button);
1809 SetPlayerMouseAction(mx, my, button);
1812 if (button == MB_PRESSED && !motion_status && !button_hold &&
1813 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1814 DumpTileFromScreen(mx, my);
1824 static boolean is_string_suffix(char *string, char *suffix)
1826 int string_len = strlen(string);
1827 int suffix_len = strlen(suffix);
1829 if (suffix_len > string_len)
1832 return (strEqual(&string[string_len - suffix_len], suffix));
1835 #define MAX_CHEAT_INPUT_LEN 32
1837 static void HandleKeysSpecial(Key key)
1839 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1840 char letter = getCharFromKey(key);
1841 int cheat_input_len = strlen(cheat_input);
1847 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1849 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1850 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1852 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1855 cheat_input[cheat_input_len++] = letter;
1856 cheat_input[cheat_input_len] = '\0';
1858 #if DEBUG_EVENTS_KEY
1859 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1862 if (game_status == GAME_MODE_MAIN)
1864 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1865 is_string_suffix(cheat_input, ":ist"))
1867 InsertSolutionTape();
1869 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1870 is_string_suffix(cheat_input, ":pst"))
1874 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1875 is_string_suffix(cheat_input, ":rg"))
1877 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1880 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1881 is_string_suffix(cheat_input, ":rs"))
1883 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1886 else if (is_string_suffix(cheat_input, ":reload-music") ||
1887 is_string_suffix(cheat_input, ":rm"))
1889 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1892 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1893 is_string_suffix(cheat_input, ":ra"))
1895 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1896 1 << ARTWORK_TYPE_SOUNDS |
1897 1 << ARTWORK_TYPE_MUSIC);
1900 else if (is_string_suffix(cheat_input, ":dump-level") ||
1901 is_string_suffix(cheat_input, ":dl"))
1905 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1906 is_string_suffix(cheat_input, ":dt"))
1910 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1911 is_string_suffix(cheat_input, ":ft"))
1913 /* fix single-player tapes that contain player input for more than one
1914 player (due to a bug in 3.3.1.2 and earlier versions), which results
1915 in playing levels with more than one player in multi-player mode,
1916 even though the tape was originally recorded in single-player mode */
1918 // remove player input actions for all players but the first one
1919 for (i = 1; i < MAX_PLAYERS; i++)
1920 tape.player_participates[i] = FALSE;
1922 tape.changed = TRUE;
1924 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1925 is_string_suffix(cheat_input, ":snl"))
1927 SaveNativeLevel(&level);
1929 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1930 is_string_suffix(cheat_input, ":fps"))
1932 global.show_frames_per_second = !global.show_frames_per_second;
1935 else if (game_status == GAME_MODE_PLAYING)
1938 if (is_string_suffix(cheat_input, ".q"))
1939 DEBUG_SetMaximumDynamite();
1942 else if (game_status == GAME_MODE_EDITOR)
1944 if (is_string_suffix(cheat_input, ":dump-brush") ||
1945 is_string_suffix(cheat_input, ":DB"))
1949 else if (is_string_suffix(cheat_input, ":DDB"))
1954 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1956 if (letter == 'x') // copy brush to clipboard (small size)
1958 CopyBrushToClipboard_Small();
1960 else if (letter == 'c') // copy brush to clipboard (normal size)
1962 CopyBrushToClipboard();
1964 else if (letter == 'v') // paste brush from Clipboard
1966 CopyClipboardToBrush();
1971 // special key shortcuts for all game modes
1972 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1973 is_string_suffix(cheat_input, ":dea") ||
1974 is_string_suffix(cheat_input, ":DEA"))
1976 DumpGadgetIdentifiers();
1977 DumpScreenIdentifiers();
1981 boolean HandleKeysDebug(Key key, int key_status)
1986 if (key_status != KEY_PRESSED)
1989 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1991 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1993 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1995 if (key == setup.debug.frame_delay_key[i] &&
1996 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1998 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1999 setup.debug.frame_delay[i] : setup.game_frame_delay);
2001 if (!setup.debug.frame_delay_game_only)
2002 MenuFrameDelay = GameFrameDelay;
2004 SetVideoFrameDelay(GameFrameDelay);
2006 if (GameFrameDelay > ONE_SECOND_DELAY)
2007 Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
2008 else if (GameFrameDelay != 0)
2009 Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
2010 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
2011 GAME_FRAME_DELAY * 100 / GameFrameDelay);
2013 Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
2020 if (game_status == GAME_MODE_PLAYING)
2024 options.debug = !options.debug;
2026 Error(ERR_INFO, "debug mode %s",
2027 (options.debug ? "enabled" : "disabled"));
2031 else if (key == KSYM_v)
2033 Error(ERR_INFO, "currently using game engine version %d",
2034 game.engine_version);
2044 void HandleKey(Key key, int key_status)
2046 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2047 static boolean ignore_repeated_key = FALSE;
2048 static struct SetupKeyboardInfo ski;
2049 static struct SetupShortcutInfo ssi;
2058 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
2059 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
2060 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
2061 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
2062 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2063 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2068 if (HandleKeysDebug(key, key_status))
2069 return; // do not handle already processed keys again
2071 // map special keys (media keys / remote control buttons) to default keys
2072 if (key == KSYM_PlayPause)
2074 else if (key == KSYM_Select)
2077 HandleSpecialGameControllerKeys(key, key_status);
2079 if (game_status == GAME_MODE_PLAYING)
2081 // only needed for single-step tape recording mode
2082 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2085 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2087 byte key_action = 0;
2088 byte key_snap_action = 0;
2090 if (setup.input[pnr].use_joystick)
2093 ski = setup.input[pnr].key;
2095 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2096 if (key == *key_info[i].key_custom)
2097 key_action |= key_info[i].action;
2099 // use combined snap+direction keys for the first player only
2102 ssi = setup.shortcut;
2104 // also remember normal snap key when handling snap+direction keys
2105 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2107 for (i = 0; i < NUM_DIRECTIONS; i++)
2109 if (key == *key_info[i].key_snap)
2111 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2112 key_snap_action |= key_info[i].action;
2117 if (key_status == KEY_PRESSED)
2119 stored_player[pnr].action |= key_action;
2120 stored_player[pnr].snap_action |= key_snap_action;
2124 stored_player[pnr].action &= ~key_action;
2125 stored_player[pnr].snap_action &= ~key_snap_action;
2128 // restore snap action if one of several pressed snap keys was released
2129 if (stored_player[pnr].snap_action)
2130 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2132 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2134 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2136 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2138 // if snap key already pressed, keep pause mode when releasing
2139 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2140 has_snapped[pnr] = TRUE;
2142 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2144 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2146 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2147 getRedDiskReleaseFlag_SP() == 0)
2149 // add a single inactive frame before dropping starts
2150 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2151 stored_player[pnr].force_dropping = TRUE;
2154 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2156 // if snap key was pressed without direction, leave pause mode
2157 if (!has_snapped[pnr])
2158 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2160 has_snapped[pnr] = FALSE;
2163 else if (tape.recording && tape.pausing && !tape.use_mouse)
2165 // prevent key release events from un-pausing a paused game
2166 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2167 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2170 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2171 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2177 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2178 if (key == key_info[i].key_default)
2179 joy |= key_info[i].action;
2184 if (key_status == KEY_PRESSED)
2185 key_joystick_mapping |= joy;
2187 key_joystick_mapping &= ~joy;
2192 if (game_status != GAME_MODE_PLAYING)
2193 key_joystick_mapping = 0;
2195 if (key_status == KEY_RELEASED)
2197 // reset flag to ignore repeated "key pressed" events after key release
2198 ignore_repeated_key = FALSE;
2203 if ((key == KSYM_F11 ||
2204 ((key == KSYM_Return ||
2205 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2206 video.fullscreen_available &&
2207 !ignore_repeated_key)
2209 setup.fullscreen = !setup.fullscreen;
2211 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2213 if (game_status == GAME_MODE_SETUP)
2214 RedrawSetupScreenAfterFullscreenToggle();
2216 // set flag to ignore repeated "key pressed" events
2217 ignore_repeated_key = TRUE;
2222 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2223 key == KSYM_minus || key == KSYM_KP_Subtract ||
2224 key == KSYM_plus || key == KSYM_KP_Add ||
2225 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2226 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2227 video.window_scaling_available &&
2228 !video.fullscreen_enabled)
2230 if (key == KSYM_0 || key == KSYM_KP_0)
2231 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2232 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2233 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2235 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2237 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2238 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2239 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2240 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2242 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2244 if (game_status == GAME_MODE_SETUP)
2245 RedrawSetupScreenAfterFullscreenToggle();
2250 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2251 key == KSYM_Return ||
2252 key == KSYM_Escape), TRUE))
2254 // do not handle this key event anymore
2255 if (key != KSYM_Escape) // always allow ESC key to be handled
2259 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2260 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2267 if (game_status == GAME_MODE_MAIN &&
2268 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2270 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2275 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2277 if (key == setup.shortcut.save_game)
2279 else if (key == setup.shortcut.load_game)
2281 else if (key == setup.shortcut.toggle_pause)
2282 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2284 HandleTapeButtonKeys(key);
2285 HandleSoundButtonKeys(key);
2288 if (game_status == GAME_MODE_PLAYING && !network_playing)
2290 int centered_player_nr_next = -999;
2292 if (key == setup.shortcut.focus_player_all)
2293 centered_player_nr_next = -1;
2295 for (i = 0; i < MAX_PLAYERS; i++)
2296 if (key == setup.shortcut.focus_player[i])
2297 centered_player_nr_next = i;
2299 if (centered_player_nr_next != -999)
2301 game.centered_player_nr_next = centered_player_nr_next;
2302 game.set_centered_player = TRUE;
2306 tape.centered_player_nr_next = game.centered_player_nr_next;
2307 tape.set_centered_player = TRUE;
2312 HandleKeysSpecial(key);
2314 if (HandleGadgetsKeyInput(key))
2315 return; // do not handle already processed keys again
2317 switch (game_status)
2319 case GAME_MODE_PSEUDO_TYPENAME:
2320 HandleTypeName(0, key);
2323 case GAME_MODE_TITLE:
2324 case GAME_MODE_MAIN:
2325 case GAME_MODE_LEVELS:
2326 case GAME_MODE_LEVELNR:
2327 case GAME_MODE_SETUP:
2328 case GAME_MODE_INFO:
2329 case GAME_MODE_SCORES:
2331 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2338 if (game_status == GAME_MODE_TITLE)
2339 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2340 else if (game_status == GAME_MODE_MAIN)
2341 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2342 else if (game_status == GAME_MODE_LEVELS)
2343 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2344 else if (game_status == GAME_MODE_LEVELNR)
2345 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2346 else if (game_status == GAME_MODE_SETUP)
2347 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2348 else if (game_status == GAME_MODE_INFO)
2349 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2350 else if (game_status == GAME_MODE_SCORES)
2351 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2355 if (game_status != GAME_MODE_MAIN)
2356 FadeSkipNextFadeIn();
2358 if (game_status == GAME_MODE_TITLE)
2359 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2360 else if (game_status == GAME_MODE_LEVELS)
2361 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2362 else if (game_status == GAME_MODE_LEVELNR)
2363 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2364 else if (game_status == GAME_MODE_SETUP)
2365 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2366 else if (game_status == GAME_MODE_INFO)
2367 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2368 else if (game_status == GAME_MODE_SCORES)
2369 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2373 if (game_status == GAME_MODE_LEVELS)
2374 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2375 else if (game_status == GAME_MODE_LEVELNR)
2376 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2377 else if (game_status == GAME_MODE_SETUP)
2378 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2379 else if (game_status == GAME_MODE_INFO)
2380 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2381 else if (game_status == GAME_MODE_SCORES)
2382 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2385 case KSYM_Page_Down:
2386 if (game_status == GAME_MODE_LEVELS)
2387 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2388 else if (game_status == GAME_MODE_LEVELNR)
2389 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2390 else if (game_status == GAME_MODE_SETUP)
2391 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2392 else if (game_status == GAME_MODE_INFO)
2393 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2394 else if (game_status == GAME_MODE_SCORES)
2395 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2403 case GAME_MODE_EDITOR:
2404 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2405 HandleLevelEditorKeyInput(key);
2408 case GAME_MODE_PLAYING:
2413 RequestQuitGame(setup.ask_on_escape);
2423 if (key == KSYM_Escape)
2425 SetGameStatus(GAME_MODE_MAIN);
2434 void HandleNoEvent(void)
2436 HandleMouseCursor();
2438 switch (game_status)
2440 case GAME_MODE_PLAYING:
2441 HandleButtonOrFinger(-1, -1, -1);
2446 void HandleEventActions(void)
2448 // if (button_status && game_status != GAME_MODE_PLAYING)
2449 if (button_status && (game_status != GAME_MODE_PLAYING ||
2451 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2453 HandleButton(0, 0, button_status, -button_status);
2460 if (network.enabled)
2463 switch (game_status)
2465 case GAME_MODE_MAIN:
2466 DrawPreviewLevelAnimation();
2469 case GAME_MODE_EDITOR:
2470 HandleLevelEditorIdle();
2478 static void HandleTileCursor(int dx, int dy, int button)
2481 ClearPlayerMouseAction();
2488 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2489 (dx < 0 ? MB_LEFTBUTTON :
2490 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2492 else if (!tile_cursor.moving)
2494 int old_xpos = tile_cursor.xpos;
2495 int old_ypos = tile_cursor.ypos;
2496 int new_xpos = old_xpos;
2497 int new_ypos = old_ypos;
2499 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2500 new_xpos = old_xpos + dx;
2502 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2503 new_ypos = old_ypos + dy;
2505 SetTileCursorTargetXY(new_xpos, new_ypos);
2509 static int HandleJoystickForAllPlayers(void)
2513 boolean no_joysticks_configured = TRUE;
2514 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2515 static byte joy_action_last[MAX_PLAYERS];
2517 for (i = 0; i < MAX_PLAYERS; i++)
2518 if (setup.input[i].use_joystick)
2519 no_joysticks_configured = FALSE;
2521 // if no joysticks configured, map connected joysticks to players
2522 if (no_joysticks_configured)
2523 use_as_joystick_nr = TRUE;
2525 for (i = 0; i < MAX_PLAYERS; i++)
2527 byte joy_action = 0;
2529 joy_action = JoystickExt(i, use_as_joystick_nr);
2530 result |= joy_action;
2532 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2533 joy_action != joy_action_last[i])
2534 stored_player[i].action = joy_action;
2536 joy_action_last[i] = joy_action;
2542 void HandleJoystick(void)
2544 static unsigned int joytest_delay = 0;
2545 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2546 static int joytest_last = 0;
2547 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2548 int delay_value = GADGET_FRAME_DELAY;
2549 int joystick = HandleJoystickForAllPlayers();
2550 int keyboard = key_joystick_mapping;
2551 int joy = (joystick | keyboard);
2552 int joytest = joystick;
2553 int left = joy & JOY_LEFT;
2554 int right = joy & JOY_RIGHT;
2555 int up = joy & JOY_UP;
2556 int down = joy & JOY_DOWN;
2557 int button = joy & JOY_BUTTON;
2558 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2559 int dx = (left ? -1 : right ? 1 : 0);
2560 int dy = (up ? -1 : down ? 1 : 0);
2561 boolean use_delay_value_first = (joytest != joytest_last);
2563 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2565 // do not handle this button event anymore
2569 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2570 anyTextGadgetActive()))
2572 // leave name input in main menu or text input gadget
2573 HandleKey(KSYM_Escape, KEY_PRESSED);
2574 HandleKey(KSYM_Escape, KEY_RELEASED);
2579 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2581 if (game_status == GAME_MODE_PLAYING)
2583 // when playing MM style levels, also use delay for keyboard events
2584 joytest |= keyboard;
2586 // only use first delay value for new events, but not for changed events
2587 use_delay_value_first = (!joytest != !joytest_last);
2589 // only use delay after the initial keyboard event
2593 // for any joystick or keyboard event, enable playfield tile cursor
2594 if (dx || dy || button)
2595 SetTileCursorEnabled(TRUE);
2598 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2600 // delay joystick/keyboard actions if axes/keys continually pressed
2601 newbutton = dx = dy = 0;
2605 // first start with longer delay, then continue with shorter delay
2606 joytest_delay_value =
2607 (use_delay_value_first ? delay_value_first : delay_value);
2610 joytest_last = joytest;
2612 switch (game_status)
2614 case GAME_MODE_TITLE:
2615 case GAME_MODE_MAIN:
2616 case GAME_MODE_LEVELS:
2617 case GAME_MODE_LEVELNR:
2618 case GAME_MODE_SETUP:
2619 case GAME_MODE_INFO:
2620 case GAME_MODE_SCORES:
2622 if (anyTextGadgetActive())
2625 if (game_status == GAME_MODE_TITLE)
2626 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2627 else if (game_status == GAME_MODE_MAIN)
2628 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2629 else if (game_status == GAME_MODE_LEVELS)
2630 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2631 else if (game_status == GAME_MODE_LEVELNR)
2632 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2633 else if (game_status == GAME_MODE_SETUP)
2634 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2635 else if (game_status == GAME_MODE_INFO)
2636 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2637 else if (game_status == GAME_MODE_SCORES)
2638 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2643 case GAME_MODE_PLAYING:
2645 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2646 if (tape.playing || keyboard)
2647 newbutton = ((joy & JOY_BUTTON) != 0);
2650 if (newbutton && game.all_players_gone)
2657 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2659 if (joystick & JOY_ACTION)
2660 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2662 else if (tape.recording && tape.pausing && !tape.use_mouse)
2664 if (joystick & JOY_ACTION)
2665 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2668 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2669 HandleTileCursor(dx, dy, button);
2678 void HandleSpecialGameControllerButtons(Event *event)
2683 switch (event->type)
2685 case SDL_CONTROLLERBUTTONDOWN:
2686 key_status = KEY_PRESSED;
2689 case SDL_CONTROLLERBUTTONUP:
2690 key_status = KEY_RELEASED;
2697 switch (event->cbutton.button)
2699 case SDL_CONTROLLER_BUTTON_START:
2703 case SDL_CONTROLLER_BUTTON_BACK:
2711 HandleKey(key, key_status);
2714 void HandleSpecialGameControllerKeys(Key key, int key_status)
2716 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2717 int button = SDL_CONTROLLER_BUTTON_INVALID;
2719 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2720 if (key == KSYM_Rewind)
2721 button = SDL_CONTROLLER_BUTTON_A;
2722 else if (key == KSYM_FastForward || key == KSYM_Menu)
2723 button = SDL_CONTROLLER_BUTTON_B;
2725 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2729 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2730 SDL_CONTROLLERBUTTONUP);
2732 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2733 event.cbutton.button = button;
2734 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2737 HandleJoystickEvent(&event);
2742 boolean DoKeysymAction(int keysym)
2746 Key key = (Key)(-keysym);
2748 HandleKey(key, KEY_PRESSED);
2749 HandleKey(key, KEY_RELEASED);