1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://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 ClearTouchInfo(void);
47 static void HandleNoEvent(void);
48 static void HandleEventActions(void);
51 // event filter to set mouse x/y position (for pointer class global animations)
52 // (this is especially required to ensure smooth global animation mouse pointer
53 // movement when the screen is updated without handling events; this can happen
54 // when drawing door/envelope request animations, for example)
56 int FilterMouseMotionEvents(void *userdata, Event *event)
58 if (event->type == EVENT_MOTIONNOTIFY)
60 int mouse_x = ((MotionEvent *)event)->x;
61 int mouse_y = ((MotionEvent *)event)->y;
63 UpdateRawMousePosition(mouse_x, mouse_y);
69 // event filter especially needed for SDL event filtering due to
70 // delay problems with lots of mouse motion events when mouse button
71 // not pressed (X11 can handle this with 'PointerMotionHintMask')
73 // event filter addition for SDL2: as SDL2 does not have a function to enable
74 // or disable keyboard auto-repeat, filter repeated keyboard events instead
76 static int FilterEvents(const Event *event)
80 // skip repeated key press events if keyboard auto-repeat is disabled
81 if (event->type == EVENT_KEYPRESS &&
86 if (event->type == EVENT_BUTTONPRESS ||
87 event->type == EVENT_BUTTONRELEASE)
89 ((ButtonEvent *)event)->x -= video.screen_xoffset;
90 ((ButtonEvent *)event)->y -= video.screen_yoffset;
92 else if (event->type == EVENT_MOTIONNOTIFY)
94 ((MotionEvent *)event)->x -= video.screen_xoffset;
95 ((MotionEvent *)event)->y -= video.screen_yoffset;
98 if (event->type == EVENT_BUTTONPRESS ||
99 event->type == EVENT_BUTTONRELEASE ||
100 event->type == EVENT_MOTIONNOTIFY)
102 // do not reset mouse cursor before all pending events have been processed
103 if (gfx.cursor_mode == cursor_mode_last &&
104 ((game_status == GAME_MODE_TITLE &&
105 gfx.cursor_mode == CURSOR_NONE) ||
106 (game_status == GAME_MODE_PLAYING &&
107 gfx.cursor_mode == CURSOR_PLAYFIELD)))
109 SetMouseCursor(CURSOR_DEFAULT);
111 DelayReached(&special_cursor_delay, 0);
113 cursor_mode_last = CURSOR_DEFAULT;
117 // non-motion events are directly passed to event handler functions
118 if (event->type != EVENT_MOTIONNOTIFY)
121 motion = (MotionEvent *)event;
122 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
123 motion->y >= SY && motion->y < SY + SYSIZE);
125 // set correct mouse x/y position (for pointer class global animations)
126 // (this is required in rare cases where the mouse x/y position calculated
127 // from raw values (to apply logical screen size scaling corrections) does
128 // not match the final mouse event x/y position -- this may happen because
129 // the SDL renderer's viewport position is internally represented as float,
130 // but only accessible as integer, which may lead to rounding errors)
131 gfx.mouse_x = motion->x;
132 gfx.mouse_y = motion->y;
134 // skip mouse motion events without pressed button outside level editor
135 if (button_status == MB_RELEASED &&
136 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
142 // to prevent delay problems, skip mouse motion events if the very next
143 // event is also a mouse motion event (and therefore effectively only
144 // handling the last of a row of mouse motion events in the event queue)
146 static boolean SkipPressedMouseMotionEvent(const Event *event)
148 // nothing to do if the current event is not a mouse motion event
149 if (event->type != EVENT_MOTIONNOTIFY)
152 // only skip motion events with pressed button outside the game
153 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
160 PeekEvent(&next_event);
162 // if next event is also a mouse motion event, skip the current one
163 if (next_event.type == EVENT_MOTIONNOTIFY)
170 static boolean WaitValidEvent(Event *event)
174 if (!FilterEvents(event))
177 if (SkipPressedMouseMotionEvent(event))
183 /* this is especially needed for event modifications for the Android target:
184 if mouse coordinates should be modified in the event filter function,
185 using a properly installed SDL event filter does not work, because in
186 the event filter, mouse coordinates in the event structure are still
187 physical pixel positions, not logical (scaled) screen positions, so this
188 has to be handled at a later stage in the event processing functions
189 (when device pixel positions are already converted to screen positions) */
191 boolean NextValidEvent(Event *event)
193 while (PendingEvent())
194 if (WaitValidEvent(event))
200 void StopProcessingEvents(void)
202 stop_processing_events = TRUE;
205 static void HandleEvents(void)
208 unsigned int event_frame_delay = 0;
209 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
211 ResetDelayCounter(&event_frame_delay);
213 stop_processing_events = FALSE;
215 while (NextValidEvent(&event))
219 case EVENT_BUTTONPRESS:
220 case EVENT_BUTTONRELEASE:
221 HandleButtonEvent((ButtonEvent *) &event);
224 case EVENT_MOTIONNOTIFY:
225 HandleMotionEvent((MotionEvent *) &event);
228 case EVENT_WHEELMOTION:
229 HandleWheelEvent((WheelEvent *) &event);
232 case SDL_WINDOWEVENT:
233 HandleWindowEvent((WindowEvent *) &event);
236 case EVENT_FINGERPRESS:
237 case EVENT_FINGERRELEASE:
238 case EVENT_FINGERMOTION:
239 HandleFingerEvent((FingerEvent *) &event);
242 case EVENT_TEXTINPUT:
243 HandleTextEvent((TextEvent *) &event);
246 case SDL_APP_WILLENTERBACKGROUND:
247 case SDL_APP_DIDENTERBACKGROUND:
248 case SDL_APP_WILLENTERFOREGROUND:
249 case SDL_APP_DIDENTERFOREGROUND:
250 HandlePauseResumeEvent((PauseResumeEvent *) &event);
254 case EVENT_KEYRELEASE:
255 HandleKeyEvent((KeyEvent *) &event);
259 HandleUserEvent((UserEvent *) &event);
263 HandleOtherEvents(&event);
267 // do not handle events for longer than standard frame delay period
268 if (DelayReached(&event_frame_delay, event_frame_delay_value))
271 // do not handle any further events if triggered by a special flag
272 if (stop_processing_events)
277 void HandleOtherEvents(Event *event)
281 case SDL_CONTROLLERBUTTONDOWN:
282 case SDL_CONTROLLERBUTTONUP:
283 // for any game controller button event, disable overlay buttons
284 SetOverlayEnabled(FALSE);
286 HandleSpecialGameControllerButtons(event);
289 case SDL_CONTROLLERDEVICEADDED:
290 case SDL_CONTROLLERDEVICEREMOVED:
291 case SDL_CONTROLLERAXISMOTION:
292 case SDL_JOYAXISMOTION:
293 case SDL_JOYBUTTONDOWN:
294 case SDL_JOYBUTTONUP:
295 HandleJoystickEvent(event);
299 case SDL_DROPCOMPLETE:
302 HandleDropEvent(event);
314 static void HandleMouseCursor(void)
316 if (game_status == GAME_MODE_TITLE)
318 // when showing title screens, hide mouse pointer (if not moved)
320 if (gfx.cursor_mode != CURSOR_NONE &&
321 DelayReached(&special_cursor_delay, special_cursor_delay_value))
323 SetMouseCursor(CURSOR_NONE);
326 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
329 // when playing, display a special mouse pointer inside the playfield
331 // display normal pointer if mouse pressed
332 if (button_status != MB_RELEASED)
333 DelayReached(&special_cursor_delay, 0);
335 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
336 cursor_inside_playfield &&
337 DelayReached(&special_cursor_delay, special_cursor_delay_value))
339 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
341 SetMouseCursor(CURSOR_PLAYFIELD);
344 else if (gfx.cursor_mode != CURSOR_DEFAULT)
346 SetMouseCursor(CURSOR_DEFAULT);
349 // this is set after all pending events have been processed
350 cursor_mode_last = gfx.cursor_mode;
362 // execute event related actions after pending events have been processed
363 HandleEventActions();
365 // don't use all CPU time when idle; the main loop while playing
366 // has its own synchronization and is CPU friendly, too
368 if (game_status == GAME_MODE_PLAYING)
371 // always copy backbuffer to visible screen for every video frame
374 // reset video frame delay to default (may change again while playing)
375 SetVideoFrameDelay(MenuFrameDelay);
377 if (game_status == GAME_MODE_QUIT)
382 void ClearAutoRepeatKeyEvents(void)
384 while (PendingEvent())
388 PeekEvent(&next_event);
390 // if event is repeated key press event, remove it from event queue
391 if (next_event.type == EVENT_KEYPRESS &&
392 next_event.key.repeat)
393 WaitEvent(&next_event);
399 void ClearEventQueue(void)
403 while (NextValidEvent(&event))
407 case EVENT_BUTTONRELEASE:
408 button_status = MB_RELEASED;
411 case EVENT_KEYRELEASE:
415 case SDL_CONTROLLERBUTTONUP:
416 HandleJoystickEvent(&event);
421 HandleOtherEvents(&event);
427 static void ClearPlayerMouseAction(void)
429 local_player->mouse_action.lx = 0;
430 local_player->mouse_action.ly = 0;
431 local_player->mouse_action.button = 0;
434 void ClearPlayerAction(void)
438 // simulate key release events for still pressed keys
439 key_joystick_mapping = 0;
440 for (i = 0; i < MAX_PLAYERS; i++)
442 stored_player[i].action = 0;
443 stored_player[i].snap_action = 0;
447 ClearJoystickState();
448 ClearPlayerMouseAction();
451 static void SetPlayerMouseAction(int mx, int my, int button)
453 int lx = getLevelFromScreenX(mx);
454 int ly = getLevelFromScreenY(my);
455 int new_button = (!local_player->mouse_action.button && button);
457 if (local_player->mouse_action.button_hint)
458 button = local_player->mouse_action.button_hint;
460 ClearPlayerMouseAction();
462 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
465 local_player->mouse_action.lx = lx;
466 local_player->mouse_action.ly = ly;
467 local_player->mouse_action.button = button;
469 if (tape.recording && tape.pausing && tape.use_mouse_actions)
471 // un-pause a paused game only if mouse button was newly pressed down
473 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
476 SetTileCursorXY(lx, ly);
479 static Key GetKeyFromGridButton(int grid_button)
481 return (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
482 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
483 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
484 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
485 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
486 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
490 #if defined(PLATFORM_ANDROID)
491 static boolean CheckVirtualButtonPressed(int mx, int my, int button)
493 float touch_x = (float)(mx + video.screen_xoffset) / video.screen_width;
494 float touch_y = (float)(my + video.screen_yoffset) / video.screen_height;
495 int x = touch_x * overlay.grid_xsize;
496 int y = touch_y * overlay.grid_ysize;
497 int grid_button = overlay.grid_button[x][y];
498 Key key = GetKeyFromGridButton(grid_button);
499 int key_status = (button == MB_RELEASED ? KEY_RELEASED : KEY_PRESSED);
501 return (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
505 void HandleButtonEvent(ButtonEvent *event)
507 #if DEBUG_EVENTS_BUTTON
508 Debug("event:button", "button %d %s, x/y %d/%d\n",
510 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
514 // for any mouse button event, disable playfield tile cursor
515 SetTileCursorEnabled(FALSE);
517 #if defined(HAS_SCREEN_KEYBOARD)
518 if (video.shifted_up)
519 event->y += video.shifted_up_pos;
522 motion_status = FALSE;
524 if (event->type == EVENT_BUTTONPRESS)
525 button_status = event->button;
527 button_status = MB_RELEASED;
529 HandleButton(event->x, event->y, button_status, event->button);
532 void HandleMotionEvent(MotionEvent *event)
534 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
537 motion_status = TRUE;
539 #if DEBUG_EVENTS_MOTION
540 Debug("event:motion", "button %d moved, x/y %d/%d\n",
541 button_status, event->x, event->y);
544 HandleButton(event->x, event->y, button_status, button_status);
547 void HandleWheelEvent(WheelEvent *event)
551 #if DEBUG_EVENTS_WHEEL
553 Debug("event:wheel", "mouse == %d, x/y == %d/%d\n",
554 event->which, event->x, event->y);
556 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
557 Debug("event:wheel", "mouse == %d, x/y == %d/%d, direction == %s\n",
558 event->which, event->x, event->y,
559 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
560 "SDL_MOUSEWHEEL_FLIPPED"));
564 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
565 event->x > 0 ? MB_WHEEL_RIGHT :
566 event->y < 0 ? MB_WHEEL_DOWN :
567 event->y > 0 ? MB_WHEEL_UP : 0);
569 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
570 // accelerated mouse wheel available on Mac and Windows
571 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
573 // no accelerated mouse wheel available on Unix/Linux
574 wheel_steps = DEFAULT_WHEEL_STEPS;
577 motion_status = FALSE;
579 button_status = button_nr;
580 HandleButton(0, 0, button_status, -button_nr);
582 button_status = MB_RELEASED;
583 HandleButton(0, 0, button_status, -button_nr);
586 void HandleWindowEvent(WindowEvent *event)
588 #if DEBUG_EVENTS_WINDOW
589 int subtype = event->event;
592 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
593 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
594 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
595 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
596 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
597 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
598 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
599 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
600 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
601 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
602 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
603 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
604 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
605 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
608 Debug("event:window", "name: '%s', data1: %ld, data2: %ld",
609 event_name, event->data1, event->data2);
613 // (not needed, as the screen gets redrawn every 20 ms anyway)
614 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
615 event->event == SDL_WINDOWEVENT_RESIZED ||
616 event->event == SDL_WINDOWEVENT_EXPOSED)
620 if (event->event == SDL_WINDOWEVENT_RESIZED)
622 if (!video.fullscreen_enabled)
624 int new_window_width = event->data1;
625 int new_window_height = event->data2;
627 // if window size has changed after resizing, calculate new scaling factor
628 if (new_window_width != video.window_width ||
629 new_window_height != video.window_height)
631 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
632 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
634 // (extreme window scaling allowed, but cannot be saved permanently)
635 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
636 setup.window_scaling_percent =
637 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
638 MAX_WINDOW_SCALING_PERCENT);
640 video.window_width = new_window_width;
641 video.window_height = new_window_height;
643 if (game_status == GAME_MODE_SETUP)
644 RedrawSetupScreenAfterFullscreenToggle();
646 UpdateMousePosition();
651 #if defined(PLATFORM_ANDROID)
654 int new_display_width = event->data1;
655 int new_display_height = event->data2;
657 // if fullscreen display size has changed, device has been rotated
658 if (new_display_width != video.display_width ||
659 new_display_height != video.display_height)
661 int nr = GRID_ACTIVE_NR(); // previous screen orientation
663 video.display_width = new_display_width;
664 video.display_height = new_display_height;
666 SDLSetScreenProperties();
667 SetGadgetsPosition_OverlayTouchButtons();
669 // check if screen orientation has changed (should always be true here)
670 if (nr != GRID_ACTIVE_NR())
674 if (game_status == GAME_MODE_SETUP)
675 RedrawSetupScreenAfterScreenRotation(nr);
677 nr = GRID_ACTIVE_NR();
679 overlay.grid_xsize = setup.touch.grid_xsize[nr];
680 overlay.grid_ysize = setup.touch.grid_ysize[nr];
682 for (x = 0; x < MAX_GRID_XSIZE; x++)
683 for (y = 0; y < MAX_GRID_YSIZE; y++)
684 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
692 #define NUM_TOUCH_FINGERS 3
697 SDL_FingerID finger_id;
701 } touch_info[NUM_TOUCH_FINGERS];
703 static void SetTouchInfo(int pos, SDL_FingerID finger_id, int counter,
704 Key key, byte action)
706 touch_info[pos].touched = (action != JOY_NO_ACTION);
707 touch_info[pos].finger_id = finger_id;
708 touch_info[pos].counter = counter;
709 touch_info[pos].key = key;
710 touch_info[pos].action = action;
713 static void ClearTouchInfo(void)
717 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
718 SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
721 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
723 int x = event->x * overlay.grid_xsize;
724 int y = event->y * overlay.grid_ysize;
725 int grid_button = overlay.grid_button[x][y];
726 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
727 Key key = GetKeyFromGridButton(grid_button);
728 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
730 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
734 // for any touch input event, enable overlay buttons (if activated)
735 SetOverlayEnabled(TRUE);
737 Debug("event:finger", "key '%s' was '%s' [fingerId: %lld]",
738 getKeyNameFromKey(key), key_status_name, event->fingerId);
740 if (key_status == KEY_PRESSED)
741 overlay.grid_button_action |= grid_button_action;
743 overlay.grid_button_action &= ~grid_button_action;
745 // check if we already know this touch event's finger id
746 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
748 if (touch_info[i].touched &&
749 touch_info[i].finger_id == event->fingerId)
751 // Debug("event:finger", "MARK 1: %d", i);
757 if (i >= NUM_TOUCH_FINGERS)
759 if (key_status == KEY_PRESSED)
761 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
763 // unknown finger id -- get new, empty slot, if available
764 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
766 if (touch_info[i].counter < oldest_counter)
769 oldest_counter = touch_info[i].counter;
771 // Debug("event:finger", "MARK 2: %d", i);
774 if (!touch_info[i].touched)
776 // Debug("event:finger", "MARK 3: %d", i);
782 if (i >= NUM_TOUCH_FINGERS)
784 // all slots allocated -- use oldest slot
787 // Debug("event:finger", "MARK 4: %d", i);
792 // release of previously unknown key (should not happen)
794 if (key != KSYM_UNDEFINED)
796 HandleKey(key, KEY_RELEASED);
798 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [1]",
799 getKeyNameFromKey(key), "KEY_RELEASED", i);
804 if (i < NUM_TOUCH_FINGERS)
806 if (key_status == KEY_PRESSED)
808 if (touch_info[i].key != key)
810 if (touch_info[i].key != KSYM_UNDEFINED)
812 HandleKey(touch_info[i].key, KEY_RELEASED);
814 // undraw previous grid button when moving finger away
815 overlay.grid_button_action &= ~touch_info[i].action;
817 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [2]",
818 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
821 if (key != KSYM_UNDEFINED)
823 HandleKey(key, KEY_PRESSED);
825 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [3]",
826 getKeyNameFromKey(key), "KEY_PRESSED", i);
830 SetTouchInfo(i, event->fingerId, Counter(), key, grid_button_action);
834 if (touch_info[i].key != KSYM_UNDEFINED)
836 HandleKey(touch_info[i].key, KEY_RELEASED);
838 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [4]",
839 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
842 SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
847 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
849 static Key motion_key_x = KSYM_UNDEFINED;
850 static Key motion_key_y = KSYM_UNDEFINED;
851 static Key button_key = KSYM_UNDEFINED;
852 static float motion_x1, motion_y1;
853 static float button_x1, button_y1;
854 static SDL_FingerID motion_id = -1;
855 static SDL_FingerID button_id = -1;
856 int move_trigger_distance_percent = setup.touch.move_distance;
857 int drop_trigger_distance_percent = setup.touch.drop_distance;
858 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
859 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
860 float event_x = event->x;
861 float event_y = event->y;
863 if (event->type == EVENT_FINGERPRESS)
865 if (event_x > 1.0 / 3.0)
869 motion_id = event->fingerId;
874 motion_key_x = KSYM_UNDEFINED;
875 motion_key_y = KSYM_UNDEFINED;
877 Debug("event:finger", "---------- MOVE STARTED (WAIT) ----------");
883 button_id = event->fingerId;
888 button_key = setup.input[0].key.snap;
890 HandleKey(button_key, KEY_PRESSED);
892 Debug("event:finger", "---------- SNAP STARTED ----------");
895 else if (event->type == EVENT_FINGERRELEASE)
897 if (event->fingerId == motion_id)
901 if (motion_key_x != KSYM_UNDEFINED)
902 HandleKey(motion_key_x, KEY_RELEASED);
903 if (motion_key_y != KSYM_UNDEFINED)
904 HandleKey(motion_key_y, KEY_RELEASED);
906 motion_key_x = KSYM_UNDEFINED;
907 motion_key_y = KSYM_UNDEFINED;
909 Debug("event:finger", "---------- MOVE STOPPED ----------");
911 else if (event->fingerId == button_id)
915 if (button_key != KSYM_UNDEFINED)
916 HandleKey(button_key, KEY_RELEASED);
918 button_key = KSYM_UNDEFINED;
920 Debug("event:finger", "---------- SNAP STOPPED ----------");
923 else if (event->type == EVENT_FINGERMOTION)
925 if (event->fingerId == motion_id)
927 float distance_x = ABS(event_x - motion_x1);
928 float distance_y = ABS(event_y - motion_y1);
929 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
930 event_x > motion_x1 ? setup.input[0].key.right :
932 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
933 event_y > motion_y1 ? setup.input[0].key.down :
936 if (distance_x < move_trigger_distance / 2 ||
937 distance_x < distance_y)
938 new_motion_key_x = KSYM_UNDEFINED;
940 if (distance_y < move_trigger_distance / 2 ||
941 distance_y < distance_x)
942 new_motion_key_y = KSYM_UNDEFINED;
944 if (distance_x > move_trigger_distance ||
945 distance_y > move_trigger_distance)
947 if (new_motion_key_x != motion_key_x)
949 if (motion_key_x != KSYM_UNDEFINED)
950 HandleKey(motion_key_x, KEY_RELEASED);
951 if (new_motion_key_x != KSYM_UNDEFINED)
952 HandleKey(new_motion_key_x, KEY_PRESSED);
955 if (new_motion_key_y != motion_key_y)
957 if (motion_key_y != KSYM_UNDEFINED)
958 HandleKey(motion_key_y, KEY_RELEASED);
959 if (new_motion_key_y != KSYM_UNDEFINED)
960 HandleKey(new_motion_key_y, KEY_PRESSED);
966 motion_key_x = new_motion_key_x;
967 motion_key_y = new_motion_key_y;
969 Debug("event:finger", "---------- MOVE STARTED (MOVE) ----------");
972 else if (event->fingerId == button_id)
974 float distance_x = ABS(event_x - button_x1);
975 float distance_y = ABS(event_y - button_y1);
977 if (distance_x < drop_trigger_distance / 2 &&
978 distance_y > drop_trigger_distance)
980 if (button_key == setup.input[0].key.snap)
981 HandleKey(button_key, KEY_RELEASED);
986 button_key = setup.input[0].key.drop;
988 HandleKey(button_key, KEY_PRESSED);
990 Debug("event:finger", "---------- DROP STARTED ----------");
996 void HandleFingerEvent(FingerEvent *event)
998 #if DEBUG_EVENTS_FINGER
999 Debug("event:finger", "finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
1000 event->type == EVENT_FINGERPRESS ? "pressed" :
1001 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
1005 event->dx, event->dy,
1009 runtime.uses_touch_device = TRUE;
1011 if (game_status != GAME_MODE_PLAYING)
1014 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1016 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1017 local_player->mouse_action.button_hint =
1018 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1019 event->x < 0.5 ? MB_LEFTBUTTON :
1020 event->x > 0.5 ? MB_RIGHTBUTTON :
1026 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1027 HandleFingerEvent_VirtualButtons(event);
1028 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1029 HandleFingerEvent_WipeGestures(event);
1032 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1034 static int old_mx = 0, old_my = 0;
1035 static int last_button = MB_LEFTBUTTON;
1036 static boolean touched = FALSE;
1037 static boolean tapped = FALSE;
1039 // screen tile was tapped (but finger not touching the screen anymore)
1040 // (this point will also be reached without receiving a touch event)
1041 if (tapped && !touched)
1043 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1048 // stop here if this function was not triggered by a touch event
1052 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1054 // finger started touching the screen
1064 ClearPlayerMouseAction();
1066 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1069 else if (button == MB_RELEASED && touched)
1071 // finger stopped touching the screen
1076 SetPlayerMouseAction(old_mx, old_my, last_button);
1078 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1080 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1085 // finger moved while touching the screen
1087 int old_x = getLevelFromScreenX(old_mx);
1088 int old_y = getLevelFromScreenY(old_my);
1089 int new_x = getLevelFromScreenX(mx);
1090 int new_y = getLevelFromScreenY(my);
1092 if (new_x != old_x || new_y != old_y)
1097 // finger moved left or right from (horizontal) starting position
1099 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1101 SetPlayerMouseAction(old_mx, old_my, button_nr);
1103 last_button = button_nr;
1105 Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1109 // finger stays at or returned to (horizontal) starting position
1111 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1113 Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1118 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1120 static int old_mx = 0, old_my = 0;
1121 static int last_button = MB_LEFTBUTTON;
1122 static boolean touched = FALSE;
1123 static boolean tapped = FALSE;
1125 // screen tile was tapped (but finger not touching the screen anymore)
1126 // (this point will also be reached without receiving a touch event)
1127 if (tapped && !touched)
1129 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1134 // stop here if this function was not triggered by a touch event
1138 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1140 // finger started touching the screen
1150 ClearPlayerMouseAction();
1152 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1155 else if (button == MB_RELEASED && touched)
1157 // finger stopped touching the screen
1162 SetPlayerMouseAction(old_mx, old_my, last_button);
1164 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1166 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1171 // finger moved while touching the screen
1173 int old_x = getLevelFromScreenX(old_mx);
1174 int old_y = getLevelFromScreenY(old_my);
1175 int new_x = getLevelFromScreenX(mx);
1176 int new_y = getLevelFromScreenY(my);
1178 if (new_x != old_x || new_y != old_y)
1180 // finger moved away from starting position
1182 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1184 // quickly alternate between clicking and releasing for maximum speed
1185 if (FrameCounter % 2 == 0)
1186 button_nr = MB_RELEASED;
1188 SetPlayerMouseAction(old_mx, old_my, button_nr);
1191 last_button = button_nr;
1195 Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1199 // finger stays at or returned to starting position
1201 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1203 Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1208 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1210 static int old_mx = 0, old_my = 0;
1211 static Key motion_key_x = KSYM_UNDEFINED;
1212 static Key motion_key_y = KSYM_UNDEFINED;
1213 static boolean touched = FALSE;
1214 static boolean started_on_player = FALSE;
1215 static boolean player_is_dropping = FALSE;
1216 static int player_drop_count = 0;
1217 static int last_player_x = -1;
1218 static int last_player_y = -1;
1220 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1229 started_on_player = FALSE;
1230 player_is_dropping = FALSE;
1231 player_drop_count = 0;
1235 motion_key_x = KSYM_UNDEFINED;
1236 motion_key_y = KSYM_UNDEFINED;
1238 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1241 else if (button == MB_RELEASED && touched)
1248 if (motion_key_x != KSYM_UNDEFINED)
1249 HandleKey(motion_key_x, KEY_RELEASED);
1250 if (motion_key_y != KSYM_UNDEFINED)
1251 HandleKey(motion_key_y, KEY_RELEASED);
1253 if (started_on_player)
1255 if (player_is_dropping)
1257 Debug("event:finger", "---------- DROP STOPPED ----------");
1259 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1263 Debug("event:finger", "---------- SNAP STOPPED ----------");
1265 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1269 motion_key_x = KSYM_UNDEFINED;
1270 motion_key_y = KSYM_UNDEFINED;
1272 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1277 int src_x = local_player->jx;
1278 int src_y = local_player->jy;
1279 int dst_x = getLevelFromScreenX(old_mx);
1280 int dst_y = getLevelFromScreenY(old_my);
1281 int dx = dst_x - src_x;
1282 int dy = dst_y - src_y;
1283 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1284 dx > 0 ? setup.input[0].key.right :
1286 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1287 dy > 0 ? setup.input[0].key.down :
1290 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1291 (last_player_x != local_player->jx ||
1292 last_player_y != local_player->jy))
1294 // in case of asymmetric diagonal movement, use "preferred" direction
1296 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1298 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1299 game_em.ply[0]->last_move_dir = last_move_dir;
1301 local_player->last_move_dir = last_move_dir;
1303 // (required to prevent accidentally forcing direction for next movement)
1304 last_player_x = local_player->jx;
1305 last_player_y = local_player->jy;
1308 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1310 started_on_player = TRUE;
1311 player_drop_count = getPlayerInventorySize(0);
1312 player_is_dropping = (player_drop_count > 0);
1314 if (player_is_dropping)
1316 Debug("event:finger", "---------- DROP STARTED ----------");
1318 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1322 Debug("event:finger", "---------- SNAP STARTED ----------");
1324 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1327 else if (dx != 0 || dy != 0)
1329 if (player_is_dropping &&
1330 player_drop_count == getPlayerInventorySize(0))
1332 Debug("event:finger", "---------- DROP -> SNAP ----------");
1334 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1335 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1337 player_is_dropping = FALSE;
1341 if (new_motion_key_x != motion_key_x)
1343 Debug("event:finger", "---------- %s %s ----------",
1344 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1345 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1347 if (motion_key_x != KSYM_UNDEFINED)
1348 HandleKey(motion_key_x, KEY_RELEASED);
1349 if (new_motion_key_x != KSYM_UNDEFINED)
1350 HandleKey(new_motion_key_x, KEY_PRESSED);
1353 if (new_motion_key_y != motion_key_y)
1355 Debug("event:finger", "---------- %s %s ----------",
1356 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1357 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1359 if (motion_key_y != KSYM_UNDEFINED)
1360 HandleKey(motion_key_y, KEY_RELEASED);
1361 if (new_motion_key_y != KSYM_UNDEFINED)
1362 HandleKey(new_motion_key_y, KEY_PRESSED);
1365 motion_key_x = new_motion_key_x;
1366 motion_key_y = new_motion_key_y;
1370 static void HandleButtonOrFinger(int mx, int my, int button)
1372 boolean valid_mouse_event = (mx != -1 && my != -1 && button != -1);
1374 if (game_status != GAME_MODE_PLAYING)
1377 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1379 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1380 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1381 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1382 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1383 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1384 SetPlayerMouseAction(mx, my, button); // special case
1388 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1389 HandleButtonOrFinger_FollowFinger(mx, my, button);
1390 else if (game.use_mouse_actions && valid_mouse_event)
1391 SetPlayerMouseAction(mx, my, button);
1395 static boolean checkTextInputKey(Key key)
1397 // when playing, only handle raw key events and ignore text input
1398 if (game_status == GAME_MODE_PLAYING)
1401 // if Shift or right Alt key is pressed, handle key as text input
1402 if ((GetKeyModState() & KMOD_TextInput) != KMOD_None)
1405 // ignore raw keys as text input when not in text input mode
1406 if (KSYM_RAW(key) && !textinput_status)
1409 // else handle all printable keys as text input
1410 return KSYM_PRINTABLE(key);
1413 void HandleTextEvent(TextEvent *event)
1415 char *text = event->text;
1416 Key key = getKeyFromKeyName(text);
1418 #if DEBUG_EVENTS_TEXT
1419 Debug("event:text", "text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1422 text[0], (int)(text[0]),
1424 getKeyNameFromKey(key),
1428 if (checkTextInputKey(key))
1430 // process printable keys (with uppercase etc.) in text input mode
1431 HandleKey(key, KEY_PRESSED);
1432 HandleKey(key, KEY_RELEASED);
1436 void HandlePauseResumeEvent(PauseResumeEvent *event)
1438 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1442 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1448 void HandleKeyEvent(KeyEvent *event)
1450 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1451 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1452 Key key = GetEventKey(event, with_modifiers);
1453 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1455 #if DEBUG_EVENTS_KEY
1456 Debug("event:key", "key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1457 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1458 event->keysym.scancode,
1463 getKeyNameFromKey(key));
1466 #if defined(PLATFORM_ANDROID)
1467 if (key == KSYM_Back)
1469 // always map the "back" button to the "escape" key on Android devices
1472 else if (key == KSYM_Menu)
1474 // the "menu" button can be used to toggle displaying virtual buttons
1475 if (key_status == KEY_PRESSED)
1476 SetOverlayEnabled(!GetOverlayEnabled());
1480 // for any other "real" key event, disable virtual buttons
1481 SetOverlayEnabled(FALSE);
1483 // for any other "real" key event, disable overlay touch buttons
1484 runtime.uses_touch_device = FALSE;
1488 HandleKeyModState(keymod, key_status);
1490 // process all keys if not in text input mode or if non-printable keys
1491 if (!checkTextInputKey(key))
1492 HandleKey(key, key_status);
1495 static int HandleDropFileEvent(char *filename)
1497 Debug("event:dropfile", "filename == '%s'", filename);
1499 // check and extract dropped zip files into correct user data directory
1500 if (!strSuffixLower(filename, ".zip"))
1502 Warn("file '%s' not supported", filename);
1504 return TREE_TYPE_UNDEFINED;
1507 TreeInfo *tree_node = NULL;
1508 int tree_type = GetZipFileTreeType(filename);
1509 char *directory = TREE_USERDIR(tree_type);
1511 if (directory == NULL)
1513 Warn("zip file '%s' has invalid content!", filename);
1515 return TREE_TYPE_UNDEFINED;
1518 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1519 game_status == GAME_MODE_LEVELS &&
1520 leveldir_current->node_parent != NULL)
1522 // extract new level set next to currently selected level set
1523 tree_node = leveldir_current;
1525 // get parent directory of currently selected level set directory
1526 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1528 // use private level directory instead of top-level package level directory
1529 if (strPrefix(directory, options.level_directory) &&
1530 strEqual(leveldir_current->node_parent->fullpath, "."))
1531 directory = getUserLevelDir(NULL);
1534 // extract level or artwork set from zip file to target directory
1535 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1537 if (top_dir == NULL)
1539 // error message already issued by "ExtractZipFileIntoDirectory()"
1541 return TREE_TYPE_UNDEFINED;
1544 // add extracted level or artwork set to tree info structure
1545 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1547 // update menu screen (and possibly change current level set)
1548 DrawScreenAfterAddingSet(top_dir, tree_type);
1553 static void HandleDropTextEvent(char *text)
1555 Debug("event:droptext", "text == '%s'", text);
1558 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1559 int num_artwork_sets_succeeded,
1560 int num_files_failed)
1562 // only show request dialog if no other request dialog already active
1563 if (game.request_active)
1566 // this case can happen with drag-and-drop with older SDL versions
1567 if (num_level_sets_succeeded == 0 &&
1568 num_artwork_sets_succeeded == 0 &&
1569 num_files_failed == 0)
1574 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1576 char message_part1[50];
1578 sprintf(message_part1, "New %s set%s added",
1579 (num_artwork_sets_succeeded == 0 ? "level" :
1580 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1581 (num_level_sets_succeeded +
1582 num_artwork_sets_succeeded > 1 ? "s" : ""));
1584 if (num_files_failed > 0)
1585 sprintf(message, "%s, but %d dropped file%s failed!",
1586 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1588 sprintf(message, "%s!", message_part1);
1590 else if (num_files_failed > 0)
1592 sprintf(message, "Failed to process dropped file%s!",
1593 num_files_failed > 1 ? "s" : "");
1596 Request(message, REQ_CONFIRM);
1599 void HandleDropEvent(Event *event)
1601 static boolean confirm_on_drop_complete = FALSE;
1602 static int num_level_sets_succeeded = 0;
1603 static int num_artwork_sets_succeeded = 0;
1604 static int num_files_failed = 0;
1606 switch (event->type)
1610 confirm_on_drop_complete = TRUE;
1611 num_level_sets_succeeded = 0;
1612 num_artwork_sets_succeeded = 0;
1613 num_files_failed = 0;
1620 int tree_type = HandleDropFileEvent(event->drop.file);
1622 if (tree_type == TREE_TYPE_LEVEL_DIR)
1623 num_level_sets_succeeded++;
1624 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1625 tree_type == TREE_TYPE_SOUNDS_DIR ||
1626 tree_type == TREE_TYPE_MUSIC_DIR)
1627 num_artwork_sets_succeeded++;
1631 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1632 if (!confirm_on_drop_complete)
1634 // process all remaining events, including further SDL_DROPFILE events
1637 HandleDropCompleteEvent(num_level_sets_succeeded,
1638 num_artwork_sets_succeeded,
1641 num_level_sets_succeeded = 0;
1642 num_artwork_sets_succeeded = 0;
1643 num_files_failed = 0;
1651 HandleDropTextEvent(event->drop.file);
1656 case SDL_DROPCOMPLETE:
1658 HandleDropCompleteEvent(num_level_sets_succeeded,
1659 num_artwork_sets_succeeded,
1666 if (event->drop.file != NULL)
1667 SDL_free(event->drop.file);
1670 void HandleUserEvent(UserEvent *event)
1672 switch (event->code)
1674 case USEREVENT_ANIM_DELAY_ACTION:
1675 case USEREVENT_ANIM_EVENT_ACTION:
1676 // execute action functions until matching action was found
1677 if (DoKeysymAction(event->value1) ||
1678 DoGadgetAction(event->value1) ||
1679 DoScreenAction(event->value1))
1688 void HandleButton(int mx, int my, int button, int button_nr)
1690 static int old_mx = 0, old_my = 0;
1691 boolean button_hold = FALSE;
1692 boolean handle_gadgets = TRUE;
1698 button_nr = -button_nr;
1707 #if defined(PLATFORM_ANDROID)
1708 // when playing, only handle gadgets when using "follow finger" controls
1709 // or when using touch controls in combination with the MM game engine
1710 // or when using gadgets that do not overlap with virtual buttons
1712 (game_status != GAME_MODE_PLAYING ||
1713 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1714 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1715 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1716 !CheckVirtualButtonPressed(mx, my, button)));
1718 // always recognize potentially releasing already pressed gadgets
1719 if (button == MB_RELEASED)
1720 handle_gadgets = TRUE;
1722 // always recognize pressing or releasing overlay touch buttons
1723 if (CheckPosition_OverlayTouchButtons(mx, my, button) && !motion_status)
1724 handle_gadgets = TRUE;
1727 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1729 // do not handle this button event anymore
1730 return; // force mouse event not to be handled at all
1733 if (handle_gadgets && HandleGadgets(mx, my, button))
1735 // do not handle this button event anymore
1736 mx = my = -32; // force mouse event to be outside screen tiles
1739 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1742 // do not use scroll wheel button events for anything other than gadgets
1743 if (IS_WHEEL_BUTTON(button_nr))
1746 switch (game_status)
1748 case GAME_MODE_TITLE:
1749 HandleTitleScreen(mx, my, 0, 0, button);
1752 case GAME_MODE_MAIN:
1753 HandleMainMenu(mx, my, 0, 0, button);
1756 case GAME_MODE_PSEUDO_TYPENAME:
1757 HandleTypeName(0, KSYM_Return);
1760 case GAME_MODE_LEVELS:
1761 HandleChooseLevelSet(mx, my, 0, 0, button);
1764 case GAME_MODE_LEVELNR:
1765 HandleChooseLevelNr(mx, my, 0, 0, button);
1768 case GAME_MODE_SCORES:
1769 HandleHallOfFame(0, 0, 0, 0, button);
1772 case GAME_MODE_EDITOR:
1773 HandleLevelEditorIdle();
1776 case GAME_MODE_INFO:
1777 HandleInfoScreen(mx, my, 0, 0, button);
1780 case GAME_MODE_SETUP:
1781 HandleSetupScreen(mx, my, 0, 0, button);
1784 case GAME_MODE_PLAYING:
1785 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1786 HandleButtonOrFinger(mx, my, button);
1788 SetPlayerMouseAction(mx, my, button);
1791 if (button == MB_PRESSED && !motion_status && !button_hold &&
1792 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1793 DumpTileFromScreen(mx, my);
1803 #define MAX_CHEAT_INPUT_LEN 32
1805 static void HandleKeysSpecial(Key key)
1807 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1808 char letter = getCharFromKey(key);
1809 int cheat_input_len = strlen(cheat_input);
1815 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1817 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1818 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1820 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1823 cheat_input[cheat_input_len++] = letter;
1824 cheat_input[cheat_input_len] = '\0';
1826 #if DEBUG_EVENTS_KEY
1827 Debug("event:key:special", "'%s' [%d]", cheat_input, cheat_input_len);
1830 if (game_status == GAME_MODE_MAIN)
1832 if (strSuffix(cheat_input, ":insert-solution-tape") ||
1833 strSuffix(cheat_input, ":ist"))
1835 InsertSolutionTape();
1837 else if (strSuffix(cheat_input, ":play-solution-tape") ||
1838 strSuffix(cheat_input, ":pst"))
1842 else if (strSuffix(cheat_input, ":reload-graphics") ||
1843 strSuffix(cheat_input, ":rg"))
1845 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1848 else if (strSuffix(cheat_input, ":reload-sounds") ||
1849 strSuffix(cheat_input, ":rs"))
1851 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1854 else if (strSuffix(cheat_input, ":reload-music") ||
1855 strSuffix(cheat_input, ":rm"))
1857 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1860 else if (strSuffix(cheat_input, ":reload-artwork") ||
1861 strSuffix(cheat_input, ":ra"))
1863 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1864 1 << ARTWORK_TYPE_SOUNDS |
1865 1 << ARTWORK_TYPE_MUSIC);
1868 else if (strSuffix(cheat_input, ":dump-level") ||
1869 strSuffix(cheat_input, ":dl"))
1873 else if (strSuffix(cheat_input, ":dump-tape") ||
1874 strSuffix(cheat_input, ":dt"))
1878 else if (strSuffix(cheat_input, ":undo-tape") ||
1879 strSuffix(cheat_input, ":ut"))
1883 else if (strSuffix(cheat_input, ":fix-tape") ||
1884 strSuffix(cheat_input, ":ft"))
1886 FixTape_ForceSinglePlayer();
1888 else if (strSuffix(cheat_input, ":save-native-level") ||
1889 strSuffix(cheat_input, ":snl"))
1891 SaveNativeLevel(&level);
1893 else if (strSuffix(cheat_input, ":frames-per-second") ||
1894 strSuffix(cheat_input, ":fps"))
1896 global.show_frames_per_second = !global.show_frames_per_second;
1899 else if (game_status == GAME_MODE_PLAYING)
1902 if (strSuffix(cheat_input, ".q"))
1903 DEBUG_SetMaximumDynamite();
1906 else if (game_status == GAME_MODE_EDITOR)
1908 if (strSuffix(cheat_input, ":dump-brush") ||
1909 strSuffix(cheat_input, ":DB"))
1913 else if (strSuffix(cheat_input, ":DDB"))
1918 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1920 if (letter == 'x') // copy brush to clipboard (small size)
1922 CopyBrushToClipboard_Small();
1924 else if (letter == 'c') // copy brush to clipboard (normal size)
1926 CopyBrushToClipboard();
1928 else if (letter == 'v') // paste brush from Clipboard
1930 CopyClipboardToBrush();
1932 else if (letter == 'z') // undo or redo last operation
1934 if (GetKeyModState() & KMOD_Shift)
1935 RedoLevelEditorOperation();
1937 UndoLevelEditorOperation();
1942 // special key shortcuts for all game modes
1943 if (strSuffix(cheat_input, ":dump-event-actions") ||
1944 strSuffix(cheat_input, ":dea") ||
1945 strSuffix(cheat_input, ":DEA"))
1947 DumpGadgetIdentifiers();
1948 DumpScreenIdentifiers();
1952 boolean HandleKeysDebug(Key key, int key_status)
1957 if (key_status != KEY_PRESSED)
1960 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1962 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1964 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1966 if (key == setup.debug.frame_delay_key[i] &&
1967 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1969 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1970 setup.debug.frame_delay[i] : setup.game_frame_delay);
1972 if (!setup.debug.frame_delay_game_only)
1973 MenuFrameDelay = GameFrameDelay;
1975 SetVideoFrameDelay(GameFrameDelay);
1977 if (GameFrameDelay > ONE_SECOND_DELAY)
1978 Debug("event:key:debug", "frame delay == %d ms", GameFrameDelay);
1979 else if (GameFrameDelay != 0)
1980 Debug("event:key:debug", "frame delay == %d ms (max. %d fps / %d %%)",
1981 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1982 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1984 Debug("event:key:debug", "frame delay == 0 ms (maximum speed)");
1991 if (game_status == GAME_MODE_PLAYING)
1995 options.debug = !options.debug;
1997 Debug("event:key:debug", "debug mode %s",
1998 (options.debug ? "enabled" : "disabled"));
2002 else if (key == KSYM_v)
2004 Debug("event:key:debug", "currently using game engine version %d",
2005 game.engine_version);
2015 void HandleKey(Key key, int key_status)
2017 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2018 static boolean ignore_repeated_key = FALSE;
2019 static struct SetupKeyboardInfo ski;
2020 static struct SetupShortcutInfo ssi;
2029 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
2030 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
2031 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
2032 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
2033 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2034 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2039 if (HandleKeysDebug(key, key_status))
2040 return; // do not handle already processed keys again
2042 // map special keys (media keys / remote control buttons) to default keys
2043 if (key == KSYM_PlayPause)
2045 else if (key == KSYM_Select)
2048 HandleSpecialGameControllerKeys(key, key_status);
2050 if (game_status == GAME_MODE_PLAYING)
2052 // only needed for single-step tape recording mode
2053 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2056 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2058 byte key_action = 0;
2059 byte key_snap_action = 0;
2061 if (setup.input[pnr].use_joystick)
2064 ski = setup.input[pnr].key;
2066 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2067 if (key == *key_info[i].key_custom)
2068 key_action |= key_info[i].action;
2070 // use combined snap+direction keys for the first player only
2073 ssi = setup.shortcut;
2075 // also remember normal snap key when handling snap+direction keys
2076 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2078 for (i = 0; i < NUM_DIRECTIONS; i++)
2080 if (key == *key_info[i].key_snap)
2082 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2083 key_snap_action |= key_info[i].action;
2088 if (key_status == KEY_PRESSED)
2090 stored_player[pnr].action |= key_action;
2091 stored_player[pnr].snap_action |= key_snap_action;
2095 stored_player[pnr].action &= ~key_action;
2096 stored_player[pnr].snap_action &= ~key_snap_action;
2099 // restore snap action if one of several pressed snap keys was released
2100 if (stored_player[pnr].snap_action)
2101 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2103 if (tape.recording && tape.pausing && tape.use_key_actions)
2105 if (tape.single_step)
2107 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2109 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2111 // if snap key already pressed, keep pause mode when releasing
2112 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2113 has_snapped[pnr] = TRUE;
2115 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2117 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2119 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2120 getRedDiskReleaseFlag_SP() == 0)
2122 // add a single inactive frame before dropping starts
2123 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2124 stored_player[pnr].force_dropping = TRUE;
2127 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2129 // if snap key was pressed without direction, leave pause mode
2130 if (!has_snapped[pnr])
2131 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2133 has_snapped[pnr] = FALSE;
2138 // prevent key release events from un-pausing a paused game
2139 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2140 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2144 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2145 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2151 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2152 if (key == key_info[i].key_default)
2153 joy |= key_info[i].action;
2158 if (key_status == KEY_PRESSED)
2159 key_joystick_mapping |= joy;
2161 key_joystick_mapping &= ~joy;
2166 if (game_status != GAME_MODE_PLAYING)
2167 key_joystick_mapping = 0;
2169 if (key_status == KEY_RELEASED)
2171 // reset flag to ignore repeated "key pressed" events after key release
2172 ignore_repeated_key = FALSE;
2177 if ((key == KSYM_F11 ||
2178 ((key == KSYM_Return ||
2179 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2180 video.fullscreen_available &&
2181 !ignore_repeated_key)
2183 setup.fullscreen = !setup.fullscreen;
2185 ToggleFullscreenIfNeeded();
2187 if (game_status == GAME_MODE_SETUP)
2188 RedrawSetupScreenAfterFullscreenToggle();
2190 UpdateMousePosition();
2192 // set flag to ignore repeated "key pressed" events
2193 ignore_repeated_key = TRUE;
2198 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2199 key == KSYM_minus || key == KSYM_KP_Subtract ||
2200 key == KSYM_plus || key == KSYM_KP_Add ||
2201 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2202 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2203 video.window_scaling_available &&
2204 !video.fullscreen_enabled)
2206 if (key == KSYM_0 || key == KSYM_KP_0)
2207 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2208 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2209 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2211 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2213 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2214 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2215 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2216 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2218 ChangeWindowScalingIfNeeded();
2220 if (game_status == GAME_MODE_SETUP)
2221 RedrawSetupScreenAfterFullscreenToggle();
2223 UpdateMousePosition();
2228 // some key events are handled like clicks for global animations
2229 boolean click = (key == KSYM_space ||
2230 key == KSYM_Return ||
2231 key == KSYM_Escape);
2233 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2235 // do not handle this key event anymore
2236 if (key != KSYM_Escape) // always allow ESC key to be handled
2240 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2241 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2248 if (game_status == GAME_MODE_MAIN &&
2249 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2251 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2256 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2258 if (key == setup.shortcut.save_game)
2260 else if (key == setup.shortcut.load_game)
2262 else if (key == setup.shortcut.toggle_pause)
2263 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2265 HandleTapeButtonKeys(key);
2266 HandleSoundButtonKeys(key);
2269 if (game_status == GAME_MODE_PLAYING && !network_playing)
2271 int centered_player_nr_next = -999;
2273 if (key == setup.shortcut.focus_player_all)
2274 centered_player_nr_next = -1;
2276 for (i = 0; i < MAX_PLAYERS; i++)
2277 if (key == setup.shortcut.focus_player[i])
2278 centered_player_nr_next = i;
2280 if (centered_player_nr_next != -999)
2282 game.centered_player_nr_next = centered_player_nr_next;
2283 game.set_centered_player = TRUE;
2287 tape.centered_player_nr_next = game.centered_player_nr_next;
2288 tape.set_centered_player = TRUE;
2293 HandleKeysSpecial(key);
2295 if (HandleGadgetsKeyInput(key))
2296 return; // do not handle already processed keys again
2298 switch (game_status)
2300 case GAME_MODE_PSEUDO_TYPENAME:
2301 HandleTypeName(0, key);
2304 case GAME_MODE_TITLE:
2305 case GAME_MODE_MAIN:
2306 case GAME_MODE_LEVELS:
2307 case GAME_MODE_LEVELNR:
2308 case GAME_MODE_SETUP:
2309 case GAME_MODE_INFO:
2310 case GAME_MODE_SCORES:
2312 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2319 if (game_status == GAME_MODE_TITLE)
2320 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2321 else if (game_status == GAME_MODE_MAIN)
2322 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2323 else if (game_status == GAME_MODE_LEVELS)
2324 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2325 else if (game_status == GAME_MODE_LEVELNR)
2326 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2327 else if (game_status == GAME_MODE_SETUP)
2328 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2329 else if (game_status == GAME_MODE_INFO)
2330 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2331 else if (game_status == GAME_MODE_SCORES)
2332 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2336 if (game_status != GAME_MODE_MAIN)
2337 FadeSkipNextFadeIn();
2339 if (game_status == GAME_MODE_TITLE)
2340 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2341 else if (game_status == GAME_MODE_LEVELS)
2342 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2343 else if (game_status == GAME_MODE_LEVELNR)
2344 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2345 else if (game_status == GAME_MODE_SETUP)
2346 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2347 else if (game_status == GAME_MODE_INFO)
2348 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2349 else if (game_status == GAME_MODE_SCORES)
2350 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2354 if (game_status == GAME_MODE_LEVELS)
2355 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2356 else if (game_status == GAME_MODE_LEVELNR)
2357 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2358 else if (game_status == GAME_MODE_SETUP)
2359 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2360 else if (game_status == GAME_MODE_INFO)
2361 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2362 else if (game_status == GAME_MODE_SCORES)
2363 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2366 case KSYM_Page_Down:
2367 if (game_status == GAME_MODE_LEVELS)
2368 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2369 else if (game_status == GAME_MODE_LEVELNR)
2370 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2371 else if (game_status == GAME_MODE_SETUP)
2372 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2373 else if (game_status == GAME_MODE_INFO)
2374 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2375 else if (game_status == GAME_MODE_SCORES)
2376 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2384 case GAME_MODE_EDITOR:
2385 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2386 HandleLevelEditorKeyInput(key);
2389 case GAME_MODE_PLAYING:
2394 RequestQuitGame(setup.ask_on_escape);
2404 if (key == KSYM_Escape)
2406 SetGameStatus(GAME_MODE_MAIN);
2415 void HandleNoEvent(void)
2417 HandleMouseCursor();
2419 switch (game_status)
2421 case GAME_MODE_PLAYING:
2422 HandleButtonOrFinger(-1, -1, -1);
2427 void HandleEventActions(void)
2429 // if (button_status && game_status != GAME_MODE_PLAYING)
2430 if (button_status && (game_status != GAME_MODE_PLAYING ||
2432 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2434 HandleButton(0, 0, button_status, -button_status);
2441 if (network.enabled)
2444 switch (game_status)
2446 case GAME_MODE_MAIN:
2447 DrawPreviewLevelAnimation();
2450 case GAME_MODE_EDITOR:
2451 HandleLevelEditorIdle();
2459 static void HandleTileCursor(int dx, int dy, int button)
2462 ClearPlayerMouseAction();
2469 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2470 (dx < 0 ? MB_LEFTBUTTON :
2471 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2473 else if (!tile_cursor.moving)
2475 int old_xpos = tile_cursor.xpos;
2476 int old_ypos = tile_cursor.ypos;
2477 int new_xpos = old_xpos;
2478 int new_ypos = old_ypos;
2480 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2481 new_xpos = old_xpos + dx;
2483 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2484 new_ypos = old_ypos + dy;
2486 SetTileCursorTargetXY(new_xpos, new_ypos);
2490 static int HandleJoystickForAllPlayers(void)
2494 boolean no_joysticks_configured = TRUE;
2495 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2496 static byte joy_action_last[MAX_PLAYERS];
2498 for (i = 0; i < MAX_PLAYERS; i++)
2499 if (setup.input[i].use_joystick)
2500 no_joysticks_configured = FALSE;
2502 // if no joysticks configured, map connected joysticks to players
2503 if (no_joysticks_configured)
2504 use_as_joystick_nr = TRUE;
2506 for (i = 0; i < MAX_PLAYERS; i++)
2508 byte joy_action = 0;
2510 joy_action = JoystickExt(i, use_as_joystick_nr);
2511 result |= joy_action;
2513 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2514 joy_action != joy_action_last[i])
2515 stored_player[i].action = joy_action;
2517 joy_action_last[i] = joy_action;
2523 void HandleJoystick(void)
2525 static unsigned int joytest_delay = 0;
2526 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2527 static int joytest_last = 0;
2528 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2529 int delay_value = GADGET_FRAME_DELAY;
2530 int joystick = HandleJoystickForAllPlayers();
2531 int keyboard = key_joystick_mapping;
2532 int joy = (joystick | keyboard);
2533 int joytest = joystick;
2534 int left = joy & JOY_LEFT;
2535 int right = joy & JOY_RIGHT;
2536 int up = joy & JOY_UP;
2537 int down = joy & JOY_DOWN;
2538 int button = joy & JOY_BUTTON;
2539 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2540 int dx = (left ? -1 : right ? 1 : 0);
2541 int dy = (up ? -1 : down ? 1 : 0);
2542 boolean use_delay_value_first = (joytest != joytest_last);
2544 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2546 // do not handle this button event anymore
2550 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2551 anyTextGadgetActive()))
2553 // leave name input in main menu or text input gadget
2554 HandleKey(KSYM_Escape, KEY_PRESSED);
2555 HandleKey(KSYM_Escape, KEY_RELEASED);
2560 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2562 if (game_status == GAME_MODE_PLAYING)
2564 // when playing MM style levels, also use delay for keyboard events
2565 joytest |= keyboard;
2567 // only use first delay value for new events, but not for changed events
2568 use_delay_value_first = (!joytest != !joytest_last);
2570 // only use delay after the initial keyboard event
2574 // for any joystick or keyboard event, enable playfield tile cursor
2575 if (dx || dy || button)
2576 SetTileCursorEnabled(TRUE);
2579 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2581 // delay joystick/keyboard actions if axes/keys continually pressed
2582 newbutton = dx = dy = 0;
2586 // first start with longer delay, then continue with shorter delay
2587 joytest_delay_value =
2588 (use_delay_value_first ? delay_value_first : delay_value);
2591 joytest_last = joytest;
2593 switch (game_status)
2595 case GAME_MODE_TITLE:
2596 case GAME_MODE_MAIN:
2597 case GAME_MODE_LEVELS:
2598 case GAME_MODE_LEVELNR:
2599 case GAME_MODE_SETUP:
2600 case GAME_MODE_INFO:
2601 case GAME_MODE_SCORES:
2603 if (anyTextGadgetActive())
2606 if (game_status == GAME_MODE_TITLE)
2607 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2608 else if (game_status == GAME_MODE_MAIN)
2609 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2610 else if (game_status == GAME_MODE_LEVELS)
2611 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2612 else if (game_status == GAME_MODE_LEVELNR)
2613 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2614 else if (game_status == GAME_MODE_SETUP)
2615 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2616 else if (game_status == GAME_MODE_INFO)
2617 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2618 else if (game_status == GAME_MODE_SCORES)
2619 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2624 case GAME_MODE_PLAYING:
2626 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2627 if (tape.playing || keyboard)
2628 newbutton = ((joy & JOY_BUTTON) != 0);
2631 if (newbutton && game.all_players_gone)
2638 if (tape.recording && tape.pausing && tape.use_key_actions)
2640 if (tape.single_step)
2642 if (joystick & JOY_ACTION)
2643 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2647 if (joystick & JOY_ACTION)
2648 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2652 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2653 HandleTileCursor(dx, dy, button);
2662 void HandleSpecialGameControllerButtons(Event *event)
2667 switch (event->type)
2669 case SDL_CONTROLLERBUTTONDOWN:
2670 key_status = KEY_PRESSED;
2673 case SDL_CONTROLLERBUTTONUP:
2674 key_status = KEY_RELEASED;
2681 switch (event->cbutton.button)
2683 case SDL_CONTROLLER_BUTTON_START:
2687 case SDL_CONTROLLER_BUTTON_BACK:
2695 HandleKey(key, key_status);
2698 void HandleSpecialGameControllerKeys(Key key, int key_status)
2700 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2701 int button = SDL_CONTROLLER_BUTTON_INVALID;
2703 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2704 if (key == KSYM_Rewind)
2705 button = SDL_CONTROLLER_BUTTON_A;
2706 else if (key == KSYM_FastForward || key == KSYM_Menu)
2707 button = SDL_CONTROLLER_BUTTON_B;
2709 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2713 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2714 SDL_CONTROLLERBUTTONUP);
2716 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2717 event.cbutton.button = button;
2718 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2721 HandleJoystickEvent(&event);
2726 boolean DoKeysymAction(int keysym)
2730 Key key = (Key)(-keysym);
2732 HandleKey(key, KEY_PRESSED);
2733 HandleKey(key, KEY_RELEASED);