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;
79 // non-motion events are directly passed to event handler functions
80 if (event->type != EVENT_MOTIONNOTIFY)
83 motion = (MotionEvent *)event;
84 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
85 motion->y >= SY && motion->y < SY + SYSIZE);
87 // do no reset mouse cursor before all pending events have been processed
88 if (gfx.cursor_mode == cursor_mode_last &&
89 ((game_status == GAME_MODE_TITLE &&
90 gfx.cursor_mode == CURSOR_NONE) ||
91 (game_status == GAME_MODE_PLAYING &&
92 gfx.cursor_mode == CURSOR_PLAYFIELD)))
94 SetMouseCursor(CURSOR_DEFAULT);
96 DelayReached(&special_cursor_delay, 0);
98 cursor_mode_last = CURSOR_DEFAULT;
101 // skip mouse motion events without pressed button outside level editor
102 if (button_status == MB_RELEASED &&
103 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
109 // to prevent delay problems, skip mouse motion events if the very next
110 // event is also a mouse motion event (and therefore effectively only
111 // handling the last of a row of mouse motion events in the event queue)
113 static boolean SkipPressedMouseMotionEvent(const Event *event)
115 // nothing to do if the current event is not a mouse motion event
116 if (event->type != EVENT_MOTIONNOTIFY)
119 // only skip motion events with pressed button outside the game
120 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
127 PeekEvent(&next_event);
129 // if next event is also a mouse motion event, skip the current one
130 if (next_event.type == EVENT_MOTIONNOTIFY)
137 static boolean WaitValidEvent(Event *event)
141 if (!FilterEvents(event))
144 if (SkipPressedMouseMotionEvent(event))
150 /* this is especially needed for event modifications for the Android target:
151 if mouse coordinates should be modified in the event filter function,
152 using a properly installed SDL event filter does not work, because in
153 the event filter, mouse coordinates in the event structure are still
154 physical pixel positions, not logical (scaled) screen positions, so this
155 has to be handled at a later stage in the event processing functions
156 (when device pixel positions are already converted to screen positions) */
158 boolean NextValidEvent(Event *event)
160 while (PendingEvent())
161 if (WaitValidEvent(event))
167 static void HandleEvents(void)
170 unsigned int event_frame_delay = 0;
171 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
173 ResetDelayCounter(&event_frame_delay);
175 while (NextValidEvent(&event))
179 case EVENT_BUTTONPRESS:
180 case EVENT_BUTTONRELEASE:
181 HandleButtonEvent((ButtonEvent *) &event);
184 case EVENT_MOTIONNOTIFY:
185 HandleMotionEvent((MotionEvent *) &event);
188 case EVENT_WHEELMOTION:
189 HandleWheelEvent((WheelEvent *) &event);
192 case SDL_WINDOWEVENT:
193 HandleWindowEvent((WindowEvent *) &event);
196 case EVENT_FINGERPRESS:
197 case EVENT_FINGERRELEASE:
198 case EVENT_FINGERMOTION:
199 HandleFingerEvent((FingerEvent *) &event);
202 case EVENT_TEXTINPUT:
203 HandleTextEvent((TextEvent *) &event);
206 case SDL_APP_WILLENTERBACKGROUND:
207 case SDL_APP_DIDENTERBACKGROUND:
208 case SDL_APP_WILLENTERFOREGROUND:
209 case SDL_APP_DIDENTERFOREGROUND:
210 HandlePauseResumeEvent((PauseResumeEvent *) &event);
214 case EVENT_KEYRELEASE:
215 HandleKeyEvent((KeyEvent *) &event);
219 HandleOtherEvents(&event);
223 // do not handle events for longer than standard frame delay period
224 if (DelayReached(&event_frame_delay, event_frame_delay_value))
229 void HandleOtherEvents(Event *event)
234 HandleExposeEvent((ExposeEvent *) event);
237 case EVENT_UNMAPNOTIFY:
239 // This causes the game to stop not only when iconified, but also
240 // when on another virtual desktop, which might be not desired.
241 SleepWhileUnmapped();
247 HandleFocusEvent((FocusChangeEvent *) event);
250 case EVENT_CLIENTMESSAGE:
251 HandleClientMessageEvent((ClientMessageEvent *) event);
254 case SDL_CONTROLLERBUTTONDOWN:
255 case SDL_CONTROLLERBUTTONUP:
256 // for any game controller button event, disable overlay buttons
257 SetOverlayEnabled(FALSE);
259 HandleSpecialGameControllerButtons(event);
262 case SDL_CONTROLLERDEVICEADDED:
263 case SDL_CONTROLLERDEVICEREMOVED:
264 case SDL_CONTROLLERAXISMOTION:
265 case SDL_JOYAXISMOTION:
266 case SDL_JOYBUTTONDOWN:
267 case SDL_JOYBUTTONUP:
268 HandleJoystickEvent(event);
271 #if defined(USE_DRAG_AND_DROP)
273 case SDL_DROPCOMPLETE:
276 HandleDropEvent(event);
285 static void HandleMouseCursor(void)
287 if (game_status == GAME_MODE_TITLE)
289 // when showing title screens, hide mouse pointer (if not moved)
291 if (gfx.cursor_mode != CURSOR_NONE &&
292 DelayReached(&special_cursor_delay, special_cursor_delay_value))
294 SetMouseCursor(CURSOR_NONE);
297 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
300 // when playing, display a special mouse pointer inside the playfield
302 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
303 cursor_inside_playfield &&
304 DelayReached(&special_cursor_delay, special_cursor_delay_value))
306 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
308 SetMouseCursor(CURSOR_PLAYFIELD);
311 else if (gfx.cursor_mode != CURSOR_DEFAULT)
313 SetMouseCursor(CURSOR_DEFAULT);
316 // this is set after all pending events have been processed
317 cursor_mode_last = gfx.cursor_mode;
329 // execute event related actions after pending events have been processed
330 HandleEventActions();
332 // don't use all CPU time when idle; the main loop while playing
333 // has its own synchronization and is CPU friendly, too
335 if (game_status == GAME_MODE_PLAYING)
338 // always copy backbuffer to visible screen for every video frame
341 // reset video frame delay to default (may change again while playing)
342 SetVideoFrameDelay(MenuFrameDelay);
344 if (game_status == GAME_MODE_QUIT)
349 void ClearAutoRepeatKeyEvents(void)
351 while (PendingEvent())
355 PeekEvent(&next_event);
357 // if event is repeated key press event, remove it from event queue
358 if (next_event.type == EVENT_KEYPRESS &&
359 next_event.key.repeat)
360 WaitEvent(&next_event);
366 void ClearEventQueue(void)
370 while (NextValidEvent(&event))
374 case EVENT_BUTTONRELEASE:
375 button_status = MB_RELEASED;
378 case EVENT_KEYRELEASE:
382 case SDL_CONTROLLERBUTTONUP:
383 HandleJoystickEvent(&event);
388 HandleOtherEvents(&event);
394 static void ClearPlayerMouseAction(void)
396 local_player->mouse_action.lx = 0;
397 local_player->mouse_action.ly = 0;
398 local_player->mouse_action.button = 0;
401 void ClearPlayerAction(void)
405 // simulate key release events for still pressed keys
406 key_joystick_mapping = 0;
407 for (i = 0; i < MAX_PLAYERS; i++)
409 stored_player[i].action = 0;
410 stored_player[i].snap_action = 0;
413 ClearJoystickState();
414 ClearPlayerMouseAction();
417 static void SetPlayerMouseAction(int mx, int my, int button)
419 int lx = getLevelFromScreenX(mx);
420 int ly = getLevelFromScreenY(my);
421 int new_button = (!local_player->mouse_action.button && button);
423 if (local_player->mouse_action.button_hint)
424 button = local_player->mouse_action.button_hint;
426 ClearPlayerMouseAction();
428 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
431 local_player->mouse_action.lx = lx;
432 local_player->mouse_action.ly = ly;
433 local_player->mouse_action.button = button;
435 if (tape.recording && tape.pausing && tape.use_mouse)
437 // un-pause a paused game only if mouse button was newly pressed down
439 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
442 SetTileCursorXY(lx, ly);
445 void SleepWhileUnmapped(void)
447 boolean window_unmapped = TRUE;
449 KeyboardAutoRepeatOn();
451 while (window_unmapped)
455 if (!WaitValidEvent(&event))
460 case EVENT_BUTTONRELEASE:
461 button_status = MB_RELEASED;
464 case EVENT_KEYRELEASE:
465 key_joystick_mapping = 0;
468 case SDL_CONTROLLERBUTTONUP:
469 HandleJoystickEvent(&event);
470 key_joystick_mapping = 0;
473 case EVENT_MAPNOTIFY:
474 window_unmapped = FALSE;
477 case EVENT_UNMAPNOTIFY:
478 // this is only to surely prevent the 'should not happen' case
479 // of recursively looping between 'SleepWhileUnmapped()' and
480 // 'HandleOtherEvents()' which usually calls this funtion.
484 HandleOtherEvents(&event);
489 if (game_status == GAME_MODE_PLAYING)
490 KeyboardAutoRepeatOffUnlessAutoplay();
493 void HandleExposeEvent(ExposeEvent *event)
497 void HandleButtonEvent(ButtonEvent *event)
499 #if DEBUG_EVENTS_BUTTON
500 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
502 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
506 // for any mouse button event, disable playfield tile cursor
507 SetTileCursorEnabled(FALSE);
509 #if defined(HAS_SCREEN_KEYBOARD)
510 if (video.shifted_up)
511 event->y += video.shifted_up_pos;
514 motion_status = FALSE;
516 if (event->type == EVENT_BUTTONPRESS)
517 button_status = event->button;
519 button_status = MB_RELEASED;
521 HandleButton(event->x, event->y, button_status, event->button);
524 void HandleMotionEvent(MotionEvent *event)
526 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
529 motion_status = TRUE;
531 #if DEBUG_EVENTS_MOTION
532 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
533 button_status, event->x, event->y);
536 HandleButton(event->x, event->y, button_status, button_status);
539 void HandleWheelEvent(WheelEvent *event)
543 #if DEBUG_EVENTS_WHEEL
545 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
546 event->which, event->x, event->y);
548 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
549 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
550 event->which, event->x, event->y,
551 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
552 "SDL_MOUSEWHEEL_FLIPPED"));
556 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
557 event->x > 0 ? MB_WHEEL_RIGHT :
558 event->y < 0 ? MB_WHEEL_DOWN :
559 event->y > 0 ? MB_WHEEL_UP : 0);
561 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
562 // accelerated mouse wheel available on Mac and Windows
563 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
565 // no accelerated mouse wheel available on Unix/Linux
566 wheel_steps = DEFAULT_WHEEL_STEPS;
569 motion_status = FALSE;
571 button_status = button_nr;
572 HandleButton(0, 0, button_status, -button_nr);
574 button_status = MB_RELEASED;
575 HandleButton(0, 0, button_status, -button_nr);
578 void HandleWindowEvent(WindowEvent *event)
580 #if DEBUG_EVENTS_WINDOW
581 int subtype = event->event;
584 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
585 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
586 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
587 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
588 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
589 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
590 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
591 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
592 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
593 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
594 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
595 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
596 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
597 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
600 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
601 event_name, event->data1, event->data2);
605 // (not needed, as the screen gets redrawn every 20 ms anyway)
606 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
607 event->event == SDL_WINDOWEVENT_RESIZED ||
608 event->event == SDL_WINDOWEVENT_EXPOSED)
612 if (event->event == SDL_WINDOWEVENT_RESIZED)
614 if (!video.fullscreen_enabled)
616 int new_window_width = event->data1;
617 int new_window_height = event->data2;
619 // if window size has changed after resizing, calculate new scaling factor
620 if (new_window_width != video.window_width ||
621 new_window_height != video.window_height)
623 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
624 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
626 // (extreme window scaling allowed, but cannot be saved permanently)
627 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
628 setup.window_scaling_percent =
629 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
630 MAX_WINDOW_SCALING_PERCENT);
632 video.window_width = new_window_width;
633 video.window_height = new_window_height;
635 if (game_status == GAME_MODE_SETUP)
636 RedrawSetupScreenAfterFullscreenToggle();
641 #if defined(PLATFORM_ANDROID)
644 int new_display_width = event->data1;
645 int new_display_height = event->data2;
647 // if fullscreen display size has changed, device has been rotated
648 if (new_display_width != video.display_width ||
649 new_display_height != video.display_height)
651 int nr = GRID_ACTIVE_NR(); // previous screen orientation
653 video.display_width = new_display_width;
654 video.display_height = new_display_height;
656 SDLSetScreenProperties();
658 // check if screen orientation has changed (should always be true here)
659 if (nr != GRID_ACTIVE_NR())
663 if (game_status == GAME_MODE_SETUP)
664 RedrawSetupScreenAfterScreenRotation(nr);
666 nr = GRID_ACTIVE_NR();
668 overlay.grid_xsize = setup.touch.grid_xsize[nr];
669 overlay.grid_ysize = setup.touch.grid_ysize[nr];
671 for (x = 0; x < MAX_GRID_XSIZE; x++)
672 for (y = 0; y < MAX_GRID_YSIZE; y++)
673 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
681 #define NUM_TOUCH_FINGERS 3
686 SDL_FingerID finger_id;
689 } touch_info[NUM_TOUCH_FINGERS];
691 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
694 int x = event->x * overlay.grid_xsize;
695 int y = event->y * overlay.grid_ysize;
696 int grid_button = overlay.grid_button[x][y];
697 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
698 Key key = (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
699 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
700 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
701 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
702 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
703 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
706 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
707 float event_x = (event->x);
708 float event_y = (event->y - ypos) / (1 - ypos);
709 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
710 event_y > 2.0 / 3.0 && event_y < 1 ?
711 setup.input[0].key.snap :
712 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
713 event_y > 2.0 / 3.0 && event_y < 1 ?
714 setup.input[0].key.drop :
715 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
716 event_y > 0 && event_y < 1.0 / 3.0 ?
717 setup.input[0].key.up :
718 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
719 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
720 setup.input[0].key.left :
721 event_x > 8.0 / 9.0 && event_x < 1 &&
722 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
723 setup.input[0].key.right :
724 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
725 event_y > 2.0 / 3.0 && event_y < 1 ?
726 setup.input[0].key.down :
729 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
731 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
735 virtual_button_pressed = (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
737 // for any touch input event, enable overlay buttons (if activated)
738 SetOverlayEnabled(TRUE);
740 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
741 getKeyNameFromKey(key), key_status_name, event->fingerId);
743 if (key_status == KEY_PRESSED)
744 overlay.grid_button_action |= grid_button_action;
746 overlay.grid_button_action &= ~grid_button_action;
748 // check if we already know this touch event's finger id
749 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
751 if (touch_info[i].touched &&
752 touch_info[i].finger_id == event->fingerId)
754 // Error(ERR_DEBUG, "MARK 1: %d", i);
760 if (i >= NUM_TOUCH_FINGERS)
762 if (key_status == KEY_PRESSED)
764 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
766 // unknown finger id -- get new, empty slot, if available
767 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
769 if (touch_info[i].counter < oldest_counter)
772 oldest_counter = touch_info[i].counter;
774 // Error(ERR_DEBUG, "MARK 2: %d", i);
777 if (!touch_info[i].touched)
779 // Error(ERR_DEBUG, "MARK 3: %d", i);
785 if (i >= NUM_TOUCH_FINGERS)
787 // all slots allocated -- use oldest slot
790 // Error(ERR_DEBUG, "MARK 4: %d", i);
795 // release of previously unknown key (should not happen)
797 if (key != KSYM_UNDEFINED)
799 HandleKey(key, KEY_RELEASED);
801 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
802 getKeyNameFromKey(key), "KEY_RELEASED", i);
807 if (i < NUM_TOUCH_FINGERS)
809 if (key_status == KEY_PRESSED)
811 if (touch_info[i].key != key)
813 if (touch_info[i].key != KSYM_UNDEFINED)
815 HandleKey(touch_info[i].key, KEY_RELEASED);
817 Error(ERR_DEBUG, "=> 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 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
826 getKeyNameFromKey(key), "KEY_PRESSED", i);
830 touch_info[i].touched = TRUE;
831 touch_info[i].finger_id = event->fingerId;
832 touch_info[i].counter = Counter();
833 touch_info[i].key = key;
837 if (touch_info[i].key != KSYM_UNDEFINED)
839 HandleKey(touch_info[i].key, KEY_RELEASED);
841 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
842 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
845 touch_info[i].touched = FALSE;
846 touch_info[i].finger_id = 0;
847 touch_info[i].counter = 0;
848 touch_info[i].key = 0;
853 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
855 static Key motion_key_x = KSYM_UNDEFINED;
856 static Key motion_key_y = KSYM_UNDEFINED;
857 static Key button_key = KSYM_UNDEFINED;
858 static float motion_x1, motion_y1;
859 static float button_x1, button_y1;
860 static SDL_FingerID motion_id = -1;
861 static SDL_FingerID button_id = -1;
862 int move_trigger_distance_percent = setup.touch.move_distance;
863 int drop_trigger_distance_percent = setup.touch.drop_distance;
864 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
865 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
866 float event_x = event->x;
867 float event_y = event->y;
869 if (event->type == EVENT_FINGERPRESS)
871 if (event_x > 1.0 / 3.0)
875 motion_id = event->fingerId;
880 motion_key_x = KSYM_UNDEFINED;
881 motion_key_y = KSYM_UNDEFINED;
883 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
889 button_id = event->fingerId;
894 button_key = setup.input[0].key.snap;
896 HandleKey(button_key, KEY_PRESSED);
898 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
901 else if (event->type == EVENT_FINGERRELEASE)
903 if (event->fingerId == motion_id)
907 if (motion_key_x != KSYM_UNDEFINED)
908 HandleKey(motion_key_x, KEY_RELEASED);
909 if (motion_key_y != KSYM_UNDEFINED)
910 HandleKey(motion_key_y, KEY_RELEASED);
912 motion_key_x = KSYM_UNDEFINED;
913 motion_key_y = KSYM_UNDEFINED;
915 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
917 else if (event->fingerId == button_id)
921 if (button_key != KSYM_UNDEFINED)
922 HandleKey(button_key, KEY_RELEASED);
924 button_key = KSYM_UNDEFINED;
926 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
929 else if (event->type == EVENT_FINGERMOTION)
931 if (event->fingerId == motion_id)
933 float distance_x = ABS(event_x - motion_x1);
934 float distance_y = ABS(event_y - motion_y1);
935 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
936 event_x > motion_x1 ? setup.input[0].key.right :
938 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
939 event_y > motion_y1 ? setup.input[0].key.down :
942 if (distance_x < move_trigger_distance / 2 ||
943 distance_x < distance_y)
944 new_motion_key_x = KSYM_UNDEFINED;
946 if (distance_y < move_trigger_distance / 2 ||
947 distance_y < distance_x)
948 new_motion_key_y = KSYM_UNDEFINED;
950 if (distance_x > move_trigger_distance ||
951 distance_y > move_trigger_distance)
953 if (new_motion_key_x != motion_key_x)
955 if (motion_key_x != KSYM_UNDEFINED)
956 HandleKey(motion_key_x, KEY_RELEASED);
957 if (new_motion_key_x != KSYM_UNDEFINED)
958 HandleKey(new_motion_key_x, KEY_PRESSED);
961 if (new_motion_key_y != motion_key_y)
963 if (motion_key_y != KSYM_UNDEFINED)
964 HandleKey(motion_key_y, KEY_RELEASED);
965 if (new_motion_key_y != KSYM_UNDEFINED)
966 HandleKey(new_motion_key_y, KEY_PRESSED);
972 motion_key_x = new_motion_key_x;
973 motion_key_y = new_motion_key_y;
975 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
978 else if (event->fingerId == button_id)
980 float distance_x = ABS(event_x - button_x1);
981 float distance_y = ABS(event_y - button_y1);
983 if (distance_x < drop_trigger_distance / 2 &&
984 distance_y > drop_trigger_distance)
986 if (button_key == setup.input[0].key.snap)
987 HandleKey(button_key, KEY_RELEASED);
992 button_key = setup.input[0].key.drop;
994 HandleKey(button_key, KEY_PRESSED);
996 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1002 void HandleFingerEvent(FingerEvent *event)
1004 #if DEBUG_EVENTS_FINGER
1005 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
1006 event->type == EVENT_FINGERPRESS ? "pressed" :
1007 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
1011 event->dx, event->dy,
1015 if (game_status != GAME_MODE_PLAYING)
1018 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1020 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1021 local_player->mouse_action.button_hint =
1022 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1023 event->x < 0.5 ? MB_LEFTBUTTON :
1024 event->x > 0.5 ? MB_RIGHTBUTTON :
1030 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1031 HandleFingerEvent_VirtualButtons(event);
1032 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1033 HandleFingerEvent_WipeGestures(event);
1036 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1038 static int old_mx = 0, old_my = 0;
1039 static int last_button = MB_LEFTBUTTON;
1040 static boolean touched = FALSE;
1041 static boolean tapped = FALSE;
1043 // screen tile was tapped (but finger not touching the screen anymore)
1044 // (this point will also be reached without receiving a touch event)
1045 if (tapped && !touched)
1047 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1052 // stop here if this function was not triggered by a touch event
1056 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1058 // finger started touching the screen
1068 ClearPlayerMouseAction();
1070 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1073 else if (button == MB_RELEASED && touched)
1075 // finger stopped touching the screen
1080 SetPlayerMouseAction(old_mx, old_my, last_button);
1082 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1084 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1089 // finger moved while touching the screen
1091 int old_x = getLevelFromScreenX(old_mx);
1092 int old_y = getLevelFromScreenY(old_my);
1093 int new_x = getLevelFromScreenX(mx);
1094 int new_y = getLevelFromScreenY(my);
1096 if (new_x != old_x || new_y != old_y)
1101 // finger moved left or right from (horizontal) starting position
1103 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1105 SetPlayerMouseAction(old_mx, old_my, button_nr);
1107 last_button = button_nr;
1109 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1113 // finger stays at or returned to (horizontal) starting position
1115 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1117 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1122 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1124 static int old_mx = 0, old_my = 0;
1125 static int last_button = MB_LEFTBUTTON;
1126 static boolean touched = FALSE;
1127 static boolean tapped = FALSE;
1129 // screen tile was tapped (but finger not touching the screen anymore)
1130 // (this point will also be reached without receiving a touch event)
1131 if (tapped && !touched)
1133 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1138 // stop here if this function was not triggered by a touch event
1142 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1144 // finger started touching the screen
1154 ClearPlayerMouseAction();
1156 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1159 else if (button == MB_RELEASED && touched)
1161 // finger stopped touching the screen
1166 SetPlayerMouseAction(old_mx, old_my, last_button);
1168 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1170 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1175 // finger moved while touching the screen
1177 int old_x = getLevelFromScreenX(old_mx);
1178 int old_y = getLevelFromScreenY(old_my);
1179 int new_x = getLevelFromScreenX(mx);
1180 int new_y = getLevelFromScreenY(my);
1182 if (new_x != old_x || new_y != old_y)
1184 // finger moved away from starting position
1186 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1188 // quickly alternate between clicking and releasing for maximum speed
1189 if (FrameCounter % 2 == 0)
1190 button_nr = MB_RELEASED;
1192 SetPlayerMouseAction(old_mx, old_my, button_nr);
1195 last_button = button_nr;
1199 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1203 // finger stays at or returned to starting position
1205 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1207 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1212 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1214 static int old_mx = 0, old_my = 0;
1215 static Key motion_key_x = KSYM_UNDEFINED;
1216 static Key motion_key_y = KSYM_UNDEFINED;
1217 static boolean touched = FALSE;
1218 static boolean started_on_player = FALSE;
1219 static boolean player_is_dropping = FALSE;
1220 static int player_drop_count = 0;
1221 static int last_player_x = -1;
1222 static int last_player_y = -1;
1224 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1233 started_on_player = FALSE;
1234 player_is_dropping = FALSE;
1235 player_drop_count = 0;
1239 motion_key_x = KSYM_UNDEFINED;
1240 motion_key_y = KSYM_UNDEFINED;
1242 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1245 else if (button == MB_RELEASED && touched)
1252 if (motion_key_x != KSYM_UNDEFINED)
1253 HandleKey(motion_key_x, KEY_RELEASED);
1254 if (motion_key_y != KSYM_UNDEFINED)
1255 HandleKey(motion_key_y, KEY_RELEASED);
1257 if (started_on_player)
1259 if (player_is_dropping)
1261 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1263 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1267 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1269 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1273 motion_key_x = KSYM_UNDEFINED;
1274 motion_key_y = KSYM_UNDEFINED;
1276 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1281 int src_x = local_player->jx;
1282 int src_y = local_player->jy;
1283 int dst_x = getLevelFromScreenX(old_mx);
1284 int dst_y = getLevelFromScreenY(old_my);
1285 int dx = dst_x - src_x;
1286 int dy = dst_y - src_y;
1287 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1288 dx > 0 ? setup.input[0].key.right :
1290 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1291 dy > 0 ? setup.input[0].key.down :
1294 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1295 (last_player_x != local_player->jx ||
1296 last_player_y != local_player->jy))
1298 // in case of asymmetric diagonal movement, use "preferred" direction
1300 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1302 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1303 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1305 local_player->last_move_dir = last_move_dir;
1307 // (required to prevent accidentally forcing direction for next movement)
1308 last_player_x = local_player->jx;
1309 last_player_y = local_player->jy;
1312 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1314 started_on_player = TRUE;
1315 player_drop_count = getPlayerInventorySize(0);
1316 player_is_dropping = (player_drop_count > 0);
1318 if (player_is_dropping)
1320 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1322 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1326 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1328 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1331 else if (dx != 0 || dy != 0)
1333 if (player_is_dropping &&
1334 player_drop_count == getPlayerInventorySize(0))
1336 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1338 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1339 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1341 player_is_dropping = FALSE;
1345 if (new_motion_key_x != motion_key_x)
1347 Error(ERR_DEBUG, "---------- %s %s ----------",
1348 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1349 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1351 if (motion_key_x != KSYM_UNDEFINED)
1352 HandleKey(motion_key_x, KEY_RELEASED);
1353 if (new_motion_key_x != KSYM_UNDEFINED)
1354 HandleKey(new_motion_key_x, KEY_PRESSED);
1357 if (new_motion_key_y != motion_key_y)
1359 Error(ERR_DEBUG, "---------- %s %s ----------",
1360 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1361 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1363 if (motion_key_y != KSYM_UNDEFINED)
1364 HandleKey(motion_key_y, KEY_RELEASED);
1365 if (new_motion_key_y != KSYM_UNDEFINED)
1366 HandleKey(new_motion_key_y, KEY_PRESSED);
1369 motion_key_x = new_motion_key_x;
1370 motion_key_y = new_motion_key_y;
1374 static void HandleButtonOrFinger(int mx, int my, int button)
1376 if (game_status != GAME_MODE_PLAYING)
1379 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1381 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1382 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1383 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1384 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1385 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1386 SetPlayerMouseAction(mx, my, button); // special case
1390 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1391 HandleButtonOrFinger_FollowFinger(mx, my, button);
1395 static boolean checkTextInputKeyModState(void)
1397 // when playing, only handle raw key events and ignore text input
1398 if (game_status == GAME_MODE_PLAYING)
1401 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1404 void HandleTextEvent(TextEvent *event)
1406 char *text = event->text;
1407 Key key = getKeyFromKeyName(text);
1409 #if DEBUG_EVENTS_TEXT
1410 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1413 text[0], (int)(text[0]),
1415 getKeyNameFromKey(key),
1419 #if !defined(HAS_SCREEN_KEYBOARD)
1420 // non-mobile devices: only handle key input with modifier keys pressed here
1421 // (every other key input is handled directly as physical key input event)
1422 if (!checkTextInputKeyModState())
1426 // process text input as "classic" (with uppercase etc.) key input event
1427 HandleKey(key, KEY_PRESSED);
1428 HandleKey(key, KEY_RELEASED);
1431 void HandlePauseResumeEvent(PauseResumeEvent *event)
1433 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1437 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1443 void HandleKeyEvent(KeyEvent *event)
1445 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1446 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1447 Key key = GetEventKey(event, with_modifiers);
1448 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1450 #if DEBUG_EVENTS_KEY
1451 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1452 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1453 event->keysym.scancode,
1458 getKeyNameFromKey(key));
1461 #if defined(PLATFORM_ANDROID)
1462 if (key == KSYM_Back)
1464 // always map the "back" button to the "escape" key on Android devices
1467 else if (key == KSYM_Menu)
1469 // the "menu" button can be used to toggle displaying virtual buttons
1470 if (key_status == KEY_PRESSED)
1471 SetOverlayEnabled(!GetOverlayEnabled());
1475 // for any other "real" key event, disable virtual buttons
1476 SetOverlayEnabled(FALSE);
1480 HandleKeyModState(keymod, key_status);
1482 // only handle raw key input without text modifier keys pressed
1483 if (!checkTextInputKeyModState())
1484 HandleKey(key, key_status);
1487 void HandleFocusEvent(FocusChangeEvent *event)
1489 static int old_joystick_status = -1;
1491 if (event->type == EVENT_FOCUSOUT)
1493 KeyboardAutoRepeatOn();
1494 old_joystick_status = joystick.status;
1495 joystick.status = JOYSTICK_NOT_AVAILABLE;
1497 ClearPlayerAction();
1499 else if (event->type == EVENT_FOCUSIN)
1501 /* When there are two Rocks'n'Diamonds windows which overlap and
1502 the player moves the pointer from one game window to the other,
1503 a 'FocusOut' event is generated for the window the pointer is
1504 leaving and a 'FocusIn' event is generated for the window the
1505 pointer is entering. In some cases, it can happen that the
1506 'FocusIn' event is handled by the one game process before the
1507 'FocusOut' event by the other game process. In this case the
1508 X11 environment would end up with activated keyboard auto repeat,
1509 because unfortunately this is a global setting and not (which
1510 would be far better) set for each X11 window individually.
1511 The effect would be keyboard auto repeat while playing the game
1512 (game_status == GAME_MODE_PLAYING), which is not desired.
1513 To avoid this special case, we just wait 1/10 second before
1514 processing the 'FocusIn' event. */
1516 if (game_status == GAME_MODE_PLAYING)
1519 KeyboardAutoRepeatOffUnlessAutoplay();
1522 if (old_joystick_status != -1)
1523 joystick.status = old_joystick_status;
1527 void HandleClientMessageEvent(ClientMessageEvent *event)
1529 if (CheckCloseWindowEvent(event))
1533 #if defined(USE_DRAG_AND_DROP)
1534 static boolean HandleDropFileEvent(char *filename)
1536 Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
1538 // check and extract dropped zip files into correct user data directory
1539 if (!strSuffixLower(filename, ".zip"))
1541 Error(ERR_WARN, "file '%s' not supported", filename);
1546 TreeInfo *tree_node = NULL;
1547 int tree_type = GetZipFileTreeType(filename);
1548 char *directory = TREE_USERDIR(tree_type);
1550 if (directory == NULL)
1552 Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1557 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1558 game_status == GAME_MODE_LEVELS &&
1559 leveldir_current->node_parent != NULL)
1561 // extract new level set next to currently selected level set
1562 tree_node = leveldir_current;
1564 // get parent directory of currently selected level set directory
1565 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1567 // use private level directory instead of top-level package level directory
1568 if (strPrefix(directory, options.level_directory) &&
1569 strEqual(leveldir_current->node_parent->fullpath, "."))
1570 directory = getUserLevelDir(NULL);
1573 // extract level or artwork set from zip file to target directory
1574 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1576 if (top_dir == NULL)
1578 // error message already issued by "ExtractZipFileIntoDirectory()"
1583 // add extracted level or artwork set to tree info structure
1584 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1586 // update menu screen (and possibly change current level set)
1587 DrawScreenAfterAddingSet(top_dir, tree_type);
1592 static void HandleDropTextEvent(char *text)
1594 Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1597 void HandleDropEvent(Event *event)
1599 static int files_succeeded = 0;
1600 static int files_failed = 0;
1602 switch (event->type)
1606 files_succeeded = 0;
1614 boolean success = HandleDropFileEvent(event->drop.file);
1626 HandleDropTextEvent(event->drop.file);
1631 case SDL_DROPCOMPLETE:
1633 // only show request dialog if no other request dialog already active
1634 if (!game.request_active)
1636 if (files_succeeded > 0 && files_failed > 0)
1637 Request("New level or artwork set(s) added, "
1638 "but some dropped file(s) failed!", REQ_CONFIRM);
1639 else if (files_succeeded > 0)
1640 Request("New level or artwork set(s) added!", REQ_CONFIRM);
1641 else if (files_failed > 0)
1642 Request("Failed to process dropped file(s)!", REQ_CONFIRM);
1649 if (event->drop.file != NULL)
1650 SDL_free(event->drop.file);
1654 void HandleButton(int mx, int my, int button, int button_nr)
1656 static int old_mx = 0, old_my = 0;
1657 boolean button_hold = FALSE;
1658 boolean handle_gadgets = TRUE;
1664 button_nr = -button_nr;
1673 #if defined(PLATFORM_ANDROID)
1674 // when playing, only handle gadgets when using "follow finger" controls
1675 // or when using touch controls in combination with the MM game engine
1676 // or when using gadgets that do not overlap with virtual buttons
1678 (game_status != GAME_MODE_PLAYING ||
1679 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1680 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1681 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1682 !virtual_button_pressed));
1685 if (HandleGlobalAnimClicks(mx, my, button))
1687 // do not handle this button event anymore
1688 return; // force mouse event not to be handled at all
1691 if (handle_gadgets && HandleGadgets(mx, my, button))
1693 // do not handle this button event anymore
1694 mx = my = -32; // force mouse event to be outside screen tiles
1697 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1700 // do not use scroll wheel button events for anything other than gadgets
1701 if (IS_WHEEL_BUTTON(button_nr))
1704 switch (game_status)
1706 case GAME_MODE_TITLE:
1707 HandleTitleScreen(mx, my, 0, 0, button);
1710 case GAME_MODE_MAIN:
1711 HandleMainMenu(mx, my, 0, 0, button);
1714 case GAME_MODE_PSEUDO_TYPENAME:
1715 HandleTypeName(0, KSYM_Return);
1718 case GAME_MODE_LEVELS:
1719 HandleChooseLevelSet(mx, my, 0, 0, button);
1722 case GAME_MODE_LEVELNR:
1723 HandleChooseLevelNr(mx, my, 0, 0, button);
1726 case GAME_MODE_SCORES:
1727 HandleHallOfFame(0, 0, 0, 0, button);
1730 case GAME_MODE_EDITOR:
1731 HandleLevelEditorIdle();
1734 case GAME_MODE_INFO:
1735 HandleInfoScreen(mx, my, 0, 0, button);
1738 case GAME_MODE_SETUP:
1739 HandleSetupScreen(mx, my, 0, 0, button);
1742 case GAME_MODE_PLAYING:
1743 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1744 HandleButtonOrFinger(mx, my, button);
1746 SetPlayerMouseAction(mx, my, button);
1749 if (button == MB_PRESSED && !motion_status && !button_hold &&
1750 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1751 DumpTileFromScreen(mx, my);
1761 static boolean is_string_suffix(char *string, char *suffix)
1763 int string_len = strlen(string);
1764 int suffix_len = strlen(suffix);
1766 if (suffix_len > string_len)
1769 return (strEqual(&string[string_len - suffix_len], suffix));
1772 #define MAX_CHEAT_INPUT_LEN 32
1774 static void HandleKeysSpecial(Key key)
1776 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1777 char letter = getCharFromKey(key);
1778 int cheat_input_len = strlen(cheat_input);
1784 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1786 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1787 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1789 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1792 cheat_input[cheat_input_len++] = letter;
1793 cheat_input[cheat_input_len] = '\0';
1795 #if DEBUG_EVENTS_KEY
1796 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1799 if (game_status == GAME_MODE_MAIN)
1801 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1802 is_string_suffix(cheat_input, ":ist"))
1804 InsertSolutionTape();
1806 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1807 is_string_suffix(cheat_input, ":pst"))
1811 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1812 is_string_suffix(cheat_input, ":rg"))
1814 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1817 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1818 is_string_suffix(cheat_input, ":rs"))
1820 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1823 else if (is_string_suffix(cheat_input, ":reload-music") ||
1824 is_string_suffix(cheat_input, ":rm"))
1826 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1829 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1830 is_string_suffix(cheat_input, ":ra"))
1832 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1833 1 << ARTWORK_TYPE_SOUNDS |
1834 1 << ARTWORK_TYPE_MUSIC);
1837 else if (is_string_suffix(cheat_input, ":dump-level") ||
1838 is_string_suffix(cheat_input, ":dl"))
1842 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1843 is_string_suffix(cheat_input, ":dt"))
1847 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1848 is_string_suffix(cheat_input, ":ft"))
1850 /* fix single-player tapes that contain player input for more than one
1851 player (due to a bug in 3.3.1.2 and earlier versions), which results
1852 in playing levels with more than one player in multi-player mode,
1853 even though the tape was originally recorded in single-player mode */
1855 // remove player input actions for all players but the first one
1856 for (i = 1; i < MAX_PLAYERS; i++)
1857 tape.player_participates[i] = FALSE;
1859 tape.changed = TRUE;
1861 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1862 is_string_suffix(cheat_input, ":snl"))
1864 SaveNativeLevel(&level);
1866 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1867 is_string_suffix(cheat_input, ":fps"))
1869 global.show_frames_per_second = !global.show_frames_per_second;
1872 else if (game_status == GAME_MODE_PLAYING)
1875 if (is_string_suffix(cheat_input, ".q"))
1876 DEBUG_SetMaximumDynamite();
1879 else if (game_status == GAME_MODE_EDITOR)
1881 if (is_string_suffix(cheat_input, ":dump-brush") ||
1882 is_string_suffix(cheat_input, ":DB"))
1886 else if (is_string_suffix(cheat_input, ":DDB"))
1891 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1893 if (letter == 'x') // copy brush to clipboard (small size)
1895 CopyBrushToClipboard_Small();
1897 else if (letter == 'c') // copy brush to clipboard (normal size)
1899 CopyBrushToClipboard();
1901 else if (letter == 'v') // paste brush from Clipboard
1903 CopyClipboardToBrush();
1908 // special key shortcuts for all game modes
1909 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1910 is_string_suffix(cheat_input, ":dea") ||
1911 is_string_suffix(cheat_input, ":DEA"))
1913 DumpGadgetIdentifiers();
1914 DumpScreenIdentifiers();
1918 boolean HandleKeysDebug(Key key, int key_status)
1923 if (key_status != KEY_PRESSED)
1926 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1928 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1930 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1932 if (key == setup.debug.frame_delay_key[i] &&
1933 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1935 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1936 setup.debug.frame_delay[i] : setup.game_frame_delay);
1938 if (!setup.debug.frame_delay_game_only)
1939 MenuFrameDelay = GameFrameDelay;
1941 SetVideoFrameDelay(GameFrameDelay);
1943 if (GameFrameDelay > ONE_SECOND_DELAY)
1944 Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
1945 else if (GameFrameDelay != 0)
1946 Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
1947 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1948 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1950 Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
1957 if (game_status == GAME_MODE_PLAYING)
1961 options.debug = !options.debug;
1963 Error(ERR_INFO, "debug mode %s",
1964 (options.debug ? "enabled" : "disabled"));
1968 else if (key == KSYM_v)
1970 Error(ERR_INFO, "currently using game engine version %d",
1971 game.engine_version);
1981 void HandleKey(Key key, int key_status)
1983 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1984 static boolean ignore_repeated_key = FALSE;
1985 static struct SetupKeyboardInfo ski;
1986 static struct SetupShortcutInfo ssi;
1995 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1996 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1997 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1998 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1999 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2000 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2005 if (HandleKeysDebug(key, key_status))
2006 return; // do not handle already processed keys again
2008 // map special keys (media keys / remote control buttons) to default keys
2009 if (key == KSYM_PlayPause)
2011 else if (key == KSYM_Select)
2014 HandleSpecialGameControllerKeys(key, key_status);
2016 if (game_status == GAME_MODE_PLAYING)
2018 // only needed for single-step tape recording mode
2019 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2022 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2024 byte key_action = 0;
2025 byte key_snap_action = 0;
2027 if (setup.input[pnr].use_joystick)
2030 ski = setup.input[pnr].key;
2032 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2033 if (key == *key_info[i].key_custom)
2034 key_action |= key_info[i].action;
2036 // use combined snap+direction keys for the first player only
2039 ssi = setup.shortcut;
2041 // also remember normal snap key when handling snap+direction keys
2042 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2044 for (i = 0; i < NUM_DIRECTIONS; i++)
2046 if (key == *key_info[i].key_snap)
2048 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2049 key_snap_action |= key_info[i].action;
2054 if (key_status == KEY_PRESSED)
2056 stored_player[pnr].action |= key_action;
2057 stored_player[pnr].snap_action |= key_snap_action;
2061 stored_player[pnr].action &= ~key_action;
2062 stored_player[pnr].snap_action &= ~key_snap_action;
2065 // restore snap action if one of several pressed snap keys was released
2066 if (stored_player[pnr].snap_action)
2067 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2069 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2071 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2073 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2075 // if snap key already pressed, keep pause mode when releasing
2076 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2077 has_snapped[pnr] = TRUE;
2079 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2081 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2083 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2084 getRedDiskReleaseFlag_SP() == 0)
2086 // add a single inactive frame before dropping starts
2087 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2088 stored_player[pnr].force_dropping = TRUE;
2091 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2093 // if snap key was pressed without direction, leave pause mode
2094 if (!has_snapped[pnr])
2095 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2097 has_snapped[pnr] = FALSE;
2100 else if (tape.recording && tape.pausing && !tape.use_mouse)
2102 // prevent key release events from un-pausing a paused game
2103 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2104 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2107 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2108 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2114 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2115 if (key == key_info[i].key_default)
2116 joy |= key_info[i].action;
2121 if (key_status == KEY_PRESSED)
2122 key_joystick_mapping |= joy;
2124 key_joystick_mapping &= ~joy;
2129 if (game_status != GAME_MODE_PLAYING)
2130 key_joystick_mapping = 0;
2132 if (key_status == KEY_RELEASED)
2134 // reset flag to ignore repeated "key pressed" events after key release
2135 ignore_repeated_key = FALSE;
2140 if ((key == KSYM_F11 ||
2141 ((key == KSYM_Return ||
2142 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2143 video.fullscreen_available &&
2144 !ignore_repeated_key)
2146 setup.fullscreen = !setup.fullscreen;
2148 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2150 if (game_status == GAME_MODE_SETUP)
2151 RedrawSetupScreenAfterFullscreenToggle();
2153 // set flag to ignore repeated "key pressed" events
2154 ignore_repeated_key = TRUE;
2159 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2160 key == KSYM_minus || key == KSYM_KP_Subtract ||
2161 key == KSYM_plus || key == KSYM_KP_Add ||
2162 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2163 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2164 video.window_scaling_available &&
2165 !video.fullscreen_enabled)
2167 if (key == KSYM_0 || key == KSYM_KP_0)
2168 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2169 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2170 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2172 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2174 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2175 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2176 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2177 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2179 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2181 if (game_status == GAME_MODE_SETUP)
2182 RedrawSetupScreenAfterFullscreenToggle();
2187 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2188 key == KSYM_Return ||
2189 key == KSYM_Escape)))
2191 // do not handle this key event anymore
2192 if (key != KSYM_Escape) // always allow ESC key to be handled
2196 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2197 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2204 if (game_status == GAME_MODE_MAIN &&
2205 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2207 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2212 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2214 if (key == setup.shortcut.save_game)
2216 else if (key == setup.shortcut.load_game)
2218 else if (key == setup.shortcut.toggle_pause)
2219 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2221 HandleTapeButtonKeys(key);
2222 HandleSoundButtonKeys(key);
2225 if (game_status == GAME_MODE_PLAYING && !network_playing)
2227 int centered_player_nr_next = -999;
2229 if (key == setup.shortcut.focus_player_all)
2230 centered_player_nr_next = -1;
2232 for (i = 0; i < MAX_PLAYERS; i++)
2233 if (key == setup.shortcut.focus_player[i])
2234 centered_player_nr_next = i;
2236 if (centered_player_nr_next != -999)
2238 game.centered_player_nr_next = centered_player_nr_next;
2239 game.set_centered_player = TRUE;
2243 tape.centered_player_nr_next = game.centered_player_nr_next;
2244 tape.set_centered_player = TRUE;
2249 HandleKeysSpecial(key);
2251 if (HandleGadgetsKeyInput(key))
2252 return; // do not handle already processed keys again
2254 switch (game_status)
2256 case GAME_MODE_PSEUDO_TYPENAME:
2257 HandleTypeName(0, key);
2260 case GAME_MODE_TITLE:
2261 case GAME_MODE_MAIN:
2262 case GAME_MODE_LEVELS:
2263 case GAME_MODE_LEVELNR:
2264 case GAME_MODE_SETUP:
2265 case GAME_MODE_INFO:
2266 case GAME_MODE_SCORES:
2268 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2275 if (game_status == GAME_MODE_TITLE)
2276 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2277 else if (game_status == GAME_MODE_MAIN)
2278 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2279 else if (game_status == GAME_MODE_LEVELS)
2280 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2281 else if (game_status == GAME_MODE_LEVELNR)
2282 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2283 else if (game_status == GAME_MODE_SETUP)
2284 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2285 else if (game_status == GAME_MODE_INFO)
2286 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2287 else if (game_status == GAME_MODE_SCORES)
2288 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2292 if (game_status != GAME_MODE_MAIN)
2293 FadeSkipNextFadeIn();
2295 if (game_status == GAME_MODE_TITLE)
2296 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2297 else if (game_status == GAME_MODE_LEVELS)
2298 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2299 else if (game_status == GAME_MODE_LEVELNR)
2300 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2301 else if (game_status == GAME_MODE_SETUP)
2302 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2303 else if (game_status == GAME_MODE_INFO)
2304 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2305 else if (game_status == GAME_MODE_SCORES)
2306 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2310 if (game_status == GAME_MODE_LEVELS)
2311 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2312 else if (game_status == GAME_MODE_LEVELNR)
2313 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2314 else if (game_status == GAME_MODE_SETUP)
2315 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2316 else if (game_status == GAME_MODE_INFO)
2317 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2318 else if (game_status == GAME_MODE_SCORES)
2319 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2322 case KSYM_Page_Down:
2323 if (game_status == GAME_MODE_LEVELS)
2324 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2325 else if (game_status == GAME_MODE_LEVELNR)
2326 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2327 else if (game_status == GAME_MODE_SETUP)
2328 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2329 else if (game_status == GAME_MODE_INFO)
2330 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2331 else if (game_status == GAME_MODE_SCORES)
2332 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2340 case GAME_MODE_EDITOR:
2341 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2342 HandleLevelEditorKeyInput(key);
2345 case GAME_MODE_PLAYING:
2350 RequestQuitGame(setup.ask_on_escape);
2360 if (key == KSYM_Escape)
2362 SetGameStatus(GAME_MODE_MAIN);
2371 void HandleNoEvent(void)
2373 HandleMouseCursor();
2375 switch (game_status)
2377 case GAME_MODE_PLAYING:
2378 HandleButtonOrFinger(-1, -1, -1);
2383 void HandleEventActions(void)
2385 // if (button_status && game_status != GAME_MODE_PLAYING)
2386 if (button_status && (game_status != GAME_MODE_PLAYING ||
2388 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2390 HandleButton(0, 0, button_status, -button_status);
2397 if (network.enabled)
2400 switch (game_status)
2402 case GAME_MODE_MAIN:
2403 DrawPreviewLevelAnimation();
2406 case GAME_MODE_EDITOR:
2407 HandleLevelEditorIdle();
2415 static void HandleTileCursor(int dx, int dy, int button)
2418 ClearPlayerMouseAction();
2425 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2426 (dx < 0 ? MB_LEFTBUTTON :
2427 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2429 else if (!tile_cursor.moving)
2431 int old_xpos = tile_cursor.xpos;
2432 int old_ypos = tile_cursor.ypos;
2433 int new_xpos = old_xpos;
2434 int new_ypos = old_ypos;
2436 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2437 new_xpos = old_xpos + dx;
2439 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2440 new_ypos = old_ypos + dy;
2442 SetTileCursorTargetXY(new_xpos, new_ypos);
2446 static int HandleJoystickForAllPlayers(void)
2450 boolean no_joysticks_configured = TRUE;
2451 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2452 static byte joy_action_last[MAX_PLAYERS];
2454 for (i = 0; i < MAX_PLAYERS; i++)
2455 if (setup.input[i].use_joystick)
2456 no_joysticks_configured = FALSE;
2458 // if no joysticks configured, map connected joysticks to players
2459 if (no_joysticks_configured)
2460 use_as_joystick_nr = TRUE;
2462 for (i = 0; i < MAX_PLAYERS; i++)
2464 byte joy_action = 0;
2466 joy_action = JoystickExt(i, use_as_joystick_nr);
2467 result |= joy_action;
2469 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2470 joy_action != joy_action_last[i])
2471 stored_player[i].action = joy_action;
2473 joy_action_last[i] = joy_action;
2479 void HandleJoystick(void)
2481 static unsigned int joytest_delay = 0;
2482 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2483 static int joytest_last = 0;
2484 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2485 int delay_value = GADGET_FRAME_DELAY;
2486 int joystick = HandleJoystickForAllPlayers();
2487 int keyboard = key_joystick_mapping;
2488 int joy = (joystick | keyboard);
2489 int joytest = joystick;
2490 int left = joy & JOY_LEFT;
2491 int right = joy & JOY_RIGHT;
2492 int up = joy & JOY_UP;
2493 int down = joy & JOY_DOWN;
2494 int button = joy & JOY_BUTTON;
2495 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2496 int dx = (left ? -1 : right ? 1 : 0);
2497 int dy = (up ? -1 : down ? 1 : 0);
2498 boolean use_delay_value_first = (joytest != joytest_last);
2500 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2502 // do not handle this button event anymore
2506 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2507 anyTextGadgetActive()))
2509 // leave name input in main menu or text input gadget
2510 HandleKey(KSYM_Escape, KEY_PRESSED);
2511 HandleKey(KSYM_Escape, KEY_RELEASED);
2516 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2518 if (game_status == GAME_MODE_PLAYING)
2520 // when playing MM style levels, also use delay for keyboard events
2521 joytest |= keyboard;
2523 // only use first delay value for new events, but not for changed events
2524 use_delay_value_first = (!joytest != !joytest_last);
2526 // only use delay after the initial keyboard event
2530 // for any joystick or keyboard event, enable playfield tile cursor
2531 if (dx || dy || button)
2532 SetTileCursorEnabled(TRUE);
2535 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2537 // delay joystick/keyboard actions if axes/keys continually pressed
2538 newbutton = dx = dy = 0;
2542 // first start with longer delay, then continue with shorter delay
2543 joytest_delay_value =
2544 (use_delay_value_first ? delay_value_first : delay_value);
2547 joytest_last = joytest;
2549 switch (game_status)
2551 case GAME_MODE_TITLE:
2552 case GAME_MODE_MAIN:
2553 case GAME_MODE_LEVELS:
2554 case GAME_MODE_LEVELNR:
2555 case GAME_MODE_SETUP:
2556 case GAME_MODE_INFO:
2557 case GAME_MODE_SCORES:
2559 if (anyTextGadgetActive())
2562 if (game_status == GAME_MODE_TITLE)
2563 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2564 else if (game_status == GAME_MODE_MAIN)
2565 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2566 else if (game_status == GAME_MODE_LEVELS)
2567 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2568 else if (game_status == GAME_MODE_LEVELNR)
2569 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2570 else if (game_status == GAME_MODE_SETUP)
2571 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2572 else if (game_status == GAME_MODE_INFO)
2573 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2574 else if (game_status == GAME_MODE_SCORES)
2575 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2580 case GAME_MODE_PLAYING:
2582 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2583 if (tape.playing || keyboard)
2584 newbutton = ((joy & JOY_BUTTON) != 0);
2587 if (newbutton && game.all_players_gone)
2594 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2596 if (joystick & JOY_ACTION)
2597 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2599 else if (tape.recording && tape.pausing && !tape.use_mouse)
2601 if (joystick & JOY_ACTION)
2602 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2605 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2606 HandleTileCursor(dx, dy, button);
2615 void HandleSpecialGameControllerButtons(Event *event)
2620 switch (event->type)
2622 case SDL_CONTROLLERBUTTONDOWN:
2623 key_status = KEY_PRESSED;
2626 case SDL_CONTROLLERBUTTONUP:
2627 key_status = KEY_RELEASED;
2634 switch (event->cbutton.button)
2636 case SDL_CONTROLLER_BUTTON_START:
2640 case SDL_CONTROLLER_BUTTON_BACK:
2648 HandleKey(key, key_status);
2651 void HandleSpecialGameControllerKeys(Key key, int key_status)
2653 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2654 int button = SDL_CONTROLLER_BUTTON_INVALID;
2656 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2657 if (key == KSYM_Rewind)
2658 button = SDL_CONTROLLER_BUTTON_A;
2659 else if (key == KSYM_FastForward || key == KSYM_Menu)
2660 button = SDL_CONTROLLER_BUTTON_B;
2662 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2666 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2667 SDL_CONTROLLERBUTTONUP);
2669 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2670 event.cbutton.button = button;
2671 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2674 HandleJoystickEvent(&event);
2679 boolean DoKeysymAction(int keysym)
2683 Key key = (Key)(-keysym);
2685 HandleKey(key, KEY_PRESSED);
2686 HandleKey(key, KEY_RELEASED);