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 stop_processing_events = FALSE;
45 // forward declarations for internal use
46 static void HandleNoEvent(void);
47 static void HandleEventActions(void);
50 // event filter to set mouse x/y position (for pointer class global animations)
51 // (this is especially required to ensure smooth global animation mouse pointer
52 // movement when the screen is updated without handling events; this can happen
53 // when drawing door/envelope request animations, for example)
55 int FilterMouseMotionEvents(void *userdata, Event *event)
57 if (event->type == EVENT_MOTIONNOTIFY)
59 int mouse_x = ((MotionEvent *)event)->x;
60 int mouse_y = ((MotionEvent *)event)->y;
62 UpdateRawMousePosition(mouse_x, mouse_y);
68 // event filter especially needed for SDL event filtering due to
69 // delay problems with lots of mouse motion events when mouse button
70 // not pressed (X11 can handle this with 'PointerMotionHintMask')
72 // event filter addition for SDL2: as SDL2 does not have a function to enable
73 // or disable keyboard auto-repeat, filter repeated keyboard events instead
75 static int FilterEvents(const Event *event)
79 // skip repeated key press events if keyboard auto-repeat is disabled
80 if (event->type == EVENT_KEYPRESS &&
85 if (event->type == EVENT_BUTTONPRESS ||
86 event->type == EVENT_BUTTONRELEASE)
88 ((ButtonEvent *)event)->x -= video.screen_xoffset;
89 ((ButtonEvent *)event)->y -= video.screen_yoffset;
91 else if (event->type == EVENT_MOTIONNOTIFY)
93 ((MotionEvent *)event)->x -= video.screen_xoffset;
94 ((MotionEvent *)event)->y -= video.screen_yoffset;
97 // non-motion events are directly passed to event handler functions
98 if (event->type != EVENT_MOTIONNOTIFY)
101 motion = (MotionEvent *)event;
102 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
103 motion->y >= SY && motion->y < SY + SYSIZE);
105 // set correct mouse x/y position (for pointer class global animations)
106 // (this is required in rare cases where the mouse x/y position calculated
107 // from raw values (to apply logical screen size scaling corrections) does
108 // not match the final mouse event x/y position -- this may happen because
109 // the SDL renderer's viewport position is internally represented as float,
110 // but only accessible as integer, which may lead to rounding errors)
111 gfx.mouse_x = motion->x;
112 gfx.mouse_y = motion->y;
114 // do no reset mouse cursor before all pending events have been processed
115 if (gfx.cursor_mode == cursor_mode_last &&
116 ((game_status == GAME_MODE_TITLE &&
117 gfx.cursor_mode == CURSOR_NONE) ||
118 (game_status == GAME_MODE_PLAYING &&
119 gfx.cursor_mode == CURSOR_PLAYFIELD)))
121 SetMouseCursor(CURSOR_DEFAULT);
123 DelayReached(&special_cursor_delay, 0);
125 cursor_mode_last = CURSOR_DEFAULT;
128 // skip mouse motion events without pressed button outside level editor
129 if (button_status == MB_RELEASED &&
130 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
136 // to prevent delay problems, skip mouse motion events if the very next
137 // event is also a mouse motion event (and therefore effectively only
138 // handling the last of a row of mouse motion events in the event queue)
140 static boolean SkipPressedMouseMotionEvent(const Event *event)
142 // nothing to do if the current event is not a mouse motion event
143 if (event->type != EVENT_MOTIONNOTIFY)
146 // only skip motion events with pressed button outside the game
147 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
154 PeekEvent(&next_event);
156 // if next event is also a mouse motion event, skip the current one
157 if (next_event.type == EVENT_MOTIONNOTIFY)
164 static boolean WaitValidEvent(Event *event)
168 if (!FilterEvents(event))
171 if (SkipPressedMouseMotionEvent(event))
177 /* this is especially needed for event modifications for the Android target:
178 if mouse coordinates should be modified in the event filter function,
179 using a properly installed SDL event filter does not work, because in
180 the event filter, mouse coordinates in the event structure are still
181 physical pixel positions, not logical (scaled) screen positions, so this
182 has to be handled at a later stage in the event processing functions
183 (when device pixel positions are already converted to screen positions) */
185 boolean NextValidEvent(Event *event)
187 while (PendingEvent())
188 if (WaitValidEvent(event))
194 void StopProcessingEvents(void)
196 stop_processing_events = TRUE;
199 static void HandleEvents(void)
202 unsigned int event_frame_delay = 0;
203 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
205 ResetDelayCounter(&event_frame_delay);
207 stop_processing_events = FALSE;
209 while (NextValidEvent(&event))
213 case EVENT_BUTTONPRESS:
214 case EVENT_BUTTONRELEASE:
215 HandleButtonEvent((ButtonEvent *) &event);
218 case EVENT_MOTIONNOTIFY:
219 HandleMotionEvent((MotionEvent *) &event);
222 case EVENT_WHEELMOTION:
223 HandleWheelEvent((WheelEvent *) &event);
226 case SDL_WINDOWEVENT:
227 HandleWindowEvent((WindowEvent *) &event);
230 case EVENT_FINGERPRESS:
231 case EVENT_FINGERRELEASE:
232 case EVENT_FINGERMOTION:
233 HandleFingerEvent((FingerEvent *) &event);
236 case EVENT_TEXTINPUT:
237 HandleTextEvent((TextEvent *) &event);
240 case SDL_APP_WILLENTERBACKGROUND:
241 case SDL_APP_DIDENTERBACKGROUND:
242 case SDL_APP_WILLENTERFOREGROUND:
243 case SDL_APP_DIDENTERFOREGROUND:
244 HandlePauseResumeEvent((PauseResumeEvent *) &event);
248 case EVENT_KEYRELEASE:
249 HandleKeyEvent((KeyEvent *) &event);
253 HandleUserEvent((UserEvent *) &event);
257 HandleOtherEvents(&event);
261 // do not handle events for longer than standard frame delay period
262 if (DelayReached(&event_frame_delay, event_frame_delay_value))
265 // do not handle any further events if triggered by a special flag
266 if (stop_processing_events)
271 void HandleOtherEvents(Event *event)
275 case SDL_CONTROLLERBUTTONDOWN:
276 case SDL_CONTROLLERBUTTONUP:
277 // for any game controller button event, disable overlay buttons
278 SetOverlayEnabled(FALSE);
280 HandleSpecialGameControllerButtons(event);
283 case SDL_CONTROLLERDEVICEADDED:
284 case SDL_CONTROLLERDEVICEREMOVED:
285 case SDL_CONTROLLERAXISMOTION:
286 case SDL_JOYAXISMOTION:
287 case SDL_JOYBUTTONDOWN:
288 case SDL_JOYBUTTONUP:
289 HandleJoystickEvent(event);
293 case SDL_DROPCOMPLETE:
296 HandleDropEvent(event);
308 static void HandleMouseCursor(void)
310 if (game_status == GAME_MODE_TITLE)
312 // when showing title screens, hide mouse pointer (if not moved)
314 if (gfx.cursor_mode != CURSOR_NONE &&
315 DelayReached(&special_cursor_delay, special_cursor_delay_value))
317 SetMouseCursor(CURSOR_NONE);
320 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
323 // when playing, display a special mouse pointer inside the playfield
325 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
326 cursor_inside_playfield &&
327 DelayReached(&special_cursor_delay, special_cursor_delay_value))
329 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
331 SetMouseCursor(CURSOR_PLAYFIELD);
334 else if (gfx.cursor_mode != CURSOR_DEFAULT)
336 SetMouseCursor(CURSOR_DEFAULT);
339 // this is set after all pending events have been processed
340 cursor_mode_last = gfx.cursor_mode;
352 // execute event related actions after pending events have been processed
353 HandleEventActions();
355 // don't use all CPU time when idle; the main loop while playing
356 // has its own synchronization and is CPU friendly, too
358 if (game_status == GAME_MODE_PLAYING)
361 // always copy backbuffer to visible screen for every video frame
364 // reset video frame delay to default (may change again while playing)
365 SetVideoFrameDelay(MenuFrameDelay);
367 if (game_status == GAME_MODE_QUIT)
372 void ClearAutoRepeatKeyEvents(void)
374 while (PendingEvent())
378 PeekEvent(&next_event);
380 // if event is repeated key press event, remove it from event queue
381 if (next_event.type == EVENT_KEYPRESS &&
382 next_event.key.repeat)
383 WaitEvent(&next_event);
389 void ClearEventQueue(void)
393 while (NextValidEvent(&event))
397 case EVENT_BUTTONRELEASE:
398 button_status = MB_RELEASED;
401 case EVENT_KEYRELEASE:
405 case SDL_CONTROLLERBUTTONUP:
406 HandleJoystickEvent(&event);
411 HandleOtherEvents(&event);
417 static void ClearPlayerMouseAction(void)
419 local_player->mouse_action.lx = 0;
420 local_player->mouse_action.ly = 0;
421 local_player->mouse_action.button = 0;
424 void ClearPlayerAction(void)
428 // simulate key release events for still pressed keys
429 key_joystick_mapping = 0;
430 for (i = 0; i < MAX_PLAYERS; i++)
432 stored_player[i].action = 0;
433 stored_player[i].snap_action = 0;
436 ClearJoystickState();
437 ClearPlayerMouseAction();
440 static void SetPlayerMouseAction(int mx, int my, int button)
442 int lx = getLevelFromScreenX(mx);
443 int ly = getLevelFromScreenY(my);
444 int new_button = (!local_player->mouse_action.button && button);
446 if (local_player->mouse_action.button_hint)
447 button = local_player->mouse_action.button_hint;
449 ClearPlayerMouseAction();
451 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
454 local_player->mouse_action.lx = lx;
455 local_player->mouse_action.ly = ly;
456 local_player->mouse_action.button = button;
458 if (tape.recording && tape.pausing && tape.use_mouse)
460 // un-pause a paused game only if mouse button was newly pressed down
462 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
465 SetTileCursorXY(lx, ly);
468 static Key GetKeyFromGridButton(int grid_button)
470 return (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
471 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
472 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
473 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
474 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
475 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
479 #if defined(PLATFORM_ANDROID)
480 static boolean CheckVirtualButtonPressed(int mx, int my, int button)
482 float touch_x = (float)(mx + video.screen_xoffset) / video.screen_width;
483 float touch_y = (float)(my + video.screen_yoffset) / video.screen_height;
484 int x = touch_x * overlay.grid_xsize;
485 int y = touch_y * overlay.grid_ysize;
486 int grid_button = overlay.grid_button[x][y];
487 Key key = GetKeyFromGridButton(grid_button);
488 int key_status = (button == MB_RELEASED ? KEY_RELEASED : KEY_PRESSED);
490 return (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
494 void HandleButtonEvent(ButtonEvent *event)
496 #if DEBUG_EVENTS_BUTTON
497 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
499 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
503 // for any mouse button event, disable playfield tile cursor
504 SetTileCursorEnabled(FALSE);
506 #if defined(HAS_SCREEN_KEYBOARD)
507 if (video.shifted_up)
508 event->y += video.shifted_up_pos;
511 motion_status = FALSE;
513 if (event->type == EVENT_BUTTONPRESS)
514 button_status = event->button;
516 button_status = MB_RELEASED;
518 HandleButton(event->x, event->y, button_status, event->button);
521 void HandleMotionEvent(MotionEvent *event)
523 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
526 motion_status = TRUE;
528 #if DEBUG_EVENTS_MOTION
529 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
530 button_status, event->x, event->y);
533 HandleButton(event->x, event->y, button_status, button_status);
536 void HandleWheelEvent(WheelEvent *event)
540 #if DEBUG_EVENTS_WHEEL
542 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
543 event->which, event->x, event->y);
545 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
546 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
547 event->which, event->x, event->y,
548 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
549 "SDL_MOUSEWHEEL_FLIPPED"));
553 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
554 event->x > 0 ? MB_WHEEL_RIGHT :
555 event->y < 0 ? MB_WHEEL_DOWN :
556 event->y > 0 ? MB_WHEEL_UP : 0);
558 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
559 // accelerated mouse wheel available on Mac and Windows
560 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
562 // no accelerated mouse wheel available on Unix/Linux
563 wheel_steps = DEFAULT_WHEEL_STEPS;
566 motion_status = FALSE;
568 button_status = button_nr;
569 HandleButton(0, 0, button_status, -button_nr);
571 button_status = MB_RELEASED;
572 HandleButton(0, 0, button_status, -button_nr);
575 void HandleWindowEvent(WindowEvent *event)
577 #if DEBUG_EVENTS_WINDOW
578 int subtype = event->event;
581 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
582 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
583 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
584 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
585 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
586 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
587 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
588 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
589 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
590 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
591 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
592 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
593 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
594 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
597 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
598 event_name, event->data1, event->data2);
602 // (not needed, as the screen gets redrawn every 20 ms anyway)
603 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
604 event->event == SDL_WINDOWEVENT_RESIZED ||
605 event->event == SDL_WINDOWEVENT_EXPOSED)
609 if (event->event == SDL_WINDOWEVENT_RESIZED)
611 if (!video.fullscreen_enabled)
613 int new_window_width = event->data1;
614 int new_window_height = event->data2;
616 // if window size has changed after resizing, calculate new scaling factor
617 if (new_window_width != video.window_width ||
618 new_window_height != video.window_height)
620 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
621 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
623 // (extreme window scaling allowed, but cannot be saved permanently)
624 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
625 setup.window_scaling_percent =
626 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
627 MAX_WINDOW_SCALING_PERCENT);
629 video.window_width = new_window_width;
630 video.window_height = new_window_height;
632 if (game_status == GAME_MODE_SETUP)
633 RedrawSetupScreenAfterFullscreenToggle();
635 UpdateMousePosition();
640 #if defined(PLATFORM_ANDROID)
643 int new_display_width = event->data1;
644 int new_display_height = event->data2;
646 // if fullscreen display size has changed, device has been rotated
647 if (new_display_width != video.display_width ||
648 new_display_height != video.display_height)
650 int nr = GRID_ACTIVE_NR(); // previous screen orientation
652 video.display_width = new_display_width;
653 video.display_height = new_display_height;
655 SDLSetScreenProperties();
656 SetGadgetsPosition_OverlayTouchButtons();
658 // check if screen orientation has changed (should always be true here)
659 if (nr != GRID_ACTIVE_NR())
663 if (game_status == GAME_MODE_SETUP)
664 RedrawSetupScreenAfterScreenRotation(nr);
666 nr = GRID_ACTIVE_NR();
668 overlay.grid_xsize = setup.touch.grid_xsize[nr];
669 overlay.grid_ysize = setup.touch.grid_ysize[nr];
671 for (x = 0; x < MAX_GRID_XSIZE; x++)
672 for (y = 0; y < MAX_GRID_YSIZE; y++)
673 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
681 #define NUM_TOUCH_FINGERS 3
686 SDL_FingerID finger_id;
690 } touch_info[NUM_TOUCH_FINGERS];
692 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
694 int x = event->x * overlay.grid_xsize;
695 int y = event->y * overlay.grid_ysize;
696 int grid_button = overlay.grid_button[x][y];
697 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
698 Key key = GetKeyFromGridButton(grid_button);
699 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
701 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
705 // for any touch input event, enable overlay buttons (if activated)
706 SetOverlayEnabled(TRUE);
708 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
709 getKeyNameFromKey(key), key_status_name, event->fingerId);
711 if (key_status == KEY_PRESSED)
712 overlay.grid_button_action |= grid_button_action;
714 overlay.grid_button_action &= ~grid_button_action;
716 // check if we already know this touch event's finger id
717 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
719 if (touch_info[i].touched &&
720 touch_info[i].finger_id == event->fingerId)
722 // Error(ERR_DEBUG, "MARK 1: %d", i);
728 if (i >= NUM_TOUCH_FINGERS)
730 if (key_status == KEY_PRESSED)
732 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
734 // unknown finger id -- get new, empty slot, if available
735 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
737 if (touch_info[i].counter < oldest_counter)
740 oldest_counter = touch_info[i].counter;
742 // Error(ERR_DEBUG, "MARK 2: %d", i);
745 if (!touch_info[i].touched)
747 // Error(ERR_DEBUG, "MARK 3: %d", i);
753 if (i >= NUM_TOUCH_FINGERS)
755 // all slots allocated -- use oldest slot
758 // Error(ERR_DEBUG, "MARK 4: %d", i);
763 // release of previously unknown key (should not happen)
765 if (key != KSYM_UNDEFINED)
767 HandleKey(key, KEY_RELEASED);
769 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
770 getKeyNameFromKey(key), "KEY_RELEASED", i);
775 if (i < NUM_TOUCH_FINGERS)
777 if (key_status == KEY_PRESSED)
779 if (touch_info[i].key != key)
781 if (touch_info[i].key != KSYM_UNDEFINED)
783 HandleKey(touch_info[i].key, KEY_RELEASED);
785 // undraw previous grid button when moving finger away
786 overlay.grid_button_action &= ~touch_info[i].action;
788 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
789 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
792 if (key != KSYM_UNDEFINED)
794 HandleKey(key, KEY_PRESSED);
796 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
797 getKeyNameFromKey(key), "KEY_PRESSED", i);
801 touch_info[i].touched = TRUE;
802 touch_info[i].finger_id = event->fingerId;
803 touch_info[i].counter = Counter();
804 touch_info[i].key = key;
805 touch_info[i].action = grid_button_action;
809 if (touch_info[i].key != KSYM_UNDEFINED)
811 HandleKey(touch_info[i].key, KEY_RELEASED);
813 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
814 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
817 touch_info[i].touched = FALSE;
818 touch_info[i].finger_id = 0;
819 touch_info[i].counter = 0;
820 touch_info[i].key = 0;
821 touch_info[i].action = JOY_NO_ACTION;
826 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
828 static Key motion_key_x = KSYM_UNDEFINED;
829 static Key motion_key_y = KSYM_UNDEFINED;
830 static Key button_key = KSYM_UNDEFINED;
831 static float motion_x1, motion_y1;
832 static float button_x1, button_y1;
833 static SDL_FingerID motion_id = -1;
834 static SDL_FingerID button_id = -1;
835 int move_trigger_distance_percent = setup.touch.move_distance;
836 int drop_trigger_distance_percent = setup.touch.drop_distance;
837 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
838 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
839 float event_x = event->x;
840 float event_y = event->y;
842 if (event->type == EVENT_FINGERPRESS)
844 if (event_x > 1.0 / 3.0)
848 motion_id = event->fingerId;
853 motion_key_x = KSYM_UNDEFINED;
854 motion_key_y = KSYM_UNDEFINED;
856 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
862 button_id = event->fingerId;
867 button_key = setup.input[0].key.snap;
869 HandleKey(button_key, KEY_PRESSED);
871 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
874 else if (event->type == EVENT_FINGERRELEASE)
876 if (event->fingerId == motion_id)
880 if (motion_key_x != KSYM_UNDEFINED)
881 HandleKey(motion_key_x, KEY_RELEASED);
882 if (motion_key_y != KSYM_UNDEFINED)
883 HandleKey(motion_key_y, KEY_RELEASED);
885 motion_key_x = KSYM_UNDEFINED;
886 motion_key_y = KSYM_UNDEFINED;
888 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
890 else if (event->fingerId == button_id)
894 if (button_key != KSYM_UNDEFINED)
895 HandleKey(button_key, KEY_RELEASED);
897 button_key = KSYM_UNDEFINED;
899 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
902 else if (event->type == EVENT_FINGERMOTION)
904 if (event->fingerId == motion_id)
906 float distance_x = ABS(event_x - motion_x1);
907 float distance_y = ABS(event_y - motion_y1);
908 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
909 event_x > motion_x1 ? setup.input[0].key.right :
911 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
912 event_y > motion_y1 ? setup.input[0].key.down :
915 if (distance_x < move_trigger_distance / 2 ||
916 distance_x < distance_y)
917 new_motion_key_x = KSYM_UNDEFINED;
919 if (distance_y < move_trigger_distance / 2 ||
920 distance_y < distance_x)
921 new_motion_key_y = KSYM_UNDEFINED;
923 if (distance_x > move_trigger_distance ||
924 distance_y > move_trigger_distance)
926 if (new_motion_key_x != motion_key_x)
928 if (motion_key_x != KSYM_UNDEFINED)
929 HandleKey(motion_key_x, KEY_RELEASED);
930 if (new_motion_key_x != KSYM_UNDEFINED)
931 HandleKey(new_motion_key_x, KEY_PRESSED);
934 if (new_motion_key_y != motion_key_y)
936 if (motion_key_y != KSYM_UNDEFINED)
937 HandleKey(motion_key_y, KEY_RELEASED);
938 if (new_motion_key_y != KSYM_UNDEFINED)
939 HandleKey(new_motion_key_y, KEY_PRESSED);
945 motion_key_x = new_motion_key_x;
946 motion_key_y = new_motion_key_y;
948 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
951 else if (event->fingerId == button_id)
953 float distance_x = ABS(event_x - button_x1);
954 float distance_y = ABS(event_y - button_y1);
956 if (distance_x < drop_trigger_distance / 2 &&
957 distance_y > drop_trigger_distance)
959 if (button_key == setup.input[0].key.snap)
960 HandleKey(button_key, KEY_RELEASED);
965 button_key = setup.input[0].key.drop;
967 HandleKey(button_key, KEY_PRESSED);
969 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
975 void HandleFingerEvent(FingerEvent *event)
977 #if DEBUG_EVENTS_FINGER
978 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
979 event->type == EVENT_FINGERPRESS ? "pressed" :
980 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
984 event->dx, event->dy,
988 runtime.uses_touch_device = TRUE;
990 if (game_status != GAME_MODE_PLAYING)
993 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
995 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
996 local_player->mouse_action.button_hint =
997 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
998 event->x < 0.5 ? MB_LEFTBUTTON :
999 event->x > 0.5 ? MB_RIGHTBUTTON :
1005 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1006 HandleFingerEvent_VirtualButtons(event);
1007 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1008 HandleFingerEvent_WipeGestures(event);
1011 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1013 static int old_mx = 0, old_my = 0;
1014 static int last_button = MB_LEFTBUTTON;
1015 static boolean touched = FALSE;
1016 static boolean tapped = FALSE;
1018 // screen tile was tapped (but finger not touching the screen anymore)
1019 // (this point will also be reached without receiving a touch event)
1020 if (tapped && !touched)
1022 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1027 // stop here if this function was not triggered by a touch event
1031 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1033 // finger started touching the screen
1043 ClearPlayerMouseAction();
1045 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1048 else if (button == MB_RELEASED && touched)
1050 // finger stopped touching the screen
1055 SetPlayerMouseAction(old_mx, old_my, last_button);
1057 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1059 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1064 // finger moved while touching the screen
1066 int old_x = getLevelFromScreenX(old_mx);
1067 int old_y = getLevelFromScreenY(old_my);
1068 int new_x = getLevelFromScreenX(mx);
1069 int new_y = getLevelFromScreenY(my);
1071 if (new_x != old_x || new_y != old_y)
1076 // finger moved left or right from (horizontal) starting position
1078 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1080 SetPlayerMouseAction(old_mx, old_my, button_nr);
1082 last_button = button_nr;
1084 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1088 // finger stays at or returned to (horizontal) starting position
1090 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1092 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1097 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1099 static int old_mx = 0, old_my = 0;
1100 static int last_button = MB_LEFTBUTTON;
1101 static boolean touched = FALSE;
1102 static boolean tapped = FALSE;
1104 // screen tile was tapped (but finger not touching the screen anymore)
1105 // (this point will also be reached without receiving a touch event)
1106 if (tapped && !touched)
1108 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1113 // stop here if this function was not triggered by a touch event
1117 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1119 // finger started touching the screen
1129 ClearPlayerMouseAction();
1131 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1134 else if (button == MB_RELEASED && touched)
1136 // finger stopped touching the screen
1141 SetPlayerMouseAction(old_mx, old_my, last_button);
1143 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1145 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1150 // finger moved while touching the screen
1152 int old_x = getLevelFromScreenX(old_mx);
1153 int old_y = getLevelFromScreenY(old_my);
1154 int new_x = getLevelFromScreenX(mx);
1155 int new_y = getLevelFromScreenY(my);
1157 if (new_x != old_x || new_y != old_y)
1159 // finger moved away from starting position
1161 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1163 // quickly alternate between clicking and releasing for maximum speed
1164 if (FrameCounter % 2 == 0)
1165 button_nr = MB_RELEASED;
1167 SetPlayerMouseAction(old_mx, old_my, button_nr);
1170 last_button = button_nr;
1174 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1178 // finger stays at or returned to starting position
1180 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1182 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1187 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1189 static int old_mx = 0, old_my = 0;
1190 static Key motion_key_x = KSYM_UNDEFINED;
1191 static Key motion_key_y = KSYM_UNDEFINED;
1192 static boolean touched = FALSE;
1193 static boolean started_on_player = FALSE;
1194 static boolean player_is_dropping = FALSE;
1195 static int player_drop_count = 0;
1196 static int last_player_x = -1;
1197 static int last_player_y = -1;
1199 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1208 started_on_player = FALSE;
1209 player_is_dropping = FALSE;
1210 player_drop_count = 0;
1214 motion_key_x = KSYM_UNDEFINED;
1215 motion_key_y = KSYM_UNDEFINED;
1217 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1220 else if (button == MB_RELEASED && touched)
1227 if (motion_key_x != KSYM_UNDEFINED)
1228 HandleKey(motion_key_x, KEY_RELEASED);
1229 if (motion_key_y != KSYM_UNDEFINED)
1230 HandleKey(motion_key_y, KEY_RELEASED);
1232 if (started_on_player)
1234 if (player_is_dropping)
1236 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1238 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1242 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1244 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1248 motion_key_x = KSYM_UNDEFINED;
1249 motion_key_y = KSYM_UNDEFINED;
1251 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1256 int src_x = local_player->jx;
1257 int src_y = local_player->jy;
1258 int dst_x = getLevelFromScreenX(old_mx);
1259 int dst_y = getLevelFromScreenY(old_my);
1260 int dx = dst_x - src_x;
1261 int dy = dst_y - src_y;
1262 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1263 dx > 0 ? setup.input[0].key.right :
1265 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1266 dy > 0 ? setup.input[0].key.down :
1269 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1270 (last_player_x != local_player->jx ||
1271 last_player_y != local_player->jy))
1273 // in case of asymmetric diagonal movement, use "preferred" direction
1275 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1277 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1278 game_em.ply[0]->last_move_dir = last_move_dir;
1280 local_player->last_move_dir = last_move_dir;
1282 // (required to prevent accidentally forcing direction for next movement)
1283 last_player_x = local_player->jx;
1284 last_player_y = local_player->jy;
1287 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1289 started_on_player = TRUE;
1290 player_drop_count = getPlayerInventorySize(0);
1291 player_is_dropping = (player_drop_count > 0);
1293 if (player_is_dropping)
1295 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1297 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1301 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1303 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1306 else if (dx != 0 || dy != 0)
1308 if (player_is_dropping &&
1309 player_drop_count == getPlayerInventorySize(0))
1311 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1313 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1314 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1316 player_is_dropping = FALSE;
1320 if (new_motion_key_x != motion_key_x)
1322 Error(ERR_DEBUG, "---------- %s %s ----------",
1323 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1324 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1326 if (motion_key_x != KSYM_UNDEFINED)
1327 HandleKey(motion_key_x, KEY_RELEASED);
1328 if (new_motion_key_x != KSYM_UNDEFINED)
1329 HandleKey(new_motion_key_x, KEY_PRESSED);
1332 if (new_motion_key_y != motion_key_y)
1334 Error(ERR_DEBUG, "---------- %s %s ----------",
1335 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1336 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1338 if (motion_key_y != KSYM_UNDEFINED)
1339 HandleKey(motion_key_y, KEY_RELEASED);
1340 if (new_motion_key_y != KSYM_UNDEFINED)
1341 HandleKey(new_motion_key_y, KEY_PRESSED);
1344 motion_key_x = new_motion_key_x;
1345 motion_key_y = new_motion_key_y;
1349 static void HandleButtonOrFinger(int mx, int my, int button)
1351 if (game_status != GAME_MODE_PLAYING)
1354 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1356 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1357 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1358 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1359 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1360 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1361 SetPlayerMouseAction(mx, my, button); // special case
1365 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1366 HandleButtonOrFinger_FollowFinger(mx, my, button);
1370 static boolean checkTextInputKeyModState(void)
1372 // when playing, only handle raw key events and ignore text input
1373 if (game_status == GAME_MODE_PLAYING)
1376 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1379 void HandleTextEvent(TextEvent *event)
1381 char *text = event->text;
1382 Key key = getKeyFromKeyName(text);
1384 #if DEBUG_EVENTS_TEXT
1385 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1388 text[0], (int)(text[0]),
1390 getKeyNameFromKey(key),
1394 #if !defined(HAS_SCREEN_KEYBOARD)
1395 // non-mobile devices: only handle key input with modifier keys pressed here
1396 // (every other key input is handled directly as physical key input event)
1397 if (!checkTextInputKeyModState())
1401 // process text input as "classic" (with uppercase etc.) key input event
1402 HandleKey(key, KEY_PRESSED);
1403 HandleKey(key, KEY_RELEASED);
1406 void HandlePauseResumeEvent(PauseResumeEvent *event)
1408 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1412 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1418 void HandleKeyEvent(KeyEvent *event)
1420 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1421 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1422 Key key = GetEventKey(event, with_modifiers);
1423 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1425 #if DEBUG_EVENTS_KEY
1426 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1427 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1428 event->keysym.scancode,
1433 getKeyNameFromKey(key));
1436 #if defined(PLATFORM_ANDROID)
1437 if (key == KSYM_Back)
1439 // always map the "back" button to the "escape" key on Android devices
1442 else if (key == KSYM_Menu)
1444 // the "menu" button can be used to toggle displaying virtual buttons
1445 if (key_status == KEY_PRESSED)
1446 SetOverlayEnabled(!GetOverlayEnabled());
1450 // for any other "real" key event, disable virtual buttons
1451 SetOverlayEnabled(FALSE);
1453 // for any other "real" key event, disable overlay touch buttons
1454 runtime.uses_touch_device = FALSE;
1458 HandleKeyModState(keymod, key_status);
1460 // only handle raw key input without text modifier keys pressed
1461 if (!checkTextInputKeyModState())
1462 HandleKey(key, key_status);
1465 static int HandleDropFileEvent(char *filename)
1467 Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
1469 // check and extract dropped zip files into correct user data directory
1470 if (!strSuffixLower(filename, ".zip"))
1472 Error(ERR_WARN, "file '%s' not supported", filename);
1474 return TREE_TYPE_UNDEFINED;
1477 TreeInfo *tree_node = NULL;
1478 int tree_type = GetZipFileTreeType(filename);
1479 char *directory = TREE_USERDIR(tree_type);
1481 if (directory == NULL)
1483 Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1485 return TREE_TYPE_UNDEFINED;
1488 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1489 game_status == GAME_MODE_LEVELS &&
1490 leveldir_current->node_parent != NULL)
1492 // extract new level set next to currently selected level set
1493 tree_node = leveldir_current;
1495 // get parent directory of currently selected level set directory
1496 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1498 // use private level directory instead of top-level package level directory
1499 if (strPrefix(directory, options.level_directory) &&
1500 strEqual(leveldir_current->node_parent->fullpath, "."))
1501 directory = getUserLevelDir(NULL);
1504 // extract level or artwork set from zip file to target directory
1505 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1507 if (top_dir == NULL)
1509 // error message already issued by "ExtractZipFileIntoDirectory()"
1511 return TREE_TYPE_UNDEFINED;
1514 // add extracted level or artwork set to tree info structure
1515 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1517 // update menu screen (and possibly change current level set)
1518 DrawScreenAfterAddingSet(top_dir, tree_type);
1523 static void HandleDropTextEvent(char *text)
1525 Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1528 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1529 int num_artwork_sets_succeeded,
1530 int num_files_failed)
1532 // only show request dialog if no other request dialog already active
1533 if (game.request_active)
1536 // this case can happen with drag-and-drop with older SDL versions
1537 if (num_level_sets_succeeded == 0 &&
1538 num_artwork_sets_succeeded == 0 &&
1539 num_files_failed == 0)
1544 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1546 char message_part1[50];
1548 sprintf(message_part1, "New %s set%s added",
1549 (num_artwork_sets_succeeded == 0 ? "level" :
1550 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1551 (num_level_sets_succeeded +
1552 num_artwork_sets_succeeded > 1 ? "s" : ""));
1554 if (num_files_failed > 0)
1555 sprintf(message, "%s, but %d dropped file%s failed!",
1556 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1558 sprintf(message, "%s!", message_part1);
1560 else if (num_files_failed > 0)
1562 sprintf(message, "Failed to process dropped file%s!",
1563 num_files_failed > 1 ? "s" : "");
1566 Request(message, REQ_CONFIRM);
1569 void HandleDropEvent(Event *event)
1571 static boolean confirm_on_drop_complete = FALSE;
1572 static int num_level_sets_succeeded = 0;
1573 static int num_artwork_sets_succeeded = 0;
1574 static int num_files_failed = 0;
1576 switch (event->type)
1580 confirm_on_drop_complete = TRUE;
1581 num_level_sets_succeeded = 0;
1582 num_artwork_sets_succeeded = 0;
1583 num_files_failed = 0;
1590 int tree_type = HandleDropFileEvent(event->drop.file);
1592 if (tree_type == TREE_TYPE_LEVEL_DIR)
1593 num_level_sets_succeeded++;
1594 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1595 tree_type == TREE_TYPE_SOUNDS_DIR ||
1596 tree_type == TREE_TYPE_MUSIC_DIR)
1597 num_artwork_sets_succeeded++;
1601 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1602 if (!confirm_on_drop_complete)
1604 // process all remaining events, including further SDL_DROPFILE events
1607 HandleDropCompleteEvent(num_level_sets_succeeded,
1608 num_artwork_sets_succeeded,
1611 num_level_sets_succeeded = 0;
1612 num_artwork_sets_succeeded = 0;
1613 num_files_failed = 0;
1621 HandleDropTextEvent(event->drop.file);
1626 case SDL_DROPCOMPLETE:
1628 HandleDropCompleteEvent(num_level_sets_succeeded,
1629 num_artwork_sets_succeeded,
1636 if (event->drop.file != NULL)
1637 SDL_free(event->drop.file);
1640 void HandleUserEvent(UserEvent *event)
1642 switch (event->code)
1644 case USEREVENT_ANIM_DELAY_ACTION:
1645 case USEREVENT_ANIM_EVENT_ACTION:
1646 // execute action functions until matching action was found
1647 if (DoKeysymAction(event->value1) ||
1648 DoGadgetAction(event->value1) ||
1649 DoScreenAction(event->value1))
1658 void HandleButton(int mx, int my, int button, int button_nr)
1660 static int old_mx = 0, old_my = 0;
1661 boolean button_hold = FALSE;
1662 boolean handle_gadgets = TRUE;
1668 button_nr = -button_nr;
1677 #if defined(PLATFORM_ANDROID)
1678 // when playing, only handle gadgets when using "follow finger" controls
1679 // or when using touch controls in combination with the MM game engine
1680 // or when using gadgets that do not overlap with virtual buttons
1682 (game_status != GAME_MODE_PLAYING ||
1683 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1684 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1685 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1686 !CheckVirtualButtonPressed(mx, my, button)));
1688 // always recognize potentially releasing already pressed gadgets
1689 if (button == MB_RELEASED)
1690 handle_gadgets = TRUE;
1692 // always recognize pressing or releasing overlay touch buttons
1693 if (CheckPosition_OverlayTouchButtons(mx, my, button) && !motion_status)
1694 handle_gadgets = TRUE;
1697 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1699 // do not handle this button event anymore
1700 return; // force mouse event not to be handled at all
1703 if (handle_gadgets && HandleGadgets(mx, my, button))
1705 // do not handle this button event anymore
1706 mx = my = -32; // force mouse event to be outside screen tiles
1709 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1712 // do not use scroll wheel button events for anything other than gadgets
1713 if (IS_WHEEL_BUTTON(button_nr))
1716 switch (game_status)
1718 case GAME_MODE_TITLE:
1719 HandleTitleScreen(mx, my, 0, 0, button);
1722 case GAME_MODE_MAIN:
1723 HandleMainMenu(mx, my, 0, 0, button);
1726 case GAME_MODE_PSEUDO_TYPENAME:
1727 HandleTypeName(0, KSYM_Return);
1730 case GAME_MODE_LEVELS:
1731 HandleChooseLevelSet(mx, my, 0, 0, button);
1734 case GAME_MODE_LEVELNR:
1735 HandleChooseLevelNr(mx, my, 0, 0, button);
1738 case GAME_MODE_SCORES:
1739 HandleHallOfFame(0, 0, 0, 0, button);
1742 case GAME_MODE_EDITOR:
1743 HandleLevelEditorIdle();
1746 case GAME_MODE_INFO:
1747 HandleInfoScreen(mx, my, 0, 0, button);
1750 case GAME_MODE_SETUP:
1751 HandleSetupScreen(mx, my, 0, 0, button);
1754 case GAME_MODE_PLAYING:
1755 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1756 HandleButtonOrFinger(mx, my, button);
1758 SetPlayerMouseAction(mx, my, button);
1761 if (button == MB_PRESSED && !motion_status && !button_hold &&
1762 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1763 DumpTileFromScreen(mx, my);
1773 static boolean is_string_suffix(char *string, char *suffix)
1775 int string_len = strlen(string);
1776 int suffix_len = strlen(suffix);
1778 if (suffix_len > string_len)
1781 return (strEqual(&string[string_len - suffix_len], suffix));
1784 #define MAX_CHEAT_INPUT_LEN 32
1786 static void HandleKeysSpecial(Key key)
1788 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1789 char letter = getCharFromKey(key);
1790 int cheat_input_len = strlen(cheat_input);
1796 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1798 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1799 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1801 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1804 cheat_input[cheat_input_len++] = letter;
1805 cheat_input[cheat_input_len] = '\0';
1807 #if DEBUG_EVENTS_KEY
1808 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1811 if (game_status == GAME_MODE_MAIN)
1813 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1814 is_string_suffix(cheat_input, ":ist"))
1816 InsertSolutionTape();
1818 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1819 is_string_suffix(cheat_input, ":pst"))
1823 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1824 is_string_suffix(cheat_input, ":rg"))
1826 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1829 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1830 is_string_suffix(cheat_input, ":rs"))
1832 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1835 else if (is_string_suffix(cheat_input, ":reload-music") ||
1836 is_string_suffix(cheat_input, ":rm"))
1838 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1841 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1842 is_string_suffix(cheat_input, ":ra"))
1844 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1845 1 << ARTWORK_TYPE_SOUNDS |
1846 1 << ARTWORK_TYPE_MUSIC);
1849 else if (is_string_suffix(cheat_input, ":dump-level") ||
1850 is_string_suffix(cheat_input, ":dl"))
1854 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1855 is_string_suffix(cheat_input, ":dt"))
1859 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1860 is_string_suffix(cheat_input, ":ft"))
1862 /* fix single-player tapes that contain player input for more than one
1863 player (due to a bug in 3.3.1.2 and earlier versions), which results
1864 in playing levels with more than one player in multi-player mode,
1865 even though the tape was originally recorded in single-player mode */
1867 // remove player input actions for all players but the first one
1868 for (i = 1; i < MAX_PLAYERS; i++)
1869 tape.player_participates[i] = FALSE;
1871 tape.changed = TRUE;
1873 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1874 is_string_suffix(cheat_input, ":snl"))
1876 SaveNativeLevel(&level);
1878 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1879 is_string_suffix(cheat_input, ":fps"))
1881 global.show_frames_per_second = !global.show_frames_per_second;
1884 else if (game_status == GAME_MODE_PLAYING)
1887 if (is_string_suffix(cheat_input, ".q"))
1888 DEBUG_SetMaximumDynamite();
1891 else if (game_status == GAME_MODE_EDITOR)
1893 if (is_string_suffix(cheat_input, ":dump-brush") ||
1894 is_string_suffix(cheat_input, ":DB"))
1898 else if (is_string_suffix(cheat_input, ":DDB"))
1903 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1905 if (letter == 'x') // copy brush to clipboard (small size)
1907 CopyBrushToClipboard_Small();
1909 else if (letter == 'c') // copy brush to clipboard (normal size)
1911 CopyBrushToClipboard();
1913 else if (letter == 'v') // paste brush from Clipboard
1915 CopyClipboardToBrush();
1917 else if (letter == 'z') // undo or redo last operation
1919 if (GetKeyModState() & KMOD_Shift)
1920 RedoLevelEditorOperation();
1922 UndoLevelEditorOperation();
1927 // special key shortcuts for all game modes
1928 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1929 is_string_suffix(cheat_input, ":dea") ||
1930 is_string_suffix(cheat_input, ":DEA"))
1932 DumpGadgetIdentifiers();
1933 DumpScreenIdentifiers();
1937 boolean HandleKeysDebug(Key key, int key_status)
1942 if (key_status != KEY_PRESSED)
1945 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1947 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1949 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1951 if (key == setup.debug.frame_delay_key[i] &&
1952 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1954 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1955 setup.debug.frame_delay[i] : setup.game_frame_delay);
1957 if (!setup.debug.frame_delay_game_only)
1958 MenuFrameDelay = GameFrameDelay;
1960 SetVideoFrameDelay(GameFrameDelay);
1962 if (GameFrameDelay > ONE_SECOND_DELAY)
1963 Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
1964 else if (GameFrameDelay != 0)
1965 Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
1966 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1967 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1969 Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
1976 if (game_status == GAME_MODE_PLAYING)
1980 options.debug = !options.debug;
1982 Error(ERR_INFO, "debug mode %s",
1983 (options.debug ? "enabled" : "disabled"));
1987 else if (key == KSYM_v)
1989 Error(ERR_INFO, "currently using game engine version %d",
1990 game.engine_version);
2000 void HandleKey(Key key, int key_status)
2002 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2003 static boolean ignore_repeated_key = FALSE;
2004 static struct SetupKeyboardInfo ski;
2005 static struct SetupShortcutInfo ssi;
2014 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
2015 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
2016 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
2017 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
2018 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2019 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2024 if (HandleKeysDebug(key, key_status))
2025 return; // do not handle already processed keys again
2027 // map special keys (media keys / remote control buttons) to default keys
2028 if (key == KSYM_PlayPause)
2030 else if (key == KSYM_Select)
2033 HandleSpecialGameControllerKeys(key, key_status);
2035 if (game_status == GAME_MODE_PLAYING)
2037 // only needed for single-step tape recording mode
2038 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2041 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2043 byte key_action = 0;
2044 byte key_snap_action = 0;
2046 if (setup.input[pnr].use_joystick)
2049 ski = setup.input[pnr].key;
2051 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2052 if (key == *key_info[i].key_custom)
2053 key_action |= key_info[i].action;
2055 // use combined snap+direction keys for the first player only
2058 ssi = setup.shortcut;
2060 // also remember normal snap key when handling snap+direction keys
2061 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2063 for (i = 0; i < NUM_DIRECTIONS; i++)
2065 if (key == *key_info[i].key_snap)
2067 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2068 key_snap_action |= key_info[i].action;
2073 if (key_status == KEY_PRESSED)
2075 stored_player[pnr].action |= key_action;
2076 stored_player[pnr].snap_action |= key_snap_action;
2080 stored_player[pnr].action &= ~key_action;
2081 stored_player[pnr].snap_action &= ~key_snap_action;
2084 // restore snap action if one of several pressed snap keys was released
2085 if (stored_player[pnr].snap_action)
2086 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2088 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2090 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2092 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2094 // if snap key already pressed, keep pause mode when releasing
2095 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2096 has_snapped[pnr] = TRUE;
2098 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2100 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2102 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2103 getRedDiskReleaseFlag_SP() == 0)
2105 // add a single inactive frame before dropping starts
2106 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2107 stored_player[pnr].force_dropping = TRUE;
2110 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2112 // if snap key was pressed without direction, leave pause mode
2113 if (!has_snapped[pnr])
2114 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2116 has_snapped[pnr] = FALSE;
2119 else if (tape.recording && tape.pausing && !tape.use_mouse)
2121 // prevent key release events from un-pausing a paused game
2122 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2123 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2126 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2127 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2133 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2134 if (key == key_info[i].key_default)
2135 joy |= key_info[i].action;
2140 if (key_status == KEY_PRESSED)
2141 key_joystick_mapping |= joy;
2143 key_joystick_mapping &= ~joy;
2148 if (game_status != GAME_MODE_PLAYING)
2149 key_joystick_mapping = 0;
2151 if (key_status == KEY_RELEASED)
2153 // reset flag to ignore repeated "key pressed" events after key release
2154 ignore_repeated_key = FALSE;
2159 if ((key == KSYM_F11 ||
2160 ((key == KSYM_Return ||
2161 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2162 video.fullscreen_available &&
2163 !ignore_repeated_key)
2165 setup.fullscreen = !setup.fullscreen;
2167 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2169 if (game_status == GAME_MODE_SETUP)
2170 RedrawSetupScreenAfterFullscreenToggle();
2172 UpdateMousePosition();
2174 // set flag to ignore repeated "key pressed" events
2175 ignore_repeated_key = TRUE;
2180 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2181 key == KSYM_minus || key == KSYM_KP_Subtract ||
2182 key == KSYM_plus || key == KSYM_KP_Add ||
2183 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2184 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2185 video.window_scaling_available &&
2186 !video.fullscreen_enabled)
2188 if (key == KSYM_0 || key == KSYM_KP_0)
2189 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2190 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2191 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2193 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2195 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2196 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2197 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2198 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2200 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2202 if (game_status == GAME_MODE_SETUP)
2203 RedrawSetupScreenAfterFullscreenToggle();
2205 UpdateMousePosition();
2210 // some key events are handled like clicks for global animations
2211 boolean click = (key == KSYM_space ||
2212 key == KSYM_Return ||
2213 key == KSYM_Escape);
2215 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2217 // do not handle this key event anymore
2218 if (key != KSYM_Escape) // always allow ESC key to be handled
2222 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2223 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2230 if (game_status == GAME_MODE_MAIN &&
2231 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2233 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2238 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2240 if (key == setup.shortcut.save_game)
2242 else if (key == setup.shortcut.load_game)
2244 else if (key == setup.shortcut.toggle_pause)
2245 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2247 HandleTapeButtonKeys(key);
2248 HandleSoundButtonKeys(key);
2251 if (game_status == GAME_MODE_PLAYING && !network_playing)
2253 int centered_player_nr_next = -999;
2255 if (key == setup.shortcut.focus_player_all)
2256 centered_player_nr_next = -1;
2258 for (i = 0; i < MAX_PLAYERS; i++)
2259 if (key == setup.shortcut.focus_player[i])
2260 centered_player_nr_next = i;
2262 if (centered_player_nr_next != -999)
2264 game.centered_player_nr_next = centered_player_nr_next;
2265 game.set_centered_player = TRUE;
2269 tape.centered_player_nr_next = game.centered_player_nr_next;
2270 tape.set_centered_player = TRUE;
2275 HandleKeysSpecial(key);
2277 if (HandleGadgetsKeyInput(key))
2278 return; // do not handle already processed keys again
2280 switch (game_status)
2282 case GAME_MODE_PSEUDO_TYPENAME:
2283 HandleTypeName(0, key);
2286 case GAME_MODE_TITLE:
2287 case GAME_MODE_MAIN:
2288 case GAME_MODE_LEVELS:
2289 case GAME_MODE_LEVELNR:
2290 case GAME_MODE_SETUP:
2291 case GAME_MODE_INFO:
2292 case GAME_MODE_SCORES:
2294 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2301 if (game_status == GAME_MODE_TITLE)
2302 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2303 else if (game_status == GAME_MODE_MAIN)
2304 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2305 else if (game_status == GAME_MODE_LEVELS)
2306 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2307 else if (game_status == GAME_MODE_LEVELNR)
2308 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2309 else if (game_status == GAME_MODE_SETUP)
2310 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2311 else if (game_status == GAME_MODE_INFO)
2312 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2313 else if (game_status == GAME_MODE_SCORES)
2314 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2318 if (game_status != GAME_MODE_MAIN)
2319 FadeSkipNextFadeIn();
2321 if (game_status == GAME_MODE_TITLE)
2322 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2323 else if (game_status == GAME_MODE_LEVELS)
2324 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2325 else if (game_status == GAME_MODE_LEVELNR)
2326 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2327 else if (game_status == GAME_MODE_SETUP)
2328 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2329 else if (game_status == GAME_MODE_INFO)
2330 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2331 else if (game_status == GAME_MODE_SCORES)
2332 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2336 if (game_status == GAME_MODE_LEVELS)
2337 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2338 else if (game_status == GAME_MODE_LEVELNR)
2339 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2340 else if (game_status == GAME_MODE_SETUP)
2341 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2342 else if (game_status == GAME_MODE_INFO)
2343 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2344 else if (game_status == GAME_MODE_SCORES)
2345 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2348 case KSYM_Page_Down:
2349 if (game_status == GAME_MODE_LEVELS)
2350 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2351 else if (game_status == GAME_MODE_LEVELNR)
2352 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2353 else if (game_status == GAME_MODE_SETUP)
2354 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2355 else if (game_status == GAME_MODE_INFO)
2356 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2357 else if (game_status == GAME_MODE_SCORES)
2358 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2366 case GAME_MODE_EDITOR:
2367 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2368 HandleLevelEditorKeyInput(key);
2371 case GAME_MODE_PLAYING:
2376 RequestQuitGame(setup.ask_on_escape);
2386 if (key == KSYM_Escape)
2388 SetGameStatus(GAME_MODE_MAIN);
2397 void HandleNoEvent(void)
2399 HandleMouseCursor();
2401 switch (game_status)
2403 case GAME_MODE_PLAYING:
2404 HandleButtonOrFinger(-1, -1, -1);
2409 void HandleEventActions(void)
2411 // if (button_status && game_status != GAME_MODE_PLAYING)
2412 if (button_status && (game_status != GAME_MODE_PLAYING ||
2414 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2416 HandleButton(0, 0, button_status, -button_status);
2423 if (network.enabled)
2426 switch (game_status)
2428 case GAME_MODE_MAIN:
2429 DrawPreviewLevelAnimation();
2432 case GAME_MODE_EDITOR:
2433 HandleLevelEditorIdle();
2441 static void HandleTileCursor(int dx, int dy, int button)
2444 ClearPlayerMouseAction();
2451 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2452 (dx < 0 ? MB_LEFTBUTTON :
2453 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2455 else if (!tile_cursor.moving)
2457 int old_xpos = tile_cursor.xpos;
2458 int old_ypos = tile_cursor.ypos;
2459 int new_xpos = old_xpos;
2460 int new_ypos = old_ypos;
2462 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2463 new_xpos = old_xpos + dx;
2465 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2466 new_ypos = old_ypos + dy;
2468 SetTileCursorTargetXY(new_xpos, new_ypos);
2472 static int HandleJoystickForAllPlayers(void)
2476 boolean no_joysticks_configured = TRUE;
2477 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2478 static byte joy_action_last[MAX_PLAYERS];
2480 for (i = 0; i < MAX_PLAYERS; i++)
2481 if (setup.input[i].use_joystick)
2482 no_joysticks_configured = FALSE;
2484 // if no joysticks configured, map connected joysticks to players
2485 if (no_joysticks_configured)
2486 use_as_joystick_nr = TRUE;
2488 for (i = 0; i < MAX_PLAYERS; i++)
2490 byte joy_action = 0;
2492 joy_action = JoystickExt(i, use_as_joystick_nr);
2493 result |= joy_action;
2495 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2496 joy_action != joy_action_last[i])
2497 stored_player[i].action = joy_action;
2499 joy_action_last[i] = joy_action;
2505 void HandleJoystick(void)
2507 static unsigned int joytest_delay = 0;
2508 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2509 static int joytest_last = 0;
2510 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2511 int delay_value = GADGET_FRAME_DELAY;
2512 int joystick = HandleJoystickForAllPlayers();
2513 int keyboard = key_joystick_mapping;
2514 int joy = (joystick | keyboard);
2515 int joytest = joystick;
2516 int left = joy & JOY_LEFT;
2517 int right = joy & JOY_RIGHT;
2518 int up = joy & JOY_UP;
2519 int down = joy & JOY_DOWN;
2520 int button = joy & JOY_BUTTON;
2521 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2522 int dx = (left ? -1 : right ? 1 : 0);
2523 int dy = (up ? -1 : down ? 1 : 0);
2524 boolean use_delay_value_first = (joytest != joytest_last);
2526 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2528 // do not handle this button event anymore
2532 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2533 anyTextGadgetActive()))
2535 // leave name input in main menu or text input gadget
2536 HandleKey(KSYM_Escape, KEY_PRESSED);
2537 HandleKey(KSYM_Escape, KEY_RELEASED);
2542 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2544 if (game_status == GAME_MODE_PLAYING)
2546 // when playing MM style levels, also use delay for keyboard events
2547 joytest |= keyboard;
2549 // only use first delay value for new events, but not for changed events
2550 use_delay_value_first = (!joytest != !joytest_last);
2552 // only use delay after the initial keyboard event
2556 // for any joystick or keyboard event, enable playfield tile cursor
2557 if (dx || dy || button)
2558 SetTileCursorEnabled(TRUE);
2561 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2563 // delay joystick/keyboard actions if axes/keys continually pressed
2564 newbutton = dx = dy = 0;
2568 // first start with longer delay, then continue with shorter delay
2569 joytest_delay_value =
2570 (use_delay_value_first ? delay_value_first : delay_value);
2573 joytest_last = joytest;
2575 switch (game_status)
2577 case GAME_MODE_TITLE:
2578 case GAME_MODE_MAIN:
2579 case GAME_MODE_LEVELS:
2580 case GAME_MODE_LEVELNR:
2581 case GAME_MODE_SETUP:
2582 case GAME_MODE_INFO:
2583 case GAME_MODE_SCORES:
2585 if (anyTextGadgetActive())
2588 if (game_status == GAME_MODE_TITLE)
2589 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2590 else if (game_status == GAME_MODE_MAIN)
2591 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2592 else if (game_status == GAME_MODE_LEVELS)
2593 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2594 else if (game_status == GAME_MODE_LEVELNR)
2595 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2596 else if (game_status == GAME_MODE_SETUP)
2597 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2598 else if (game_status == GAME_MODE_INFO)
2599 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2600 else if (game_status == GAME_MODE_SCORES)
2601 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2606 case GAME_MODE_PLAYING:
2608 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2609 if (tape.playing || keyboard)
2610 newbutton = ((joy & JOY_BUTTON) != 0);
2613 if (newbutton && game.all_players_gone)
2620 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2622 if (joystick & JOY_ACTION)
2623 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2625 else if (tape.recording && tape.pausing && !tape.use_mouse)
2627 if (joystick & JOY_ACTION)
2628 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2631 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2632 HandleTileCursor(dx, dy, button);
2641 void HandleSpecialGameControllerButtons(Event *event)
2646 switch (event->type)
2648 case SDL_CONTROLLERBUTTONDOWN:
2649 key_status = KEY_PRESSED;
2652 case SDL_CONTROLLERBUTTONUP:
2653 key_status = KEY_RELEASED;
2660 switch (event->cbutton.button)
2662 case SDL_CONTROLLER_BUTTON_START:
2666 case SDL_CONTROLLER_BUTTON_BACK:
2674 HandleKey(key, key_status);
2677 void HandleSpecialGameControllerKeys(Key key, int key_status)
2679 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2680 int button = SDL_CONTROLLER_BUTTON_INVALID;
2682 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2683 if (key == KSYM_Rewind)
2684 button = SDL_CONTROLLER_BUTTON_A;
2685 else if (key == KSYM_FastForward || key == KSYM_Menu)
2686 button = SDL_CONTROLLER_BUTTON_B;
2688 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2692 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2693 SDL_CONTROLLERBUTTONUP);
2695 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2696 event.cbutton.button = button;
2697 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2700 HandleJoystickEvent(&event);
2705 boolean DoKeysymAction(int keysym)
2709 Key key = (Key)(-keysym);
2711 HandleKey(key, KEY_PRESSED);
2712 HandleKey(key, KEY_RELEASED);