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;
690 } touch_info[NUM_TOUCH_FINGERS];
692 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
695 int x = event->x * overlay.grid_xsize;
696 int y = event->y * overlay.grid_ysize;
697 int grid_button = overlay.grid_button[x][y];
698 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
699 Key key = (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
700 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
701 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
702 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
703 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
704 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
707 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
708 float event_x = (event->x);
709 float event_y = (event->y - ypos) / (1 - ypos);
710 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
711 event_y > 2.0 / 3.0 && event_y < 1 ?
712 setup.input[0].key.snap :
713 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
714 event_y > 2.0 / 3.0 && event_y < 1 ?
715 setup.input[0].key.drop :
716 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
717 event_y > 0 && event_y < 1.0 / 3.0 ?
718 setup.input[0].key.up :
719 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
720 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
721 setup.input[0].key.left :
722 event_x > 8.0 / 9.0 && event_x < 1 &&
723 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
724 setup.input[0].key.right :
725 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
726 event_y > 2.0 / 3.0 && event_y < 1 ?
727 setup.input[0].key.down :
730 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
732 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
736 virtual_button_pressed = (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
738 // for any touch input event, enable overlay buttons (if activated)
739 SetOverlayEnabled(TRUE);
741 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
742 getKeyNameFromKey(key), key_status_name, event->fingerId);
744 if (key_status == KEY_PRESSED)
745 overlay.grid_button_action |= grid_button_action;
747 overlay.grid_button_action &= ~grid_button_action;
749 // check if we already know this touch event's finger id
750 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
752 if (touch_info[i].touched &&
753 touch_info[i].finger_id == event->fingerId)
755 // Error(ERR_DEBUG, "MARK 1: %d", i);
761 if (i >= NUM_TOUCH_FINGERS)
763 if (key_status == KEY_PRESSED)
765 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
767 // unknown finger id -- get new, empty slot, if available
768 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
770 if (touch_info[i].counter < oldest_counter)
773 oldest_counter = touch_info[i].counter;
775 // Error(ERR_DEBUG, "MARK 2: %d", i);
778 if (!touch_info[i].touched)
780 // Error(ERR_DEBUG, "MARK 3: %d", i);
786 if (i >= NUM_TOUCH_FINGERS)
788 // all slots allocated -- use oldest slot
791 // Error(ERR_DEBUG, "MARK 4: %d", i);
796 // release of previously unknown key (should not happen)
798 if (key != KSYM_UNDEFINED)
800 HandleKey(key, KEY_RELEASED);
802 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
803 getKeyNameFromKey(key), "KEY_RELEASED", i);
808 if (i < NUM_TOUCH_FINGERS)
810 if (key_status == KEY_PRESSED)
812 if (touch_info[i].key != key)
814 if (touch_info[i].key != KSYM_UNDEFINED)
816 HandleKey(touch_info[i].key, KEY_RELEASED);
818 // undraw previous grid button when moving finger away
819 overlay.grid_button_action &= ~touch_info[i].action;
821 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
822 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
825 if (key != KSYM_UNDEFINED)
827 HandleKey(key, KEY_PRESSED);
829 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
830 getKeyNameFromKey(key), "KEY_PRESSED", i);
834 touch_info[i].touched = TRUE;
835 touch_info[i].finger_id = event->fingerId;
836 touch_info[i].counter = Counter();
837 touch_info[i].key = key;
838 touch_info[i].action = grid_button_action;
842 if (touch_info[i].key != KSYM_UNDEFINED)
844 HandleKey(touch_info[i].key, KEY_RELEASED);
846 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
847 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
850 touch_info[i].touched = FALSE;
851 touch_info[i].finger_id = 0;
852 touch_info[i].counter = 0;
853 touch_info[i].key = 0;
854 touch_info[i].action = JOY_NO_ACTION;
859 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
861 static Key motion_key_x = KSYM_UNDEFINED;
862 static Key motion_key_y = KSYM_UNDEFINED;
863 static Key button_key = KSYM_UNDEFINED;
864 static float motion_x1, motion_y1;
865 static float button_x1, button_y1;
866 static SDL_FingerID motion_id = -1;
867 static SDL_FingerID button_id = -1;
868 int move_trigger_distance_percent = setup.touch.move_distance;
869 int drop_trigger_distance_percent = setup.touch.drop_distance;
870 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
871 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
872 float event_x = event->x;
873 float event_y = event->y;
875 if (event->type == EVENT_FINGERPRESS)
877 if (event_x > 1.0 / 3.0)
881 motion_id = event->fingerId;
886 motion_key_x = KSYM_UNDEFINED;
887 motion_key_y = KSYM_UNDEFINED;
889 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
895 button_id = event->fingerId;
900 button_key = setup.input[0].key.snap;
902 HandleKey(button_key, KEY_PRESSED);
904 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
907 else if (event->type == EVENT_FINGERRELEASE)
909 if (event->fingerId == motion_id)
913 if (motion_key_x != KSYM_UNDEFINED)
914 HandleKey(motion_key_x, KEY_RELEASED);
915 if (motion_key_y != KSYM_UNDEFINED)
916 HandleKey(motion_key_y, KEY_RELEASED);
918 motion_key_x = KSYM_UNDEFINED;
919 motion_key_y = KSYM_UNDEFINED;
921 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
923 else if (event->fingerId == button_id)
927 if (button_key != KSYM_UNDEFINED)
928 HandleKey(button_key, KEY_RELEASED);
930 button_key = KSYM_UNDEFINED;
932 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
935 else if (event->type == EVENT_FINGERMOTION)
937 if (event->fingerId == motion_id)
939 float distance_x = ABS(event_x - motion_x1);
940 float distance_y = ABS(event_y - motion_y1);
941 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
942 event_x > motion_x1 ? setup.input[0].key.right :
944 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
945 event_y > motion_y1 ? setup.input[0].key.down :
948 if (distance_x < move_trigger_distance / 2 ||
949 distance_x < distance_y)
950 new_motion_key_x = KSYM_UNDEFINED;
952 if (distance_y < move_trigger_distance / 2 ||
953 distance_y < distance_x)
954 new_motion_key_y = KSYM_UNDEFINED;
956 if (distance_x > move_trigger_distance ||
957 distance_y > move_trigger_distance)
959 if (new_motion_key_x != motion_key_x)
961 if (motion_key_x != KSYM_UNDEFINED)
962 HandleKey(motion_key_x, KEY_RELEASED);
963 if (new_motion_key_x != KSYM_UNDEFINED)
964 HandleKey(new_motion_key_x, KEY_PRESSED);
967 if (new_motion_key_y != motion_key_y)
969 if (motion_key_y != KSYM_UNDEFINED)
970 HandleKey(motion_key_y, KEY_RELEASED);
971 if (new_motion_key_y != KSYM_UNDEFINED)
972 HandleKey(new_motion_key_y, KEY_PRESSED);
978 motion_key_x = new_motion_key_x;
979 motion_key_y = new_motion_key_y;
981 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
984 else if (event->fingerId == button_id)
986 float distance_x = ABS(event_x - button_x1);
987 float distance_y = ABS(event_y - button_y1);
989 if (distance_x < drop_trigger_distance / 2 &&
990 distance_y > drop_trigger_distance)
992 if (button_key == setup.input[0].key.snap)
993 HandleKey(button_key, KEY_RELEASED);
998 button_key = setup.input[0].key.drop;
1000 HandleKey(button_key, KEY_PRESSED);
1002 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1008 void HandleFingerEvent(FingerEvent *event)
1010 #if DEBUG_EVENTS_FINGER
1011 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
1012 event->type == EVENT_FINGERPRESS ? "pressed" :
1013 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
1017 event->dx, event->dy,
1021 if (game_status != GAME_MODE_PLAYING)
1024 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1026 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1027 local_player->mouse_action.button_hint =
1028 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1029 event->x < 0.5 ? MB_LEFTBUTTON :
1030 event->x > 0.5 ? MB_RIGHTBUTTON :
1036 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1037 HandleFingerEvent_VirtualButtons(event);
1038 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1039 HandleFingerEvent_WipeGestures(event);
1042 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1044 static int old_mx = 0, old_my = 0;
1045 static int last_button = MB_LEFTBUTTON;
1046 static boolean touched = FALSE;
1047 static boolean tapped = FALSE;
1049 // screen tile was tapped (but finger not touching the screen anymore)
1050 // (this point will also be reached without receiving a touch event)
1051 if (tapped && !touched)
1053 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1058 // stop here if this function was not triggered by a touch event
1062 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1064 // finger started touching the screen
1074 ClearPlayerMouseAction();
1076 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1079 else if (button == MB_RELEASED && touched)
1081 // finger stopped touching the screen
1086 SetPlayerMouseAction(old_mx, old_my, last_button);
1088 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1090 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1095 // finger moved while touching the screen
1097 int old_x = getLevelFromScreenX(old_mx);
1098 int old_y = getLevelFromScreenY(old_my);
1099 int new_x = getLevelFromScreenX(mx);
1100 int new_y = getLevelFromScreenY(my);
1102 if (new_x != old_x || new_y != old_y)
1107 // finger moved left or right from (horizontal) starting position
1109 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1111 SetPlayerMouseAction(old_mx, old_my, button_nr);
1113 last_button = button_nr;
1115 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1119 // finger stays at or returned to (horizontal) starting position
1121 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1123 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1128 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1130 static int old_mx = 0, old_my = 0;
1131 static int last_button = MB_LEFTBUTTON;
1132 static boolean touched = FALSE;
1133 static boolean tapped = FALSE;
1135 // screen tile was tapped (but finger not touching the screen anymore)
1136 // (this point will also be reached without receiving a touch event)
1137 if (tapped && !touched)
1139 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1144 // stop here if this function was not triggered by a touch event
1148 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1150 // finger started touching the screen
1160 ClearPlayerMouseAction();
1162 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1165 else if (button == MB_RELEASED && touched)
1167 // finger stopped touching the screen
1172 SetPlayerMouseAction(old_mx, old_my, last_button);
1174 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1176 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1181 // finger moved while touching the screen
1183 int old_x = getLevelFromScreenX(old_mx);
1184 int old_y = getLevelFromScreenY(old_my);
1185 int new_x = getLevelFromScreenX(mx);
1186 int new_y = getLevelFromScreenY(my);
1188 if (new_x != old_x || new_y != old_y)
1190 // finger moved away from starting position
1192 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1194 // quickly alternate between clicking and releasing for maximum speed
1195 if (FrameCounter % 2 == 0)
1196 button_nr = MB_RELEASED;
1198 SetPlayerMouseAction(old_mx, old_my, button_nr);
1201 last_button = button_nr;
1205 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1209 // finger stays at or returned to starting position
1211 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1213 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1218 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1220 static int old_mx = 0, old_my = 0;
1221 static Key motion_key_x = KSYM_UNDEFINED;
1222 static Key motion_key_y = KSYM_UNDEFINED;
1223 static boolean touched = FALSE;
1224 static boolean started_on_player = FALSE;
1225 static boolean player_is_dropping = FALSE;
1226 static int player_drop_count = 0;
1227 static int last_player_x = -1;
1228 static int last_player_y = -1;
1230 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1239 started_on_player = FALSE;
1240 player_is_dropping = FALSE;
1241 player_drop_count = 0;
1245 motion_key_x = KSYM_UNDEFINED;
1246 motion_key_y = KSYM_UNDEFINED;
1248 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1251 else if (button == MB_RELEASED && touched)
1258 if (motion_key_x != KSYM_UNDEFINED)
1259 HandleKey(motion_key_x, KEY_RELEASED);
1260 if (motion_key_y != KSYM_UNDEFINED)
1261 HandleKey(motion_key_y, KEY_RELEASED);
1263 if (started_on_player)
1265 if (player_is_dropping)
1267 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1269 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1273 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1275 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1279 motion_key_x = KSYM_UNDEFINED;
1280 motion_key_y = KSYM_UNDEFINED;
1282 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1287 int src_x = local_player->jx;
1288 int src_y = local_player->jy;
1289 int dst_x = getLevelFromScreenX(old_mx);
1290 int dst_y = getLevelFromScreenY(old_my);
1291 int dx = dst_x - src_x;
1292 int dy = dst_y - src_y;
1293 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1294 dx > 0 ? setup.input[0].key.right :
1296 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1297 dy > 0 ? setup.input[0].key.down :
1300 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1301 (last_player_x != local_player->jx ||
1302 last_player_y != local_player->jy))
1304 // in case of asymmetric diagonal movement, use "preferred" direction
1306 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1308 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1309 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1311 local_player->last_move_dir = last_move_dir;
1313 // (required to prevent accidentally forcing direction for next movement)
1314 last_player_x = local_player->jx;
1315 last_player_y = local_player->jy;
1318 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1320 started_on_player = TRUE;
1321 player_drop_count = getPlayerInventorySize(0);
1322 player_is_dropping = (player_drop_count > 0);
1324 if (player_is_dropping)
1326 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1328 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1332 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1334 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1337 else if (dx != 0 || dy != 0)
1339 if (player_is_dropping &&
1340 player_drop_count == getPlayerInventorySize(0))
1342 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1344 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1345 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1347 player_is_dropping = FALSE;
1351 if (new_motion_key_x != motion_key_x)
1353 Error(ERR_DEBUG, "---------- %s %s ----------",
1354 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1355 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1357 if (motion_key_x != KSYM_UNDEFINED)
1358 HandleKey(motion_key_x, KEY_RELEASED);
1359 if (new_motion_key_x != KSYM_UNDEFINED)
1360 HandleKey(new_motion_key_x, KEY_PRESSED);
1363 if (new_motion_key_y != motion_key_y)
1365 Error(ERR_DEBUG, "---------- %s %s ----------",
1366 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1367 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1369 if (motion_key_y != KSYM_UNDEFINED)
1370 HandleKey(motion_key_y, KEY_RELEASED);
1371 if (new_motion_key_y != KSYM_UNDEFINED)
1372 HandleKey(new_motion_key_y, KEY_PRESSED);
1375 motion_key_x = new_motion_key_x;
1376 motion_key_y = new_motion_key_y;
1380 static void HandleButtonOrFinger(int mx, int my, int button)
1382 if (game_status != GAME_MODE_PLAYING)
1385 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1387 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1388 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1389 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1390 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1391 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1392 SetPlayerMouseAction(mx, my, button); // special case
1396 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1397 HandleButtonOrFinger_FollowFinger(mx, my, button);
1401 static boolean checkTextInputKeyModState(void)
1403 // when playing, only handle raw key events and ignore text input
1404 if (game_status == GAME_MODE_PLAYING)
1407 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1410 void HandleTextEvent(TextEvent *event)
1412 char *text = event->text;
1413 Key key = getKeyFromKeyName(text);
1415 #if DEBUG_EVENTS_TEXT
1416 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1419 text[0], (int)(text[0]),
1421 getKeyNameFromKey(key),
1425 #if !defined(HAS_SCREEN_KEYBOARD)
1426 // non-mobile devices: only handle key input with modifier keys pressed here
1427 // (every other key input is handled directly as physical key input event)
1428 if (!checkTextInputKeyModState())
1432 // process text input as "classic" (with uppercase etc.) key input event
1433 HandleKey(key, KEY_PRESSED);
1434 HandleKey(key, KEY_RELEASED);
1437 void HandlePauseResumeEvent(PauseResumeEvent *event)
1439 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1443 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1449 void HandleKeyEvent(KeyEvent *event)
1451 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1452 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1453 Key key = GetEventKey(event, with_modifiers);
1454 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1456 #if DEBUG_EVENTS_KEY
1457 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1458 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1459 event->keysym.scancode,
1464 getKeyNameFromKey(key));
1467 #if defined(PLATFORM_ANDROID)
1468 if (key == KSYM_Back)
1470 // always map the "back" button to the "escape" key on Android devices
1473 else if (key == KSYM_Menu)
1475 // the "menu" button can be used to toggle displaying virtual buttons
1476 if (key_status == KEY_PRESSED)
1477 SetOverlayEnabled(!GetOverlayEnabled());
1481 // for any other "real" key event, disable virtual buttons
1482 SetOverlayEnabled(FALSE);
1486 HandleKeyModState(keymod, key_status);
1488 // only handle raw key input without text modifier keys pressed
1489 if (!checkTextInputKeyModState())
1490 HandleKey(key, key_status);
1493 void HandleFocusEvent(FocusChangeEvent *event)
1495 static int old_joystick_status = -1;
1497 if (event->type == EVENT_FOCUSOUT)
1499 KeyboardAutoRepeatOn();
1500 old_joystick_status = joystick.status;
1501 joystick.status = JOYSTICK_NOT_AVAILABLE;
1503 ClearPlayerAction();
1505 else if (event->type == EVENT_FOCUSIN)
1507 /* When there are two Rocks'n'Diamonds windows which overlap and
1508 the player moves the pointer from one game window to the other,
1509 a 'FocusOut' event is generated for the window the pointer is
1510 leaving and a 'FocusIn' event is generated for the window the
1511 pointer is entering. In some cases, it can happen that the
1512 'FocusIn' event is handled by the one game process before the
1513 'FocusOut' event by the other game process. In this case the
1514 X11 environment would end up with activated keyboard auto repeat,
1515 because unfortunately this is a global setting and not (which
1516 would be far better) set for each X11 window individually.
1517 The effect would be keyboard auto repeat while playing the game
1518 (game_status == GAME_MODE_PLAYING), which is not desired.
1519 To avoid this special case, we just wait 1/10 second before
1520 processing the 'FocusIn' event. */
1522 if (game_status == GAME_MODE_PLAYING)
1525 KeyboardAutoRepeatOffUnlessAutoplay();
1528 if (old_joystick_status != -1)
1529 joystick.status = old_joystick_status;
1533 void HandleClientMessageEvent(ClientMessageEvent *event)
1535 if (CheckCloseWindowEvent(event))
1539 #if defined(USE_DRAG_AND_DROP)
1540 static boolean HandleDropFileEvent(char *filename)
1542 Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
1544 // check and extract dropped zip files into correct user data directory
1545 if (!strSuffixLower(filename, ".zip"))
1547 Error(ERR_WARN, "file '%s' not supported", filename);
1552 TreeInfo *tree_node = NULL;
1553 int tree_type = GetZipFileTreeType(filename);
1554 char *directory = TREE_USERDIR(tree_type);
1556 if (directory == NULL)
1558 Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1563 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1564 game_status == GAME_MODE_LEVELS &&
1565 leveldir_current->node_parent != NULL)
1567 // extract new level set next to currently selected level set
1568 tree_node = leveldir_current;
1570 // get parent directory of currently selected level set directory
1571 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1573 // use private level directory instead of top-level package level directory
1574 if (strPrefix(directory, options.level_directory) &&
1575 strEqual(leveldir_current->node_parent->fullpath, "."))
1576 directory = getUserLevelDir(NULL);
1579 // extract level or artwork set from zip file to target directory
1580 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1582 if (top_dir == NULL)
1584 // error message already issued by "ExtractZipFileIntoDirectory()"
1589 // add extracted level or artwork set to tree info structure
1590 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1592 // update menu screen (and possibly change current level set)
1593 DrawScreenAfterAddingSet(top_dir, tree_type);
1598 static void HandleDropTextEvent(char *text)
1600 Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1603 void HandleDropEvent(Event *event)
1605 static int files_succeeded = 0;
1606 static int files_failed = 0;
1608 switch (event->type)
1612 files_succeeded = 0;
1620 boolean success = HandleDropFileEvent(event->drop.file);
1632 HandleDropTextEvent(event->drop.file);
1637 case SDL_DROPCOMPLETE:
1639 // only show request dialog if no other request dialog already active
1640 if (!game.request_active)
1642 if (files_succeeded > 0 && files_failed > 0)
1643 Request("New level or artwork set(s) added, "
1644 "but some dropped file(s) failed!", REQ_CONFIRM);
1645 else if (files_succeeded > 0)
1646 Request("New level or artwork set(s) added!", REQ_CONFIRM);
1647 else if (files_failed > 0)
1648 Request("Failed to process dropped file(s)!", REQ_CONFIRM);
1655 if (event->drop.file != NULL)
1656 SDL_free(event->drop.file);
1660 void HandleButton(int mx, int my, int button, int button_nr)
1662 static int old_mx = 0, old_my = 0;
1663 boolean button_hold = FALSE;
1664 boolean handle_gadgets = TRUE;
1670 button_nr = -button_nr;
1679 #if defined(PLATFORM_ANDROID)
1680 // when playing, only handle gadgets when using "follow finger" controls
1681 // or when using touch controls in combination with the MM game engine
1682 // or when using gadgets that do not overlap with virtual buttons
1684 (game_status != GAME_MODE_PLAYING ||
1685 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1686 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1687 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1688 !virtual_button_pressed));
1691 if (HandleGlobalAnimClicks(mx, my, button))
1693 // do not handle this button event anymore
1694 return; // force mouse event not to be handled at all
1697 if (handle_gadgets && HandleGadgets(mx, my, button))
1699 // do not handle this button event anymore
1700 mx = my = -32; // force mouse event to be outside screen tiles
1703 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1706 // do not use scroll wheel button events for anything other than gadgets
1707 if (IS_WHEEL_BUTTON(button_nr))
1710 switch (game_status)
1712 case GAME_MODE_TITLE:
1713 HandleTitleScreen(mx, my, 0, 0, button);
1716 case GAME_MODE_MAIN:
1717 HandleMainMenu(mx, my, 0, 0, button);
1720 case GAME_MODE_PSEUDO_TYPENAME:
1721 HandleTypeName(0, KSYM_Return);
1724 case GAME_MODE_LEVELS:
1725 HandleChooseLevelSet(mx, my, 0, 0, button);
1728 case GAME_MODE_LEVELNR:
1729 HandleChooseLevelNr(mx, my, 0, 0, button);
1732 case GAME_MODE_SCORES:
1733 HandleHallOfFame(0, 0, 0, 0, button);
1736 case GAME_MODE_EDITOR:
1737 HandleLevelEditorIdle();
1740 case GAME_MODE_INFO:
1741 HandleInfoScreen(mx, my, 0, 0, button);
1744 case GAME_MODE_SETUP:
1745 HandleSetupScreen(mx, my, 0, 0, button);
1748 case GAME_MODE_PLAYING:
1749 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1750 HandleButtonOrFinger(mx, my, button);
1752 SetPlayerMouseAction(mx, my, button);
1755 if (button == MB_PRESSED && !motion_status && !button_hold &&
1756 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1757 DumpTileFromScreen(mx, my);
1767 static boolean is_string_suffix(char *string, char *suffix)
1769 int string_len = strlen(string);
1770 int suffix_len = strlen(suffix);
1772 if (suffix_len > string_len)
1775 return (strEqual(&string[string_len - suffix_len], suffix));
1778 #define MAX_CHEAT_INPUT_LEN 32
1780 static void HandleKeysSpecial(Key key)
1782 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1783 char letter = getCharFromKey(key);
1784 int cheat_input_len = strlen(cheat_input);
1790 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1792 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1793 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1795 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1798 cheat_input[cheat_input_len++] = letter;
1799 cheat_input[cheat_input_len] = '\0';
1801 #if DEBUG_EVENTS_KEY
1802 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1805 if (game_status == GAME_MODE_MAIN)
1807 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1808 is_string_suffix(cheat_input, ":ist"))
1810 InsertSolutionTape();
1812 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1813 is_string_suffix(cheat_input, ":pst"))
1817 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1818 is_string_suffix(cheat_input, ":rg"))
1820 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1823 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1824 is_string_suffix(cheat_input, ":rs"))
1826 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1829 else if (is_string_suffix(cheat_input, ":reload-music") ||
1830 is_string_suffix(cheat_input, ":rm"))
1832 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1835 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1836 is_string_suffix(cheat_input, ":ra"))
1838 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1839 1 << ARTWORK_TYPE_SOUNDS |
1840 1 << ARTWORK_TYPE_MUSIC);
1843 else if (is_string_suffix(cheat_input, ":dump-level") ||
1844 is_string_suffix(cheat_input, ":dl"))
1848 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1849 is_string_suffix(cheat_input, ":dt"))
1853 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1854 is_string_suffix(cheat_input, ":ft"))
1856 /* fix single-player tapes that contain player input for more than one
1857 player (due to a bug in 3.3.1.2 and earlier versions), which results
1858 in playing levels with more than one player in multi-player mode,
1859 even though the tape was originally recorded in single-player mode */
1861 // remove player input actions for all players but the first one
1862 for (i = 1; i < MAX_PLAYERS; i++)
1863 tape.player_participates[i] = FALSE;
1865 tape.changed = TRUE;
1867 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1868 is_string_suffix(cheat_input, ":snl"))
1870 SaveNativeLevel(&level);
1872 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1873 is_string_suffix(cheat_input, ":fps"))
1875 global.show_frames_per_second = !global.show_frames_per_second;
1878 else if (game_status == GAME_MODE_PLAYING)
1881 if (is_string_suffix(cheat_input, ".q"))
1882 DEBUG_SetMaximumDynamite();
1885 else if (game_status == GAME_MODE_EDITOR)
1887 if (is_string_suffix(cheat_input, ":dump-brush") ||
1888 is_string_suffix(cheat_input, ":DB"))
1892 else if (is_string_suffix(cheat_input, ":DDB"))
1897 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1899 if (letter == 'x') // copy brush to clipboard (small size)
1901 CopyBrushToClipboard_Small();
1903 else if (letter == 'c') // copy brush to clipboard (normal size)
1905 CopyBrushToClipboard();
1907 else if (letter == 'v') // paste brush from Clipboard
1909 CopyClipboardToBrush();
1914 // special key shortcuts for all game modes
1915 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1916 is_string_suffix(cheat_input, ":dea") ||
1917 is_string_suffix(cheat_input, ":DEA"))
1919 DumpGadgetIdentifiers();
1920 DumpScreenIdentifiers();
1924 boolean HandleKeysDebug(Key key, int key_status)
1929 if (key_status != KEY_PRESSED)
1932 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1934 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1936 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1938 if (key == setup.debug.frame_delay_key[i] &&
1939 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1941 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1942 setup.debug.frame_delay[i] : setup.game_frame_delay);
1944 if (!setup.debug.frame_delay_game_only)
1945 MenuFrameDelay = GameFrameDelay;
1947 SetVideoFrameDelay(GameFrameDelay);
1949 if (GameFrameDelay > ONE_SECOND_DELAY)
1950 Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
1951 else if (GameFrameDelay != 0)
1952 Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
1953 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1954 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1956 Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
1963 if (game_status == GAME_MODE_PLAYING)
1967 options.debug = !options.debug;
1969 Error(ERR_INFO, "debug mode %s",
1970 (options.debug ? "enabled" : "disabled"));
1974 else if (key == KSYM_v)
1976 Error(ERR_INFO, "currently using game engine version %d",
1977 game.engine_version);
1987 void HandleKey(Key key, int key_status)
1989 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1990 static boolean ignore_repeated_key = FALSE;
1991 static struct SetupKeyboardInfo ski;
1992 static struct SetupShortcutInfo ssi;
2001 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
2002 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
2003 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
2004 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
2005 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2006 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2011 if (HandleKeysDebug(key, key_status))
2012 return; // do not handle already processed keys again
2014 // map special keys (media keys / remote control buttons) to default keys
2015 if (key == KSYM_PlayPause)
2017 else if (key == KSYM_Select)
2020 HandleSpecialGameControllerKeys(key, key_status);
2022 if (game_status == GAME_MODE_PLAYING)
2024 // only needed for single-step tape recording mode
2025 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2028 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2030 byte key_action = 0;
2031 byte key_snap_action = 0;
2033 if (setup.input[pnr].use_joystick)
2036 ski = setup.input[pnr].key;
2038 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2039 if (key == *key_info[i].key_custom)
2040 key_action |= key_info[i].action;
2042 // use combined snap+direction keys for the first player only
2045 ssi = setup.shortcut;
2047 // also remember normal snap key when handling snap+direction keys
2048 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2050 for (i = 0; i < NUM_DIRECTIONS; i++)
2052 if (key == *key_info[i].key_snap)
2054 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2055 key_snap_action |= key_info[i].action;
2060 if (key_status == KEY_PRESSED)
2062 stored_player[pnr].action |= key_action;
2063 stored_player[pnr].snap_action |= key_snap_action;
2067 stored_player[pnr].action &= ~key_action;
2068 stored_player[pnr].snap_action &= ~key_snap_action;
2071 // restore snap action if one of several pressed snap keys was released
2072 if (stored_player[pnr].snap_action)
2073 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2075 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2077 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2079 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2081 // if snap key already pressed, keep pause mode when releasing
2082 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2083 has_snapped[pnr] = TRUE;
2085 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2087 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2089 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2090 getRedDiskReleaseFlag_SP() == 0)
2092 // add a single inactive frame before dropping starts
2093 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2094 stored_player[pnr].force_dropping = TRUE;
2097 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2099 // if snap key was pressed without direction, leave pause mode
2100 if (!has_snapped[pnr])
2101 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2103 has_snapped[pnr] = FALSE;
2106 else if (tape.recording && tape.pausing && !tape.use_mouse)
2108 // prevent key release events from un-pausing a paused game
2109 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2110 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2113 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2114 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2120 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2121 if (key == key_info[i].key_default)
2122 joy |= key_info[i].action;
2127 if (key_status == KEY_PRESSED)
2128 key_joystick_mapping |= joy;
2130 key_joystick_mapping &= ~joy;
2135 if (game_status != GAME_MODE_PLAYING)
2136 key_joystick_mapping = 0;
2138 if (key_status == KEY_RELEASED)
2140 // reset flag to ignore repeated "key pressed" events after key release
2141 ignore_repeated_key = FALSE;
2146 if ((key == KSYM_F11 ||
2147 ((key == KSYM_Return ||
2148 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2149 video.fullscreen_available &&
2150 !ignore_repeated_key)
2152 setup.fullscreen = !setup.fullscreen;
2154 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2156 if (game_status == GAME_MODE_SETUP)
2157 RedrawSetupScreenAfterFullscreenToggle();
2159 // set flag to ignore repeated "key pressed" events
2160 ignore_repeated_key = TRUE;
2165 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2166 key == KSYM_minus || key == KSYM_KP_Subtract ||
2167 key == KSYM_plus || key == KSYM_KP_Add ||
2168 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2169 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2170 video.window_scaling_available &&
2171 !video.fullscreen_enabled)
2173 if (key == KSYM_0 || key == KSYM_KP_0)
2174 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2175 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2176 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2178 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2180 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2181 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2182 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2183 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2185 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2187 if (game_status == GAME_MODE_SETUP)
2188 RedrawSetupScreenAfterFullscreenToggle();
2193 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2194 key == KSYM_Return ||
2195 key == KSYM_Escape)))
2197 // do not handle this key event anymore
2198 if (key != KSYM_Escape) // always allow ESC key to be handled
2202 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2203 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2210 if (game_status == GAME_MODE_MAIN &&
2211 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2213 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2218 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2220 if (key == setup.shortcut.save_game)
2222 else if (key == setup.shortcut.load_game)
2224 else if (key == setup.shortcut.toggle_pause)
2225 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2227 HandleTapeButtonKeys(key);
2228 HandleSoundButtonKeys(key);
2231 if (game_status == GAME_MODE_PLAYING && !network_playing)
2233 int centered_player_nr_next = -999;
2235 if (key == setup.shortcut.focus_player_all)
2236 centered_player_nr_next = -1;
2238 for (i = 0; i < MAX_PLAYERS; i++)
2239 if (key == setup.shortcut.focus_player[i])
2240 centered_player_nr_next = i;
2242 if (centered_player_nr_next != -999)
2244 game.centered_player_nr_next = centered_player_nr_next;
2245 game.set_centered_player = TRUE;
2249 tape.centered_player_nr_next = game.centered_player_nr_next;
2250 tape.set_centered_player = TRUE;
2255 HandleKeysSpecial(key);
2257 if (HandleGadgetsKeyInput(key))
2258 return; // do not handle already processed keys again
2260 switch (game_status)
2262 case GAME_MODE_PSEUDO_TYPENAME:
2263 HandleTypeName(0, key);
2266 case GAME_MODE_TITLE:
2267 case GAME_MODE_MAIN:
2268 case GAME_MODE_LEVELS:
2269 case GAME_MODE_LEVELNR:
2270 case GAME_MODE_SETUP:
2271 case GAME_MODE_INFO:
2272 case GAME_MODE_SCORES:
2274 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2281 if (game_status == GAME_MODE_TITLE)
2282 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2283 else if (game_status == GAME_MODE_MAIN)
2284 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2285 else if (game_status == GAME_MODE_LEVELS)
2286 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2287 else if (game_status == GAME_MODE_LEVELNR)
2288 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2289 else if (game_status == GAME_MODE_SETUP)
2290 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2291 else if (game_status == GAME_MODE_INFO)
2292 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2293 else if (game_status == GAME_MODE_SCORES)
2294 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2298 if (game_status != GAME_MODE_MAIN)
2299 FadeSkipNextFadeIn();
2301 if (game_status == GAME_MODE_TITLE)
2302 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2303 else if (game_status == GAME_MODE_LEVELS)
2304 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2305 else if (game_status == GAME_MODE_LEVELNR)
2306 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2307 else if (game_status == GAME_MODE_SETUP)
2308 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2309 else if (game_status == GAME_MODE_INFO)
2310 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2311 else if (game_status == GAME_MODE_SCORES)
2312 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2316 if (game_status == GAME_MODE_LEVELS)
2317 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2318 else if (game_status == GAME_MODE_LEVELNR)
2319 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2320 else if (game_status == GAME_MODE_SETUP)
2321 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2322 else if (game_status == GAME_MODE_INFO)
2323 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2324 else if (game_status == GAME_MODE_SCORES)
2325 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2328 case KSYM_Page_Down:
2329 if (game_status == GAME_MODE_LEVELS)
2330 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2331 else if (game_status == GAME_MODE_LEVELNR)
2332 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2333 else if (game_status == GAME_MODE_SETUP)
2334 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2335 else if (game_status == GAME_MODE_INFO)
2336 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2337 else if (game_status == GAME_MODE_SCORES)
2338 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2346 case GAME_MODE_EDITOR:
2347 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2348 HandleLevelEditorKeyInput(key);
2351 case GAME_MODE_PLAYING:
2356 RequestQuitGame(setup.ask_on_escape);
2366 if (key == KSYM_Escape)
2368 SetGameStatus(GAME_MODE_MAIN);
2377 void HandleNoEvent(void)
2379 HandleMouseCursor();
2381 switch (game_status)
2383 case GAME_MODE_PLAYING:
2384 HandleButtonOrFinger(-1, -1, -1);
2389 void HandleEventActions(void)
2391 // if (button_status && game_status != GAME_MODE_PLAYING)
2392 if (button_status && (game_status != GAME_MODE_PLAYING ||
2394 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2396 HandleButton(0, 0, button_status, -button_status);
2403 if (network.enabled)
2406 switch (game_status)
2408 case GAME_MODE_MAIN:
2409 DrawPreviewLevelAnimation();
2412 case GAME_MODE_EDITOR:
2413 HandleLevelEditorIdle();
2421 static void HandleTileCursor(int dx, int dy, int button)
2424 ClearPlayerMouseAction();
2431 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2432 (dx < 0 ? MB_LEFTBUTTON :
2433 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2435 else if (!tile_cursor.moving)
2437 int old_xpos = tile_cursor.xpos;
2438 int old_ypos = tile_cursor.ypos;
2439 int new_xpos = old_xpos;
2440 int new_ypos = old_ypos;
2442 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2443 new_xpos = old_xpos + dx;
2445 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2446 new_ypos = old_ypos + dy;
2448 SetTileCursorTargetXY(new_xpos, new_ypos);
2452 static int HandleJoystickForAllPlayers(void)
2456 boolean no_joysticks_configured = TRUE;
2457 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2458 static byte joy_action_last[MAX_PLAYERS];
2460 for (i = 0; i < MAX_PLAYERS; i++)
2461 if (setup.input[i].use_joystick)
2462 no_joysticks_configured = FALSE;
2464 // if no joysticks configured, map connected joysticks to players
2465 if (no_joysticks_configured)
2466 use_as_joystick_nr = TRUE;
2468 for (i = 0; i < MAX_PLAYERS; i++)
2470 byte joy_action = 0;
2472 joy_action = JoystickExt(i, use_as_joystick_nr);
2473 result |= joy_action;
2475 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2476 joy_action != joy_action_last[i])
2477 stored_player[i].action = joy_action;
2479 joy_action_last[i] = joy_action;
2485 void HandleJoystick(void)
2487 static unsigned int joytest_delay = 0;
2488 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2489 static int joytest_last = 0;
2490 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2491 int delay_value = GADGET_FRAME_DELAY;
2492 int joystick = HandleJoystickForAllPlayers();
2493 int keyboard = key_joystick_mapping;
2494 int joy = (joystick | keyboard);
2495 int joytest = joystick;
2496 int left = joy & JOY_LEFT;
2497 int right = joy & JOY_RIGHT;
2498 int up = joy & JOY_UP;
2499 int down = joy & JOY_DOWN;
2500 int button = joy & JOY_BUTTON;
2501 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2502 int dx = (left ? -1 : right ? 1 : 0);
2503 int dy = (up ? -1 : down ? 1 : 0);
2504 boolean use_delay_value_first = (joytest != joytest_last);
2506 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2508 // do not handle this button event anymore
2512 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2513 anyTextGadgetActive()))
2515 // leave name input in main menu or text input gadget
2516 HandleKey(KSYM_Escape, KEY_PRESSED);
2517 HandleKey(KSYM_Escape, KEY_RELEASED);
2522 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2524 if (game_status == GAME_MODE_PLAYING)
2526 // when playing MM style levels, also use delay for keyboard events
2527 joytest |= keyboard;
2529 // only use first delay value for new events, but not for changed events
2530 use_delay_value_first = (!joytest != !joytest_last);
2532 // only use delay after the initial keyboard event
2536 // for any joystick or keyboard event, enable playfield tile cursor
2537 if (dx || dy || button)
2538 SetTileCursorEnabled(TRUE);
2541 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2543 // delay joystick/keyboard actions if axes/keys continually pressed
2544 newbutton = dx = dy = 0;
2548 // first start with longer delay, then continue with shorter delay
2549 joytest_delay_value =
2550 (use_delay_value_first ? delay_value_first : delay_value);
2553 joytest_last = joytest;
2555 switch (game_status)
2557 case GAME_MODE_TITLE:
2558 case GAME_MODE_MAIN:
2559 case GAME_MODE_LEVELS:
2560 case GAME_MODE_LEVELNR:
2561 case GAME_MODE_SETUP:
2562 case GAME_MODE_INFO:
2563 case GAME_MODE_SCORES:
2565 if (anyTextGadgetActive())
2568 if (game_status == GAME_MODE_TITLE)
2569 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2570 else if (game_status == GAME_MODE_MAIN)
2571 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2572 else if (game_status == GAME_MODE_LEVELS)
2573 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2574 else if (game_status == GAME_MODE_LEVELNR)
2575 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2576 else if (game_status == GAME_MODE_SETUP)
2577 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2578 else if (game_status == GAME_MODE_INFO)
2579 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2580 else if (game_status == GAME_MODE_SCORES)
2581 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2586 case GAME_MODE_PLAYING:
2588 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2589 if (tape.playing || keyboard)
2590 newbutton = ((joy & JOY_BUTTON) != 0);
2593 if (newbutton && game.all_players_gone)
2600 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2602 if (joystick & JOY_ACTION)
2603 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2605 else if (tape.recording && tape.pausing && !tape.use_mouse)
2607 if (joystick & JOY_ACTION)
2608 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2611 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2612 HandleTileCursor(dx, dy, button);
2621 void HandleSpecialGameControllerButtons(Event *event)
2626 switch (event->type)
2628 case SDL_CONTROLLERBUTTONDOWN:
2629 key_status = KEY_PRESSED;
2632 case SDL_CONTROLLERBUTTONUP:
2633 key_status = KEY_RELEASED;
2640 switch (event->cbutton.button)
2642 case SDL_CONTROLLER_BUTTON_START:
2646 case SDL_CONTROLLER_BUTTON_BACK:
2654 HandleKey(key, key_status);
2657 void HandleSpecialGameControllerKeys(Key key, int key_status)
2659 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2660 int button = SDL_CONTROLLER_BUTTON_INVALID;
2662 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2663 if (key == KSYM_Rewind)
2664 button = SDL_CONTROLLER_BUTTON_A;
2665 else if (key == KSYM_FastForward || key == KSYM_Menu)
2666 button = SDL_CONTROLLER_BUTTON_B;
2668 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2672 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2673 SDL_CONTROLLERBUTTONUP);
2675 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2676 event.cbutton.button = button;
2677 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2680 HandleJoystickEvent(&event);
2685 boolean DoKeysymAction(int keysym)
2689 Key key = (Key)(-keysym);
2691 HandleKey(key, KEY_PRESSED);
2692 HandleKey(key, KEY_RELEASED);