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 boolean HandleKeysDebug(Key key, int key_status)
1786 if (key_status != KEY_PRESSED)
1789 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1791 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1793 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1795 if (key == setup.debug.frame_delay_key[i] &&
1796 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1798 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1799 setup.debug.frame_delay[i] : setup.game_frame_delay);
1801 if (!setup.debug.frame_delay_game_only)
1802 MenuFrameDelay = GameFrameDelay;
1804 SetVideoFrameDelay(GameFrameDelay);
1806 if (GameFrameDelay > ONE_SECOND_DELAY)
1807 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1808 else if (GameFrameDelay != 0)
1809 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1810 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1811 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1813 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1820 if (game_status == GAME_MODE_PLAYING)
1824 options.debug = !options.debug;
1826 Error(ERR_DEBUG, "debug mode %s",
1827 (options.debug ? "enabled" : "disabled"));
1831 else if (key == KSYM_v)
1833 Error(ERR_DEBUG, "currently using game engine version %d",
1834 game.engine_version);
1844 void HandleKey(Key key, int key_status)
1846 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1847 static boolean ignore_repeated_key = FALSE;
1848 static struct SetupKeyboardInfo ski;
1849 static struct SetupShortcutInfo ssi;
1858 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1859 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1860 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1861 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1862 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1863 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1868 if (HandleKeysDebug(key, key_status))
1869 return; // do not handle already processed keys again
1871 // map special keys (media keys / remote control buttons) to default keys
1872 if (key == KSYM_PlayPause)
1874 else if (key == KSYM_Select)
1877 HandleSpecialGameControllerKeys(key, key_status);
1879 if (game_status == GAME_MODE_PLAYING)
1881 // only needed for single-step tape recording mode
1882 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1885 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1887 byte key_action = 0;
1889 if (setup.input[pnr].use_joystick)
1892 ski = setup.input[pnr].key;
1894 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1895 if (key == *key_info[i].key_custom)
1896 key_action |= key_info[i].action;
1898 // use combined snap+direction keys for the first player only
1901 ssi = setup.shortcut;
1903 for (i = 0; i < NUM_DIRECTIONS; i++)
1904 if (key == *key_info[i].key_snap)
1905 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1908 if (key_status == KEY_PRESSED)
1909 stored_player[pnr].action |= key_action;
1911 stored_player[pnr].action &= ~key_action;
1913 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
1915 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1917 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1919 // if snap key already pressed, keep pause mode when releasing
1920 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1921 has_snapped[pnr] = TRUE;
1923 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1925 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1927 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1928 getRedDiskReleaseFlag_SP() == 0)
1930 // add a single inactive frame before dropping starts
1931 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1932 stored_player[pnr].force_dropping = TRUE;
1935 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1937 // if snap key was pressed without direction, leave pause mode
1938 if (!has_snapped[pnr])
1939 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1941 has_snapped[pnr] = FALSE;
1944 else if (tape.recording && tape.pausing && !tape.use_mouse)
1946 // prevent key release events from un-pausing a paused game
1947 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1948 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1951 // for MM style levels, handle in-game keyboard input in HandleJoystick()
1952 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1958 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1959 if (key == key_info[i].key_default)
1960 joy |= key_info[i].action;
1965 if (key_status == KEY_PRESSED)
1966 key_joystick_mapping |= joy;
1968 key_joystick_mapping &= ~joy;
1973 if (game_status != GAME_MODE_PLAYING)
1974 key_joystick_mapping = 0;
1976 if (key_status == KEY_RELEASED)
1978 // reset flag to ignore repeated "key pressed" events after key release
1979 ignore_repeated_key = FALSE;
1984 if ((key == KSYM_F11 ||
1985 ((key == KSYM_Return ||
1986 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1987 video.fullscreen_available &&
1988 !ignore_repeated_key)
1990 setup.fullscreen = !setup.fullscreen;
1992 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1994 if (game_status == GAME_MODE_SETUP)
1995 RedrawSetupScreenAfterFullscreenToggle();
1997 // set flag to ignore repeated "key pressed" events
1998 ignore_repeated_key = TRUE;
2003 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2004 key == KSYM_minus || key == KSYM_KP_Subtract ||
2005 key == KSYM_plus || key == KSYM_KP_Add ||
2006 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2007 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2008 video.window_scaling_available &&
2009 !video.fullscreen_enabled)
2011 if (key == KSYM_0 || key == KSYM_KP_0)
2012 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2013 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2014 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2016 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2018 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2019 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2020 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2021 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2023 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2025 if (game_status == GAME_MODE_SETUP)
2026 RedrawSetupScreenAfterFullscreenToggle();
2031 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2032 key == KSYM_Return ||
2033 key == KSYM_Escape)))
2035 // do not handle this key event anymore
2036 if (key != KSYM_Escape) // always allow ESC key to be handled
2040 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2041 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2048 if (game_status == GAME_MODE_MAIN &&
2049 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2051 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2056 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2058 if (key == setup.shortcut.save_game)
2060 else if (key == setup.shortcut.load_game)
2062 else if (key == setup.shortcut.toggle_pause)
2063 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2065 HandleTapeButtonKeys(key);
2066 HandleSoundButtonKeys(key);
2069 if (game_status == GAME_MODE_PLAYING && !network_playing)
2071 int centered_player_nr_next = -999;
2073 if (key == setup.shortcut.focus_player_all)
2074 centered_player_nr_next = -1;
2076 for (i = 0; i < MAX_PLAYERS; i++)
2077 if (key == setup.shortcut.focus_player[i])
2078 centered_player_nr_next = i;
2080 if (centered_player_nr_next != -999)
2082 game.centered_player_nr_next = centered_player_nr_next;
2083 game.set_centered_player = TRUE;
2087 tape.centered_player_nr_next = game.centered_player_nr_next;
2088 tape.set_centered_player = TRUE;
2093 HandleKeysSpecial(key);
2095 if (HandleGadgetsKeyInput(key))
2096 return; // do not handle already processed keys again
2098 switch (game_status)
2100 case GAME_MODE_PSEUDO_TYPENAME:
2101 HandleTypeName(0, key);
2104 case GAME_MODE_TITLE:
2105 case GAME_MODE_MAIN:
2106 case GAME_MODE_LEVELS:
2107 case GAME_MODE_LEVELNR:
2108 case GAME_MODE_SETUP:
2109 case GAME_MODE_INFO:
2110 case GAME_MODE_SCORES:
2112 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2119 if (game_status == GAME_MODE_TITLE)
2120 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2121 else if (game_status == GAME_MODE_MAIN)
2122 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2123 else if (game_status == GAME_MODE_LEVELS)
2124 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2125 else if (game_status == GAME_MODE_LEVELNR)
2126 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2127 else if (game_status == GAME_MODE_SETUP)
2128 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2129 else if (game_status == GAME_MODE_INFO)
2130 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2131 else if (game_status == GAME_MODE_SCORES)
2132 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2136 if (game_status != GAME_MODE_MAIN)
2137 FadeSkipNextFadeIn();
2139 if (game_status == GAME_MODE_TITLE)
2140 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2141 else if (game_status == GAME_MODE_LEVELS)
2142 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2143 else if (game_status == GAME_MODE_LEVELNR)
2144 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2145 else if (game_status == GAME_MODE_SETUP)
2146 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2147 else if (game_status == GAME_MODE_INFO)
2148 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2149 else if (game_status == GAME_MODE_SCORES)
2150 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2154 if (game_status == GAME_MODE_LEVELS)
2155 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2156 else if (game_status == GAME_MODE_LEVELNR)
2157 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2158 else if (game_status == GAME_MODE_SETUP)
2159 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2160 else if (game_status == GAME_MODE_INFO)
2161 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2162 else if (game_status == GAME_MODE_SCORES)
2163 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2166 case KSYM_Page_Down:
2167 if (game_status == GAME_MODE_LEVELS)
2168 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2169 else if (game_status == GAME_MODE_LEVELNR)
2170 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2171 else if (game_status == GAME_MODE_SETUP)
2172 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2173 else if (game_status == GAME_MODE_INFO)
2174 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2175 else if (game_status == GAME_MODE_SCORES)
2176 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2184 case GAME_MODE_EDITOR:
2185 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2186 HandleLevelEditorKeyInput(key);
2189 case GAME_MODE_PLAYING:
2194 RequestQuitGame(setup.ask_on_escape);
2204 if (key == KSYM_Escape)
2206 SetGameStatus(GAME_MODE_MAIN);
2215 void HandleNoEvent(void)
2217 HandleMouseCursor();
2219 switch (game_status)
2221 case GAME_MODE_PLAYING:
2222 HandleButtonOrFinger(-1, -1, -1);
2227 void HandleEventActions(void)
2229 // if (button_status && game_status != GAME_MODE_PLAYING)
2230 if (button_status && (game_status != GAME_MODE_PLAYING ||
2232 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2234 HandleButton(0, 0, button_status, -button_status);
2241 if (network.enabled)
2244 switch (game_status)
2246 case GAME_MODE_MAIN:
2247 DrawPreviewLevelAnimation();
2250 case GAME_MODE_EDITOR:
2251 HandleLevelEditorIdle();
2259 static void HandleTileCursor(int dx, int dy, int button)
2262 ClearPlayerMouseAction();
2269 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2270 (dx < 0 ? MB_LEFTBUTTON :
2271 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2273 else if (!tile_cursor.moving)
2275 int old_xpos = tile_cursor.xpos;
2276 int old_ypos = tile_cursor.ypos;
2277 int new_xpos = old_xpos;
2278 int new_ypos = old_ypos;
2280 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2281 new_xpos = old_xpos + dx;
2283 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2284 new_ypos = old_ypos + dy;
2286 SetTileCursorTargetXY(new_xpos, new_ypos);
2290 static int HandleJoystickForAllPlayers(void)
2294 boolean no_joysticks_configured = TRUE;
2295 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2296 static byte joy_action_last[MAX_PLAYERS];
2298 for (i = 0; i < MAX_PLAYERS; i++)
2299 if (setup.input[i].use_joystick)
2300 no_joysticks_configured = FALSE;
2302 // if no joysticks configured, map connected joysticks to players
2303 if (no_joysticks_configured)
2304 use_as_joystick_nr = TRUE;
2306 for (i = 0; i < MAX_PLAYERS; i++)
2308 byte joy_action = 0;
2310 joy_action = JoystickExt(i, use_as_joystick_nr);
2311 result |= joy_action;
2313 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2314 joy_action != joy_action_last[i])
2315 stored_player[i].action = joy_action;
2317 joy_action_last[i] = joy_action;
2323 void HandleJoystick(void)
2325 static unsigned int joytest_delay = 0;
2326 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2327 static int joytest_last = 0;
2328 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2329 int delay_value = GADGET_FRAME_DELAY;
2330 int joystick = HandleJoystickForAllPlayers();
2331 int keyboard = key_joystick_mapping;
2332 int joy = (joystick | keyboard);
2333 int joytest = joystick;
2334 int left = joy & JOY_LEFT;
2335 int right = joy & JOY_RIGHT;
2336 int up = joy & JOY_UP;
2337 int down = joy & JOY_DOWN;
2338 int button = joy & JOY_BUTTON;
2339 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2340 int dx = (left ? -1 : right ? 1 : 0);
2341 int dy = (up ? -1 : down ? 1 : 0);
2342 boolean use_delay_value_first = (joytest != joytest_last);
2344 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2346 // do not handle this button event anymore
2350 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2352 if (game_status == GAME_MODE_PLAYING)
2354 // when playing MM style levels, also use delay for keyboard events
2355 joytest |= keyboard;
2357 // only use first delay value for new events, but not for changed events
2358 use_delay_value_first = (!joytest != !joytest_last);
2360 // only use delay after the initial keyboard event
2364 // for any joystick or keyboard event, enable playfield tile cursor
2365 if (dx || dy || button)
2366 SetTileCursorEnabled(TRUE);
2369 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2371 // delay joystick/keyboard actions if axes/keys continually pressed
2372 newbutton = dx = dy = 0;
2376 // first start with longer delay, then continue with shorter delay
2377 joytest_delay_value =
2378 (use_delay_value_first ? delay_value_first : delay_value);
2381 joytest_last = joytest;
2383 switch (game_status)
2385 case GAME_MODE_TITLE:
2386 case GAME_MODE_MAIN:
2387 case GAME_MODE_LEVELS:
2388 case GAME_MODE_LEVELNR:
2389 case GAME_MODE_SETUP:
2390 case GAME_MODE_INFO:
2391 case GAME_MODE_SCORES:
2393 if (anyTextGadgetActive())
2396 if (game_status == GAME_MODE_TITLE)
2397 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2398 else if (game_status == GAME_MODE_MAIN)
2399 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2400 else if (game_status == GAME_MODE_LEVELS)
2401 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2402 else if (game_status == GAME_MODE_LEVELNR)
2403 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2404 else if (game_status == GAME_MODE_SETUP)
2405 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2406 else if (game_status == GAME_MODE_INFO)
2407 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2408 else if (game_status == GAME_MODE_SCORES)
2409 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2414 case GAME_MODE_PLAYING:
2416 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2417 if (tape.playing || keyboard)
2418 newbutton = ((joy & JOY_BUTTON) != 0);
2421 if (newbutton && game.all_players_gone)
2428 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2430 if (joystick & JOY_ACTION)
2431 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2433 else if (tape.recording && tape.pausing && !tape.use_mouse)
2435 if (joystick & JOY_ACTION)
2436 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2439 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2440 HandleTileCursor(dx, dy, button);
2449 void HandleSpecialGameControllerButtons(Event *event)
2454 switch (event->type)
2456 case SDL_CONTROLLERBUTTONDOWN:
2457 key_status = KEY_PRESSED;
2460 case SDL_CONTROLLERBUTTONUP:
2461 key_status = KEY_RELEASED;
2468 switch (event->cbutton.button)
2470 case SDL_CONTROLLER_BUTTON_START:
2474 case SDL_CONTROLLER_BUTTON_BACK:
2482 HandleKey(key, key_status);
2485 void HandleSpecialGameControllerKeys(Key key, int key_status)
2487 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2488 int button = SDL_CONTROLLER_BUTTON_INVALID;
2490 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2491 if (key == KSYM_Rewind)
2492 button = SDL_CONTROLLER_BUTTON_A;
2493 else if (key == KSYM_FastForward || key == KSYM_Menu)
2494 button = SDL_CONTROLLER_BUTTON_B;
2496 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2500 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2501 SDL_CONTROLLERBUTTONUP);
2503 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2504 event.cbutton.button = button;
2505 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2508 HandleJoystickEvent(&event);
2513 boolean DoKeysymAction(int keysym)
2517 Key key = (Key)(-keysym);
2519 HandleKey(key, KEY_PRESSED);
2520 HandleKey(key, KEY_RELEASED);