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 level.native_em_level->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);
1455 HandleKeyModState(keymod, key_status);
1457 // only handle raw key input without text modifier keys pressed
1458 if (!checkTextInputKeyModState())
1459 HandleKey(key, key_status);
1462 static int HandleDropFileEvent(char *filename)
1464 Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
1466 // check and extract dropped zip files into correct user data directory
1467 if (!strSuffixLower(filename, ".zip"))
1469 Error(ERR_WARN, "file '%s' not supported", filename);
1471 return TREE_TYPE_UNDEFINED;
1474 TreeInfo *tree_node = NULL;
1475 int tree_type = GetZipFileTreeType(filename);
1476 char *directory = TREE_USERDIR(tree_type);
1478 if (directory == NULL)
1480 Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1482 return TREE_TYPE_UNDEFINED;
1485 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1486 game_status == GAME_MODE_LEVELS &&
1487 leveldir_current->node_parent != NULL)
1489 // extract new level set next to currently selected level set
1490 tree_node = leveldir_current;
1492 // get parent directory of currently selected level set directory
1493 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1495 // use private level directory instead of top-level package level directory
1496 if (strPrefix(directory, options.level_directory) &&
1497 strEqual(leveldir_current->node_parent->fullpath, "."))
1498 directory = getUserLevelDir(NULL);
1501 // extract level or artwork set from zip file to target directory
1502 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1504 if (top_dir == NULL)
1506 // error message already issued by "ExtractZipFileIntoDirectory()"
1508 return TREE_TYPE_UNDEFINED;
1511 // add extracted level or artwork set to tree info structure
1512 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1514 // update menu screen (and possibly change current level set)
1515 DrawScreenAfterAddingSet(top_dir, tree_type);
1520 static void HandleDropTextEvent(char *text)
1522 Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1525 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1526 int num_artwork_sets_succeeded,
1527 int num_files_failed)
1529 // only show request dialog if no other request dialog already active
1530 if (game.request_active)
1533 // this case can happen with drag-and-drop with older SDL versions
1534 if (num_level_sets_succeeded == 0 &&
1535 num_artwork_sets_succeeded == 0 &&
1536 num_files_failed == 0)
1541 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1543 char message_part1[50];
1545 sprintf(message_part1, "New %s set%s added",
1546 (num_artwork_sets_succeeded == 0 ? "level" :
1547 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1548 (num_level_sets_succeeded +
1549 num_artwork_sets_succeeded > 1 ? "s" : ""));
1551 if (num_files_failed > 0)
1552 sprintf(message, "%s, but %d dropped file%s failed!",
1553 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1555 sprintf(message, "%s!", message_part1);
1557 else if (num_files_failed > 0)
1559 sprintf(message, "Failed to process dropped file%s!",
1560 num_files_failed > 1 ? "s" : "");
1563 Request(message, REQ_CONFIRM);
1566 void HandleDropEvent(Event *event)
1568 static boolean confirm_on_drop_complete = FALSE;
1569 static int num_level_sets_succeeded = 0;
1570 static int num_artwork_sets_succeeded = 0;
1571 static int num_files_failed = 0;
1573 switch (event->type)
1577 confirm_on_drop_complete = TRUE;
1578 num_level_sets_succeeded = 0;
1579 num_artwork_sets_succeeded = 0;
1580 num_files_failed = 0;
1587 int tree_type = HandleDropFileEvent(event->drop.file);
1589 if (tree_type == TREE_TYPE_LEVEL_DIR)
1590 num_level_sets_succeeded++;
1591 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1592 tree_type == TREE_TYPE_SOUNDS_DIR ||
1593 tree_type == TREE_TYPE_MUSIC_DIR)
1594 num_artwork_sets_succeeded++;
1598 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1599 if (!confirm_on_drop_complete)
1601 // process all remaining events, including further SDL_DROPFILE events
1604 HandleDropCompleteEvent(num_level_sets_succeeded,
1605 num_artwork_sets_succeeded,
1608 num_level_sets_succeeded = 0;
1609 num_artwork_sets_succeeded = 0;
1610 num_files_failed = 0;
1618 HandleDropTextEvent(event->drop.file);
1623 case SDL_DROPCOMPLETE:
1625 HandleDropCompleteEvent(num_level_sets_succeeded,
1626 num_artwork_sets_succeeded,
1633 if (event->drop.file != NULL)
1634 SDL_free(event->drop.file);
1637 void HandleUserEvent(UserEvent *event)
1639 switch (event->code)
1641 case USEREVENT_ANIM_DELAY_ACTION:
1642 case USEREVENT_ANIM_EVENT_ACTION:
1643 // execute action functions until matching action was found
1644 if (DoKeysymAction(event->value1) ||
1645 DoGadgetAction(event->value1) ||
1646 DoScreenAction(event->value1))
1655 void HandleButton(int mx, int my, int button, int button_nr)
1657 static int old_mx = 0, old_my = 0;
1658 boolean button_hold = FALSE;
1659 boolean handle_gadgets = TRUE;
1665 button_nr = -button_nr;
1674 #if defined(PLATFORM_ANDROID)
1675 // when playing, only handle gadgets when using "follow finger" controls
1676 // or when using touch controls in combination with the MM game engine
1677 // or when using gadgets that do not overlap with virtual buttons
1679 (game_status != GAME_MODE_PLAYING ||
1680 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1681 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1682 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1683 !CheckVirtualButtonPressed(mx, my, button)));
1685 // always recognize potentially releasing already pressed gadgets
1686 if (button == MB_RELEASED)
1687 handle_gadgets = TRUE;
1689 // always recognize pressing or releasing overlay touch buttons
1690 if (CheckPosition_OverlayTouchButtons(mx, my, button) && !motion_status)
1691 handle_gadgets = TRUE;
1694 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1696 // do not handle this button event anymore
1697 return; // force mouse event not to be handled at all
1700 if (handle_gadgets && HandleGadgets(mx, my, button))
1702 // do not handle this button event anymore
1703 mx = my = -32; // force mouse event to be outside screen tiles
1706 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1709 // do not use scroll wheel button events for anything other than gadgets
1710 if (IS_WHEEL_BUTTON(button_nr))
1713 switch (game_status)
1715 case GAME_MODE_TITLE:
1716 HandleTitleScreen(mx, my, 0, 0, button);
1719 case GAME_MODE_MAIN:
1720 HandleMainMenu(mx, my, 0, 0, button);
1723 case GAME_MODE_PSEUDO_TYPENAME:
1724 HandleTypeName(0, KSYM_Return);
1727 case GAME_MODE_LEVELS:
1728 HandleChooseLevelSet(mx, my, 0, 0, button);
1731 case GAME_MODE_LEVELNR:
1732 HandleChooseLevelNr(mx, my, 0, 0, button);
1735 case GAME_MODE_SCORES:
1736 HandleHallOfFame(0, 0, 0, 0, button);
1739 case GAME_MODE_EDITOR:
1740 HandleLevelEditorIdle();
1743 case GAME_MODE_INFO:
1744 HandleInfoScreen(mx, my, 0, 0, button);
1747 case GAME_MODE_SETUP:
1748 HandleSetupScreen(mx, my, 0, 0, button);
1751 case GAME_MODE_PLAYING:
1752 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1753 HandleButtonOrFinger(mx, my, button);
1755 SetPlayerMouseAction(mx, my, button);
1758 if (button == MB_PRESSED && !motion_status && !button_hold &&
1759 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1760 DumpTileFromScreen(mx, my);
1770 static boolean is_string_suffix(char *string, char *suffix)
1772 int string_len = strlen(string);
1773 int suffix_len = strlen(suffix);
1775 if (suffix_len > string_len)
1778 return (strEqual(&string[string_len - suffix_len], suffix));
1781 #define MAX_CHEAT_INPUT_LEN 32
1783 static void HandleKeysSpecial(Key key)
1785 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1786 char letter = getCharFromKey(key);
1787 int cheat_input_len = strlen(cheat_input);
1793 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1795 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1796 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1798 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1801 cheat_input[cheat_input_len++] = letter;
1802 cheat_input[cheat_input_len] = '\0';
1804 #if DEBUG_EVENTS_KEY
1805 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1808 if (game_status == GAME_MODE_MAIN)
1810 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1811 is_string_suffix(cheat_input, ":ist"))
1813 InsertSolutionTape();
1815 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1816 is_string_suffix(cheat_input, ":pst"))
1820 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1821 is_string_suffix(cheat_input, ":rg"))
1823 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1826 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1827 is_string_suffix(cheat_input, ":rs"))
1829 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1832 else if (is_string_suffix(cheat_input, ":reload-music") ||
1833 is_string_suffix(cheat_input, ":rm"))
1835 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1838 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1839 is_string_suffix(cheat_input, ":ra"))
1841 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1842 1 << ARTWORK_TYPE_SOUNDS |
1843 1 << ARTWORK_TYPE_MUSIC);
1846 else if (is_string_suffix(cheat_input, ":dump-level") ||
1847 is_string_suffix(cheat_input, ":dl"))
1851 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1852 is_string_suffix(cheat_input, ":dt"))
1856 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1857 is_string_suffix(cheat_input, ":ft"))
1859 /* fix single-player tapes that contain player input for more than one
1860 player (due to a bug in 3.3.1.2 and earlier versions), which results
1861 in playing levels with more than one player in multi-player mode,
1862 even though the tape was originally recorded in single-player mode */
1864 // remove player input actions for all players but the first one
1865 for (i = 1; i < MAX_PLAYERS; i++)
1866 tape.player_participates[i] = FALSE;
1868 tape.changed = TRUE;
1870 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1871 is_string_suffix(cheat_input, ":snl"))
1873 SaveNativeLevel(&level);
1875 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1876 is_string_suffix(cheat_input, ":fps"))
1878 global.show_frames_per_second = !global.show_frames_per_second;
1881 else if (game_status == GAME_MODE_PLAYING)
1884 if (is_string_suffix(cheat_input, ".q"))
1885 DEBUG_SetMaximumDynamite();
1888 else if (game_status == GAME_MODE_EDITOR)
1890 if (is_string_suffix(cheat_input, ":dump-brush") ||
1891 is_string_suffix(cheat_input, ":DB"))
1895 else if (is_string_suffix(cheat_input, ":DDB"))
1900 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1902 if (letter == 'x') // copy brush to clipboard (small size)
1904 CopyBrushToClipboard_Small();
1906 else if (letter == 'c') // copy brush to clipboard (normal size)
1908 CopyBrushToClipboard();
1910 else if (letter == 'v') // paste brush from Clipboard
1912 CopyClipboardToBrush();
1914 else if (letter == 'z') // undo or redo last operation
1916 if (GetKeyModState() & KMOD_Shift)
1917 RedoLevelEditorOperation();
1919 UndoLevelEditorOperation();
1924 // special key shortcuts for all game modes
1925 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1926 is_string_suffix(cheat_input, ":dea") ||
1927 is_string_suffix(cheat_input, ":DEA"))
1929 DumpGadgetIdentifiers();
1930 DumpScreenIdentifiers();
1934 boolean HandleKeysDebug(Key key, int key_status)
1939 if (key_status != KEY_PRESSED)
1942 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1944 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1946 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1948 if (key == setup.debug.frame_delay_key[i] &&
1949 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1951 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1952 setup.debug.frame_delay[i] : setup.game_frame_delay);
1954 if (!setup.debug.frame_delay_game_only)
1955 MenuFrameDelay = GameFrameDelay;
1957 SetVideoFrameDelay(GameFrameDelay);
1959 if (GameFrameDelay > ONE_SECOND_DELAY)
1960 Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
1961 else if (GameFrameDelay != 0)
1962 Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
1963 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1964 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1966 Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
1973 if (game_status == GAME_MODE_PLAYING)
1977 options.debug = !options.debug;
1979 Error(ERR_INFO, "debug mode %s",
1980 (options.debug ? "enabled" : "disabled"));
1984 else if (key == KSYM_v)
1986 Error(ERR_INFO, "currently using game engine version %d",
1987 game.engine_version);
1997 void HandleKey(Key key, int key_status)
1999 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2000 static boolean ignore_repeated_key = FALSE;
2001 static struct SetupKeyboardInfo ski;
2002 static struct SetupShortcutInfo ssi;
2011 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
2012 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
2013 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
2014 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
2015 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2016 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2021 if (HandleKeysDebug(key, key_status))
2022 return; // do not handle already processed keys again
2024 // map special keys (media keys / remote control buttons) to default keys
2025 if (key == KSYM_PlayPause)
2027 else if (key == KSYM_Select)
2030 HandleSpecialGameControllerKeys(key, key_status);
2032 if (game_status == GAME_MODE_PLAYING)
2034 // only needed for single-step tape recording mode
2035 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2038 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2040 byte key_action = 0;
2041 byte key_snap_action = 0;
2043 if (setup.input[pnr].use_joystick)
2046 ski = setup.input[pnr].key;
2048 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2049 if (key == *key_info[i].key_custom)
2050 key_action |= key_info[i].action;
2052 // use combined snap+direction keys for the first player only
2055 ssi = setup.shortcut;
2057 // also remember normal snap key when handling snap+direction keys
2058 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2060 for (i = 0; i < NUM_DIRECTIONS; i++)
2062 if (key == *key_info[i].key_snap)
2064 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2065 key_snap_action |= key_info[i].action;
2070 if (key_status == KEY_PRESSED)
2072 stored_player[pnr].action |= key_action;
2073 stored_player[pnr].snap_action |= key_snap_action;
2077 stored_player[pnr].action &= ~key_action;
2078 stored_player[pnr].snap_action &= ~key_snap_action;
2081 // restore snap action if one of several pressed snap keys was released
2082 if (stored_player[pnr].snap_action)
2083 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2085 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2087 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2089 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2091 // if snap key already pressed, keep pause mode when releasing
2092 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2093 has_snapped[pnr] = TRUE;
2095 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2097 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2099 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2100 getRedDiskReleaseFlag_SP() == 0)
2102 // add a single inactive frame before dropping starts
2103 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2104 stored_player[pnr].force_dropping = TRUE;
2107 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2109 // if snap key was pressed without direction, leave pause mode
2110 if (!has_snapped[pnr])
2111 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2113 has_snapped[pnr] = FALSE;
2116 else if (tape.recording && tape.pausing && !tape.use_mouse)
2118 // prevent key release events from un-pausing a paused game
2119 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2120 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2123 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2124 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2130 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2131 if (key == key_info[i].key_default)
2132 joy |= key_info[i].action;
2137 if (key_status == KEY_PRESSED)
2138 key_joystick_mapping |= joy;
2140 key_joystick_mapping &= ~joy;
2145 if (game_status != GAME_MODE_PLAYING)
2146 key_joystick_mapping = 0;
2148 if (key_status == KEY_RELEASED)
2150 // reset flag to ignore repeated "key pressed" events after key release
2151 ignore_repeated_key = FALSE;
2156 if ((key == KSYM_F11 ||
2157 ((key == KSYM_Return ||
2158 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2159 video.fullscreen_available &&
2160 !ignore_repeated_key)
2162 setup.fullscreen = !setup.fullscreen;
2164 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2166 if (game_status == GAME_MODE_SETUP)
2167 RedrawSetupScreenAfterFullscreenToggle();
2169 UpdateMousePosition();
2171 // set flag to ignore repeated "key pressed" events
2172 ignore_repeated_key = TRUE;
2177 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2178 key == KSYM_minus || key == KSYM_KP_Subtract ||
2179 key == KSYM_plus || key == KSYM_KP_Add ||
2180 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2181 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2182 video.window_scaling_available &&
2183 !video.fullscreen_enabled)
2185 if (key == KSYM_0 || key == KSYM_KP_0)
2186 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2187 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2188 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2190 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2192 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2193 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2194 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2195 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2197 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2199 if (game_status == GAME_MODE_SETUP)
2200 RedrawSetupScreenAfterFullscreenToggle();
2202 UpdateMousePosition();
2207 // some key events are handled like clicks for global animations
2208 boolean click = (key == KSYM_space ||
2209 key == KSYM_Return ||
2210 key == KSYM_Escape);
2212 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2214 // do not handle this key event anymore
2215 if (key != KSYM_Escape) // always allow ESC key to be handled
2219 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2220 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2227 if (game_status == GAME_MODE_MAIN &&
2228 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2230 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2235 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2237 if (key == setup.shortcut.save_game)
2239 else if (key == setup.shortcut.load_game)
2241 else if (key == setup.shortcut.toggle_pause)
2242 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2244 HandleTapeButtonKeys(key);
2245 HandleSoundButtonKeys(key);
2248 if (game_status == GAME_MODE_PLAYING && !network_playing)
2250 int centered_player_nr_next = -999;
2252 if (key == setup.shortcut.focus_player_all)
2253 centered_player_nr_next = -1;
2255 for (i = 0; i < MAX_PLAYERS; i++)
2256 if (key == setup.shortcut.focus_player[i])
2257 centered_player_nr_next = i;
2259 if (centered_player_nr_next != -999)
2261 game.centered_player_nr_next = centered_player_nr_next;
2262 game.set_centered_player = TRUE;
2266 tape.centered_player_nr_next = game.centered_player_nr_next;
2267 tape.set_centered_player = TRUE;
2272 HandleKeysSpecial(key);
2274 if (HandleGadgetsKeyInput(key))
2275 return; // do not handle already processed keys again
2277 switch (game_status)
2279 case GAME_MODE_PSEUDO_TYPENAME:
2280 HandleTypeName(0, key);
2283 case GAME_MODE_TITLE:
2284 case GAME_MODE_MAIN:
2285 case GAME_MODE_LEVELS:
2286 case GAME_MODE_LEVELNR:
2287 case GAME_MODE_SETUP:
2288 case GAME_MODE_INFO:
2289 case GAME_MODE_SCORES:
2291 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2298 if (game_status == GAME_MODE_TITLE)
2299 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2300 else if (game_status == GAME_MODE_MAIN)
2301 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2302 else if (game_status == GAME_MODE_LEVELS)
2303 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2304 else if (game_status == GAME_MODE_LEVELNR)
2305 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2306 else if (game_status == GAME_MODE_SETUP)
2307 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2308 else if (game_status == GAME_MODE_INFO)
2309 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2310 else if (game_status == GAME_MODE_SCORES)
2311 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2315 if (game_status != GAME_MODE_MAIN)
2316 FadeSkipNextFadeIn();
2318 if (game_status == GAME_MODE_TITLE)
2319 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2320 else if (game_status == GAME_MODE_LEVELS)
2321 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2322 else if (game_status == GAME_MODE_LEVELNR)
2323 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2324 else if (game_status == GAME_MODE_SETUP)
2325 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2326 else if (game_status == GAME_MODE_INFO)
2327 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2328 else if (game_status == GAME_MODE_SCORES)
2329 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2333 if (game_status == GAME_MODE_LEVELS)
2334 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2335 else if (game_status == GAME_MODE_LEVELNR)
2336 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2337 else if (game_status == GAME_MODE_SETUP)
2338 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2339 else if (game_status == GAME_MODE_INFO)
2340 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2341 else if (game_status == GAME_MODE_SCORES)
2342 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2345 case KSYM_Page_Down:
2346 if (game_status == GAME_MODE_LEVELS)
2347 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2348 else if (game_status == GAME_MODE_LEVELNR)
2349 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2350 else if (game_status == GAME_MODE_SETUP)
2351 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2352 else if (game_status == GAME_MODE_INFO)
2353 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2354 else if (game_status == GAME_MODE_SCORES)
2355 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2363 case GAME_MODE_EDITOR:
2364 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2365 HandleLevelEditorKeyInput(key);
2368 case GAME_MODE_PLAYING:
2373 RequestQuitGame(setup.ask_on_escape);
2383 if (key == KSYM_Escape)
2385 SetGameStatus(GAME_MODE_MAIN);
2394 void HandleNoEvent(void)
2396 HandleMouseCursor();
2398 switch (game_status)
2400 case GAME_MODE_PLAYING:
2401 HandleButtonOrFinger(-1, -1, -1);
2406 void HandleEventActions(void)
2408 // if (button_status && game_status != GAME_MODE_PLAYING)
2409 if (button_status && (game_status != GAME_MODE_PLAYING ||
2411 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2413 HandleButton(0, 0, button_status, -button_status);
2420 if (network.enabled)
2423 switch (game_status)
2425 case GAME_MODE_MAIN:
2426 DrawPreviewLevelAnimation();
2429 case GAME_MODE_EDITOR:
2430 HandleLevelEditorIdle();
2438 static void HandleTileCursor(int dx, int dy, int button)
2441 ClearPlayerMouseAction();
2448 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2449 (dx < 0 ? MB_LEFTBUTTON :
2450 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2452 else if (!tile_cursor.moving)
2454 int old_xpos = tile_cursor.xpos;
2455 int old_ypos = tile_cursor.ypos;
2456 int new_xpos = old_xpos;
2457 int new_ypos = old_ypos;
2459 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2460 new_xpos = old_xpos + dx;
2462 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2463 new_ypos = old_ypos + dy;
2465 SetTileCursorTargetXY(new_xpos, new_ypos);
2469 static int HandleJoystickForAllPlayers(void)
2473 boolean no_joysticks_configured = TRUE;
2474 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2475 static byte joy_action_last[MAX_PLAYERS];
2477 for (i = 0; i < MAX_PLAYERS; i++)
2478 if (setup.input[i].use_joystick)
2479 no_joysticks_configured = FALSE;
2481 // if no joysticks configured, map connected joysticks to players
2482 if (no_joysticks_configured)
2483 use_as_joystick_nr = TRUE;
2485 for (i = 0; i < MAX_PLAYERS; i++)
2487 byte joy_action = 0;
2489 joy_action = JoystickExt(i, use_as_joystick_nr);
2490 result |= joy_action;
2492 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2493 joy_action != joy_action_last[i])
2494 stored_player[i].action = joy_action;
2496 joy_action_last[i] = joy_action;
2502 void HandleJoystick(void)
2504 static unsigned int joytest_delay = 0;
2505 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2506 static int joytest_last = 0;
2507 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2508 int delay_value = GADGET_FRAME_DELAY;
2509 int joystick = HandleJoystickForAllPlayers();
2510 int keyboard = key_joystick_mapping;
2511 int joy = (joystick | keyboard);
2512 int joytest = joystick;
2513 int left = joy & JOY_LEFT;
2514 int right = joy & JOY_RIGHT;
2515 int up = joy & JOY_UP;
2516 int down = joy & JOY_DOWN;
2517 int button = joy & JOY_BUTTON;
2518 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2519 int dx = (left ? -1 : right ? 1 : 0);
2520 int dy = (up ? -1 : down ? 1 : 0);
2521 boolean use_delay_value_first = (joytest != joytest_last);
2523 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2525 // do not handle this button event anymore
2529 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2530 anyTextGadgetActive()))
2532 // leave name input in main menu or text input gadget
2533 HandleKey(KSYM_Escape, KEY_PRESSED);
2534 HandleKey(KSYM_Escape, KEY_RELEASED);
2539 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2541 if (game_status == GAME_MODE_PLAYING)
2543 // when playing MM style levels, also use delay for keyboard events
2544 joytest |= keyboard;
2546 // only use first delay value for new events, but not for changed events
2547 use_delay_value_first = (!joytest != !joytest_last);
2549 // only use delay after the initial keyboard event
2553 // for any joystick or keyboard event, enable playfield tile cursor
2554 if (dx || dy || button)
2555 SetTileCursorEnabled(TRUE);
2558 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2560 // delay joystick/keyboard actions if axes/keys continually pressed
2561 newbutton = dx = dy = 0;
2565 // first start with longer delay, then continue with shorter delay
2566 joytest_delay_value =
2567 (use_delay_value_first ? delay_value_first : delay_value);
2570 joytest_last = joytest;
2572 switch (game_status)
2574 case GAME_MODE_TITLE:
2575 case GAME_MODE_MAIN:
2576 case GAME_MODE_LEVELS:
2577 case GAME_MODE_LEVELNR:
2578 case GAME_MODE_SETUP:
2579 case GAME_MODE_INFO:
2580 case GAME_MODE_SCORES:
2582 if (anyTextGadgetActive())
2585 if (game_status == GAME_MODE_TITLE)
2586 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2587 else if (game_status == GAME_MODE_MAIN)
2588 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2589 else if (game_status == GAME_MODE_LEVELS)
2590 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2591 else if (game_status == GAME_MODE_LEVELNR)
2592 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2593 else if (game_status == GAME_MODE_SETUP)
2594 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2595 else if (game_status == GAME_MODE_INFO)
2596 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2597 else if (game_status == GAME_MODE_SCORES)
2598 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2603 case GAME_MODE_PLAYING:
2605 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2606 if (tape.playing || keyboard)
2607 newbutton = ((joy & JOY_BUTTON) != 0);
2610 if (newbutton && game.all_players_gone)
2617 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2619 if (joystick & JOY_ACTION)
2620 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2622 else if (tape.recording && tape.pausing && !tape.use_mouse)
2624 if (joystick & JOY_ACTION)
2625 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2628 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2629 HandleTileCursor(dx, dy, button);
2638 void HandleSpecialGameControllerButtons(Event *event)
2643 switch (event->type)
2645 case SDL_CONTROLLERBUTTONDOWN:
2646 key_status = KEY_PRESSED;
2649 case SDL_CONTROLLERBUTTONUP:
2650 key_status = KEY_RELEASED;
2657 switch (event->cbutton.button)
2659 case SDL_CONTROLLER_BUTTON_START:
2663 case SDL_CONTROLLER_BUTTON_BACK:
2671 HandleKey(key, key_status);
2674 void HandleSpecialGameControllerKeys(Key key, int key_status)
2676 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2677 int button = SDL_CONTROLLER_BUTTON_INVALID;
2679 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2680 if (key == KSYM_Rewind)
2681 button = SDL_CONTROLLER_BUTTON_A;
2682 else if (key == KSYM_FastForward || key == KSYM_Menu)
2683 button = SDL_CONTROLLER_BUTTON_B;
2685 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2689 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2690 SDL_CONTROLLERBUTTONUP);
2692 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2693 event.cbutton.button = button;
2694 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2697 HandleJoystickEvent(&event);
2702 boolean DoKeysymAction(int keysym)
2706 Key key = (Key)(-keysym);
2708 HandleKey(key, KEY_PRESSED);
2709 HandleKey(key, KEY_RELEASED);