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