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;
43 // forward declarations for internal use
44 static void HandleNoEvent(void);
45 static void HandleEventActions(void);
48 // event filter especially needed for SDL event filtering due to
49 // delay problems with lots of mouse motion events when mouse button
50 // not pressed (X11 can handle this with 'PointerMotionHintMask')
52 // event filter addition for SDL2: as SDL2 does not have a function to enable
53 // or disable keyboard auto-repeat, filter repeated keyboard events instead
55 static int FilterEvents(const Event *event)
59 // skip repeated key press events if keyboard auto-repeat is disabled
60 if (event->type == EVENT_KEYPRESS &&
65 if (event->type == EVENT_BUTTONPRESS ||
66 event->type == EVENT_BUTTONRELEASE)
68 ((ButtonEvent *)event)->x -= video.screen_xoffset;
69 ((ButtonEvent *)event)->y -= video.screen_yoffset;
71 else if (event->type == EVENT_MOTIONNOTIFY)
73 ((MotionEvent *)event)->x -= video.screen_xoffset;
74 ((MotionEvent *)event)->y -= video.screen_yoffset;
77 // non-motion events are directly passed to event handler functions
78 if (event->type != EVENT_MOTIONNOTIFY)
81 motion = (MotionEvent *)event;
82 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
83 motion->y >= SY && motion->y < SY + SYSIZE);
85 // do no reset mouse cursor before all pending events have been processed
86 if (gfx.cursor_mode == cursor_mode_last &&
87 ((game_status == GAME_MODE_TITLE &&
88 gfx.cursor_mode == CURSOR_NONE) ||
89 (game_status == GAME_MODE_PLAYING &&
90 gfx.cursor_mode == CURSOR_PLAYFIELD)))
92 SetMouseCursor(CURSOR_DEFAULT);
94 DelayReached(&special_cursor_delay, 0);
96 cursor_mode_last = CURSOR_DEFAULT;
99 // skip mouse motion events without pressed button outside level editor
100 if (button_status == MB_RELEASED &&
101 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
107 // to prevent delay problems, skip mouse motion events if the very next
108 // event is also a mouse motion event (and therefore effectively only
109 // handling the last of a row of mouse motion events in the event queue)
111 static boolean SkipPressedMouseMotionEvent(const Event *event)
113 // nothing to do if the current event is not a mouse motion event
114 if (event->type != EVENT_MOTIONNOTIFY)
117 // only skip motion events with pressed button outside the game
118 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
125 PeekEvent(&next_event);
127 // if next event is also a mouse motion event, skip the current one
128 if (next_event.type == EVENT_MOTIONNOTIFY)
135 static boolean WaitValidEvent(Event *event)
139 if (!FilterEvents(event))
142 if (SkipPressedMouseMotionEvent(event))
148 /* this is especially needed for event modifications for the Android target:
149 if mouse coordinates should be modified in the event filter function,
150 using a properly installed SDL event filter does not work, because in
151 the event filter, mouse coordinates in the event structure are still
152 physical pixel positions, not logical (scaled) screen positions, so this
153 has to be handled at a later stage in the event processing functions
154 (when device pixel positions are already converted to screen positions) */
156 boolean NextValidEvent(Event *event)
158 while (PendingEvent())
159 if (WaitValidEvent(event))
165 static void HandleEvents(void)
168 unsigned int event_frame_delay = 0;
169 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
171 ResetDelayCounter(&event_frame_delay);
173 while (NextValidEvent(&event))
177 case EVENT_BUTTONPRESS:
178 case EVENT_BUTTONRELEASE:
179 HandleButtonEvent((ButtonEvent *) &event);
182 case EVENT_MOTIONNOTIFY:
183 HandleMotionEvent((MotionEvent *) &event);
186 case EVENT_WHEELMOTION:
187 HandleWheelEvent((WheelEvent *) &event);
190 case SDL_WINDOWEVENT:
191 HandleWindowEvent((WindowEvent *) &event);
194 case EVENT_FINGERPRESS:
195 case EVENT_FINGERRELEASE:
196 case EVENT_FINGERMOTION:
197 HandleFingerEvent((FingerEvent *) &event);
200 case EVENT_TEXTINPUT:
201 HandleTextEvent((TextEvent *) &event);
204 case SDL_APP_WILLENTERBACKGROUND:
205 case SDL_APP_DIDENTERBACKGROUND:
206 case SDL_APP_WILLENTERFOREGROUND:
207 case SDL_APP_DIDENTERFOREGROUND:
208 HandlePauseResumeEvent((PauseResumeEvent *) &event);
212 case EVENT_KEYRELEASE:
213 HandleKeyEvent((KeyEvent *) &event);
217 HandleOtherEvents(&event);
221 // do not handle events for longer than standard frame delay period
222 if (DelayReached(&event_frame_delay, event_frame_delay_value))
227 void HandleOtherEvents(Event *event)
232 HandleExposeEvent((ExposeEvent *) event);
235 case EVENT_UNMAPNOTIFY:
237 // This causes the game to stop not only when iconified, but also
238 // when on another virtual desktop, which might be not desired.
239 SleepWhileUnmapped();
245 HandleFocusEvent((FocusChangeEvent *) event);
248 case EVENT_CLIENTMESSAGE:
249 HandleClientMessageEvent((ClientMessageEvent *) event);
252 case SDL_CONTROLLERBUTTONDOWN:
253 case SDL_CONTROLLERBUTTONUP:
254 // for any game controller button event, disable overlay buttons
255 SetOverlayEnabled(FALSE);
257 HandleSpecialGameControllerButtons(event);
260 case SDL_CONTROLLERDEVICEADDED:
261 case SDL_CONTROLLERDEVICEREMOVED:
262 case SDL_CONTROLLERAXISMOTION:
263 case SDL_JOYAXISMOTION:
264 case SDL_JOYBUTTONDOWN:
265 case SDL_JOYBUTTONUP:
266 HandleJoystickEvent(event);
270 HandleWindowManagerEvent(event);
278 static void HandleMouseCursor(void)
280 if (game_status == GAME_MODE_TITLE)
282 // when showing title screens, hide mouse pointer (if not moved)
284 if (gfx.cursor_mode != CURSOR_NONE &&
285 DelayReached(&special_cursor_delay, special_cursor_delay_value))
287 SetMouseCursor(CURSOR_NONE);
290 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
293 // when playing, display a special mouse pointer inside the playfield
295 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
296 cursor_inside_playfield &&
297 DelayReached(&special_cursor_delay, special_cursor_delay_value))
299 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
301 SetMouseCursor(CURSOR_PLAYFIELD);
304 else if (gfx.cursor_mode != CURSOR_DEFAULT)
306 SetMouseCursor(CURSOR_DEFAULT);
309 // this is set after all pending events have been processed
310 cursor_mode_last = gfx.cursor_mode;
322 // execute event related actions after pending events have been processed
323 HandleEventActions();
325 // don't use all CPU time when idle; the main loop while playing
326 // has its own synchronization and is CPU friendly, too
328 if (game_status == GAME_MODE_PLAYING)
331 // always copy backbuffer to visible screen for every video frame
334 // reset video frame delay to default (may change again while playing)
335 SetVideoFrameDelay(MenuFrameDelay);
337 if (game_status == GAME_MODE_QUIT)
342 void ClearAutoRepeatKeyEvents(void)
344 while (PendingEvent())
348 PeekEvent(&next_event);
350 // if event is repeated key press event, remove it from event queue
351 if (next_event.type == EVENT_KEYPRESS &&
352 next_event.key.repeat)
353 WaitEvent(&next_event);
359 void ClearEventQueue(void)
363 while (NextValidEvent(&event))
367 case EVENT_BUTTONRELEASE:
368 button_status = MB_RELEASED;
371 case EVENT_KEYRELEASE:
375 case SDL_CONTROLLERBUTTONUP:
376 HandleJoystickEvent(&event);
381 HandleOtherEvents(&event);
387 static void ClearPlayerMouseAction(void)
389 local_player->mouse_action.lx = 0;
390 local_player->mouse_action.ly = 0;
391 local_player->mouse_action.button = 0;
394 void ClearPlayerAction(void)
398 // simulate key release events for still pressed keys
399 key_joystick_mapping = 0;
400 for (i = 0; i < MAX_PLAYERS; i++)
401 stored_player[i].action = 0;
403 ClearJoystickState();
404 ClearPlayerMouseAction();
407 static void SetPlayerMouseAction(int mx, int my, int button)
409 int lx = getLevelFromScreenX(mx);
410 int ly = getLevelFromScreenY(my);
411 int new_button = (!local_player->mouse_action.button && button);
413 if (local_player->mouse_action.button_hint)
414 button = local_player->mouse_action.button_hint;
416 ClearPlayerMouseAction();
418 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
421 local_player->mouse_action.lx = lx;
422 local_player->mouse_action.ly = ly;
423 local_player->mouse_action.button = button;
425 if (tape.recording && tape.pausing && tape.use_mouse)
427 // un-pause a paused game only if mouse button was newly pressed down
429 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
432 SetTileCursorXY(lx, ly);
435 void SleepWhileUnmapped(void)
437 boolean window_unmapped = TRUE;
439 KeyboardAutoRepeatOn();
441 while (window_unmapped)
445 if (!WaitValidEvent(&event))
450 case EVENT_BUTTONRELEASE:
451 button_status = MB_RELEASED;
454 case EVENT_KEYRELEASE:
455 key_joystick_mapping = 0;
458 case SDL_CONTROLLERBUTTONUP:
459 HandleJoystickEvent(&event);
460 key_joystick_mapping = 0;
463 case EVENT_MAPNOTIFY:
464 window_unmapped = FALSE;
467 case EVENT_UNMAPNOTIFY:
468 // this is only to surely prevent the 'should not happen' case
469 // of recursively looping between 'SleepWhileUnmapped()' and
470 // 'HandleOtherEvents()' which usually calls this funtion.
474 HandleOtherEvents(&event);
479 if (game_status == GAME_MODE_PLAYING)
480 KeyboardAutoRepeatOffUnlessAutoplay();
483 void HandleExposeEvent(ExposeEvent *event)
487 void HandleButtonEvent(ButtonEvent *event)
489 #if DEBUG_EVENTS_BUTTON
490 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
492 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
496 // for any mouse button event, disable playfield tile cursor
497 SetTileCursorEnabled(FALSE);
499 #if defined(HAS_SCREEN_KEYBOARD)
500 if (video.shifted_up)
501 event->y += video.shifted_up_pos;
504 motion_status = FALSE;
506 if (event->type == EVENT_BUTTONPRESS)
507 button_status = event->button;
509 button_status = MB_RELEASED;
511 HandleButton(event->x, event->y, button_status, event->button);
514 void HandleMotionEvent(MotionEvent *event)
516 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
519 motion_status = TRUE;
521 #if DEBUG_EVENTS_MOTION
522 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
523 button_status, event->x, event->y);
526 HandleButton(event->x, event->y, button_status, button_status);
529 void HandleWheelEvent(WheelEvent *event)
533 #if DEBUG_EVENTS_WHEEL
535 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
536 event->which, event->x, event->y);
538 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
539 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
540 event->which, event->x, event->y,
541 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
542 "SDL_MOUSEWHEEL_FLIPPED"));
546 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
547 event->x > 0 ? MB_WHEEL_RIGHT :
548 event->y < 0 ? MB_WHEEL_DOWN :
549 event->y > 0 ? MB_WHEEL_UP : 0);
551 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
552 // accelerated mouse wheel available on Mac and Windows
553 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
555 // no accelerated mouse wheel available on Unix/Linux
556 wheel_steps = DEFAULT_WHEEL_STEPS;
559 motion_status = FALSE;
561 button_status = button_nr;
562 HandleButton(0, 0, button_status, -button_nr);
564 button_status = MB_RELEASED;
565 HandleButton(0, 0, button_status, -button_nr);
568 void HandleWindowEvent(WindowEvent *event)
570 #if DEBUG_EVENTS_WINDOW
571 int subtype = event->event;
574 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
575 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
576 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
577 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
578 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
579 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
580 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
581 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
582 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
583 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
584 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
585 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
586 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
587 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
590 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
591 event_name, event->data1, event->data2);
595 // (not needed, as the screen gets redrawn every 20 ms anyway)
596 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
597 event->event == SDL_WINDOWEVENT_RESIZED ||
598 event->event == SDL_WINDOWEVENT_EXPOSED)
602 if (event->event == SDL_WINDOWEVENT_RESIZED)
604 if (!video.fullscreen_enabled)
606 int new_window_width = event->data1;
607 int new_window_height = event->data2;
609 // if window size has changed after resizing, calculate new scaling factor
610 if (new_window_width != video.window_width ||
611 new_window_height != video.window_height)
613 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
614 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
616 // (extreme window scaling allowed, but cannot be saved permanently)
617 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
618 setup.window_scaling_percent =
619 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
620 MAX_WINDOW_SCALING_PERCENT);
622 video.window_width = new_window_width;
623 video.window_height = new_window_height;
625 if (game_status == GAME_MODE_SETUP)
626 RedrawSetupScreenAfterFullscreenToggle();
631 #if defined(PLATFORM_ANDROID)
634 int new_display_width = event->data1;
635 int new_display_height = event->data2;
637 // if fullscreen display size has changed, device has been rotated
638 if (new_display_width != video.display_width ||
639 new_display_height != video.display_height)
641 int nr = GRID_ACTIVE_NR(); // previous screen orientation
643 video.display_width = new_display_width;
644 video.display_height = new_display_height;
646 SDLSetScreenProperties();
648 // check if screen orientation has changed (should always be true here)
649 if (nr != GRID_ACTIVE_NR())
653 if (game_status == GAME_MODE_SETUP)
654 RedrawSetupScreenAfterScreenRotation(nr);
656 nr = GRID_ACTIVE_NR();
658 overlay.grid_xsize = setup.touch.grid_xsize[nr];
659 overlay.grid_ysize = setup.touch.grid_ysize[nr];
661 for (x = 0; x < MAX_GRID_XSIZE; x++)
662 for (y = 0; y < MAX_GRID_YSIZE; y++)
663 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
671 #define NUM_TOUCH_FINGERS 3
676 SDL_FingerID finger_id;
679 } touch_info[NUM_TOUCH_FINGERS];
681 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
684 int x = event->x * overlay.grid_xsize;
685 int y = event->y * overlay.grid_ysize;
686 int grid_button = overlay.grid_button[x][y];
687 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
688 Key key = (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
689 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
690 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
691 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
692 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
693 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
696 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
697 float event_x = (event->x);
698 float event_y = (event->y - ypos) / (1 - ypos);
699 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
700 event_y > 2.0 / 3.0 && event_y < 1 ?
701 setup.input[0].key.snap :
702 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
703 event_y > 2.0 / 3.0 && event_y < 1 ?
704 setup.input[0].key.drop :
705 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
706 event_y > 0 && event_y < 1.0 / 3.0 ?
707 setup.input[0].key.up :
708 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
709 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
710 setup.input[0].key.left :
711 event_x > 8.0 / 9.0 && event_x < 1 &&
712 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
713 setup.input[0].key.right :
714 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
715 event_y > 2.0 / 3.0 && event_y < 1 ?
716 setup.input[0].key.down :
719 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
721 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
725 // for any touch input event, enable overlay buttons (if activated)
726 SetOverlayEnabled(TRUE);
728 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
729 getKeyNameFromKey(key), key_status_name, event->fingerId);
731 if (key_status == KEY_PRESSED)
732 overlay.grid_button_action |= grid_button_action;
734 overlay.grid_button_action &= ~grid_button_action;
736 // check if we already know this touch event's finger id
737 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
739 if (touch_info[i].touched &&
740 touch_info[i].finger_id == event->fingerId)
742 // Error(ERR_DEBUG, "MARK 1: %d", i);
748 if (i >= NUM_TOUCH_FINGERS)
750 if (key_status == KEY_PRESSED)
752 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
754 // unknown finger id -- get new, empty slot, if available
755 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
757 if (touch_info[i].counter < oldest_counter)
760 oldest_counter = touch_info[i].counter;
762 // Error(ERR_DEBUG, "MARK 2: %d", i);
765 if (!touch_info[i].touched)
767 // Error(ERR_DEBUG, "MARK 3: %d", i);
773 if (i >= NUM_TOUCH_FINGERS)
775 // all slots allocated -- use oldest slot
778 // Error(ERR_DEBUG, "MARK 4: %d", i);
783 // release of previously unknown key (should not happen)
785 if (key != KSYM_UNDEFINED)
787 HandleKey(key, KEY_RELEASED);
789 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
790 getKeyNameFromKey(key), "KEY_RELEASED", i);
795 if (i < NUM_TOUCH_FINGERS)
797 if (key_status == KEY_PRESSED)
799 if (touch_info[i].key != key)
801 if (touch_info[i].key != KSYM_UNDEFINED)
803 HandleKey(touch_info[i].key, KEY_RELEASED);
805 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
806 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
809 if (key != KSYM_UNDEFINED)
811 HandleKey(key, KEY_PRESSED);
813 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
814 getKeyNameFromKey(key), "KEY_PRESSED", i);
818 touch_info[i].touched = TRUE;
819 touch_info[i].finger_id = event->fingerId;
820 touch_info[i].counter = Counter();
821 touch_info[i].key = key;
825 if (touch_info[i].key != KSYM_UNDEFINED)
827 HandleKey(touch_info[i].key, KEY_RELEASED);
829 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
830 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
833 touch_info[i].touched = FALSE;
834 touch_info[i].finger_id = 0;
835 touch_info[i].counter = 0;
836 touch_info[i].key = 0;
841 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
843 static Key motion_key_x = KSYM_UNDEFINED;
844 static Key motion_key_y = KSYM_UNDEFINED;
845 static Key button_key = KSYM_UNDEFINED;
846 static float motion_x1, motion_y1;
847 static float button_x1, button_y1;
848 static SDL_FingerID motion_id = -1;
849 static SDL_FingerID button_id = -1;
850 int move_trigger_distance_percent = setup.touch.move_distance;
851 int drop_trigger_distance_percent = setup.touch.drop_distance;
852 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
853 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
854 float event_x = event->x;
855 float event_y = event->y;
857 if (event->type == EVENT_FINGERPRESS)
859 if (event_x > 1.0 / 3.0)
863 motion_id = event->fingerId;
868 motion_key_x = KSYM_UNDEFINED;
869 motion_key_y = KSYM_UNDEFINED;
871 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
877 button_id = event->fingerId;
882 button_key = setup.input[0].key.snap;
884 HandleKey(button_key, KEY_PRESSED);
886 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
889 else if (event->type == EVENT_FINGERRELEASE)
891 if (event->fingerId == motion_id)
895 if (motion_key_x != KSYM_UNDEFINED)
896 HandleKey(motion_key_x, KEY_RELEASED);
897 if (motion_key_y != KSYM_UNDEFINED)
898 HandleKey(motion_key_y, KEY_RELEASED);
900 motion_key_x = KSYM_UNDEFINED;
901 motion_key_y = KSYM_UNDEFINED;
903 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
905 else if (event->fingerId == button_id)
909 if (button_key != KSYM_UNDEFINED)
910 HandleKey(button_key, KEY_RELEASED);
912 button_key = KSYM_UNDEFINED;
914 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
917 else if (event->type == EVENT_FINGERMOTION)
919 if (event->fingerId == motion_id)
921 float distance_x = ABS(event_x - motion_x1);
922 float distance_y = ABS(event_y - motion_y1);
923 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
924 event_x > motion_x1 ? setup.input[0].key.right :
926 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
927 event_y > motion_y1 ? setup.input[0].key.down :
930 if (distance_x < move_trigger_distance / 2 ||
931 distance_x < distance_y)
932 new_motion_key_x = KSYM_UNDEFINED;
934 if (distance_y < move_trigger_distance / 2 ||
935 distance_y < distance_x)
936 new_motion_key_y = KSYM_UNDEFINED;
938 if (distance_x > move_trigger_distance ||
939 distance_y > move_trigger_distance)
941 if (new_motion_key_x != motion_key_x)
943 if (motion_key_x != KSYM_UNDEFINED)
944 HandleKey(motion_key_x, KEY_RELEASED);
945 if (new_motion_key_x != KSYM_UNDEFINED)
946 HandleKey(new_motion_key_x, KEY_PRESSED);
949 if (new_motion_key_y != motion_key_y)
951 if (motion_key_y != KSYM_UNDEFINED)
952 HandleKey(motion_key_y, KEY_RELEASED);
953 if (new_motion_key_y != KSYM_UNDEFINED)
954 HandleKey(new_motion_key_y, KEY_PRESSED);
960 motion_key_x = new_motion_key_x;
961 motion_key_y = new_motion_key_y;
963 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
966 else if (event->fingerId == button_id)
968 float distance_x = ABS(event_x - button_x1);
969 float distance_y = ABS(event_y - button_y1);
971 if (distance_x < drop_trigger_distance / 2 &&
972 distance_y > drop_trigger_distance)
974 if (button_key == setup.input[0].key.snap)
975 HandleKey(button_key, KEY_RELEASED);
980 button_key = setup.input[0].key.drop;
982 HandleKey(button_key, KEY_PRESSED);
984 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
990 void HandleFingerEvent(FingerEvent *event)
992 #if DEBUG_EVENTS_FINGER
993 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
994 event->type == EVENT_FINGERPRESS ? "pressed" :
995 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
999 event->dx, event->dy,
1003 if (game_status != GAME_MODE_PLAYING)
1006 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1008 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1009 local_player->mouse_action.button_hint =
1010 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1011 event->x < 0.5 ? MB_LEFTBUTTON :
1012 event->x > 0.5 ? MB_RIGHTBUTTON :
1018 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1019 HandleFingerEvent_VirtualButtons(event);
1020 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1021 HandleFingerEvent_WipeGestures(event);
1024 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1026 static int old_mx = 0, old_my = 0;
1027 static int last_button = MB_LEFTBUTTON;
1028 static boolean touched = FALSE;
1029 static boolean tapped = FALSE;
1031 // screen tile was tapped (but finger not touching the screen anymore)
1032 // (this point will also be reached without receiving a touch event)
1033 if (tapped && !touched)
1035 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1040 // stop here if this function was not triggered by a touch event
1044 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1046 // finger started touching the screen
1056 ClearPlayerMouseAction();
1058 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1061 else if (button == MB_RELEASED && touched)
1063 // finger stopped touching the screen
1068 SetPlayerMouseAction(old_mx, old_my, last_button);
1070 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1072 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1077 // finger moved while touching the screen
1079 int old_x = getLevelFromScreenX(old_mx);
1080 int old_y = getLevelFromScreenY(old_my);
1081 int new_x = getLevelFromScreenX(mx);
1082 int new_y = getLevelFromScreenY(my);
1084 if (new_x != old_x || new_y != old_y)
1089 // finger moved left or right from (horizontal) starting position
1091 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1093 SetPlayerMouseAction(old_mx, old_my, button_nr);
1095 last_button = button_nr;
1097 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1101 // finger stays at or returned to (horizontal) starting position
1103 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1105 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1110 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1112 static int old_mx = 0, old_my = 0;
1113 static int last_button = MB_LEFTBUTTON;
1114 static boolean touched = FALSE;
1115 static boolean tapped = FALSE;
1117 // screen tile was tapped (but finger not touching the screen anymore)
1118 // (this point will also be reached without receiving a touch event)
1119 if (tapped && !touched)
1121 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1126 // stop here if this function was not triggered by a touch event
1130 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1132 // finger started touching the screen
1142 ClearPlayerMouseAction();
1144 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1147 else if (button == MB_RELEASED && touched)
1149 // finger stopped touching the screen
1154 SetPlayerMouseAction(old_mx, old_my, last_button);
1156 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1158 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1163 // finger moved while touching the screen
1165 int old_x = getLevelFromScreenX(old_mx);
1166 int old_y = getLevelFromScreenY(old_my);
1167 int new_x = getLevelFromScreenX(mx);
1168 int new_y = getLevelFromScreenY(my);
1170 if (new_x != old_x || new_y != old_y)
1172 // finger moved away from starting position
1174 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1176 // quickly alternate between clicking and releasing for maximum speed
1177 if (FrameCounter % 2 == 0)
1178 button_nr = MB_RELEASED;
1180 SetPlayerMouseAction(old_mx, old_my, button_nr);
1183 last_button = button_nr;
1187 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1191 // finger stays at or returned to starting position
1193 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1195 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1200 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1202 static int old_mx = 0, old_my = 0;
1203 static Key motion_key_x = KSYM_UNDEFINED;
1204 static Key motion_key_y = KSYM_UNDEFINED;
1205 static boolean touched = FALSE;
1206 static boolean started_on_player = FALSE;
1207 static boolean player_is_dropping = FALSE;
1208 static int player_drop_count = 0;
1209 static int last_player_x = -1;
1210 static int last_player_y = -1;
1212 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1221 started_on_player = FALSE;
1222 player_is_dropping = FALSE;
1223 player_drop_count = 0;
1227 motion_key_x = KSYM_UNDEFINED;
1228 motion_key_y = KSYM_UNDEFINED;
1230 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1233 else if (button == MB_RELEASED && touched)
1240 if (motion_key_x != KSYM_UNDEFINED)
1241 HandleKey(motion_key_x, KEY_RELEASED);
1242 if (motion_key_y != KSYM_UNDEFINED)
1243 HandleKey(motion_key_y, KEY_RELEASED);
1245 if (started_on_player)
1247 if (player_is_dropping)
1249 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1251 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1255 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1257 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1261 motion_key_x = KSYM_UNDEFINED;
1262 motion_key_y = KSYM_UNDEFINED;
1264 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1269 int src_x = local_player->jx;
1270 int src_y = local_player->jy;
1271 int dst_x = getLevelFromScreenX(old_mx);
1272 int dst_y = getLevelFromScreenY(old_my);
1273 int dx = dst_x - src_x;
1274 int dy = dst_y - src_y;
1275 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1276 dx > 0 ? setup.input[0].key.right :
1278 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1279 dy > 0 ? setup.input[0].key.down :
1282 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1283 (last_player_x != local_player->jx ||
1284 last_player_y != local_player->jy))
1286 // in case of asymmetric diagonal movement, use "preferred" direction
1288 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1290 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1291 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1293 local_player->last_move_dir = last_move_dir;
1295 // (required to prevent accidentally forcing direction for next movement)
1296 last_player_x = local_player->jx;
1297 last_player_y = local_player->jy;
1300 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1302 started_on_player = TRUE;
1303 player_drop_count = getPlayerInventorySize(0);
1304 player_is_dropping = (player_drop_count > 0);
1306 if (player_is_dropping)
1308 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1310 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1314 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1316 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1319 else if (dx != 0 || dy != 0)
1321 if (player_is_dropping &&
1322 player_drop_count == getPlayerInventorySize(0))
1324 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1326 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1327 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1329 player_is_dropping = FALSE;
1333 if (new_motion_key_x != motion_key_x)
1335 Error(ERR_DEBUG, "---------- %s %s ----------",
1336 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1337 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1339 if (motion_key_x != KSYM_UNDEFINED)
1340 HandleKey(motion_key_x, KEY_RELEASED);
1341 if (new_motion_key_x != KSYM_UNDEFINED)
1342 HandleKey(new_motion_key_x, KEY_PRESSED);
1345 if (new_motion_key_y != motion_key_y)
1347 Error(ERR_DEBUG, "---------- %s %s ----------",
1348 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1349 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1351 if (motion_key_y != KSYM_UNDEFINED)
1352 HandleKey(motion_key_y, KEY_RELEASED);
1353 if (new_motion_key_y != KSYM_UNDEFINED)
1354 HandleKey(new_motion_key_y, KEY_PRESSED);
1357 motion_key_x = new_motion_key_x;
1358 motion_key_y = new_motion_key_y;
1362 static void HandleButtonOrFinger(int mx, int my, int button)
1364 if (game_status != GAME_MODE_PLAYING)
1367 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1369 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1370 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1371 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1372 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1373 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1374 SetPlayerMouseAction(mx, my, button); // special case
1378 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1379 HandleButtonOrFinger_FollowFinger(mx, my, button);
1383 static boolean checkTextInputKeyModState(void)
1385 // when playing, only handle raw key events and ignore text input
1386 if (game_status == GAME_MODE_PLAYING)
1389 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1392 void HandleTextEvent(TextEvent *event)
1394 char *text = event->text;
1395 Key key = getKeyFromKeyName(text);
1397 #if DEBUG_EVENTS_TEXT
1398 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1401 text[0], (int)(text[0]),
1403 getKeyNameFromKey(key),
1407 #if !defined(HAS_SCREEN_KEYBOARD)
1408 // non-mobile devices: only handle key input with modifier keys pressed here
1409 // (every other key input is handled directly as physical key input event)
1410 if (!checkTextInputKeyModState())
1414 // process text input as "classic" (with uppercase etc.) key input event
1415 HandleKey(key, KEY_PRESSED);
1416 HandleKey(key, KEY_RELEASED);
1419 void HandlePauseResumeEvent(PauseResumeEvent *event)
1421 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1425 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1431 void HandleKeyEvent(KeyEvent *event)
1433 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1434 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1435 Key key = GetEventKey(event, with_modifiers);
1436 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1438 #if DEBUG_EVENTS_KEY
1439 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1440 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1441 event->keysym.scancode,
1446 getKeyNameFromKey(key));
1449 #if defined(PLATFORM_ANDROID)
1450 if (key == KSYM_Back)
1452 // always map the "back" button to the "escape" key on Android devices
1457 // for any key event other than "back" button, disable overlay buttons
1458 SetOverlayEnabled(FALSE);
1462 HandleKeyModState(keymod, key_status);
1464 // only handle raw key input without text modifier keys pressed
1465 if (!checkTextInputKeyModState())
1466 HandleKey(key, key_status);
1469 void HandleFocusEvent(FocusChangeEvent *event)
1471 static int old_joystick_status = -1;
1473 if (event->type == EVENT_FOCUSOUT)
1475 KeyboardAutoRepeatOn();
1476 old_joystick_status = joystick.status;
1477 joystick.status = JOYSTICK_NOT_AVAILABLE;
1479 ClearPlayerAction();
1481 else if (event->type == EVENT_FOCUSIN)
1483 /* When there are two Rocks'n'Diamonds windows which overlap and
1484 the player moves the pointer from one game window to the other,
1485 a 'FocusOut' event is generated for the window the pointer is
1486 leaving and a 'FocusIn' event is generated for the window the
1487 pointer is entering. In some cases, it can happen that the
1488 'FocusIn' event is handled by the one game process before the
1489 'FocusOut' event by the other game process. In this case the
1490 X11 environment would end up with activated keyboard auto repeat,
1491 because unfortunately this is a global setting and not (which
1492 would be far better) set for each X11 window individually.
1493 The effect would be keyboard auto repeat while playing the game
1494 (game_status == GAME_MODE_PLAYING), which is not desired.
1495 To avoid this special case, we just wait 1/10 second before
1496 processing the 'FocusIn' event. */
1498 if (game_status == GAME_MODE_PLAYING)
1501 KeyboardAutoRepeatOffUnlessAutoplay();
1504 if (old_joystick_status != -1)
1505 joystick.status = old_joystick_status;
1509 void HandleClientMessageEvent(ClientMessageEvent *event)
1511 if (CheckCloseWindowEvent(event))
1515 void HandleWindowManagerEvent(Event *event)
1517 SDLHandleWindowManagerEvent(event);
1520 void HandleButton(int mx, int my, int button, int button_nr)
1522 static int old_mx = 0, old_my = 0;
1523 boolean button_hold = FALSE;
1524 boolean handle_gadgets = TRUE;
1530 button_nr = -button_nr;
1539 #if defined(PLATFORM_ANDROID)
1540 // when playing, only handle gadgets when using "follow finger" controls
1541 // or when using touch controls in combination with the MM game engine
1543 (game_status != GAME_MODE_PLAYING ||
1544 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1545 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1548 if (HandleGlobalAnimClicks(mx, my, button))
1550 // do not handle this button event anymore
1551 return; // force mouse event not to be handled at all
1554 if (handle_gadgets && HandleGadgets(mx, my, button))
1556 // do not handle this button event anymore
1557 mx = my = -32; // force mouse event to be outside screen tiles
1560 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1563 // do not use scroll wheel button events for anything other than gadgets
1564 if (IS_WHEEL_BUTTON(button_nr))
1567 switch (game_status)
1569 case GAME_MODE_TITLE:
1570 HandleTitleScreen(mx, my, 0, 0, button);
1573 case GAME_MODE_MAIN:
1574 HandleMainMenu(mx, my, 0, 0, button);
1577 case GAME_MODE_PSEUDO_TYPENAME:
1578 HandleTypeName(0, KSYM_Return);
1581 case GAME_MODE_LEVELS:
1582 HandleChooseLevelSet(mx, my, 0, 0, button);
1585 case GAME_MODE_LEVELNR:
1586 HandleChooseLevelNr(mx, my, 0, 0, button);
1589 case GAME_MODE_SCORES:
1590 HandleHallOfFame(0, 0, 0, 0, button);
1593 case GAME_MODE_EDITOR:
1594 HandleLevelEditorIdle();
1597 case GAME_MODE_INFO:
1598 HandleInfoScreen(mx, my, 0, 0, button);
1601 case GAME_MODE_SETUP:
1602 HandleSetupScreen(mx, my, 0, 0, button);
1605 case GAME_MODE_PLAYING:
1606 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1607 HandleButtonOrFinger(mx, my, button);
1609 SetPlayerMouseAction(mx, my, button);
1612 if (button == MB_PRESSED && !motion_status && !button_hold &&
1613 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1614 DumpTileFromScreen(mx, my);
1624 static boolean is_string_suffix(char *string, char *suffix)
1626 int string_len = strlen(string);
1627 int suffix_len = strlen(suffix);
1629 if (suffix_len > string_len)
1632 return (strEqual(&string[string_len - suffix_len], suffix));
1635 #define MAX_CHEAT_INPUT_LEN 32
1637 static void HandleKeysSpecial(Key key)
1639 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1640 char letter = getCharFromKey(key);
1641 int cheat_input_len = strlen(cheat_input);
1647 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1649 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1650 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1652 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1655 cheat_input[cheat_input_len++] = letter;
1656 cheat_input[cheat_input_len] = '\0';
1658 #if DEBUG_EVENTS_KEY
1659 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1662 if (game_status == GAME_MODE_MAIN)
1664 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1665 is_string_suffix(cheat_input, ":ist"))
1667 InsertSolutionTape();
1669 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1670 is_string_suffix(cheat_input, ":pst"))
1674 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1675 is_string_suffix(cheat_input, ":rg"))
1677 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1680 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1681 is_string_suffix(cheat_input, ":rs"))
1683 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1686 else if (is_string_suffix(cheat_input, ":reload-music") ||
1687 is_string_suffix(cheat_input, ":rm"))
1689 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1692 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1693 is_string_suffix(cheat_input, ":ra"))
1695 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1696 1 << ARTWORK_TYPE_SOUNDS |
1697 1 << ARTWORK_TYPE_MUSIC);
1700 else if (is_string_suffix(cheat_input, ":dump-level") ||
1701 is_string_suffix(cheat_input, ":dl"))
1705 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1706 is_string_suffix(cheat_input, ":dt"))
1710 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1711 is_string_suffix(cheat_input, ":ft"))
1713 /* fix single-player tapes that contain player input for more than one
1714 player (due to a bug in 3.3.1.2 and earlier versions), which results
1715 in playing levels with more than one player in multi-player mode,
1716 even though the tape was originally recorded in single-player mode */
1718 // remove player input actions for all players but the first one
1719 for (i = 1; i < MAX_PLAYERS; i++)
1720 tape.player_participates[i] = FALSE;
1722 tape.changed = TRUE;
1724 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1725 is_string_suffix(cheat_input, ":snl"))
1727 SaveNativeLevel(&level);
1729 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1730 is_string_suffix(cheat_input, ":fps"))
1732 global.show_frames_per_second = !global.show_frames_per_second;
1735 else if (game_status == GAME_MODE_PLAYING)
1738 if (is_string_suffix(cheat_input, ".q"))
1739 DEBUG_SetMaximumDynamite();
1742 else if (game_status == GAME_MODE_EDITOR)
1744 if (is_string_suffix(cheat_input, ":dump-brush") ||
1745 is_string_suffix(cheat_input, ":DB"))
1749 else if (is_string_suffix(cheat_input, ":DDB"))
1754 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1756 if (letter == 'x') // copy brush to clipboard (small size)
1758 CopyBrushToClipboard_Small();
1760 else if (letter == 'c') // copy brush to clipboard (normal size)
1762 CopyBrushToClipboard();
1764 else if (letter == 'v') // paste brush from Clipboard
1766 CopyClipboardToBrush();
1771 // special key shortcuts for all game modes
1772 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1773 is_string_suffix(cheat_input, ":dea") ||
1774 is_string_suffix(cheat_input, ":DEA"))
1776 DumpGadgetIdentifiers();
1777 DumpScreenIdentifiers();
1781 void HandleKeysDebug(Key key)
1786 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1788 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1790 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1792 if (key == setup.debug.frame_delay_key[i] &&
1793 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1795 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1796 setup.debug.frame_delay[i] : setup.game_frame_delay);
1798 if (!setup.debug.frame_delay_game_only)
1799 MenuFrameDelay = GameFrameDelay;
1801 SetVideoFrameDelay(GameFrameDelay);
1803 if (GameFrameDelay > ONE_SECOND_DELAY)
1804 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1805 else if (GameFrameDelay != 0)
1806 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1807 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1808 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1810 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1817 if (game_status == GAME_MODE_PLAYING)
1821 options.debug = !options.debug;
1823 Error(ERR_DEBUG, "debug mode %s",
1824 (options.debug ? "enabled" : "disabled"));
1826 else if (key == KSYM_v)
1828 Error(ERR_DEBUG, "currently using game engine version %d",
1829 game.engine_version);
1835 void HandleKey(Key key, int key_status)
1837 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1838 static boolean ignore_repeated_key = FALSE;
1839 static struct SetupKeyboardInfo ski;
1840 static struct SetupShortcutInfo ssi;
1849 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1850 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1851 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1852 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1853 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1854 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1859 // map special keys (media keys / remote control buttons) to default keys
1860 if (key == KSYM_PlayPause)
1862 else if (key == KSYM_Select)
1865 HandleSpecialGameControllerKeys(key, key_status);
1867 if (game_status == GAME_MODE_PLAYING)
1869 // only needed for single-step tape recording mode
1870 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1873 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1875 byte key_action = 0;
1877 if (setup.input[pnr].use_joystick)
1880 ski = setup.input[pnr].key;
1882 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1883 if (key == *key_info[i].key_custom)
1884 key_action |= key_info[i].action;
1886 // use combined snap+direction keys for the first player only
1889 ssi = setup.shortcut;
1891 for (i = 0; i < NUM_DIRECTIONS; i++)
1892 if (key == *key_info[i].key_snap)
1893 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1896 if (key_status == KEY_PRESSED)
1897 stored_player[pnr].action |= key_action;
1899 stored_player[pnr].action &= ~key_action;
1901 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
1903 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1905 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1907 // if snap key already pressed, keep pause mode when releasing
1908 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1909 has_snapped[pnr] = TRUE;
1911 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1913 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1915 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1916 getRedDiskReleaseFlag_SP() == 0)
1918 // add a single inactive frame before dropping starts
1919 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1920 stored_player[pnr].force_dropping = TRUE;
1923 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1925 // if snap key was pressed without direction, leave pause mode
1926 if (!has_snapped[pnr])
1927 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1929 has_snapped[pnr] = FALSE;
1932 else if (tape.recording && tape.pausing && !tape.use_mouse)
1934 // prevent key release events from un-pausing a paused game
1935 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1936 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1939 // for MM style levels, handle in-game keyboard input in HandleJoystick()
1940 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1946 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1947 if (key == key_info[i].key_default)
1948 joy |= key_info[i].action;
1953 if (key_status == KEY_PRESSED)
1954 key_joystick_mapping |= joy;
1956 key_joystick_mapping &= ~joy;
1961 if (game_status != GAME_MODE_PLAYING)
1962 key_joystick_mapping = 0;
1964 if (key_status == KEY_RELEASED)
1966 // reset flag to ignore repeated "key pressed" events after key release
1967 ignore_repeated_key = FALSE;
1972 if ((key == KSYM_F11 ||
1973 ((key == KSYM_Return ||
1974 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1975 video.fullscreen_available &&
1976 !ignore_repeated_key)
1978 setup.fullscreen = !setup.fullscreen;
1980 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1982 if (game_status == GAME_MODE_SETUP)
1983 RedrawSetupScreenAfterFullscreenToggle();
1985 // set flag to ignore repeated "key pressed" events
1986 ignore_repeated_key = TRUE;
1991 if ((key == KSYM_0 || key == KSYM_KP_0 ||
1992 key == KSYM_minus || key == KSYM_KP_Subtract ||
1993 key == KSYM_plus || key == KSYM_KP_Add ||
1994 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
1995 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1996 video.window_scaling_available &&
1997 !video.fullscreen_enabled)
1999 if (key == KSYM_0 || key == KSYM_KP_0)
2000 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2001 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2002 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2004 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2006 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2007 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2008 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2009 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2011 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2013 if (game_status == GAME_MODE_SETUP)
2014 RedrawSetupScreenAfterFullscreenToggle();
2019 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2020 key == KSYM_Return ||
2021 key == KSYM_Escape)))
2023 // do not handle this key event anymore
2024 if (key != KSYM_Escape) // always allow ESC key to be handled
2028 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2029 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2036 if (game_status == GAME_MODE_MAIN &&
2037 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2039 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2044 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2046 if (key == setup.shortcut.save_game)
2048 else if (key == setup.shortcut.load_game)
2050 else if (key == setup.shortcut.toggle_pause)
2051 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2053 HandleTapeButtonKeys(key);
2054 HandleSoundButtonKeys(key);
2057 if (game_status == GAME_MODE_PLAYING && !network_playing)
2059 int centered_player_nr_next = -999;
2061 if (key == setup.shortcut.focus_player_all)
2062 centered_player_nr_next = -1;
2064 for (i = 0; i < MAX_PLAYERS; i++)
2065 if (key == setup.shortcut.focus_player[i])
2066 centered_player_nr_next = i;
2068 if (centered_player_nr_next != -999)
2070 game.centered_player_nr_next = centered_player_nr_next;
2071 game.set_centered_player = TRUE;
2075 tape.centered_player_nr_next = game.centered_player_nr_next;
2076 tape.set_centered_player = TRUE;
2081 HandleKeysSpecial(key);
2083 if (HandleGadgetsKeyInput(key))
2085 if (key != KSYM_Escape) // always allow ESC key to be handled
2086 key = KSYM_UNDEFINED;
2089 switch (game_status)
2091 case GAME_MODE_PSEUDO_TYPENAME:
2092 HandleTypeName(0, key);
2095 case GAME_MODE_TITLE:
2096 case GAME_MODE_MAIN:
2097 case GAME_MODE_LEVELS:
2098 case GAME_MODE_LEVELNR:
2099 case GAME_MODE_SETUP:
2100 case GAME_MODE_INFO:
2101 case GAME_MODE_SCORES:
2103 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2110 if (game_status == GAME_MODE_TITLE)
2111 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2112 else if (game_status == GAME_MODE_MAIN)
2113 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2114 else if (game_status == GAME_MODE_LEVELS)
2115 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2116 else if (game_status == GAME_MODE_LEVELNR)
2117 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2118 else if (game_status == GAME_MODE_SETUP)
2119 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2120 else if (game_status == GAME_MODE_INFO)
2121 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2122 else if (game_status == GAME_MODE_SCORES)
2123 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2127 if (game_status != GAME_MODE_MAIN)
2128 FadeSkipNextFadeIn();
2130 if (game_status == GAME_MODE_TITLE)
2131 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2132 else if (game_status == GAME_MODE_LEVELS)
2133 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2134 else if (game_status == GAME_MODE_LEVELNR)
2135 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2136 else if (game_status == GAME_MODE_SETUP)
2137 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2138 else if (game_status == GAME_MODE_INFO)
2139 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2140 else if (game_status == GAME_MODE_SCORES)
2141 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2145 if (game_status == GAME_MODE_LEVELS)
2146 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2147 else if (game_status == GAME_MODE_LEVELNR)
2148 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2149 else if (game_status == GAME_MODE_SETUP)
2150 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2151 else if (game_status == GAME_MODE_INFO)
2152 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2153 else if (game_status == GAME_MODE_SCORES)
2154 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2157 case KSYM_Page_Down:
2158 if (game_status == GAME_MODE_LEVELS)
2159 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2160 else if (game_status == GAME_MODE_LEVELNR)
2161 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2162 else if (game_status == GAME_MODE_SETUP)
2163 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2164 else if (game_status == GAME_MODE_INFO)
2165 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2166 else if (game_status == GAME_MODE_SCORES)
2167 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2175 case GAME_MODE_EDITOR:
2176 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2177 HandleLevelEditorKeyInput(key);
2180 case GAME_MODE_PLAYING:
2185 RequestQuitGame(setup.ask_on_escape);
2195 if (key == KSYM_Escape)
2197 SetGameStatus(GAME_MODE_MAIN);
2205 HandleKeysDebug(key);
2208 void HandleNoEvent(void)
2210 HandleMouseCursor();
2212 switch (game_status)
2214 case GAME_MODE_PLAYING:
2215 HandleButtonOrFinger(-1, -1, -1);
2220 void HandleEventActions(void)
2222 // if (button_status && game_status != GAME_MODE_PLAYING)
2223 if (button_status && (game_status != GAME_MODE_PLAYING ||
2225 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2227 HandleButton(0, 0, button_status, -button_status);
2234 if (network.enabled)
2237 switch (game_status)
2239 case GAME_MODE_MAIN:
2240 DrawPreviewLevelAnimation();
2243 case GAME_MODE_EDITOR:
2244 HandleLevelEditorIdle();
2252 static void HandleTileCursor(int dx, int dy, int button)
2255 ClearPlayerMouseAction();
2262 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2263 (dx < 0 ? MB_LEFTBUTTON :
2264 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2266 else if (!tile_cursor.moving)
2268 int old_xpos = tile_cursor.xpos;
2269 int old_ypos = tile_cursor.ypos;
2270 int new_xpos = old_xpos;
2271 int new_ypos = old_ypos;
2273 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2274 new_xpos = old_xpos + dx;
2276 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2277 new_ypos = old_ypos + dy;
2279 SetTileCursorTargetXY(new_xpos, new_ypos);
2283 static int HandleJoystickForAllPlayers(void)
2287 boolean no_joysticks_configured = TRUE;
2288 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2289 static byte joy_action_last[MAX_PLAYERS];
2291 for (i = 0; i < MAX_PLAYERS; i++)
2292 if (setup.input[i].use_joystick)
2293 no_joysticks_configured = FALSE;
2295 // if no joysticks configured, map connected joysticks to players
2296 if (no_joysticks_configured)
2297 use_as_joystick_nr = TRUE;
2299 for (i = 0; i < MAX_PLAYERS; i++)
2301 byte joy_action = 0;
2303 joy_action = JoystickExt(i, use_as_joystick_nr);
2304 result |= joy_action;
2306 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2307 joy_action != joy_action_last[i])
2308 stored_player[i].action = joy_action;
2310 joy_action_last[i] = joy_action;
2316 void HandleJoystick(void)
2318 static unsigned int joytest_delay = 0;
2319 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2320 static int joytest_last = 0;
2321 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2322 int delay_value = GADGET_FRAME_DELAY;
2323 int joystick = HandleJoystickForAllPlayers();
2324 int keyboard = key_joystick_mapping;
2325 int joy = (joystick | keyboard);
2326 int joytest = joystick;
2327 int left = joy & JOY_LEFT;
2328 int right = joy & JOY_RIGHT;
2329 int up = joy & JOY_UP;
2330 int down = joy & JOY_DOWN;
2331 int button = joy & JOY_BUTTON;
2332 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2333 int dx = (left ? -1 : right ? 1 : 0);
2334 int dy = (up ? -1 : down ? 1 : 0);
2335 boolean use_delay_value_first = (joytest != joytest_last);
2337 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2339 // do not handle this button event anymore
2343 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2345 if (game_status == GAME_MODE_PLAYING)
2347 // when playing MM style levels, also use delay for keyboard events
2348 joytest |= keyboard;
2350 // only use first delay value for new events, but not for changed events
2351 use_delay_value_first = (!joytest != !joytest_last);
2353 // only use delay after the initial keyboard event
2357 // for any joystick or keyboard event, enable playfield tile cursor
2358 if (dx || dy || button)
2359 SetTileCursorEnabled(TRUE);
2362 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2364 // delay joystick/keyboard actions if axes/keys continually pressed
2365 newbutton = dx = dy = 0;
2369 // first start with longer delay, then continue with shorter delay
2370 joytest_delay_value =
2371 (use_delay_value_first ? delay_value_first : delay_value);
2374 joytest_last = joytest;
2376 switch (game_status)
2378 case GAME_MODE_TITLE:
2379 case GAME_MODE_MAIN:
2380 case GAME_MODE_LEVELS:
2381 case GAME_MODE_LEVELNR:
2382 case GAME_MODE_SETUP:
2383 case GAME_MODE_INFO:
2384 case GAME_MODE_SCORES:
2386 if (anyTextGadgetActive())
2389 if (game_status == GAME_MODE_TITLE)
2390 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2391 else if (game_status == GAME_MODE_MAIN)
2392 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2393 else if (game_status == GAME_MODE_LEVELS)
2394 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2395 else if (game_status == GAME_MODE_LEVELNR)
2396 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2397 else if (game_status == GAME_MODE_SETUP)
2398 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2399 else if (game_status == GAME_MODE_INFO)
2400 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2401 else if (game_status == GAME_MODE_SCORES)
2402 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2407 case GAME_MODE_PLAYING:
2409 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2410 if (tape.playing || keyboard)
2411 newbutton = ((joy & JOY_BUTTON) != 0);
2414 if (newbutton && game.all_players_gone)
2421 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2423 if (joystick & JOY_ACTION)
2424 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2426 else if (tape.recording && tape.pausing && !tape.use_mouse)
2428 if (joystick & JOY_ACTION)
2429 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2432 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2433 HandleTileCursor(dx, dy, button);
2442 void HandleSpecialGameControllerButtons(Event *event)
2447 switch (event->type)
2449 case SDL_CONTROLLERBUTTONDOWN:
2450 key_status = KEY_PRESSED;
2453 case SDL_CONTROLLERBUTTONUP:
2454 key_status = KEY_RELEASED;
2461 switch (event->cbutton.button)
2463 case SDL_CONTROLLER_BUTTON_START:
2467 case SDL_CONTROLLER_BUTTON_BACK:
2475 HandleKey(key, key_status);
2478 void HandleSpecialGameControllerKeys(Key key, int key_status)
2480 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2481 int button = SDL_CONTROLLER_BUTTON_INVALID;
2483 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2484 if (key == KSYM_Rewind)
2485 button = SDL_CONTROLLER_BUTTON_A;
2486 else if (key == KSYM_FastForward || key == KSYM_Menu)
2487 button = SDL_CONTROLLER_BUTTON_B;
2489 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2493 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2494 SDL_CONTROLLERBUTTONUP);
2496 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2497 event.cbutton.button = button;
2498 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2501 HandleJoystickEvent(&event);
2506 boolean DoKeysymAction(int keysym)
2510 Key key = (Key)(-keysym);
2512 HandleKey(key, KEY_PRESSED);
2513 HandleKey(key, KEY_RELEASED);