1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_EVENTS 0
28 #define DEBUG_EVENTS_BUTTON (DEBUG_EVENTS * 0)
29 #define DEBUG_EVENTS_MOTION (DEBUG_EVENTS * 0)
30 #define DEBUG_EVENTS_WHEEL (DEBUG_EVENTS * 1)
31 #define DEBUG_EVENTS_WINDOW (DEBUG_EVENTS * 0)
32 #define DEBUG_EVENTS_FINGER (DEBUG_EVENTS * 0)
33 #define DEBUG_EVENTS_TEXT (DEBUG_EVENTS * 1)
34 #define DEBUG_EVENTS_KEY (DEBUG_EVENTS * 1)
37 static boolean cursor_inside_playfield = FALSE;
38 static int cursor_mode_last = CURSOR_DEFAULT;
39 static unsigned int special_cursor_delay = 0;
40 static unsigned int special_cursor_delay_value = 1000;
42 static boolean virtual_button_pressed = FALSE;
45 // forward declarations for internal use
46 static void HandleNoEvent(void);
47 static void HandleEventActions(void);
50 // event filter especially needed for SDL event filtering due to
51 // delay problems with lots of mouse motion events when mouse button
52 // not pressed (X11 can handle this with 'PointerMotionHintMask')
54 // event filter addition for SDL2: as SDL2 does not have a function to enable
55 // or disable keyboard auto-repeat, filter repeated keyboard events instead
57 static int FilterEvents(const Event *event)
61 // skip repeated key press events if keyboard auto-repeat is disabled
62 if (event->type == EVENT_KEYPRESS &&
67 if (event->type == EVENT_BUTTONPRESS ||
68 event->type == EVENT_BUTTONRELEASE)
70 ((ButtonEvent *)event)->x -= video.screen_xoffset;
71 ((ButtonEvent *)event)->y -= video.screen_yoffset;
73 else if (event->type == EVENT_MOTIONNOTIFY)
75 ((MotionEvent *)event)->x -= video.screen_xoffset;
76 ((MotionEvent *)event)->y -= video.screen_yoffset;
78 gfx.mouse_x = ((MotionEvent *)event)->x;
79 gfx.mouse_y = ((MotionEvent *)event)->y;
82 // non-motion events are directly passed to event handler functions
83 if (event->type != EVENT_MOTIONNOTIFY)
86 motion = (MotionEvent *)event;
87 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
88 motion->y >= SY && motion->y < SY + SYSIZE);
90 // do no reset mouse cursor before all pending events have been processed
91 if (gfx.cursor_mode == cursor_mode_last &&
92 ((game_status == GAME_MODE_TITLE &&
93 gfx.cursor_mode == CURSOR_NONE) ||
94 (game_status == GAME_MODE_PLAYING &&
95 gfx.cursor_mode == CURSOR_PLAYFIELD)))
97 SetMouseCursor(CURSOR_DEFAULT);
99 DelayReached(&special_cursor_delay, 0);
101 cursor_mode_last = CURSOR_DEFAULT;
104 // skip mouse motion events without pressed button outside level editor
105 if (button_status == MB_RELEASED &&
106 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
112 // to prevent delay problems, skip mouse motion events if the very next
113 // event is also a mouse motion event (and therefore effectively only
114 // handling the last of a row of mouse motion events in the event queue)
116 static boolean SkipPressedMouseMotionEvent(const Event *event)
118 // nothing to do if the current event is not a mouse motion event
119 if (event->type != EVENT_MOTIONNOTIFY)
122 // only skip motion events with pressed button outside the game
123 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
130 PeekEvent(&next_event);
132 // if next event is also a mouse motion event, skip the current one
133 if (next_event.type == EVENT_MOTIONNOTIFY)
140 static boolean WaitValidEvent(Event *event)
144 if (!FilterEvents(event))
147 if (SkipPressedMouseMotionEvent(event))
153 /* this is especially needed for event modifications for the Android target:
154 if mouse coordinates should be modified in the event filter function,
155 using a properly installed SDL event filter does not work, because in
156 the event filter, mouse coordinates in the event structure are still
157 physical pixel positions, not logical (scaled) screen positions, so this
158 has to be handled at a later stage in the event processing functions
159 (when device pixel positions are already converted to screen positions) */
161 boolean NextValidEvent(Event *event)
163 while (PendingEvent())
164 if (WaitValidEvent(event))
170 static void HandleEvents(void)
173 unsigned int event_frame_delay = 0;
174 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
176 ResetDelayCounter(&event_frame_delay);
178 while (NextValidEvent(&event))
182 case EVENT_BUTTONPRESS:
183 case EVENT_BUTTONRELEASE:
184 HandleButtonEvent((ButtonEvent *) &event);
187 case EVENT_MOTIONNOTIFY:
188 HandleMotionEvent((MotionEvent *) &event);
191 case EVENT_WHEELMOTION:
192 HandleWheelEvent((WheelEvent *) &event);
195 case SDL_WINDOWEVENT:
196 HandleWindowEvent((WindowEvent *) &event);
199 case EVENT_FINGERPRESS:
200 case EVENT_FINGERRELEASE:
201 case EVENT_FINGERMOTION:
202 HandleFingerEvent((FingerEvent *) &event);
205 case EVENT_TEXTINPUT:
206 HandleTextEvent((TextEvent *) &event);
209 case SDL_APP_WILLENTERBACKGROUND:
210 case SDL_APP_DIDENTERBACKGROUND:
211 case SDL_APP_WILLENTERFOREGROUND:
212 case SDL_APP_DIDENTERFOREGROUND:
213 HandlePauseResumeEvent((PauseResumeEvent *) &event);
217 case EVENT_KEYRELEASE:
218 HandleKeyEvent((KeyEvent *) &event);
222 HandleUserEvent((UserEvent *) &event);
226 HandleOtherEvents(&event);
230 // do not handle events for longer than standard frame delay period
231 if (DelayReached(&event_frame_delay, event_frame_delay_value))
236 void HandleOtherEvents(Event *event)
240 case SDL_CONTROLLERBUTTONDOWN:
241 case SDL_CONTROLLERBUTTONUP:
242 // for any game controller button event, disable overlay buttons
243 SetOverlayEnabled(FALSE);
245 HandleSpecialGameControllerButtons(event);
248 case SDL_CONTROLLERDEVICEADDED:
249 case SDL_CONTROLLERDEVICEREMOVED:
250 case SDL_CONTROLLERAXISMOTION:
251 case SDL_JOYAXISMOTION:
252 case SDL_JOYBUTTONDOWN:
253 case SDL_JOYBUTTONUP:
254 HandleJoystickEvent(event);
258 case SDL_DROPCOMPLETE:
261 HandleDropEvent(event);
273 static void HandleMouseCursor(void)
275 if (game_status == GAME_MODE_TITLE)
277 // when showing title screens, hide mouse pointer (if not moved)
279 if (gfx.cursor_mode != CURSOR_NONE &&
280 DelayReached(&special_cursor_delay, special_cursor_delay_value))
282 SetMouseCursor(CURSOR_NONE);
285 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
288 // when playing, display a special mouse pointer inside the playfield
290 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
291 cursor_inside_playfield &&
292 DelayReached(&special_cursor_delay, special_cursor_delay_value))
294 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
296 SetMouseCursor(CURSOR_PLAYFIELD);
299 else if (gfx.cursor_mode != CURSOR_DEFAULT)
301 SetMouseCursor(CURSOR_DEFAULT);
304 // this is set after all pending events have been processed
305 cursor_mode_last = gfx.cursor_mode;
317 // execute event related actions after pending events have been processed
318 HandleEventActions();
320 // don't use all CPU time when idle; the main loop while playing
321 // has its own synchronization and is CPU friendly, too
323 if (game_status == GAME_MODE_PLAYING)
326 // always copy backbuffer to visible screen for every video frame
329 // reset video frame delay to default (may change again while playing)
330 SetVideoFrameDelay(MenuFrameDelay);
332 if (game_status == GAME_MODE_QUIT)
337 void ClearAutoRepeatKeyEvents(void)
339 while (PendingEvent())
343 PeekEvent(&next_event);
345 // if event is repeated key press event, remove it from event queue
346 if (next_event.type == EVENT_KEYPRESS &&
347 next_event.key.repeat)
348 WaitEvent(&next_event);
354 void ClearEventQueue(void)
358 while (NextValidEvent(&event))
362 case EVENT_BUTTONRELEASE:
363 button_status = MB_RELEASED;
366 case EVENT_KEYRELEASE:
370 case SDL_CONTROLLERBUTTONUP:
371 HandleJoystickEvent(&event);
376 HandleOtherEvents(&event);
382 static void ClearPlayerMouseAction(void)
384 local_player->mouse_action.lx = 0;
385 local_player->mouse_action.ly = 0;
386 local_player->mouse_action.button = 0;
389 void ClearPlayerAction(void)
393 // simulate key release events for still pressed keys
394 key_joystick_mapping = 0;
395 for (i = 0; i < MAX_PLAYERS; i++)
397 stored_player[i].action = 0;
398 stored_player[i].snap_action = 0;
401 ClearJoystickState();
402 ClearPlayerMouseAction();
405 static void SetPlayerMouseAction(int mx, int my, int button)
407 int lx = getLevelFromScreenX(mx);
408 int ly = getLevelFromScreenY(my);
409 int new_button = (!local_player->mouse_action.button && button);
411 if (local_player->mouse_action.button_hint)
412 button = local_player->mouse_action.button_hint;
414 ClearPlayerMouseAction();
416 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
419 local_player->mouse_action.lx = lx;
420 local_player->mouse_action.ly = ly;
421 local_player->mouse_action.button = button;
423 if (tape.recording && tape.pausing && tape.use_mouse)
425 // un-pause a paused game only if mouse button was newly pressed down
427 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
430 SetTileCursorXY(lx, ly);
433 void HandleButtonEvent(ButtonEvent *event)
435 #if DEBUG_EVENTS_BUTTON
436 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
438 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
442 // for any mouse button event, disable playfield tile cursor
443 SetTileCursorEnabled(FALSE);
445 #if defined(HAS_SCREEN_KEYBOARD)
446 if (video.shifted_up)
447 event->y += video.shifted_up_pos;
450 motion_status = FALSE;
452 if (event->type == EVENT_BUTTONPRESS)
453 button_status = event->button;
455 button_status = MB_RELEASED;
457 HandleButton(event->x, event->y, button_status, event->button);
460 void HandleMotionEvent(MotionEvent *event)
462 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
465 motion_status = TRUE;
467 #if DEBUG_EVENTS_MOTION
468 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
469 button_status, event->x, event->y);
472 HandleButton(event->x, event->y, button_status, button_status);
475 void HandleWheelEvent(WheelEvent *event)
479 #if DEBUG_EVENTS_WHEEL
481 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
482 event->which, event->x, event->y);
484 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
485 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
486 event->which, event->x, event->y,
487 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
488 "SDL_MOUSEWHEEL_FLIPPED"));
492 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
493 event->x > 0 ? MB_WHEEL_RIGHT :
494 event->y < 0 ? MB_WHEEL_DOWN :
495 event->y > 0 ? MB_WHEEL_UP : 0);
497 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
498 // accelerated mouse wheel available on Mac and Windows
499 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
501 // no accelerated mouse wheel available on Unix/Linux
502 wheel_steps = DEFAULT_WHEEL_STEPS;
505 motion_status = FALSE;
507 button_status = button_nr;
508 HandleButton(0, 0, button_status, -button_nr);
510 button_status = MB_RELEASED;
511 HandleButton(0, 0, button_status, -button_nr);
514 void HandleWindowEvent(WindowEvent *event)
516 #if DEBUG_EVENTS_WINDOW
517 int subtype = event->event;
520 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
521 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
522 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
523 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
524 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
525 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
526 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
527 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
528 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
529 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
530 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
531 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
532 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
533 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
536 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
537 event_name, event->data1, event->data2);
541 // (not needed, as the screen gets redrawn every 20 ms anyway)
542 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
543 event->event == SDL_WINDOWEVENT_RESIZED ||
544 event->event == SDL_WINDOWEVENT_EXPOSED)
548 if (event->event == SDL_WINDOWEVENT_RESIZED)
550 if (!video.fullscreen_enabled)
552 int new_window_width = event->data1;
553 int new_window_height = event->data2;
555 // if window size has changed after resizing, calculate new scaling factor
556 if (new_window_width != video.window_width ||
557 new_window_height != video.window_height)
559 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
560 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
562 // (extreme window scaling allowed, but cannot be saved permanently)
563 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
564 setup.window_scaling_percent =
565 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
566 MAX_WINDOW_SCALING_PERCENT);
568 video.window_width = new_window_width;
569 video.window_height = new_window_height;
571 if (game_status == GAME_MODE_SETUP)
572 RedrawSetupScreenAfterFullscreenToggle();
577 #if defined(PLATFORM_ANDROID)
580 int new_display_width = event->data1;
581 int new_display_height = event->data2;
583 // if fullscreen display size has changed, device has been rotated
584 if (new_display_width != video.display_width ||
585 new_display_height != video.display_height)
587 int nr = GRID_ACTIVE_NR(); // previous screen orientation
589 video.display_width = new_display_width;
590 video.display_height = new_display_height;
592 SDLSetScreenProperties();
594 // check if screen orientation has changed (should always be true here)
595 if (nr != GRID_ACTIVE_NR())
599 if (game_status == GAME_MODE_SETUP)
600 RedrawSetupScreenAfterScreenRotation(nr);
602 nr = GRID_ACTIVE_NR();
604 overlay.grid_xsize = setup.touch.grid_xsize[nr];
605 overlay.grid_ysize = setup.touch.grid_ysize[nr];
607 for (x = 0; x < MAX_GRID_XSIZE; x++)
608 for (y = 0; y < MAX_GRID_YSIZE; y++)
609 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
617 #define NUM_TOUCH_FINGERS 3
622 SDL_FingerID finger_id;
626 } touch_info[NUM_TOUCH_FINGERS];
628 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
631 int x = event->x * overlay.grid_xsize;
632 int y = event->y * overlay.grid_ysize;
633 int grid_button = overlay.grid_button[x][y];
634 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
635 Key key = (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
636 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
637 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
638 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
639 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
640 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
643 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
644 float event_x = (event->x);
645 float event_y = (event->y - ypos) / (1 - ypos);
646 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
647 event_y > 2.0 / 3.0 && event_y < 1 ?
648 setup.input[0].key.snap :
649 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
650 event_y > 2.0 / 3.0 && event_y < 1 ?
651 setup.input[0].key.drop :
652 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
653 event_y > 0 && event_y < 1.0 / 3.0 ?
654 setup.input[0].key.up :
655 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
656 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
657 setup.input[0].key.left :
658 event_x > 8.0 / 9.0 && event_x < 1 &&
659 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
660 setup.input[0].key.right :
661 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
662 event_y > 2.0 / 3.0 && event_y < 1 ?
663 setup.input[0].key.down :
666 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
668 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
672 virtual_button_pressed = (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
674 // for any touch input event, enable overlay buttons (if activated)
675 SetOverlayEnabled(TRUE);
677 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
678 getKeyNameFromKey(key), key_status_name, event->fingerId);
680 if (key_status == KEY_PRESSED)
681 overlay.grid_button_action |= grid_button_action;
683 overlay.grid_button_action &= ~grid_button_action;
685 // check if we already know this touch event's finger id
686 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
688 if (touch_info[i].touched &&
689 touch_info[i].finger_id == event->fingerId)
691 // Error(ERR_DEBUG, "MARK 1: %d", i);
697 if (i >= NUM_TOUCH_FINGERS)
699 if (key_status == KEY_PRESSED)
701 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
703 // unknown finger id -- get new, empty slot, if available
704 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
706 if (touch_info[i].counter < oldest_counter)
709 oldest_counter = touch_info[i].counter;
711 // Error(ERR_DEBUG, "MARK 2: %d", i);
714 if (!touch_info[i].touched)
716 // Error(ERR_DEBUG, "MARK 3: %d", i);
722 if (i >= NUM_TOUCH_FINGERS)
724 // all slots allocated -- use oldest slot
727 // Error(ERR_DEBUG, "MARK 4: %d", i);
732 // release of previously unknown key (should not happen)
734 if (key != KSYM_UNDEFINED)
736 HandleKey(key, KEY_RELEASED);
738 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
739 getKeyNameFromKey(key), "KEY_RELEASED", i);
744 if (i < NUM_TOUCH_FINGERS)
746 if (key_status == KEY_PRESSED)
748 if (touch_info[i].key != key)
750 if (touch_info[i].key != KSYM_UNDEFINED)
752 HandleKey(touch_info[i].key, KEY_RELEASED);
754 // undraw previous grid button when moving finger away
755 overlay.grid_button_action &= ~touch_info[i].action;
757 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
758 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
761 if (key != KSYM_UNDEFINED)
763 HandleKey(key, KEY_PRESSED);
765 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
766 getKeyNameFromKey(key), "KEY_PRESSED", i);
770 touch_info[i].touched = TRUE;
771 touch_info[i].finger_id = event->fingerId;
772 touch_info[i].counter = Counter();
773 touch_info[i].key = key;
774 touch_info[i].action = grid_button_action;
778 if (touch_info[i].key != KSYM_UNDEFINED)
780 HandleKey(touch_info[i].key, KEY_RELEASED);
782 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
783 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
786 touch_info[i].touched = FALSE;
787 touch_info[i].finger_id = 0;
788 touch_info[i].counter = 0;
789 touch_info[i].key = 0;
790 touch_info[i].action = JOY_NO_ACTION;
795 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
797 static Key motion_key_x = KSYM_UNDEFINED;
798 static Key motion_key_y = KSYM_UNDEFINED;
799 static Key button_key = KSYM_UNDEFINED;
800 static float motion_x1, motion_y1;
801 static float button_x1, button_y1;
802 static SDL_FingerID motion_id = -1;
803 static SDL_FingerID button_id = -1;
804 int move_trigger_distance_percent = setup.touch.move_distance;
805 int drop_trigger_distance_percent = setup.touch.drop_distance;
806 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
807 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
808 float event_x = event->x;
809 float event_y = event->y;
811 if (event->type == EVENT_FINGERPRESS)
813 if (event_x > 1.0 / 3.0)
817 motion_id = event->fingerId;
822 motion_key_x = KSYM_UNDEFINED;
823 motion_key_y = KSYM_UNDEFINED;
825 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
831 button_id = event->fingerId;
836 button_key = setup.input[0].key.snap;
838 HandleKey(button_key, KEY_PRESSED);
840 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
843 else if (event->type == EVENT_FINGERRELEASE)
845 if (event->fingerId == motion_id)
849 if (motion_key_x != KSYM_UNDEFINED)
850 HandleKey(motion_key_x, KEY_RELEASED);
851 if (motion_key_y != KSYM_UNDEFINED)
852 HandleKey(motion_key_y, KEY_RELEASED);
854 motion_key_x = KSYM_UNDEFINED;
855 motion_key_y = KSYM_UNDEFINED;
857 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
859 else if (event->fingerId == button_id)
863 if (button_key != KSYM_UNDEFINED)
864 HandleKey(button_key, KEY_RELEASED);
866 button_key = KSYM_UNDEFINED;
868 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
871 else if (event->type == EVENT_FINGERMOTION)
873 if (event->fingerId == motion_id)
875 float distance_x = ABS(event_x - motion_x1);
876 float distance_y = ABS(event_y - motion_y1);
877 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
878 event_x > motion_x1 ? setup.input[0].key.right :
880 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
881 event_y > motion_y1 ? setup.input[0].key.down :
884 if (distance_x < move_trigger_distance / 2 ||
885 distance_x < distance_y)
886 new_motion_key_x = KSYM_UNDEFINED;
888 if (distance_y < move_trigger_distance / 2 ||
889 distance_y < distance_x)
890 new_motion_key_y = KSYM_UNDEFINED;
892 if (distance_x > move_trigger_distance ||
893 distance_y > move_trigger_distance)
895 if (new_motion_key_x != motion_key_x)
897 if (motion_key_x != KSYM_UNDEFINED)
898 HandleKey(motion_key_x, KEY_RELEASED);
899 if (new_motion_key_x != KSYM_UNDEFINED)
900 HandleKey(new_motion_key_x, KEY_PRESSED);
903 if (new_motion_key_y != motion_key_y)
905 if (motion_key_y != KSYM_UNDEFINED)
906 HandleKey(motion_key_y, KEY_RELEASED);
907 if (new_motion_key_y != KSYM_UNDEFINED)
908 HandleKey(new_motion_key_y, KEY_PRESSED);
914 motion_key_x = new_motion_key_x;
915 motion_key_y = new_motion_key_y;
917 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
920 else if (event->fingerId == button_id)
922 float distance_x = ABS(event_x - button_x1);
923 float distance_y = ABS(event_y - button_y1);
925 if (distance_x < drop_trigger_distance / 2 &&
926 distance_y > drop_trigger_distance)
928 if (button_key == setup.input[0].key.snap)
929 HandleKey(button_key, KEY_RELEASED);
934 button_key = setup.input[0].key.drop;
936 HandleKey(button_key, KEY_PRESSED);
938 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
944 void HandleFingerEvent(FingerEvent *event)
946 #if DEBUG_EVENTS_FINGER
947 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
948 event->type == EVENT_FINGERPRESS ? "pressed" :
949 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
953 event->dx, event->dy,
957 runtime.uses_touch_device = TRUE;
959 if (game_status != GAME_MODE_PLAYING)
962 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
964 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
965 local_player->mouse_action.button_hint =
966 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
967 event->x < 0.5 ? MB_LEFTBUTTON :
968 event->x > 0.5 ? MB_RIGHTBUTTON :
974 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
975 HandleFingerEvent_VirtualButtons(event);
976 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
977 HandleFingerEvent_WipeGestures(event);
980 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
982 static int old_mx = 0, old_my = 0;
983 static int last_button = MB_LEFTBUTTON;
984 static boolean touched = FALSE;
985 static boolean tapped = FALSE;
987 // screen tile was tapped (but finger not touching the screen anymore)
988 // (this point will also be reached without receiving a touch event)
989 if (tapped && !touched)
991 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
996 // stop here if this function was not triggered by a touch event
1000 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1002 // finger started touching the screen
1012 ClearPlayerMouseAction();
1014 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1017 else if (button == MB_RELEASED && touched)
1019 // finger stopped touching the screen
1024 SetPlayerMouseAction(old_mx, old_my, last_button);
1026 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1028 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1033 // finger moved while touching the screen
1035 int old_x = getLevelFromScreenX(old_mx);
1036 int old_y = getLevelFromScreenY(old_my);
1037 int new_x = getLevelFromScreenX(mx);
1038 int new_y = getLevelFromScreenY(my);
1040 if (new_x != old_x || new_y != old_y)
1045 // finger moved left or right from (horizontal) starting position
1047 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1049 SetPlayerMouseAction(old_mx, old_my, button_nr);
1051 last_button = button_nr;
1053 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1057 // finger stays at or returned to (horizontal) starting position
1059 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1061 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1066 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1068 static int old_mx = 0, old_my = 0;
1069 static int last_button = MB_LEFTBUTTON;
1070 static boolean touched = FALSE;
1071 static boolean tapped = FALSE;
1073 // screen tile was tapped (but finger not touching the screen anymore)
1074 // (this point will also be reached without receiving a touch event)
1075 if (tapped && !touched)
1077 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1082 // stop here if this function was not triggered by a touch event
1086 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1088 // finger started touching the screen
1098 ClearPlayerMouseAction();
1100 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1103 else if (button == MB_RELEASED && touched)
1105 // finger stopped touching the screen
1110 SetPlayerMouseAction(old_mx, old_my, last_button);
1112 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1114 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1119 // finger moved while touching the screen
1121 int old_x = getLevelFromScreenX(old_mx);
1122 int old_y = getLevelFromScreenY(old_my);
1123 int new_x = getLevelFromScreenX(mx);
1124 int new_y = getLevelFromScreenY(my);
1126 if (new_x != old_x || new_y != old_y)
1128 // finger moved away from starting position
1130 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1132 // quickly alternate between clicking and releasing for maximum speed
1133 if (FrameCounter % 2 == 0)
1134 button_nr = MB_RELEASED;
1136 SetPlayerMouseAction(old_mx, old_my, button_nr);
1139 last_button = button_nr;
1143 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1147 // finger stays at or returned to starting position
1149 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1151 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1156 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1158 static int old_mx = 0, old_my = 0;
1159 static Key motion_key_x = KSYM_UNDEFINED;
1160 static Key motion_key_y = KSYM_UNDEFINED;
1161 static boolean touched = FALSE;
1162 static boolean started_on_player = FALSE;
1163 static boolean player_is_dropping = FALSE;
1164 static int player_drop_count = 0;
1165 static int last_player_x = -1;
1166 static int last_player_y = -1;
1168 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1177 started_on_player = FALSE;
1178 player_is_dropping = FALSE;
1179 player_drop_count = 0;
1183 motion_key_x = KSYM_UNDEFINED;
1184 motion_key_y = KSYM_UNDEFINED;
1186 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1189 else if (button == MB_RELEASED && touched)
1196 if (motion_key_x != KSYM_UNDEFINED)
1197 HandleKey(motion_key_x, KEY_RELEASED);
1198 if (motion_key_y != KSYM_UNDEFINED)
1199 HandleKey(motion_key_y, KEY_RELEASED);
1201 if (started_on_player)
1203 if (player_is_dropping)
1205 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1207 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1211 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1213 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1217 motion_key_x = KSYM_UNDEFINED;
1218 motion_key_y = KSYM_UNDEFINED;
1220 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1225 int src_x = local_player->jx;
1226 int src_y = local_player->jy;
1227 int dst_x = getLevelFromScreenX(old_mx);
1228 int dst_y = getLevelFromScreenY(old_my);
1229 int dx = dst_x - src_x;
1230 int dy = dst_y - src_y;
1231 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1232 dx > 0 ? setup.input[0].key.right :
1234 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1235 dy > 0 ? setup.input[0].key.down :
1238 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1239 (last_player_x != local_player->jx ||
1240 last_player_y != local_player->jy))
1242 // in case of asymmetric diagonal movement, use "preferred" direction
1244 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1246 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1247 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1249 local_player->last_move_dir = last_move_dir;
1251 // (required to prevent accidentally forcing direction for next movement)
1252 last_player_x = local_player->jx;
1253 last_player_y = local_player->jy;
1256 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1258 started_on_player = TRUE;
1259 player_drop_count = getPlayerInventorySize(0);
1260 player_is_dropping = (player_drop_count > 0);
1262 if (player_is_dropping)
1264 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1266 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1270 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1272 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1275 else if (dx != 0 || dy != 0)
1277 if (player_is_dropping &&
1278 player_drop_count == getPlayerInventorySize(0))
1280 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1282 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1283 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1285 player_is_dropping = FALSE;
1289 if (new_motion_key_x != motion_key_x)
1291 Error(ERR_DEBUG, "---------- %s %s ----------",
1292 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1293 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1295 if (motion_key_x != KSYM_UNDEFINED)
1296 HandleKey(motion_key_x, KEY_RELEASED);
1297 if (new_motion_key_x != KSYM_UNDEFINED)
1298 HandleKey(new_motion_key_x, KEY_PRESSED);
1301 if (new_motion_key_y != motion_key_y)
1303 Error(ERR_DEBUG, "---------- %s %s ----------",
1304 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1305 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1307 if (motion_key_y != KSYM_UNDEFINED)
1308 HandleKey(motion_key_y, KEY_RELEASED);
1309 if (new_motion_key_y != KSYM_UNDEFINED)
1310 HandleKey(new_motion_key_y, KEY_PRESSED);
1313 motion_key_x = new_motion_key_x;
1314 motion_key_y = new_motion_key_y;
1318 static void HandleButtonOrFinger(int mx, int my, int button)
1320 if (game_status != GAME_MODE_PLAYING)
1323 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1325 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1326 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1327 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1328 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1329 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1330 SetPlayerMouseAction(mx, my, button); // special case
1334 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1335 HandleButtonOrFinger_FollowFinger(mx, my, button);
1339 static boolean checkTextInputKeyModState(void)
1341 // when playing, only handle raw key events and ignore text input
1342 if (game_status == GAME_MODE_PLAYING)
1345 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1348 void HandleTextEvent(TextEvent *event)
1350 char *text = event->text;
1351 Key key = getKeyFromKeyName(text);
1353 #if DEBUG_EVENTS_TEXT
1354 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1357 text[0], (int)(text[0]),
1359 getKeyNameFromKey(key),
1363 #if !defined(HAS_SCREEN_KEYBOARD)
1364 // non-mobile devices: only handle key input with modifier keys pressed here
1365 // (every other key input is handled directly as physical key input event)
1366 if (!checkTextInputKeyModState())
1370 // process text input as "classic" (with uppercase etc.) key input event
1371 HandleKey(key, KEY_PRESSED);
1372 HandleKey(key, KEY_RELEASED);
1375 void HandlePauseResumeEvent(PauseResumeEvent *event)
1377 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1381 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1387 void HandleKeyEvent(KeyEvent *event)
1389 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1390 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1391 Key key = GetEventKey(event, with_modifiers);
1392 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1394 #if DEBUG_EVENTS_KEY
1395 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1396 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1397 event->keysym.scancode,
1402 getKeyNameFromKey(key));
1405 #if defined(PLATFORM_ANDROID)
1406 if (key == KSYM_Back)
1408 // always map the "back" button to the "escape" key on Android devices
1411 else if (key == KSYM_Menu)
1413 // the "menu" button can be used to toggle displaying virtual buttons
1414 if (key_status == KEY_PRESSED)
1415 SetOverlayEnabled(!GetOverlayEnabled());
1419 // for any other "real" key event, disable virtual buttons
1420 SetOverlayEnabled(FALSE);
1424 HandleKeyModState(keymod, key_status);
1426 // only handle raw key input without text modifier keys pressed
1427 if (!checkTextInputKeyModState())
1428 HandleKey(key, key_status);
1431 static int HandleDropFileEvent(char *filename)
1433 Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
1435 // check and extract dropped zip files into correct user data directory
1436 if (!strSuffixLower(filename, ".zip"))
1438 Error(ERR_WARN, "file '%s' not supported", filename);
1440 return TREE_TYPE_UNDEFINED;
1443 TreeInfo *tree_node = NULL;
1444 int tree_type = GetZipFileTreeType(filename);
1445 char *directory = TREE_USERDIR(tree_type);
1447 if (directory == NULL)
1449 Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1451 return TREE_TYPE_UNDEFINED;
1454 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1455 game_status == GAME_MODE_LEVELS &&
1456 leveldir_current->node_parent != NULL)
1458 // extract new level set next to currently selected level set
1459 tree_node = leveldir_current;
1461 // get parent directory of currently selected level set directory
1462 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1464 // use private level directory instead of top-level package level directory
1465 if (strPrefix(directory, options.level_directory) &&
1466 strEqual(leveldir_current->node_parent->fullpath, "."))
1467 directory = getUserLevelDir(NULL);
1470 // extract level or artwork set from zip file to target directory
1471 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1473 if (top_dir == NULL)
1475 // error message already issued by "ExtractZipFileIntoDirectory()"
1477 return TREE_TYPE_UNDEFINED;
1480 // add extracted level or artwork set to tree info structure
1481 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1483 // update menu screen (and possibly change current level set)
1484 DrawScreenAfterAddingSet(top_dir, tree_type);
1489 static void HandleDropTextEvent(char *text)
1491 Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1494 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1495 int num_artwork_sets_succeeded,
1496 int num_files_failed)
1498 // only show request dialog if no other request dialog already active
1499 if (game.request_active)
1502 // this case can happen with drag-and-drop with older SDL versions
1503 if (num_level_sets_succeeded == 0 &&
1504 num_artwork_sets_succeeded == 0 &&
1505 num_files_failed == 0)
1510 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1512 char message_part1[50];
1514 sprintf(message_part1, "New %s set%s added",
1515 (num_artwork_sets_succeeded == 0 ? "level" :
1516 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1517 (num_level_sets_succeeded +
1518 num_artwork_sets_succeeded > 1 ? "s" : ""));
1520 if (num_files_failed > 0)
1521 sprintf(message, "%s, but %d dropped file%s failed!",
1522 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1524 sprintf(message, "%s!", message_part1);
1526 else if (num_files_failed > 0)
1528 sprintf(message, "Failed to process dropped file%s!",
1529 num_files_failed > 1 ? "s" : "");
1532 Request(message, REQ_CONFIRM);
1535 void HandleDropEvent(Event *event)
1537 static boolean confirm_on_drop_complete = FALSE;
1538 static int num_level_sets_succeeded = 0;
1539 static int num_artwork_sets_succeeded = 0;
1540 static int num_files_failed = 0;
1542 switch (event->type)
1546 confirm_on_drop_complete = TRUE;
1547 num_level_sets_succeeded = 0;
1548 num_artwork_sets_succeeded = 0;
1549 num_files_failed = 0;
1556 int tree_type = HandleDropFileEvent(event->drop.file);
1558 if (tree_type == TREE_TYPE_LEVEL_DIR)
1559 num_level_sets_succeeded++;
1560 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1561 tree_type == TREE_TYPE_SOUNDS_DIR ||
1562 tree_type == TREE_TYPE_MUSIC_DIR)
1563 num_artwork_sets_succeeded++;
1567 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1568 if (!confirm_on_drop_complete)
1570 // process all remaining events, including further SDL_DROPFILE events
1573 HandleDropCompleteEvent(num_level_sets_succeeded,
1574 num_artwork_sets_succeeded,
1577 num_level_sets_succeeded = 0;
1578 num_artwork_sets_succeeded = 0;
1579 num_files_failed = 0;
1587 HandleDropTextEvent(event->drop.file);
1592 case SDL_DROPCOMPLETE:
1594 HandleDropCompleteEvent(num_level_sets_succeeded,
1595 num_artwork_sets_succeeded,
1602 if (event->drop.file != NULL)
1603 SDL_free(event->drop.file);
1606 void HandleUserEvent(UserEvent *event)
1608 switch (event->code)
1610 case USEREVENT_ANIM_DELAY_ACTION:
1611 case USEREVENT_ANIM_EVENT_ACTION:
1612 // execute action functions until matching action was found
1613 if (DoKeysymAction(event->value1) ||
1614 DoGadgetAction(event->value1) ||
1615 DoScreenAction(event->value1))
1624 void HandleButton(int mx, int my, int button, int button_nr)
1626 static int old_mx = 0, old_my = 0;
1627 boolean button_hold = FALSE;
1628 boolean handle_gadgets = TRUE;
1634 button_nr = -button_nr;
1643 #if defined(PLATFORM_ANDROID)
1644 // when playing, only handle gadgets when using "follow finger" controls
1645 // or when using touch controls in combination with the MM game engine
1646 // or when using gadgets that do not overlap with virtual buttons
1648 (game_status != GAME_MODE_PLAYING ||
1649 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1650 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1651 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1652 !virtual_button_pressed));
1655 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1657 // do not handle this button event anymore
1658 return; // force mouse event not to be handled at all
1661 if (handle_gadgets && HandleGadgets(mx, my, button))
1663 // do not handle this button event anymore
1664 mx = my = -32; // force mouse event to be outside screen tiles
1667 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1670 // do not use scroll wheel button events for anything other than gadgets
1671 if (IS_WHEEL_BUTTON(button_nr))
1674 switch (game_status)
1676 case GAME_MODE_TITLE:
1677 HandleTitleScreen(mx, my, 0, 0, button);
1680 case GAME_MODE_MAIN:
1681 HandleMainMenu(mx, my, 0, 0, button);
1684 case GAME_MODE_PSEUDO_TYPENAME:
1685 HandleTypeName(0, KSYM_Return);
1688 case GAME_MODE_LEVELS:
1689 HandleChooseLevelSet(mx, my, 0, 0, button);
1692 case GAME_MODE_LEVELNR:
1693 HandleChooseLevelNr(mx, my, 0, 0, button);
1696 case GAME_MODE_SCORES:
1697 HandleHallOfFame(0, 0, 0, 0, button);
1700 case GAME_MODE_EDITOR:
1701 HandleLevelEditorIdle();
1704 case GAME_MODE_INFO:
1705 HandleInfoScreen(mx, my, 0, 0, button);
1708 case GAME_MODE_SETUP:
1709 HandleSetupScreen(mx, my, 0, 0, button);
1712 case GAME_MODE_PLAYING:
1713 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1714 HandleButtonOrFinger(mx, my, button);
1716 SetPlayerMouseAction(mx, my, button);
1719 if (button == MB_PRESSED && !motion_status && !button_hold &&
1720 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1721 DumpTileFromScreen(mx, my);
1731 static boolean is_string_suffix(char *string, char *suffix)
1733 int string_len = strlen(string);
1734 int suffix_len = strlen(suffix);
1736 if (suffix_len > string_len)
1739 return (strEqual(&string[string_len - suffix_len], suffix));
1742 #define MAX_CHEAT_INPUT_LEN 32
1744 static void HandleKeysSpecial(Key key)
1746 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1747 char letter = getCharFromKey(key);
1748 int cheat_input_len = strlen(cheat_input);
1754 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1756 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1757 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1759 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1762 cheat_input[cheat_input_len++] = letter;
1763 cheat_input[cheat_input_len] = '\0';
1765 #if DEBUG_EVENTS_KEY
1766 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1769 if (game_status == GAME_MODE_MAIN)
1771 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1772 is_string_suffix(cheat_input, ":ist"))
1774 InsertSolutionTape();
1776 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1777 is_string_suffix(cheat_input, ":pst"))
1781 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1782 is_string_suffix(cheat_input, ":rg"))
1784 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1787 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1788 is_string_suffix(cheat_input, ":rs"))
1790 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1793 else if (is_string_suffix(cheat_input, ":reload-music") ||
1794 is_string_suffix(cheat_input, ":rm"))
1796 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1799 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1800 is_string_suffix(cheat_input, ":ra"))
1802 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1803 1 << ARTWORK_TYPE_SOUNDS |
1804 1 << ARTWORK_TYPE_MUSIC);
1807 else if (is_string_suffix(cheat_input, ":dump-level") ||
1808 is_string_suffix(cheat_input, ":dl"))
1812 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1813 is_string_suffix(cheat_input, ":dt"))
1817 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1818 is_string_suffix(cheat_input, ":ft"))
1820 /* fix single-player tapes that contain player input for more than one
1821 player (due to a bug in 3.3.1.2 and earlier versions), which results
1822 in playing levels with more than one player in multi-player mode,
1823 even though the tape was originally recorded in single-player mode */
1825 // remove player input actions for all players but the first one
1826 for (i = 1; i < MAX_PLAYERS; i++)
1827 tape.player_participates[i] = FALSE;
1829 tape.changed = TRUE;
1831 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1832 is_string_suffix(cheat_input, ":snl"))
1834 SaveNativeLevel(&level);
1836 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1837 is_string_suffix(cheat_input, ":fps"))
1839 global.show_frames_per_second = !global.show_frames_per_second;
1842 else if (game_status == GAME_MODE_PLAYING)
1845 if (is_string_suffix(cheat_input, ".q"))
1846 DEBUG_SetMaximumDynamite();
1849 else if (game_status == GAME_MODE_EDITOR)
1851 if (is_string_suffix(cheat_input, ":dump-brush") ||
1852 is_string_suffix(cheat_input, ":DB"))
1856 else if (is_string_suffix(cheat_input, ":DDB"))
1861 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1863 if (letter == 'x') // copy brush to clipboard (small size)
1865 CopyBrushToClipboard_Small();
1867 else if (letter == 'c') // copy brush to clipboard (normal size)
1869 CopyBrushToClipboard();
1871 else if (letter == 'v') // paste brush from Clipboard
1873 CopyClipboardToBrush();
1878 // special key shortcuts for all game modes
1879 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1880 is_string_suffix(cheat_input, ":dea") ||
1881 is_string_suffix(cheat_input, ":DEA"))
1883 DumpGadgetIdentifiers();
1884 DumpScreenIdentifiers();
1888 boolean HandleKeysDebug(Key key, int key_status)
1893 if (key_status != KEY_PRESSED)
1896 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1898 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1900 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1902 if (key == setup.debug.frame_delay_key[i] &&
1903 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1905 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1906 setup.debug.frame_delay[i] : setup.game_frame_delay);
1908 if (!setup.debug.frame_delay_game_only)
1909 MenuFrameDelay = GameFrameDelay;
1911 SetVideoFrameDelay(GameFrameDelay);
1913 if (GameFrameDelay > ONE_SECOND_DELAY)
1914 Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
1915 else if (GameFrameDelay != 0)
1916 Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
1917 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1918 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1920 Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
1927 if (game_status == GAME_MODE_PLAYING)
1931 options.debug = !options.debug;
1933 Error(ERR_INFO, "debug mode %s",
1934 (options.debug ? "enabled" : "disabled"));
1938 else if (key == KSYM_v)
1940 Error(ERR_INFO, "currently using game engine version %d",
1941 game.engine_version);
1951 void HandleKey(Key key, int key_status)
1953 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1954 static boolean ignore_repeated_key = FALSE;
1955 static struct SetupKeyboardInfo ski;
1956 static struct SetupShortcutInfo ssi;
1965 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1966 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1967 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1968 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1969 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1970 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1975 if (HandleKeysDebug(key, key_status))
1976 return; // do not handle already processed keys again
1978 // map special keys (media keys / remote control buttons) to default keys
1979 if (key == KSYM_PlayPause)
1981 else if (key == KSYM_Select)
1984 HandleSpecialGameControllerKeys(key, key_status);
1986 if (game_status == GAME_MODE_PLAYING)
1988 // only needed for single-step tape recording mode
1989 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1992 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1994 byte key_action = 0;
1995 byte key_snap_action = 0;
1997 if (setup.input[pnr].use_joystick)
2000 ski = setup.input[pnr].key;
2002 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2003 if (key == *key_info[i].key_custom)
2004 key_action |= key_info[i].action;
2006 // use combined snap+direction keys for the first player only
2009 ssi = setup.shortcut;
2011 // also remember normal snap key when handling snap+direction keys
2012 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2014 for (i = 0; i < NUM_DIRECTIONS; i++)
2016 if (key == *key_info[i].key_snap)
2018 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2019 key_snap_action |= key_info[i].action;
2024 if (key_status == KEY_PRESSED)
2026 stored_player[pnr].action |= key_action;
2027 stored_player[pnr].snap_action |= key_snap_action;
2031 stored_player[pnr].action &= ~key_action;
2032 stored_player[pnr].snap_action &= ~key_snap_action;
2035 // restore snap action if one of several pressed snap keys was released
2036 if (stored_player[pnr].snap_action)
2037 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2039 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2041 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2043 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2045 // if snap key already pressed, keep pause mode when releasing
2046 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2047 has_snapped[pnr] = TRUE;
2049 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2051 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2053 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2054 getRedDiskReleaseFlag_SP() == 0)
2056 // add a single inactive frame before dropping starts
2057 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2058 stored_player[pnr].force_dropping = TRUE;
2061 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2063 // if snap key was pressed without direction, leave pause mode
2064 if (!has_snapped[pnr])
2065 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2067 has_snapped[pnr] = FALSE;
2070 else if (tape.recording && tape.pausing && !tape.use_mouse)
2072 // prevent key release events from un-pausing a paused game
2073 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2074 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2077 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2078 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2084 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2085 if (key == key_info[i].key_default)
2086 joy |= key_info[i].action;
2091 if (key_status == KEY_PRESSED)
2092 key_joystick_mapping |= joy;
2094 key_joystick_mapping &= ~joy;
2099 if (game_status != GAME_MODE_PLAYING)
2100 key_joystick_mapping = 0;
2102 if (key_status == KEY_RELEASED)
2104 // reset flag to ignore repeated "key pressed" events after key release
2105 ignore_repeated_key = FALSE;
2110 if ((key == KSYM_F11 ||
2111 ((key == KSYM_Return ||
2112 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2113 video.fullscreen_available &&
2114 !ignore_repeated_key)
2116 setup.fullscreen = !setup.fullscreen;
2118 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2120 if (game_status == GAME_MODE_SETUP)
2121 RedrawSetupScreenAfterFullscreenToggle();
2123 // set flag to ignore repeated "key pressed" events
2124 ignore_repeated_key = TRUE;
2129 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2130 key == KSYM_minus || key == KSYM_KP_Subtract ||
2131 key == KSYM_plus || key == KSYM_KP_Add ||
2132 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2133 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2134 video.window_scaling_available &&
2135 !video.fullscreen_enabled)
2137 if (key == KSYM_0 || key == KSYM_KP_0)
2138 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2139 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2140 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2142 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2144 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2145 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2146 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2147 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2149 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2151 if (game_status == GAME_MODE_SETUP)
2152 RedrawSetupScreenAfterFullscreenToggle();
2157 // some key events are handled like clicks for global animations
2158 boolean click = (key == KSYM_space ||
2159 key == KSYM_Return ||
2160 key == KSYM_Escape);
2162 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2164 // do not handle this key event anymore
2165 if (key != KSYM_Escape) // always allow ESC key to be handled
2169 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2170 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2177 if (game_status == GAME_MODE_MAIN &&
2178 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2180 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2185 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2187 if (key == setup.shortcut.save_game)
2189 else if (key == setup.shortcut.load_game)
2191 else if (key == setup.shortcut.toggle_pause)
2192 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2194 HandleTapeButtonKeys(key);
2195 HandleSoundButtonKeys(key);
2198 if (game_status == GAME_MODE_PLAYING && !network_playing)
2200 int centered_player_nr_next = -999;
2202 if (key == setup.shortcut.focus_player_all)
2203 centered_player_nr_next = -1;
2205 for (i = 0; i < MAX_PLAYERS; i++)
2206 if (key == setup.shortcut.focus_player[i])
2207 centered_player_nr_next = i;
2209 if (centered_player_nr_next != -999)
2211 game.centered_player_nr_next = centered_player_nr_next;
2212 game.set_centered_player = TRUE;
2216 tape.centered_player_nr_next = game.centered_player_nr_next;
2217 tape.set_centered_player = TRUE;
2222 HandleKeysSpecial(key);
2224 if (HandleGadgetsKeyInput(key))
2225 return; // do not handle already processed keys again
2227 switch (game_status)
2229 case GAME_MODE_PSEUDO_TYPENAME:
2230 HandleTypeName(0, key);
2233 case GAME_MODE_TITLE:
2234 case GAME_MODE_MAIN:
2235 case GAME_MODE_LEVELS:
2236 case GAME_MODE_LEVELNR:
2237 case GAME_MODE_SETUP:
2238 case GAME_MODE_INFO:
2239 case GAME_MODE_SCORES:
2241 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2248 if (game_status == GAME_MODE_TITLE)
2249 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2250 else if (game_status == GAME_MODE_MAIN)
2251 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2252 else if (game_status == GAME_MODE_LEVELS)
2253 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2254 else if (game_status == GAME_MODE_LEVELNR)
2255 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2256 else if (game_status == GAME_MODE_SETUP)
2257 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2258 else if (game_status == GAME_MODE_INFO)
2259 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2260 else if (game_status == GAME_MODE_SCORES)
2261 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2265 if (game_status != GAME_MODE_MAIN)
2266 FadeSkipNextFadeIn();
2268 if (game_status == GAME_MODE_TITLE)
2269 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2270 else if (game_status == GAME_MODE_LEVELS)
2271 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2272 else if (game_status == GAME_MODE_LEVELNR)
2273 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2274 else if (game_status == GAME_MODE_SETUP)
2275 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2276 else if (game_status == GAME_MODE_INFO)
2277 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2278 else if (game_status == GAME_MODE_SCORES)
2279 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2283 if (game_status == GAME_MODE_LEVELS)
2284 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2285 else if (game_status == GAME_MODE_LEVELNR)
2286 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2287 else if (game_status == GAME_MODE_SETUP)
2288 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2289 else if (game_status == GAME_MODE_INFO)
2290 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2291 else if (game_status == GAME_MODE_SCORES)
2292 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2295 case KSYM_Page_Down:
2296 if (game_status == GAME_MODE_LEVELS)
2297 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2298 else if (game_status == GAME_MODE_LEVELNR)
2299 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2300 else if (game_status == GAME_MODE_SETUP)
2301 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2302 else if (game_status == GAME_MODE_INFO)
2303 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2304 else if (game_status == GAME_MODE_SCORES)
2305 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2313 case GAME_MODE_EDITOR:
2314 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2315 HandleLevelEditorKeyInput(key);
2318 case GAME_MODE_PLAYING:
2323 RequestQuitGame(setup.ask_on_escape);
2333 if (key == KSYM_Escape)
2335 SetGameStatus(GAME_MODE_MAIN);
2344 void HandleNoEvent(void)
2346 HandleMouseCursor();
2348 switch (game_status)
2350 case GAME_MODE_PLAYING:
2351 HandleButtonOrFinger(-1, -1, -1);
2356 void HandleEventActions(void)
2358 // if (button_status && game_status != GAME_MODE_PLAYING)
2359 if (button_status && (game_status != GAME_MODE_PLAYING ||
2361 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2363 HandleButton(0, 0, button_status, -button_status);
2370 if (network.enabled)
2373 switch (game_status)
2375 case GAME_MODE_MAIN:
2376 DrawPreviewLevelAnimation();
2379 case GAME_MODE_EDITOR:
2380 HandleLevelEditorIdle();
2388 static void HandleTileCursor(int dx, int dy, int button)
2391 ClearPlayerMouseAction();
2398 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2399 (dx < 0 ? MB_LEFTBUTTON :
2400 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2402 else if (!tile_cursor.moving)
2404 int old_xpos = tile_cursor.xpos;
2405 int old_ypos = tile_cursor.ypos;
2406 int new_xpos = old_xpos;
2407 int new_ypos = old_ypos;
2409 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2410 new_xpos = old_xpos + dx;
2412 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2413 new_ypos = old_ypos + dy;
2415 SetTileCursorTargetXY(new_xpos, new_ypos);
2419 static int HandleJoystickForAllPlayers(void)
2423 boolean no_joysticks_configured = TRUE;
2424 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2425 static byte joy_action_last[MAX_PLAYERS];
2427 for (i = 0; i < MAX_PLAYERS; i++)
2428 if (setup.input[i].use_joystick)
2429 no_joysticks_configured = FALSE;
2431 // if no joysticks configured, map connected joysticks to players
2432 if (no_joysticks_configured)
2433 use_as_joystick_nr = TRUE;
2435 for (i = 0; i < MAX_PLAYERS; i++)
2437 byte joy_action = 0;
2439 joy_action = JoystickExt(i, use_as_joystick_nr);
2440 result |= joy_action;
2442 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2443 joy_action != joy_action_last[i])
2444 stored_player[i].action = joy_action;
2446 joy_action_last[i] = joy_action;
2452 void HandleJoystick(void)
2454 static unsigned int joytest_delay = 0;
2455 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2456 static int joytest_last = 0;
2457 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2458 int delay_value = GADGET_FRAME_DELAY;
2459 int joystick = HandleJoystickForAllPlayers();
2460 int keyboard = key_joystick_mapping;
2461 int joy = (joystick | keyboard);
2462 int joytest = joystick;
2463 int left = joy & JOY_LEFT;
2464 int right = joy & JOY_RIGHT;
2465 int up = joy & JOY_UP;
2466 int down = joy & JOY_DOWN;
2467 int button = joy & JOY_BUTTON;
2468 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2469 int dx = (left ? -1 : right ? 1 : 0);
2470 int dy = (up ? -1 : down ? 1 : 0);
2471 boolean use_delay_value_first = (joytest != joytest_last);
2473 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2475 // do not handle this button event anymore
2479 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2480 anyTextGadgetActive()))
2482 // leave name input in main menu or text input gadget
2483 HandleKey(KSYM_Escape, KEY_PRESSED);
2484 HandleKey(KSYM_Escape, KEY_RELEASED);
2489 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2491 if (game_status == GAME_MODE_PLAYING)
2493 // when playing MM style levels, also use delay for keyboard events
2494 joytest |= keyboard;
2496 // only use first delay value for new events, but not for changed events
2497 use_delay_value_first = (!joytest != !joytest_last);
2499 // only use delay after the initial keyboard event
2503 // for any joystick or keyboard event, enable playfield tile cursor
2504 if (dx || dy || button)
2505 SetTileCursorEnabled(TRUE);
2508 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2510 // delay joystick/keyboard actions if axes/keys continually pressed
2511 newbutton = dx = dy = 0;
2515 // first start with longer delay, then continue with shorter delay
2516 joytest_delay_value =
2517 (use_delay_value_first ? delay_value_first : delay_value);
2520 joytest_last = joytest;
2522 switch (game_status)
2524 case GAME_MODE_TITLE:
2525 case GAME_MODE_MAIN:
2526 case GAME_MODE_LEVELS:
2527 case GAME_MODE_LEVELNR:
2528 case GAME_MODE_SETUP:
2529 case GAME_MODE_INFO:
2530 case GAME_MODE_SCORES:
2532 if (anyTextGadgetActive())
2535 if (game_status == GAME_MODE_TITLE)
2536 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2537 else if (game_status == GAME_MODE_MAIN)
2538 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2539 else if (game_status == GAME_MODE_LEVELS)
2540 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2541 else if (game_status == GAME_MODE_LEVELNR)
2542 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2543 else if (game_status == GAME_MODE_SETUP)
2544 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2545 else if (game_status == GAME_MODE_INFO)
2546 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2547 else if (game_status == GAME_MODE_SCORES)
2548 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2553 case GAME_MODE_PLAYING:
2555 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2556 if (tape.playing || keyboard)
2557 newbutton = ((joy & JOY_BUTTON) != 0);
2560 if (newbutton && game.all_players_gone)
2567 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2569 if (joystick & JOY_ACTION)
2570 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2572 else if (tape.recording && tape.pausing && !tape.use_mouse)
2574 if (joystick & JOY_ACTION)
2575 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2578 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2579 HandleTileCursor(dx, dy, button);
2588 void HandleSpecialGameControllerButtons(Event *event)
2593 switch (event->type)
2595 case SDL_CONTROLLERBUTTONDOWN:
2596 key_status = KEY_PRESSED;
2599 case SDL_CONTROLLERBUTTONUP:
2600 key_status = KEY_RELEASED;
2607 switch (event->cbutton.button)
2609 case SDL_CONTROLLER_BUTTON_START:
2613 case SDL_CONTROLLER_BUTTON_BACK:
2621 HandleKey(key, key_status);
2624 void HandleSpecialGameControllerKeys(Key key, int key_status)
2626 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2627 int button = SDL_CONTROLLER_BUTTON_INVALID;
2629 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2630 if (key == KSYM_Rewind)
2631 button = SDL_CONTROLLER_BUTTON_A;
2632 else if (key == KSYM_FastForward || key == KSYM_Menu)
2633 button = SDL_CONTROLLER_BUTTON_B;
2635 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2639 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2640 SDL_CONTROLLERBUTTONUP);
2642 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2643 event.cbutton.button = button;
2644 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2647 HandleJoystickEvent(&event);
2652 boolean DoKeysymAction(int keysym)
2656 Key key = (Key)(-keysym);
2658 HandleKey(key, KEY_PRESSED);
2659 HandleKey(key, KEY_RELEASED);