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;
43 static boolean stop_processing_events = FALSE;
46 // forward declarations for internal use
47 static void HandleNoEvent(void);
48 static void HandleEventActions(void);
51 // event filter to set mouse x/y position (for pointer class global animations)
52 // (this is especially required to ensure smooth global animation mouse pointer
53 // movement when the screen is updated without handling events; this can happen
54 // when drawing door/envelope request animations, for example)
56 int FilterMouseMotionEvents(void *userdata, Event *event)
58 if (event->type == EVENT_MOTIONNOTIFY)
60 int mouse_x = ((MotionEvent *)event)->x;
61 int mouse_y = ((MotionEvent *)event)->y;
63 UpdateRawMousePosition(mouse_x, mouse_y);
69 // event filter especially needed for SDL event filtering due to
70 // delay problems with lots of mouse motion events when mouse button
71 // not pressed (X11 can handle this with 'PointerMotionHintMask')
73 // event filter addition for SDL2: as SDL2 does not have a function to enable
74 // or disable keyboard auto-repeat, filter repeated keyboard events instead
76 static int FilterEvents(const Event *event)
80 // skip repeated key press events if keyboard auto-repeat is disabled
81 if (event->type == EVENT_KEYPRESS &&
86 if (event->type == EVENT_BUTTONPRESS ||
87 event->type == EVENT_BUTTONRELEASE)
89 ((ButtonEvent *)event)->x -= video.screen_xoffset;
90 ((ButtonEvent *)event)->y -= video.screen_yoffset;
92 else if (event->type == EVENT_MOTIONNOTIFY)
94 ((MotionEvent *)event)->x -= video.screen_xoffset;
95 ((MotionEvent *)event)->y -= video.screen_yoffset;
98 // non-motion events are directly passed to event handler functions
99 if (event->type != EVENT_MOTIONNOTIFY)
102 motion = (MotionEvent *)event;
103 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
104 motion->y >= SY && motion->y < SY + SYSIZE);
106 // set correct mouse x/y position (for pointer class global animations)
107 // (this is required in rare cases where the mouse x/y position calculated
108 // from raw values (to apply logical screen size scaling corrections) does
109 // not match the final mouse event x/y position -- this may happen because
110 // the SDL renderer's viewport position is internally represented as float,
111 // but only accessible as integer, which may lead to rounding errors)
112 gfx.mouse_x = motion->x;
113 gfx.mouse_y = motion->y;
115 // do no reset mouse cursor before all pending events have been processed
116 if (gfx.cursor_mode == cursor_mode_last &&
117 ((game_status == GAME_MODE_TITLE &&
118 gfx.cursor_mode == CURSOR_NONE) ||
119 (game_status == GAME_MODE_PLAYING &&
120 gfx.cursor_mode == CURSOR_PLAYFIELD)))
122 SetMouseCursor(CURSOR_DEFAULT);
124 DelayReached(&special_cursor_delay, 0);
126 cursor_mode_last = CURSOR_DEFAULT;
129 // skip mouse motion events without pressed button outside level editor
130 if (button_status == MB_RELEASED &&
131 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
137 // to prevent delay problems, skip mouse motion events if the very next
138 // event is also a mouse motion event (and therefore effectively only
139 // handling the last of a row of mouse motion events in the event queue)
141 static boolean SkipPressedMouseMotionEvent(const Event *event)
143 // nothing to do if the current event is not a mouse motion event
144 if (event->type != EVENT_MOTIONNOTIFY)
147 // only skip motion events with pressed button outside the game
148 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
155 PeekEvent(&next_event);
157 // if next event is also a mouse motion event, skip the current one
158 if (next_event.type == EVENT_MOTIONNOTIFY)
165 static boolean WaitValidEvent(Event *event)
169 if (!FilterEvents(event))
172 if (SkipPressedMouseMotionEvent(event))
178 /* this is especially needed for event modifications for the Android target:
179 if mouse coordinates should be modified in the event filter function,
180 using a properly installed SDL event filter does not work, because in
181 the event filter, mouse coordinates in the event structure are still
182 physical pixel positions, not logical (scaled) screen positions, so this
183 has to be handled at a later stage in the event processing functions
184 (when device pixel positions are already converted to screen positions) */
186 boolean NextValidEvent(Event *event)
188 while (PendingEvent())
189 if (WaitValidEvent(event))
195 void StopProcessingEvents(void)
197 stop_processing_events = TRUE;
200 static void HandleEvents(void)
203 unsigned int event_frame_delay = 0;
204 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
206 ResetDelayCounter(&event_frame_delay);
208 stop_processing_events = FALSE;
210 while (NextValidEvent(&event))
214 case EVENT_BUTTONPRESS:
215 case EVENT_BUTTONRELEASE:
216 HandleButtonEvent((ButtonEvent *) &event);
219 case EVENT_MOTIONNOTIFY:
220 HandleMotionEvent((MotionEvent *) &event);
223 case EVENT_WHEELMOTION:
224 HandleWheelEvent((WheelEvent *) &event);
227 case SDL_WINDOWEVENT:
228 HandleWindowEvent((WindowEvent *) &event);
231 case EVENT_FINGERPRESS:
232 case EVENT_FINGERRELEASE:
233 case EVENT_FINGERMOTION:
234 HandleFingerEvent((FingerEvent *) &event);
237 case EVENT_TEXTINPUT:
238 HandleTextEvent((TextEvent *) &event);
241 case SDL_APP_WILLENTERBACKGROUND:
242 case SDL_APP_DIDENTERBACKGROUND:
243 case SDL_APP_WILLENTERFOREGROUND:
244 case SDL_APP_DIDENTERFOREGROUND:
245 HandlePauseResumeEvent((PauseResumeEvent *) &event);
249 case EVENT_KEYRELEASE:
250 HandleKeyEvent((KeyEvent *) &event);
254 HandleUserEvent((UserEvent *) &event);
258 HandleOtherEvents(&event);
262 // do not handle events for longer than standard frame delay period
263 if (DelayReached(&event_frame_delay, event_frame_delay_value))
266 // do not handle any further events if triggered by a special flag
267 if (stop_processing_events)
272 void HandleOtherEvents(Event *event)
276 case SDL_CONTROLLERBUTTONDOWN:
277 case SDL_CONTROLLERBUTTONUP:
278 // for any game controller button event, disable overlay buttons
279 SetOverlayEnabled(FALSE);
281 HandleSpecialGameControllerButtons(event);
284 case SDL_CONTROLLERDEVICEADDED:
285 case SDL_CONTROLLERDEVICEREMOVED:
286 case SDL_CONTROLLERAXISMOTION:
287 case SDL_JOYAXISMOTION:
288 case SDL_JOYBUTTONDOWN:
289 case SDL_JOYBUTTONUP:
290 HandleJoystickEvent(event);
294 case SDL_DROPCOMPLETE:
297 HandleDropEvent(event);
309 static void HandleMouseCursor(void)
311 if (game_status == GAME_MODE_TITLE)
313 // when showing title screens, hide mouse pointer (if not moved)
315 if (gfx.cursor_mode != CURSOR_NONE &&
316 DelayReached(&special_cursor_delay, special_cursor_delay_value))
318 SetMouseCursor(CURSOR_NONE);
321 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
324 // when playing, display a special mouse pointer inside the playfield
326 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
327 cursor_inside_playfield &&
328 DelayReached(&special_cursor_delay, special_cursor_delay_value))
330 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
332 SetMouseCursor(CURSOR_PLAYFIELD);
335 else if (gfx.cursor_mode != CURSOR_DEFAULT)
337 SetMouseCursor(CURSOR_DEFAULT);
340 // this is set after all pending events have been processed
341 cursor_mode_last = gfx.cursor_mode;
353 // execute event related actions after pending events have been processed
354 HandleEventActions();
356 // don't use all CPU time when idle; the main loop while playing
357 // has its own synchronization and is CPU friendly, too
359 if (game_status == GAME_MODE_PLAYING)
362 // always copy backbuffer to visible screen for every video frame
365 // reset video frame delay to default (may change again while playing)
366 SetVideoFrameDelay(MenuFrameDelay);
368 if (game_status == GAME_MODE_QUIT)
373 void ClearAutoRepeatKeyEvents(void)
375 while (PendingEvent())
379 PeekEvent(&next_event);
381 // if event is repeated key press event, remove it from event queue
382 if (next_event.type == EVENT_KEYPRESS &&
383 next_event.key.repeat)
384 WaitEvent(&next_event);
390 void ClearEventQueue(void)
394 while (NextValidEvent(&event))
398 case EVENT_BUTTONRELEASE:
399 button_status = MB_RELEASED;
402 case EVENT_KEYRELEASE:
406 case SDL_CONTROLLERBUTTONUP:
407 HandleJoystickEvent(&event);
412 HandleOtherEvents(&event);
418 static void ClearPlayerMouseAction(void)
420 local_player->mouse_action.lx = 0;
421 local_player->mouse_action.ly = 0;
422 local_player->mouse_action.button = 0;
425 void ClearPlayerAction(void)
429 // simulate key release events for still pressed keys
430 key_joystick_mapping = 0;
431 for (i = 0; i < MAX_PLAYERS; i++)
433 stored_player[i].action = 0;
434 stored_player[i].snap_action = 0;
437 ClearJoystickState();
438 ClearPlayerMouseAction();
441 static void SetPlayerMouseAction(int mx, int my, int button)
443 int lx = getLevelFromScreenX(mx);
444 int ly = getLevelFromScreenY(my);
445 int new_button = (!local_player->mouse_action.button && button);
447 if (local_player->mouse_action.button_hint)
448 button = local_player->mouse_action.button_hint;
450 ClearPlayerMouseAction();
452 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
455 local_player->mouse_action.lx = lx;
456 local_player->mouse_action.ly = ly;
457 local_player->mouse_action.button = button;
459 if (tape.recording && tape.pausing && tape.use_mouse)
461 // un-pause a paused game only if mouse button was newly pressed down
463 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
466 SetTileCursorXY(lx, ly);
469 void HandleButtonEvent(ButtonEvent *event)
471 #if DEBUG_EVENTS_BUTTON
472 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
474 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
478 // for any mouse button event, disable playfield tile cursor
479 SetTileCursorEnabled(FALSE);
481 #if defined(HAS_SCREEN_KEYBOARD)
482 if (video.shifted_up)
483 event->y += video.shifted_up_pos;
486 motion_status = FALSE;
488 if (event->type == EVENT_BUTTONPRESS)
489 button_status = event->button;
491 button_status = MB_RELEASED;
493 HandleButton(event->x, event->y, button_status, event->button);
496 void HandleMotionEvent(MotionEvent *event)
498 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
501 motion_status = TRUE;
503 #if DEBUG_EVENTS_MOTION
504 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
505 button_status, event->x, event->y);
508 HandleButton(event->x, event->y, button_status, button_status);
511 void HandleWheelEvent(WheelEvent *event)
515 #if DEBUG_EVENTS_WHEEL
517 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
518 event->which, event->x, event->y);
520 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
521 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
522 event->which, event->x, event->y,
523 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
524 "SDL_MOUSEWHEEL_FLIPPED"));
528 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
529 event->x > 0 ? MB_WHEEL_RIGHT :
530 event->y < 0 ? MB_WHEEL_DOWN :
531 event->y > 0 ? MB_WHEEL_UP : 0);
533 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
534 // accelerated mouse wheel available on Mac and Windows
535 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
537 // no accelerated mouse wheel available on Unix/Linux
538 wheel_steps = DEFAULT_WHEEL_STEPS;
541 motion_status = FALSE;
543 button_status = button_nr;
544 HandleButton(0, 0, button_status, -button_nr);
546 button_status = MB_RELEASED;
547 HandleButton(0, 0, button_status, -button_nr);
550 void HandleWindowEvent(WindowEvent *event)
552 #if DEBUG_EVENTS_WINDOW
553 int subtype = event->event;
556 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
557 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
558 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
559 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
560 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
561 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
562 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
563 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
564 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
565 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
566 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
567 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
568 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
569 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
572 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
573 event_name, event->data1, event->data2);
577 // (not needed, as the screen gets redrawn every 20 ms anyway)
578 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
579 event->event == SDL_WINDOWEVENT_RESIZED ||
580 event->event == SDL_WINDOWEVENT_EXPOSED)
584 if (event->event == SDL_WINDOWEVENT_RESIZED)
586 if (!video.fullscreen_enabled)
588 int new_window_width = event->data1;
589 int new_window_height = event->data2;
591 // if window size has changed after resizing, calculate new scaling factor
592 if (new_window_width != video.window_width ||
593 new_window_height != video.window_height)
595 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
596 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
598 // (extreme window scaling allowed, but cannot be saved permanently)
599 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
600 setup.window_scaling_percent =
601 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
602 MAX_WINDOW_SCALING_PERCENT);
604 video.window_width = new_window_width;
605 video.window_height = new_window_height;
607 if (game_status == GAME_MODE_SETUP)
608 RedrawSetupScreenAfterFullscreenToggle();
610 UpdateMousePosition();
615 #if defined(PLATFORM_ANDROID)
618 int new_display_width = event->data1;
619 int new_display_height = event->data2;
621 // if fullscreen display size has changed, device has been rotated
622 if (new_display_width != video.display_width ||
623 new_display_height != video.display_height)
625 int nr = GRID_ACTIVE_NR(); // previous screen orientation
627 video.display_width = new_display_width;
628 video.display_height = new_display_height;
630 SDLSetScreenProperties();
632 // check if screen orientation has changed (should always be true here)
633 if (nr != GRID_ACTIVE_NR())
637 if (game_status == GAME_MODE_SETUP)
638 RedrawSetupScreenAfterScreenRotation(nr);
640 nr = GRID_ACTIVE_NR();
642 overlay.grid_xsize = setup.touch.grid_xsize[nr];
643 overlay.grid_ysize = setup.touch.grid_ysize[nr];
645 for (x = 0; x < MAX_GRID_XSIZE; x++)
646 for (y = 0; y < MAX_GRID_YSIZE; y++)
647 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
655 #define NUM_TOUCH_FINGERS 3
660 SDL_FingerID finger_id;
664 } touch_info[NUM_TOUCH_FINGERS];
666 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
669 int x = event->x * overlay.grid_xsize;
670 int y = event->y * overlay.grid_ysize;
671 int grid_button = overlay.grid_button[x][y];
672 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
673 Key key = (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
674 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
675 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
676 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
677 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
678 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
681 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
682 float event_x = (event->x);
683 float event_y = (event->y - ypos) / (1 - ypos);
684 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
685 event_y > 2.0 / 3.0 && event_y < 1 ?
686 setup.input[0].key.snap :
687 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
688 event_y > 2.0 / 3.0 && event_y < 1 ?
689 setup.input[0].key.drop :
690 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
691 event_y > 0 && event_y < 1.0 / 3.0 ?
692 setup.input[0].key.up :
693 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
694 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
695 setup.input[0].key.left :
696 event_x > 8.0 / 9.0 && event_x < 1 &&
697 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
698 setup.input[0].key.right :
699 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
700 event_y > 2.0 / 3.0 && event_y < 1 ?
701 setup.input[0].key.down :
704 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
706 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
710 virtual_button_pressed = (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
712 // for any touch input event, enable overlay buttons (if activated)
713 SetOverlayEnabled(TRUE);
715 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
716 getKeyNameFromKey(key), key_status_name, event->fingerId);
718 if (key_status == KEY_PRESSED)
719 overlay.grid_button_action |= grid_button_action;
721 overlay.grid_button_action &= ~grid_button_action;
723 // check if we already know this touch event's finger id
724 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
726 if (touch_info[i].touched &&
727 touch_info[i].finger_id == event->fingerId)
729 // Error(ERR_DEBUG, "MARK 1: %d", i);
735 if (i >= NUM_TOUCH_FINGERS)
737 if (key_status == KEY_PRESSED)
739 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
741 // unknown finger id -- get new, empty slot, if available
742 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
744 if (touch_info[i].counter < oldest_counter)
747 oldest_counter = touch_info[i].counter;
749 // Error(ERR_DEBUG, "MARK 2: %d", i);
752 if (!touch_info[i].touched)
754 // Error(ERR_DEBUG, "MARK 3: %d", i);
760 if (i >= NUM_TOUCH_FINGERS)
762 // all slots allocated -- use oldest slot
765 // Error(ERR_DEBUG, "MARK 4: %d", i);
770 // release of previously unknown key (should not happen)
772 if (key != KSYM_UNDEFINED)
774 HandleKey(key, KEY_RELEASED);
776 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
777 getKeyNameFromKey(key), "KEY_RELEASED", i);
782 if (i < NUM_TOUCH_FINGERS)
784 if (key_status == KEY_PRESSED)
786 if (touch_info[i].key != key)
788 if (touch_info[i].key != KSYM_UNDEFINED)
790 HandleKey(touch_info[i].key, KEY_RELEASED);
792 // undraw previous grid button when moving finger away
793 overlay.grid_button_action &= ~touch_info[i].action;
795 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
796 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
799 if (key != KSYM_UNDEFINED)
801 HandleKey(key, KEY_PRESSED);
803 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
804 getKeyNameFromKey(key), "KEY_PRESSED", i);
808 touch_info[i].touched = TRUE;
809 touch_info[i].finger_id = event->fingerId;
810 touch_info[i].counter = Counter();
811 touch_info[i].key = key;
812 touch_info[i].action = grid_button_action;
816 if (touch_info[i].key != KSYM_UNDEFINED)
818 HandleKey(touch_info[i].key, KEY_RELEASED);
820 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
821 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
824 touch_info[i].touched = FALSE;
825 touch_info[i].finger_id = 0;
826 touch_info[i].counter = 0;
827 touch_info[i].key = 0;
828 touch_info[i].action = JOY_NO_ACTION;
833 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
835 static Key motion_key_x = KSYM_UNDEFINED;
836 static Key motion_key_y = KSYM_UNDEFINED;
837 static Key button_key = KSYM_UNDEFINED;
838 static float motion_x1, motion_y1;
839 static float button_x1, button_y1;
840 static SDL_FingerID motion_id = -1;
841 static SDL_FingerID button_id = -1;
842 int move_trigger_distance_percent = setup.touch.move_distance;
843 int drop_trigger_distance_percent = setup.touch.drop_distance;
844 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
845 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
846 float event_x = event->x;
847 float event_y = event->y;
849 if (event->type == EVENT_FINGERPRESS)
851 if (event_x > 1.0 / 3.0)
855 motion_id = event->fingerId;
860 motion_key_x = KSYM_UNDEFINED;
861 motion_key_y = KSYM_UNDEFINED;
863 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
869 button_id = event->fingerId;
874 button_key = setup.input[0].key.snap;
876 HandleKey(button_key, KEY_PRESSED);
878 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
881 else if (event->type == EVENT_FINGERRELEASE)
883 if (event->fingerId == motion_id)
887 if (motion_key_x != KSYM_UNDEFINED)
888 HandleKey(motion_key_x, KEY_RELEASED);
889 if (motion_key_y != KSYM_UNDEFINED)
890 HandleKey(motion_key_y, KEY_RELEASED);
892 motion_key_x = KSYM_UNDEFINED;
893 motion_key_y = KSYM_UNDEFINED;
895 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
897 else if (event->fingerId == button_id)
901 if (button_key != KSYM_UNDEFINED)
902 HandleKey(button_key, KEY_RELEASED);
904 button_key = KSYM_UNDEFINED;
906 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
909 else if (event->type == EVENT_FINGERMOTION)
911 if (event->fingerId == motion_id)
913 float distance_x = ABS(event_x - motion_x1);
914 float distance_y = ABS(event_y - motion_y1);
915 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
916 event_x > motion_x1 ? setup.input[0].key.right :
918 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
919 event_y > motion_y1 ? setup.input[0].key.down :
922 if (distance_x < move_trigger_distance / 2 ||
923 distance_x < distance_y)
924 new_motion_key_x = KSYM_UNDEFINED;
926 if (distance_y < move_trigger_distance / 2 ||
927 distance_y < distance_x)
928 new_motion_key_y = KSYM_UNDEFINED;
930 if (distance_x > move_trigger_distance ||
931 distance_y > move_trigger_distance)
933 if (new_motion_key_x != motion_key_x)
935 if (motion_key_x != KSYM_UNDEFINED)
936 HandleKey(motion_key_x, KEY_RELEASED);
937 if (new_motion_key_x != KSYM_UNDEFINED)
938 HandleKey(new_motion_key_x, KEY_PRESSED);
941 if (new_motion_key_y != motion_key_y)
943 if (motion_key_y != KSYM_UNDEFINED)
944 HandleKey(motion_key_y, KEY_RELEASED);
945 if (new_motion_key_y != KSYM_UNDEFINED)
946 HandleKey(new_motion_key_y, KEY_PRESSED);
952 motion_key_x = new_motion_key_x;
953 motion_key_y = new_motion_key_y;
955 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
958 else if (event->fingerId == button_id)
960 float distance_x = ABS(event_x - button_x1);
961 float distance_y = ABS(event_y - button_y1);
963 if (distance_x < drop_trigger_distance / 2 &&
964 distance_y > drop_trigger_distance)
966 if (button_key == setup.input[0].key.snap)
967 HandleKey(button_key, KEY_RELEASED);
972 button_key = setup.input[0].key.drop;
974 HandleKey(button_key, KEY_PRESSED);
976 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
982 void HandleFingerEvent(FingerEvent *event)
984 #if DEBUG_EVENTS_FINGER
985 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
986 event->type == EVENT_FINGERPRESS ? "pressed" :
987 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
991 event->dx, event->dy,
995 runtime.uses_touch_device = TRUE;
997 if (game_status != GAME_MODE_PLAYING)
1000 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1002 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1003 local_player->mouse_action.button_hint =
1004 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1005 event->x < 0.5 ? MB_LEFTBUTTON :
1006 event->x > 0.5 ? MB_RIGHTBUTTON :
1012 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1013 HandleFingerEvent_VirtualButtons(event);
1014 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1015 HandleFingerEvent_WipeGestures(event);
1018 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1020 static int old_mx = 0, old_my = 0;
1021 static int last_button = MB_LEFTBUTTON;
1022 static boolean touched = FALSE;
1023 static boolean tapped = FALSE;
1025 // screen tile was tapped (but finger not touching the screen anymore)
1026 // (this point will also be reached without receiving a touch event)
1027 if (tapped && !touched)
1029 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1034 // stop here if this function was not triggered by a touch event
1038 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1040 // finger started touching the screen
1050 ClearPlayerMouseAction();
1052 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1055 else if (button == MB_RELEASED && touched)
1057 // finger stopped touching the screen
1062 SetPlayerMouseAction(old_mx, old_my, last_button);
1064 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1066 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1071 // finger moved while touching the screen
1073 int old_x = getLevelFromScreenX(old_mx);
1074 int old_y = getLevelFromScreenY(old_my);
1075 int new_x = getLevelFromScreenX(mx);
1076 int new_y = getLevelFromScreenY(my);
1078 if (new_x != old_x || new_y != old_y)
1083 // finger moved left or right from (horizontal) starting position
1085 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1087 SetPlayerMouseAction(old_mx, old_my, button_nr);
1089 last_button = button_nr;
1091 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1095 // finger stays at or returned to (horizontal) starting position
1097 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1099 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1104 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1106 static int old_mx = 0, old_my = 0;
1107 static int last_button = MB_LEFTBUTTON;
1108 static boolean touched = FALSE;
1109 static boolean tapped = FALSE;
1111 // screen tile was tapped (but finger not touching the screen anymore)
1112 // (this point will also be reached without receiving a touch event)
1113 if (tapped && !touched)
1115 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1120 // stop here if this function was not triggered by a touch event
1124 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1126 // finger started touching the screen
1136 ClearPlayerMouseAction();
1138 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1141 else if (button == MB_RELEASED && touched)
1143 // finger stopped touching the screen
1148 SetPlayerMouseAction(old_mx, old_my, last_button);
1150 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1152 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1157 // finger moved while touching the screen
1159 int old_x = getLevelFromScreenX(old_mx);
1160 int old_y = getLevelFromScreenY(old_my);
1161 int new_x = getLevelFromScreenX(mx);
1162 int new_y = getLevelFromScreenY(my);
1164 if (new_x != old_x || new_y != old_y)
1166 // finger moved away from starting position
1168 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1170 // quickly alternate between clicking and releasing for maximum speed
1171 if (FrameCounter % 2 == 0)
1172 button_nr = MB_RELEASED;
1174 SetPlayerMouseAction(old_mx, old_my, button_nr);
1177 last_button = button_nr;
1181 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1185 // finger stays at or returned to starting position
1187 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1189 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1194 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1196 static int old_mx = 0, old_my = 0;
1197 static Key motion_key_x = KSYM_UNDEFINED;
1198 static Key motion_key_y = KSYM_UNDEFINED;
1199 static boolean touched = FALSE;
1200 static boolean started_on_player = FALSE;
1201 static boolean player_is_dropping = FALSE;
1202 static int player_drop_count = 0;
1203 static int last_player_x = -1;
1204 static int last_player_y = -1;
1206 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1215 started_on_player = FALSE;
1216 player_is_dropping = FALSE;
1217 player_drop_count = 0;
1221 motion_key_x = KSYM_UNDEFINED;
1222 motion_key_y = KSYM_UNDEFINED;
1224 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1227 else if (button == MB_RELEASED && touched)
1234 if (motion_key_x != KSYM_UNDEFINED)
1235 HandleKey(motion_key_x, KEY_RELEASED);
1236 if (motion_key_y != KSYM_UNDEFINED)
1237 HandleKey(motion_key_y, KEY_RELEASED);
1239 if (started_on_player)
1241 if (player_is_dropping)
1243 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1245 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1249 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1251 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1255 motion_key_x = KSYM_UNDEFINED;
1256 motion_key_y = KSYM_UNDEFINED;
1258 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1263 int src_x = local_player->jx;
1264 int src_y = local_player->jy;
1265 int dst_x = getLevelFromScreenX(old_mx);
1266 int dst_y = getLevelFromScreenY(old_my);
1267 int dx = dst_x - src_x;
1268 int dy = dst_y - src_y;
1269 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1270 dx > 0 ? setup.input[0].key.right :
1272 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1273 dy > 0 ? setup.input[0].key.down :
1276 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1277 (last_player_x != local_player->jx ||
1278 last_player_y != local_player->jy))
1280 // in case of asymmetric diagonal movement, use "preferred" direction
1282 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1284 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1285 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1287 local_player->last_move_dir = last_move_dir;
1289 // (required to prevent accidentally forcing direction for next movement)
1290 last_player_x = local_player->jx;
1291 last_player_y = local_player->jy;
1294 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1296 started_on_player = TRUE;
1297 player_drop_count = getPlayerInventorySize(0);
1298 player_is_dropping = (player_drop_count > 0);
1300 if (player_is_dropping)
1302 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1304 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1308 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1310 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1313 else if (dx != 0 || dy != 0)
1315 if (player_is_dropping &&
1316 player_drop_count == getPlayerInventorySize(0))
1318 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1320 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1321 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1323 player_is_dropping = FALSE;
1327 if (new_motion_key_x != motion_key_x)
1329 Error(ERR_DEBUG, "---------- %s %s ----------",
1330 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1331 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1333 if (motion_key_x != KSYM_UNDEFINED)
1334 HandleKey(motion_key_x, KEY_RELEASED);
1335 if (new_motion_key_x != KSYM_UNDEFINED)
1336 HandleKey(new_motion_key_x, KEY_PRESSED);
1339 if (new_motion_key_y != motion_key_y)
1341 Error(ERR_DEBUG, "---------- %s %s ----------",
1342 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1343 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1345 if (motion_key_y != KSYM_UNDEFINED)
1346 HandleKey(motion_key_y, KEY_RELEASED);
1347 if (new_motion_key_y != KSYM_UNDEFINED)
1348 HandleKey(new_motion_key_y, KEY_PRESSED);
1351 motion_key_x = new_motion_key_x;
1352 motion_key_y = new_motion_key_y;
1356 static void HandleButtonOrFinger(int mx, int my, int button)
1358 if (game_status != GAME_MODE_PLAYING)
1361 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1363 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1364 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1365 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1366 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1367 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1368 SetPlayerMouseAction(mx, my, button); // special case
1372 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1373 HandleButtonOrFinger_FollowFinger(mx, my, button);
1377 static boolean checkTextInputKeyModState(void)
1379 // when playing, only handle raw key events and ignore text input
1380 if (game_status == GAME_MODE_PLAYING)
1383 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1386 void HandleTextEvent(TextEvent *event)
1388 char *text = event->text;
1389 Key key = getKeyFromKeyName(text);
1391 #if DEBUG_EVENTS_TEXT
1392 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1395 text[0], (int)(text[0]),
1397 getKeyNameFromKey(key),
1401 #if !defined(HAS_SCREEN_KEYBOARD)
1402 // non-mobile devices: only handle key input with modifier keys pressed here
1403 // (every other key input is handled directly as physical key input event)
1404 if (!checkTextInputKeyModState())
1408 // process text input as "classic" (with uppercase etc.) key input event
1409 HandleKey(key, KEY_PRESSED);
1410 HandleKey(key, KEY_RELEASED);
1413 void HandlePauseResumeEvent(PauseResumeEvent *event)
1415 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1419 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1425 void HandleKeyEvent(KeyEvent *event)
1427 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1428 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1429 Key key = GetEventKey(event, with_modifiers);
1430 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1432 #if DEBUG_EVENTS_KEY
1433 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1434 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1435 event->keysym.scancode,
1440 getKeyNameFromKey(key));
1443 #if defined(PLATFORM_ANDROID)
1444 if (key == KSYM_Back)
1446 // always map the "back" button to the "escape" key on Android devices
1449 else if (key == KSYM_Menu)
1451 // the "menu" button can be used to toggle displaying virtual buttons
1452 if (key_status == KEY_PRESSED)
1453 SetOverlayEnabled(!GetOverlayEnabled());
1457 // for any other "real" key event, disable virtual buttons
1458 SetOverlayEnabled(FALSE);
1462 HandleKeyModState(keymod, key_status);
1464 // only handle raw key input without text modifier keys pressed
1465 if (!checkTextInputKeyModState())
1466 HandleKey(key, key_status);
1469 static int HandleDropFileEvent(char *filename)
1471 Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
1473 // check and extract dropped zip files into correct user data directory
1474 if (!strSuffixLower(filename, ".zip"))
1476 Error(ERR_WARN, "file '%s' not supported", filename);
1478 return TREE_TYPE_UNDEFINED;
1481 TreeInfo *tree_node = NULL;
1482 int tree_type = GetZipFileTreeType(filename);
1483 char *directory = TREE_USERDIR(tree_type);
1485 if (directory == NULL)
1487 Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1489 return TREE_TYPE_UNDEFINED;
1492 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1493 game_status == GAME_MODE_LEVELS &&
1494 leveldir_current->node_parent != NULL)
1496 // extract new level set next to currently selected level set
1497 tree_node = leveldir_current;
1499 // get parent directory of currently selected level set directory
1500 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1502 // use private level directory instead of top-level package level directory
1503 if (strPrefix(directory, options.level_directory) &&
1504 strEqual(leveldir_current->node_parent->fullpath, "."))
1505 directory = getUserLevelDir(NULL);
1508 // extract level or artwork set from zip file to target directory
1509 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1511 if (top_dir == NULL)
1513 // error message already issued by "ExtractZipFileIntoDirectory()"
1515 return TREE_TYPE_UNDEFINED;
1518 // add extracted level or artwork set to tree info structure
1519 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1521 // update menu screen (and possibly change current level set)
1522 DrawScreenAfterAddingSet(top_dir, tree_type);
1527 static void HandleDropTextEvent(char *text)
1529 Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1532 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1533 int num_artwork_sets_succeeded,
1534 int num_files_failed)
1536 // only show request dialog if no other request dialog already active
1537 if (game.request_active)
1540 // this case can happen with drag-and-drop with older SDL versions
1541 if (num_level_sets_succeeded == 0 &&
1542 num_artwork_sets_succeeded == 0 &&
1543 num_files_failed == 0)
1548 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1550 char message_part1[50];
1552 sprintf(message_part1, "New %s set%s added",
1553 (num_artwork_sets_succeeded == 0 ? "level" :
1554 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1555 (num_level_sets_succeeded +
1556 num_artwork_sets_succeeded > 1 ? "s" : ""));
1558 if (num_files_failed > 0)
1559 sprintf(message, "%s, but %d dropped file%s failed!",
1560 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1562 sprintf(message, "%s!", message_part1);
1564 else if (num_files_failed > 0)
1566 sprintf(message, "Failed to process dropped file%s!",
1567 num_files_failed > 1 ? "s" : "");
1570 Request(message, REQ_CONFIRM);
1573 void HandleDropEvent(Event *event)
1575 static boolean confirm_on_drop_complete = FALSE;
1576 static int num_level_sets_succeeded = 0;
1577 static int num_artwork_sets_succeeded = 0;
1578 static int num_files_failed = 0;
1580 switch (event->type)
1584 confirm_on_drop_complete = TRUE;
1585 num_level_sets_succeeded = 0;
1586 num_artwork_sets_succeeded = 0;
1587 num_files_failed = 0;
1594 int tree_type = HandleDropFileEvent(event->drop.file);
1596 if (tree_type == TREE_TYPE_LEVEL_DIR)
1597 num_level_sets_succeeded++;
1598 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1599 tree_type == TREE_TYPE_SOUNDS_DIR ||
1600 tree_type == TREE_TYPE_MUSIC_DIR)
1601 num_artwork_sets_succeeded++;
1605 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1606 if (!confirm_on_drop_complete)
1608 // process all remaining events, including further SDL_DROPFILE events
1611 HandleDropCompleteEvent(num_level_sets_succeeded,
1612 num_artwork_sets_succeeded,
1615 num_level_sets_succeeded = 0;
1616 num_artwork_sets_succeeded = 0;
1617 num_files_failed = 0;
1625 HandleDropTextEvent(event->drop.file);
1630 case SDL_DROPCOMPLETE:
1632 HandleDropCompleteEvent(num_level_sets_succeeded,
1633 num_artwork_sets_succeeded,
1640 if (event->drop.file != NULL)
1641 SDL_free(event->drop.file);
1644 void HandleUserEvent(UserEvent *event)
1646 switch (event->code)
1648 case USEREVENT_ANIM_DELAY_ACTION:
1649 case USEREVENT_ANIM_EVENT_ACTION:
1650 // execute action functions until matching action was found
1651 if (DoKeysymAction(event->value1) ||
1652 DoGadgetAction(event->value1) ||
1653 DoScreenAction(event->value1))
1662 void HandleButton(int mx, int my, int button, int button_nr)
1664 static int old_mx = 0, old_my = 0;
1665 boolean button_hold = FALSE;
1666 boolean handle_gadgets = TRUE;
1672 button_nr = -button_nr;
1681 #if defined(PLATFORM_ANDROID)
1682 // when playing, only handle gadgets when using "follow finger" controls
1683 // or when using touch controls in combination with the MM game engine
1684 // or when using gadgets that do not overlap with virtual buttons
1686 (game_status != GAME_MODE_PLAYING ||
1687 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1688 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1689 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1690 !virtual_button_pressed));
1693 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1695 // do not handle this button event anymore
1696 return; // force mouse event not to be handled at all
1699 if (handle_gadgets && HandleGadgets(mx, my, button))
1701 // do not handle this button event anymore
1702 mx = my = -32; // force mouse event to be outside screen tiles
1705 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1708 // do not use scroll wheel button events for anything other than gadgets
1709 if (IS_WHEEL_BUTTON(button_nr))
1712 switch (game_status)
1714 case GAME_MODE_TITLE:
1715 HandleTitleScreen(mx, my, 0, 0, button);
1718 case GAME_MODE_MAIN:
1719 HandleMainMenu(mx, my, 0, 0, button);
1722 case GAME_MODE_PSEUDO_TYPENAME:
1723 HandleTypeName(0, KSYM_Return);
1726 case GAME_MODE_LEVELS:
1727 HandleChooseLevelSet(mx, my, 0, 0, button);
1730 case GAME_MODE_LEVELNR:
1731 HandleChooseLevelNr(mx, my, 0, 0, button);
1734 case GAME_MODE_SCORES:
1735 HandleHallOfFame(0, 0, 0, 0, button);
1738 case GAME_MODE_EDITOR:
1739 HandleLevelEditorIdle();
1742 case GAME_MODE_INFO:
1743 HandleInfoScreen(mx, my, 0, 0, button);
1746 case GAME_MODE_SETUP:
1747 HandleSetupScreen(mx, my, 0, 0, button);
1750 case GAME_MODE_PLAYING:
1751 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1752 HandleButtonOrFinger(mx, my, button);
1754 SetPlayerMouseAction(mx, my, button);
1757 if (button == MB_PRESSED && !motion_status && !button_hold &&
1758 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1759 DumpTileFromScreen(mx, my);
1769 static boolean is_string_suffix(char *string, char *suffix)
1771 int string_len = strlen(string);
1772 int suffix_len = strlen(suffix);
1774 if (suffix_len > string_len)
1777 return (strEqual(&string[string_len - suffix_len], suffix));
1780 #define MAX_CHEAT_INPUT_LEN 32
1782 static void HandleKeysSpecial(Key key)
1784 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1785 char letter = getCharFromKey(key);
1786 int cheat_input_len = strlen(cheat_input);
1792 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1794 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1795 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1797 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1800 cheat_input[cheat_input_len++] = letter;
1801 cheat_input[cheat_input_len] = '\0';
1803 #if DEBUG_EVENTS_KEY
1804 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1807 if (game_status == GAME_MODE_MAIN)
1809 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1810 is_string_suffix(cheat_input, ":ist"))
1812 InsertSolutionTape();
1814 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1815 is_string_suffix(cheat_input, ":pst"))
1819 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1820 is_string_suffix(cheat_input, ":rg"))
1822 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1825 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1826 is_string_suffix(cheat_input, ":rs"))
1828 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1831 else if (is_string_suffix(cheat_input, ":reload-music") ||
1832 is_string_suffix(cheat_input, ":rm"))
1834 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1837 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1838 is_string_suffix(cheat_input, ":ra"))
1840 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1841 1 << ARTWORK_TYPE_SOUNDS |
1842 1 << ARTWORK_TYPE_MUSIC);
1845 else if (is_string_suffix(cheat_input, ":dump-level") ||
1846 is_string_suffix(cheat_input, ":dl"))
1850 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1851 is_string_suffix(cheat_input, ":dt"))
1855 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1856 is_string_suffix(cheat_input, ":ft"))
1858 /* fix single-player tapes that contain player input for more than one
1859 player (due to a bug in 3.3.1.2 and earlier versions), which results
1860 in playing levels with more than one player in multi-player mode,
1861 even though the tape was originally recorded in single-player mode */
1863 // remove player input actions for all players but the first one
1864 for (i = 1; i < MAX_PLAYERS; i++)
1865 tape.player_participates[i] = FALSE;
1867 tape.changed = TRUE;
1869 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1870 is_string_suffix(cheat_input, ":snl"))
1872 SaveNativeLevel(&level);
1874 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1875 is_string_suffix(cheat_input, ":fps"))
1877 global.show_frames_per_second = !global.show_frames_per_second;
1880 else if (game_status == GAME_MODE_PLAYING)
1883 if (is_string_suffix(cheat_input, ".q"))
1884 DEBUG_SetMaximumDynamite();
1887 else if (game_status == GAME_MODE_EDITOR)
1889 if (is_string_suffix(cheat_input, ":dump-brush") ||
1890 is_string_suffix(cheat_input, ":DB"))
1894 else if (is_string_suffix(cheat_input, ":DDB"))
1899 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1901 if (letter == 'x') // copy brush to clipboard (small size)
1903 CopyBrushToClipboard_Small();
1905 else if (letter == 'c') // copy brush to clipboard (normal size)
1907 CopyBrushToClipboard();
1909 else if (letter == 'v') // paste brush from Clipboard
1911 CopyClipboardToBrush();
1916 // special key shortcuts for all game modes
1917 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1918 is_string_suffix(cheat_input, ":dea") ||
1919 is_string_suffix(cheat_input, ":DEA"))
1921 DumpGadgetIdentifiers();
1922 DumpScreenIdentifiers();
1926 boolean HandleKeysDebug(Key key, int key_status)
1931 if (key_status != KEY_PRESSED)
1934 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1936 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1938 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1940 if (key == setup.debug.frame_delay_key[i] &&
1941 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1943 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1944 setup.debug.frame_delay[i] : setup.game_frame_delay);
1946 if (!setup.debug.frame_delay_game_only)
1947 MenuFrameDelay = GameFrameDelay;
1949 SetVideoFrameDelay(GameFrameDelay);
1951 if (GameFrameDelay > ONE_SECOND_DELAY)
1952 Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
1953 else if (GameFrameDelay != 0)
1954 Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
1955 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1956 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1958 Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
1965 if (game_status == GAME_MODE_PLAYING)
1969 options.debug = !options.debug;
1971 Error(ERR_INFO, "debug mode %s",
1972 (options.debug ? "enabled" : "disabled"));
1976 else if (key == KSYM_v)
1978 Error(ERR_INFO, "currently using game engine version %d",
1979 game.engine_version);
1989 void HandleKey(Key key, int key_status)
1991 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1992 static boolean ignore_repeated_key = FALSE;
1993 static struct SetupKeyboardInfo ski;
1994 static struct SetupShortcutInfo ssi;
2003 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
2004 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
2005 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
2006 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
2007 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2008 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2013 if (HandleKeysDebug(key, key_status))
2014 return; // do not handle already processed keys again
2016 // map special keys (media keys / remote control buttons) to default keys
2017 if (key == KSYM_PlayPause)
2019 else if (key == KSYM_Select)
2022 HandleSpecialGameControllerKeys(key, key_status);
2024 if (game_status == GAME_MODE_PLAYING)
2026 // only needed for single-step tape recording mode
2027 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2030 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2032 byte key_action = 0;
2033 byte key_snap_action = 0;
2035 if (setup.input[pnr].use_joystick)
2038 ski = setup.input[pnr].key;
2040 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2041 if (key == *key_info[i].key_custom)
2042 key_action |= key_info[i].action;
2044 // use combined snap+direction keys for the first player only
2047 ssi = setup.shortcut;
2049 // also remember normal snap key when handling snap+direction keys
2050 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2052 for (i = 0; i < NUM_DIRECTIONS; i++)
2054 if (key == *key_info[i].key_snap)
2056 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2057 key_snap_action |= key_info[i].action;
2062 if (key_status == KEY_PRESSED)
2064 stored_player[pnr].action |= key_action;
2065 stored_player[pnr].snap_action |= key_snap_action;
2069 stored_player[pnr].action &= ~key_action;
2070 stored_player[pnr].snap_action &= ~key_snap_action;
2073 // restore snap action if one of several pressed snap keys was released
2074 if (stored_player[pnr].snap_action)
2075 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2077 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2079 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2081 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2083 // if snap key already pressed, keep pause mode when releasing
2084 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2085 has_snapped[pnr] = TRUE;
2087 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2089 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2091 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2092 getRedDiskReleaseFlag_SP() == 0)
2094 // add a single inactive frame before dropping starts
2095 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2096 stored_player[pnr].force_dropping = TRUE;
2099 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2101 // if snap key was pressed without direction, leave pause mode
2102 if (!has_snapped[pnr])
2103 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2105 has_snapped[pnr] = FALSE;
2108 else if (tape.recording && tape.pausing && !tape.use_mouse)
2110 // prevent key release events from un-pausing a paused game
2111 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2112 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2115 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2116 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2122 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2123 if (key == key_info[i].key_default)
2124 joy |= key_info[i].action;
2129 if (key_status == KEY_PRESSED)
2130 key_joystick_mapping |= joy;
2132 key_joystick_mapping &= ~joy;
2137 if (game_status != GAME_MODE_PLAYING)
2138 key_joystick_mapping = 0;
2140 if (key_status == KEY_RELEASED)
2142 // reset flag to ignore repeated "key pressed" events after key release
2143 ignore_repeated_key = FALSE;
2148 if ((key == KSYM_F11 ||
2149 ((key == KSYM_Return ||
2150 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2151 video.fullscreen_available &&
2152 !ignore_repeated_key)
2154 setup.fullscreen = !setup.fullscreen;
2156 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2158 if (game_status == GAME_MODE_SETUP)
2159 RedrawSetupScreenAfterFullscreenToggle();
2161 UpdateMousePosition();
2163 // set flag to ignore repeated "key pressed" events
2164 ignore_repeated_key = TRUE;
2169 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2170 key == KSYM_minus || key == KSYM_KP_Subtract ||
2171 key == KSYM_plus || key == KSYM_KP_Add ||
2172 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2173 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2174 video.window_scaling_available &&
2175 !video.fullscreen_enabled)
2177 if (key == KSYM_0 || key == KSYM_KP_0)
2178 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2179 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2180 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2182 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2184 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2185 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2186 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2187 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2189 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2191 if (game_status == GAME_MODE_SETUP)
2192 RedrawSetupScreenAfterFullscreenToggle();
2194 UpdateMousePosition();
2199 // some key events are handled like clicks for global animations
2200 boolean click = (key == KSYM_space ||
2201 key == KSYM_Return ||
2202 key == KSYM_Escape);
2204 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2206 // do not handle this key event anymore
2207 if (key != KSYM_Escape) // always allow ESC key to be handled
2211 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2212 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2219 if (game_status == GAME_MODE_MAIN &&
2220 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2222 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2227 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2229 if (key == setup.shortcut.save_game)
2231 else if (key == setup.shortcut.load_game)
2233 else if (key == setup.shortcut.toggle_pause)
2234 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2236 HandleTapeButtonKeys(key);
2237 HandleSoundButtonKeys(key);
2240 if (game_status == GAME_MODE_PLAYING && !network_playing)
2242 int centered_player_nr_next = -999;
2244 if (key == setup.shortcut.focus_player_all)
2245 centered_player_nr_next = -1;
2247 for (i = 0; i < MAX_PLAYERS; i++)
2248 if (key == setup.shortcut.focus_player[i])
2249 centered_player_nr_next = i;
2251 if (centered_player_nr_next != -999)
2253 game.centered_player_nr_next = centered_player_nr_next;
2254 game.set_centered_player = TRUE;
2258 tape.centered_player_nr_next = game.centered_player_nr_next;
2259 tape.set_centered_player = TRUE;
2264 HandleKeysSpecial(key);
2266 if (HandleGadgetsKeyInput(key))
2267 return; // do not handle already processed keys again
2269 switch (game_status)
2271 case GAME_MODE_PSEUDO_TYPENAME:
2272 HandleTypeName(0, key);
2275 case GAME_MODE_TITLE:
2276 case GAME_MODE_MAIN:
2277 case GAME_MODE_LEVELS:
2278 case GAME_MODE_LEVELNR:
2279 case GAME_MODE_SETUP:
2280 case GAME_MODE_INFO:
2281 case GAME_MODE_SCORES:
2283 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2290 if (game_status == GAME_MODE_TITLE)
2291 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2292 else if (game_status == GAME_MODE_MAIN)
2293 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2294 else if (game_status == GAME_MODE_LEVELS)
2295 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2296 else if (game_status == GAME_MODE_LEVELNR)
2297 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2298 else if (game_status == GAME_MODE_SETUP)
2299 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2300 else if (game_status == GAME_MODE_INFO)
2301 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2302 else if (game_status == GAME_MODE_SCORES)
2303 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2307 if (game_status != GAME_MODE_MAIN)
2308 FadeSkipNextFadeIn();
2310 if (game_status == GAME_MODE_TITLE)
2311 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2312 else if (game_status == GAME_MODE_LEVELS)
2313 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2314 else if (game_status == GAME_MODE_LEVELNR)
2315 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2316 else if (game_status == GAME_MODE_SETUP)
2317 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2318 else if (game_status == GAME_MODE_INFO)
2319 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2320 else if (game_status == GAME_MODE_SCORES)
2321 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2325 if (game_status == GAME_MODE_LEVELS)
2326 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2327 else if (game_status == GAME_MODE_LEVELNR)
2328 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2329 else if (game_status == GAME_MODE_SETUP)
2330 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2331 else if (game_status == GAME_MODE_INFO)
2332 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2333 else if (game_status == GAME_MODE_SCORES)
2334 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2337 case KSYM_Page_Down:
2338 if (game_status == GAME_MODE_LEVELS)
2339 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2340 else if (game_status == GAME_MODE_LEVELNR)
2341 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2342 else if (game_status == GAME_MODE_SETUP)
2343 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2344 else if (game_status == GAME_MODE_INFO)
2345 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2346 else if (game_status == GAME_MODE_SCORES)
2347 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2355 case GAME_MODE_EDITOR:
2356 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2357 HandleLevelEditorKeyInput(key);
2360 case GAME_MODE_PLAYING:
2365 RequestQuitGame(setup.ask_on_escape);
2375 if (key == KSYM_Escape)
2377 SetGameStatus(GAME_MODE_MAIN);
2386 void HandleNoEvent(void)
2388 HandleMouseCursor();
2390 switch (game_status)
2392 case GAME_MODE_PLAYING:
2393 HandleButtonOrFinger(-1, -1, -1);
2398 void HandleEventActions(void)
2400 // if (button_status && game_status != GAME_MODE_PLAYING)
2401 if (button_status && (game_status != GAME_MODE_PLAYING ||
2403 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2405 HandleButton(0, 0, button_status, -button_status);
2412 if (network.enabled)
2415 switch (game_status)
2417 case GAME_MODE_MAIN:
2418 DrawPreviewLevelAnimation();
2421 case GAME_MODE_EDITOR:
2422 HandleLevelEditorIdle();
2430 static void HandleTileCursor(int dx, int dy, int button)
2433 ClearPlayerMouseAction();
2440 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2441 (dx < 0 ? MB_LEFTBUTTON :
2442 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2444 else if (!tile_cursor.moving)
2446 int old_xpos = tile_cursor.xpos;
2447 int old_ypos = tile_cursor.ypos;
2448 int new_xpos = old_xpos;
2449 int new_ypos = old_ypos;
2451 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2452 new_xpos = old_xpos + dx;
2454 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2455 new_ypos = old_ypos + dy;
2457 SetTileCursorTargetXY(new_xpos, new_ypos);
2461 static int HandleJoystickForAllPlayers(void)
2465 boolean no_joysticks_configured = TRUE;
2466 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2467 static byte joy_action_last[MAX_PLAYERS];
2469 for (i = 0; i < MAX_PLAYERS; i++)
2470 if (setup.input[i].use_joystick)
2471 no_joysticks_configured = FALSE;
2473 // if no joysticks configured, map connected joysticks to players
2474 if (no_joysticks_configured)
2475 use_as_joystick_nr = TRUE;
2477 for (i = 0; i < MAX_PLAYERS; i++)
2479 byte joy_action = 0;
2481 joy_action = JoystickExt(i, use_as_joystick_nr);
2482 result |= joy_action;
2484 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2485 joy_action != joy_action_last[i])
2486 stored_player[i].action = joy_action;
2488 joy_action_last[i] = joy_action;
2494 void HandleJoystick(void)
2496 static unsigned int joytest_delay = 0;
2497 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2498 static int joytest_last = 0;
2499 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2500 int delay_value = GADGET_FRAME_DELAY;
2501 int joystick = HandleJoystickForAllPlayers();
2502 int keyboard = key_joystick_mapping;
2503 int joy = (joystick | keyboard);
2504 int joytest = joystick;
2505 int left = joy & JOY_LEFT;
2506 int right = joy & JOY_RIGHT;
2507 int up = joy & JOY_UP;
2508 int down = joy & JOY_DOWN;
2509 int button = joy & JOY_BUTTON;
2510 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2511 int dx = (left ? -1 : right ? 1 : 0);
2512 int dy = (up ? -1 : down ? 1 : 0);
2513 boolean use_delay_value_first = (joytest != joytest_last);
2515 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2517 // do not handle this button event anymore
2521 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2522 anyTextGadgetActive()))
2524 // leave name input in main menu or text input gadget
2525 HandleKey(KSYM_Escape, KEY_PRESSED);
2526 HandleKey(KSYM_Escape, KEY_RELEASED);
2531 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2533 if (game_status == GAME_MODE_PLAYING)
2535 // when playing MM style levels, also use delay for keyboard events
2536 joytest |= keyboard;
2538 // only use first delay value for new events, but not for changed events
2539 use_delay_value_first = (!joytest != !joytest_last);
2541 // only use delay after the initial keyboard event
2545 // for any joystick or keyboard event, enable playfield tile cursor
2546 if (dx || dy || button)
2547 SetTileCursorEnabled(TRUE);
2550 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2552 // delay joystick/keyboard actions if axes/keys continually pressed
2553 newbutton = dx = dy = 0;
2557 // first start with longer delay, then continue with shorter delay
2558 joytest_delay_value =
2559 (use_delay_value_first ? delay_value_first : delay_value);
2562 joytest_last = joytest;
2564 switch (game_status)
2566 case GAME_MODE_TITLE:
2567 case GAME_MODE_MAIN:
2568 case GAME_MODE_LEVELS:
2569 case GAME_MODE_LEVELNR:
2570 case GAME_MODE_SETUP:
2571 case GAME_MODE_INFO:
2572 case GAME_MODE_SCORES:
2574 if (anyTextGadgetActive())
2577 if (game_status == GAME_MODE_TITLE)
2578 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2579 else if (game_status == GAME_MODE_MAIN)
2580 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2581 else if (game_status == GAME_MODE_LEVELS)
2582 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2583 else if (game_status == GAME_MODE_LEVELNR)
2584 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2585 else if (game_status == GAME_MODE_SETUP)
2586 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2587 else if (game_status == GAME_MODE_INFO)
2588 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2589 else if (game_status == GAME_MODE_SCORES)
2590 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2595 case GAME_MODE_PLAYING:
2597 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2598 if (tape.playing || keyboard)
2599 newbutton = ((joy & JOY_BUTTON) != 0);
2602 if (newbutton && game.all_players_gone)
2609 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2611 if (joystick & JOY_ACTION)
2612 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2614 else if (tape.recording && tape.pausing && !tape.use_mouse)
2616 if (joystick & JOY_ACTION)
2617 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2620 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2621 HandleTileCursor(dx, dy, button);
2630 void HandleSpecialGameControllerButtons(Event *event)
2635 switch (event->type)
2637 case SDL_CONTROLLERBUTTONDOWN:
2638 key_status = KEY_PRESSED;
2641 case SDL_CONTROLLERBUTTONUP:
2642 key_status = KEY_RELEASED;
2649 switch (event->cbutton.button)
2651 case SDL_CONTROLLER_BUTTON_START:
2655 case SDL_CONTROLLER_BUTTON_BACK:
2663 HandleKey(key, key_status);
2666 void HandleSpecialGameControllerKeys(Key key, int key_status)
2668 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2669 int button = SDL_CONTROLLER_BUTTON_INVALID;
2671 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2672 if (key == KSYM_Rewind)
2673 button = SDL_CONTROLLER_BUTTON_A;
2674 else if (key == KSYM_FastForward || key == KSYM_Menu)
2675 button = SDL_CONTROLLER_BUTTON_B;
2677 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2681 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2682 SDL_CONTROLLERBUTTONUP);
2684 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2685 event.cbutton.button = button;
2686 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2689 HandleJoystickEvent(&event);
2694 boolean DoKeysymAction(int keysym)
2698 Key key = (Key)(-keysym);
2700 HandleKey(key, KEY_PRESSED);
2701 HandleKey(key, KEY_RELEASED);