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
1455 else if (key == KSYM_Menu)
1457 // the "menu" button can be used to toggle displaying virtual buttons
1458 if (key_status == KEY_PRESSED)
1459 SetOverlayEnabled(!GetOverlayEnabled());
1463 // for any other "real" key event, disable virtual buttons
1464 SetOverlayEnabled(FALSE);
1468 HandleKeyModState(keymod, key_status);
1470 // only handle raw key input without text modifier keys pressed
1471 if (!checkTextInputKeyModState())
1472 HandleKey(key, key_status);
1475 void HandleFocusEvent(FocusChangeEvent *event)
1477 static int old_joystick_status = -1;
1479 if (event->type == EVENT_FOCUSOUT)
1481 KeyboardAutoRepeatOn();
1482 old_joystick_status = joystick.status;
1483 joystick.status = JOYSTICK_NOT_AVAILABLE;
1485 ClearPlayerAction();
1487 else if (event->type == EVENT_FOCUSIN)
1489 /* When there are two Rocks'n'Diamonds windows which overlap and
1490 the player moves the pointer from one game window to the other,
1491 a 'FocusOut' event is generated for the window the pointer is
1492 leaving and a 'FocusIn' event is generated for the window the
1493 pointer is entering. In some cases, it can happen that the
1494 'FocusIn' event is handled by the one game process before the
1495 'FocusOut' event by the other game process. In this case the
1496 X11 environment would end up with activated keyboard auto repeat,
1497 because unfortunately this is a global setting and not (which
1498 would be far better) set for each X11 window individually.
1499 The effect would be keyboard auto repeat while playing the game
1500 (game_status == GAME_MODE_PLAYING), which is not desired.
1501 To avoid this special case, we just wait 1/10 second before
1502 processing the 'FocusIn' event. */
1504 if (game_status == GAME_MODE_PLAYING)
1507 KeyboardAutoRepeatOffUnlessAutoplay();
1510 if (old_joystick_status != -1)
1511 joystick.status = old_joystick_status;
1515 void HandleClientMessageEvent(ClientMessageEvent *event)
1517 if (CheckCloseWindowEvent(event))
1521 void HandleWindowManagerEvent(Event *event)
1523 SDLHandleWindowManagerEvent(event);
1526 void HandleButton(int mx, int my, int button, int button_nr)
1528 static int old_mx = 0, old_my = 0;
1529 boolean button_hold = FALSE;
1530 boolean handle_gadgets = TRUE;
1536 button_nr = -button_nr;
1545 #if defined(PLATFORM_ANDROID)
1546 // when playing, only handle gadgets when using "follow finger" controls
1547 // or when using touch controls in combination with the MM game engine
1549 (game_status != GAME_MODE_PLAYING ||
1550 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1551 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1554 if (HandleGlobalAnimClicks(mx, my, button))
1556 // do not handle this button event anymore
1557 return; // force mouse event not to be handled at all
1560 if (handle_gadgets && HandleGadgets(mx, my, button))
1562 // do not handle this button event anymore
1563 mx = my = -32; // force mouse event to be outside screen tiles
1566 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1569 // do not use scroll wheel button events for anything other than gadgets
1570 if (IS_WHEEL_BUTTON(button_nr))
1573 switch (game_status)
1575 case GAME_MODE_TITLE:
1576 HandleTitleScreen(mx, my, 0, 0, button);
1579 case GAME_MODE_MAIN:
1580 HandleMainMenu(mx, my, 0, 0, button);
1583 case GAME_MODE_PSEUDO_TYPENAME:
1584 HandleTypeName(0, KSYM_Return);
1587 case GAME_MODE_LEVELS:
1588 HandleChooseLevelSet(mx, my, 0, 0, button);
1591 case GAME_MODE_LEVELNR:
1592 HandleChooseLevelNr(mx, my, 0, 0, button);
1595 case GAME_MODE_SCORES:
1596 HandleHallOfFame(0, 0, 0, 0, button);
1599 case GAME_MODE_EDITOR:
1600 HandleLevelEditorIdle();
1603 case GAME_MODE_INFO:
1604 HandleInfoScreen(mx, my, 0, 0, button);
1607 case GAME_MODE_SETUP:
1608 HandleSetupScreen(mx, my, 0, 0, button);
1611 case GAME_MODE_PLAYING:
1612 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1613 HandleButtonOrFinger(mx, my, button);
1615 SetPlayerMouseAction(mx, my, button);
1618 if (button == MB_PRESSED && !motion_status && !button_hold &&
1619 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1620 DumpTileFromScreen(mx, my);
1630 static boolean is_string_suffix(char *string, char *suffix)
1632 int string_len = strlen(string);
1633 int suffix_len = strlen(suffix);
1635 if (suffix_len > string_len)
1638 return (strEqual(&string[string_len - suffix_len], suffix));
1641 #define MAX_CHEAT_INPUT_LEN 32
1643 static void HandleKeysSpecial(Key key)
1645 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1646 char letter = getCharFromKey(key);
1647 int cheat_input_len = strlen(cheat_input);
1653 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1655 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1656 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1658 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1661 cheat_input[cheat_input_len++] = letter;
1662 cheat_input[cheat_input_len] = '\0';
1664 #if DEBUG_EVENTS_KEY
1665 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1668 if (game_status == GAME_MODE_MAIN)
1670 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1671 is_string_suffix(cheat_input, ":ist"))
1673 InsertSolutionTape();
1675 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1676 is_string_suffix(cheat_input, ":pst"))
1680 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1681 is_string_suffix(cheat_input, ":rg"))
1683 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1686 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1687 is_string_suffix(cheat_input, ":rs"))
1689 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1692 else if (is_string_suffix(cheat_input, ":reload-music") ||
1693 is_string_suffix(cheat_input, ":rm"))
1695 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1698 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1699 is_string_suffix(cheat_input, ":ra"))
1701 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1702 1 << ARTWORK_TYPE_SOUNDS |
1703 1 << ARTWORK_TYPE_MUSIC);
1706 else if (is_string_suffix(cheat_input, ":dump-level") ||
1707 is_string_suffix(cheat_input, ":dl"))
1711 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1712 is_string_suffix(cheat_input, ":dt"))
1716 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1717 is_string_suffix(cheat_input, ":ft"))
1719 /* fix single-player tapes that contain player input for more than one
1720 player (due to a bug in 3.3.1.2 and earlier versions), which results
1721 in playing levels with more than one player in multi-player mode,
1722 even though the tape was originally recorded in single-player mode */
1724 // remove player input actions for all players but the first one
1725 for (i = 1; i < MAX_PLAYERS; i++)
1726 tape.player_participates[i] = FALSE;
1728 tape.changed = TRUE;
1730 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1731 is_string_suffix(cheat_input, ":snl"))
1733 SaveNativeLevel(&level);
1735 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1736 is_string_suffix(cheat_input, ":fps"))
1738 global.show_frames_per_second = !global.show_frames_per_second;
1741 else if (game_status == GAME_MODE_PLAYING)
1744 if (is_string_suffix(cheat_input, ".q"))
1745 DEBUG_SetMaximumDynamite();
1748 else if (game_status == GAME_MODE_EDITOR)
1750 if (is_string_suffix(cheat_input, ":dump-brush") ||
1751 is_string_suffix(cheat_input, ":DB"))
1755 else if (is_string_suffix(cheat_input, ":DDB"))
1760 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1762 if (letter == 'x') // copy brush to clipboard (small size)
1764 CopyBrushToClipboard_Small();
1766 else if (letter == 'c') // copy brush to clipboard (normal size)
1768 CopyBrushToClipboard();
1770 else if (letter == 'v') // paste brush from Clipboard
1772 CopyClipboardToBrush();
1777 // special key shortcuts for all game modes
1778 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1779 is_string_suffix(cheat_input, ":dea") ||
1780 is_string_suffix(cheat_input, ":DEA"))
1782 DumpGadgetIdentifiers();
1783 DumpScreenIdentifiers();
1787 boolean HandleKeysDebug(Key key, int key_status)
1792 if (key_status != KEY_PRESSED)
1795 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1797 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1799 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1801 if (key == setup.debug.frame_delay_key[i] &&
1802 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1804 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1805 setup.debug.frame_delay[i] : setup.game_frame_delay);
1807 if (!setup.debug.frame_delay_game_only)
1808 MenuFrameDelay = GameFrameDelay;
1810 SetVideoFrameDelay(GameFrameDelay);
1812 if (GameFrameDelay > ONE_SECOND_DELAY)
1813 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1814 else if (GameFrameDelay != 0)
1815 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1816 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1817 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1819 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1826 if (game_status == GAME_MODE_PLAYING)
1830 options.debug = !options.debug;
1832 Error(ERR_DEBUG, "debug mode %s",
1833 (options.debug ? "enabled" : "disabled"));
1837 else if (key == KSYM_v)
1839 Error(ERR_DEBUG, "currently using game engine version %d",
1840 game.engine_version);
1850 void HandleKey(Key key, int key_status)
1852 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1853 static boolean ignore_repeated_key = FALSE;
1854 static struct SetupKeyboardInfo ski;
1855 static struct SetupShortcutInfo ssi;
1864 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1865 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1866 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1867 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1868 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1869 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1874 if (HandleKeysDebug(key, key_status))
1875 return; // do not handle already processed keys again
1877 // map special keys (media keys / remote control buttons) to default keys
1878 if (key == KSYM_PlayPause)
1880 else if (key == KSYM_Select)
1883 HandleSpecialGameControllerKeys(key, key_status);
1885 if (game_status == GAME_MODE_PLAYING)
1887 // only needed for single-step tape recording mode
1888 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1891 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1893 byte key_action = 0;
1895 if (setup.input[pnr].use_joystick)
1898 ski = setup.input[pnr].key;
1900 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1901 if (key == *key_info[i].key_custom)
1902 key_action |= key_info[i].action;
1904 // use combined snap+direction keys for the first player only
1907 ssi = setup.shortcut;
1909 for (i = 0; i < NUM_DIRECTIONS; i++)
1910 if (key == *key_info[i].key_snap)
1911 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1914 if (key_status == KEY_PRESSED)
1915 stored_player[pnr].action |= key_action;
1917 stored_player[pnr].action &= ~key_action;
1919 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
1921 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1923 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1925 // if snap key already pressed, keep pause mode when releasing
1926 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1927 has_snapped[pnr] = TRUE;
1929 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1931 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1933 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1934 getRedDiskReleaseFlag_SP() == 0)
1936 // add a single inactive frame before dropping starts
1937 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1938 stored_player[pnr].force_dropping = TRUE;
1941 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1943 // if snap key was pressed without direction, leave pause mode
1944 if (!has_snapped[pnr])
1945 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1947 has_snapped[pnr] = FALSE;
1950 else if (tape.recording && tape.pausing && !tape.use_mouse)
1952 // prevent key release events from un-pausing a paused game
1953 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1954 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1957 // for MM style levels, handle in-game keyboard input in HandleJoystick()
1958 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1964 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1965 if (key == key_info[i].key_default)
1966 joy |= key_info[i].action;
1971 if (key_status == KEY_PRESSED)
1972 key_joystick_mapping |= joy;
1974 key_joystick_mapping &= ~joy;
1979 if (game_status != GAME_MODE_PLAYING)
1980 key_joystick_mapping = 0;
1982 if (key_status == KEY_RELEASED)
1984 // reset flag to ignore repeated "key pressed" events after key release
1985 ignore_repeated_key = FALSE;
1990 if ((key == KSYM_F11 ||
1991 ((key == KSYM_Return ||
1992 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1993 video.fullscreen_available &&
1994 !ignore_repeated_key)
1996 setup.fullscreen = !setup.fullscreen;
1998 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2000 if (game_status == GAME_MODE_SETUP)
2001 RedrawSetupScreenAfterFullscreenToggle();
2003 // set flag to ignore repeated "key pressed" events
2004 ignore_repeated_key = TRUE;
2009 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2010 key == KSYM_minus || key == KSYM_KP_Subtract ||
2011 key == KSYM_plus || key == KSYM_KP_Add ||
2012 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2013 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2014 video.window_scaling_available &&
2015 !video.fullscreen_enabled)
2017 if (key == KSYM_0 || key == KSYM_KP_0)
2018 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2019 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2020 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2022 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2024 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2025 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2026 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2027 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2029 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2031 if (game_status == GAME_MODE_SETUP)
2032 RedrawSetupScreenAfterFullscreenToggle();
2037 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2038 key == KSYM_Return ||
2039 key == KSYM_Escape)))
2041 // do not handle this key event anymore
2042 if (key != KSYM_Escape) // always allow ESC key to be handled
2046 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2047 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2054 if (game_status == GAME_MODE_MAIN &&
2055 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2057 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2062 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2064 if (key == setup.shortcut.save_game)
2066 else if (key == setup.shortcut.load_game)
2068 else if (key == setup.shortcut.toggle_pause)
2069 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2071 HandleTapeButtonKeys(key);
2072 HandleSoundButtonKeys(key);
2075 if (game_status == GAME_MODE_PLAYING && !network_playing)
2077 int centered_player_nr_next = -999;
2079 if (key == setup.shortcut.focus_player_all)
2080 centered_player_nr_next = -1;
2082 for (i = 0; i < MAX_PLAYERS; i++)
2083 if (key == setup.shortcut.focus_player[i])
2084 centered_player_nr_next = i;
2086 if (centered_player_nr_next != -999)
2088 game.centered_player_nr_next = centered_player_nr_next;
2089 game.set_centered_player = TRUE;
2093 tape.centered_player_nr_next = game.centered_player_nr_next;
2094 tape.set_centered_player = TRUE;
2099 HandleKeysSpecial(key);
2101 if (HandleGadgetsKeyInput(key))
2102 return; // do not handle already processed keys again
2104 switch (game_status)
2106 case GAME_MODE_PSEUDO_TYPENAME:
2107 HandleTypeName(0, key);
2110 case GAME_MODE_TITLE:
2111 case GAME_MODE_MAIN:
2112 case GAME_MODE_LEVELS:
2113 case GAME_MODE_LEVELNR:
2114 case GAME_MODE_SETUP:
2115 case GAME_MODE_INFO:
2116 case GAME_MODE_SCORES:
2118 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2125 if (game_status == GAME_MODE_TITLE)
2126 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2127 else if (game_status == GAME_MODE_MAIN)
2128 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2129 else if (game_status == GAME_MODE_LEVELS)
2130 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2131 else if (game_status == GAME_MODE_LEVELNR)
2132 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2133 else if (game_status == GAME_MODE_SETUP)
2134 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2135 else if (game_status == GAME_MODE_INFO)
2136 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2137 else if (game_status == GAME_MODE_SCORES)
2138 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2142 if (game_status != GAME_MODE_MAIN)
2143 FadeSkipNextFadeIn();
2145 if (game_status == GAME_MODE_TITLE)
2146 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2147 else if (game_status == GAME_MODE_LEVELS)
2148 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2149 else if (game_status == GAME_MODE_LEVELNR)
2150 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2151 else if (game_status == GAME_MODE_SETUP)
2152 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2153 else if (game_status == GAME_MODE_INFO)
2154 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2155 else if (game_status == GAME_MODE_SCORES)
2156 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2160 if (game_status == GAME_MODE_LEVELS)
2161 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2162 else if (game_status == GAME_MODE_LEVELNR)
2163 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2164 else if (game_status == GAME_MODE_SETUP)
2165 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2166 else if (game_status == GAME_MODE_INFO)
2167 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2168 else if (game_status == GAME_MODE_SCORES)
2169 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2172 case KSYM_Page_Down:
2173 if (game_status == GAME_MODE_LEVELS)
2174 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2175 else if (game_status == GAME_MODE_LEVELNR)
2176 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2177 else if (game_status == GAME_MODE_SETUP)
2178 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2179 else if (game_status == GAME_MODE_INFO)
2180 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2181 else if (game_status == GAME_MODE_SCORES)
2182 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2190 case GAME_MODE_EDITOR:
2191 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2192 HandleLevelEditorKeyInput(key);
2195 case GAME_MODE_PLAYING:
2200 RequestQuitGame(setup.ask_on_escape);
2210 if (key == KSYM_Escape)
2212 SetGameStatus(GAME_MODE_MAIN);
2221 void HandleNoEvent(void)
2223 HandleMouseCursor();
2225 switch (game_status)
2227 case GAME_MODE_PLAYING:
2228 HandleButtonOrFinger(-1, -1, -1);
2233 void HandleEventActions(void)
2235 // if (button_status && game_status != GAME_MODE_PLAYING)
2236 if (button_status && (game_status != GAME_MODE_PLAYING ||
2238 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2240 HandleButton(0, 0, button_status, -button_status);
2247 if (network.enabled)
2250 switch (game_status)
2252 case GAME_MODE_MAIN:
2253 DrawPreviewLevelAnimation();
2256 case GAME_MODE_EDITOR:
2257 HandleLevelEditorIdle();
2265 static void HandleTileCursor(int dx, int dy, int button)
2268 ClearPlayerMouseAction();
2275 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2276 (dx < 0 ? MB_LEFTBUTTON :
2277 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2279 else if (!tile_cursor.moving)
2281 int old_xpos = tile_cursor.xpos;
2282 int old_ypos = tile_cursor.ypos;
2283 int new_xpos = old_xpos;
2284 int new_ypos = old_ypos;
2286 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2287 new_xpos = old_xpos + dx;
2289 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2290 new_ypos = old_ypos + dy;
2292 SetTileCursorTargetXY(new_xpos, new_ypos);
2296 static int HandleJoystickForAllPlayers(void)
2300 boolean no_joysticks_configured = TRUE;
2301 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2302 static byte joy_action_last[MAX_PLAYERS];
2304 for (i = 0; i < MAX_PLAYERS; i++)
2305 if (setup.input[i].use_joystick)
2306 no_joysticks_configured = FALSE;
2308 // if no joysticks configured, map connected joysticks to players
2309 if (no_joysticks_configured)
2310 use_as_joystick_nr = TRUE;
2312 for (i = 0; i < MAX_PLAYERS; i++)
2314 byte joy_action = 0;
2316 joy_action = JoystickExt(i, use_as_joystick_nr);
2317 result |= joy_action;
2319 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2320 joy_action != joy_action_last[i])
2321 stored_player[i].action = joy_action;
2323 joy_action_last[i] = joy_action;
2329 void HandleJoystick(void)
2331 static unsigned int joytest_delay = 0;
2332 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2333 static int joytest_last = 0;
2334 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2335 int delay_value = GADGET_FRAME_DELAY;
2336 int joystick = HandleJoystickForAllPlayers();
2337 int keyboard = key_joystick_mapping;
2338 int joy = (joystick | keyboard);
2339 int joytest = joystick;
2340 int left = joy & JOY_LEFT;
2341 int right = joy & JOY_RIGHT;
2342 int up = joy & JOY_UP;
2343 int down = joy & JOY_DOWN;
2344 int button = joy & JOY_BUTTON;
2345 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2346 int dx = (left ? -1 : right ? 1 : 0);
2347 int dy = (up ? -1 : down ? 1 : 0);
2348 boolean use_delay_value_first = (joytest != joytest_last);
2350 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2352 // do not handle this button event anymore
2356 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2358 if (game_status == GAME_MODE_PLAYING)
2360 // when playing MM style levels, also use delay for keyboard events
2361 joytest |= keyboard;
2363 // only use first delay value for new events, but not for changed events
2364 use_delay_value_first = (!joytest != !joytest_last);
2366 // only use delay after the initial keyboard event
2370 // for any joystick or keyboard event, enable playfield tile cursor
2371 if (dx || dy || button)
2372 SetTileCursorEnabled(TRUE);
2375 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2377 // delay joystick/keyboard actions if axes/keys continually pressed
2378 newbutton = dx = dy = 0;
2382 // first start with longer delay, then continue with shorter delay
2383 joytest_delay_value =
2384 (use_delay_value_first ? delay_value_first : delay_value);
2387 joytest_last = joytest;
2389 switch (game_status)
2391 case GAME_MODE_TITLE:
2392 case GAME_MODE_MAIN:
2393 case GAME_MODE_LEVELS:
2394 case GAME_MODE_LEVELNR:
2395 case GAME_MODE_SETUP:
2396 case GAME_MODE_INFO:
2397 case GAME_MODE_SCORES:
2399 if (anyTextGadgetActive())
2402 if (game_status == GAME_MODE_TITLE)
2403 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2404 else if (game_status == GAME_MODE_MAIN)
2405 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2406 else if (game_status == GAME_MODE_LEVELS)
2407 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2408 else if (game_status == GAME_MODE_LEVELNR)
2409 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2410 else if (game_status == GAME_MODE_SETUP)
2411 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2412 else if (game_status == GAME_MODE_INFO)
2413 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2414 else if (game_status == GAME_MODE_SCORES)
2415 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2420 case GAME_MODE_PLAYING:
2422 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2423 if (tape.playing || keyboard)
2424 newbutton = ((joy & JOY_BUTTON) != 0);
2427 if (newbutton && game.all_players_gone)
2434 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2436 if (joystick & JOY_ACTION)
2437 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2439 else if (tape.recording && tape.pausing && !tape.use_mouse)
2441 if (joystick & JOY_ACTION)
2442 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2445 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2446 HandleTileCursor(dx, dy, button);
2455 void HandleSpecialGameControllerButtons(Event *event)
2460 switch (event->type)
2462 case SDL_CONTROLLERBUTTONDOWN:
2463 key_status = KEY_PRESSED;
2466 case SDL_CONTROLLERBUTTONUP:
2467 key_status = KEY_RELEASED;
2474 switch (event->cbutton.button)
2476 case SDL_CONTROLLER_BUTTON_START:
2480 case SDL_CONTROLLER_BUTTON_BACK:
2488 HandleKey(key, key_status);
2491 void HandleSpecialGameControllerKeys(Key key, int key_status)
2493 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2494 int button = SDL_CONTROLLER_BUTTON_INVALID;
2496 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2497 if (key == KSYM_Rewind)
2498 button = SDL_CONTROLLER_BUTTON_A;
2499 else if (key == KSYM_FastForward || key == KSYM_Menu)
2500 button = SDL_CONTROLLER_BUTTON_B;
2502 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2506 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2507 SDL_CONTROLLERBUTTONUP);
2509 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2510 event.cbutton.button = button;
2511 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2514 HandleJoystickEvent(&event);
2519 boolean DoKeysymAction(int keysym)
2523 Key key = (Key)(-keysym);
2525 HandleKey(key, KEY_PRESSED);
2526 HandleKey(key, KEY_RELEASED);