1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_EVENTS 0
28 #define DEBUG_EVENTS_BUTTON (DEBUG_EVENTS * 0)
29 #define DEBUG_EVENTS_MOTION (DEBUG_EVENTS * 0)
30 #define DEBUG_EVENTS_WHEEL (DEBUG_EVENTS * 1)
31 #define DEBUG_EVENTS_WINDOW (DEBUG_EVENTS * 0)
32 #define DEBUG_EVENTS_FINGER (DEBUG_EVENTS * 0)
33 #define DEBUG_EVENTS_TEXT (DEBUG_EVENTS * 1)
34 #define DEBUG_EVENTS_KEY (DEBUG_EVENTS * 1)
37 static boolean cursor_inside_playfield = FALSE;
38 static int cursor_mode_last = CURSOR_DEFAULT;
39 static unsigned int special_cursor_delay = 0;
40 static unsigned int special_cursor_delay_value = 1000;
42 static boolean virtual_button_pressed = FALSE;
45 // forward declarations for internal use
46 static void HandleNoEvent(void);
47 static void HandleEventActions(void);
50 // event filter especially needed for SDL event filtering due to
51 // delay problems with lots of mouse motion events when mouse button
52 // not pressed (X11 can handle this with 'PointerMotionHintMask')
54 // event filter addition for SDL2: as SDL2 does not have a function to enable
55 // or disable keyboard auto-repeat, filter repeated keyboard events instead
57 static int FilterEvents(const Event *event)
61 // skip repeated key press events if keyboard auto-repeat is disabled
62 if (event->type == EVENT_KEYPRESS &&
67 if (event->type == EVENT_BUTTONPRESS ||
68 event->type == EVENT_BUTTONRELEASE)
70 ((ButtonEvent *)event)->x -= video.screen_xoffset;
71 ((ButtonEvent *)event)->y -= video.screen_yoffset;
73 else if (event->type == EVENT_MOTIONNOTIFY)
75 ((MotionEvent *)event)->x -= video.screen_xoffset;
76 ((MotionEvent *)event)->y -= video.screen_yoffset;
79 // non-motion events are directly passed to event handler functions
80 if (event->type != EVENT_MOTIONNOTIFY)
83 motion = (MotionEvent *)event;
84 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
85 motion->y >= SY && motion->y < SY + SYSIZE);
87 // do no reset mouse cursor before all pending events have been processed
88 if (gfx.cursor_mode == cursor_mode_last &&
89 ((game_status == GAME_MODE_TITLE &&
90 gfx.cursor_mode == CURSOR_NONE) ||
91 (game_status == GAME_MODE_PLAYING &&
92 gfx.cursor_mode == CURSOR_PLAYFIELD)))
94 SetMouseCursor(CURSOR_DEFAULT);
96 DelayReached(&special_cursor_delay, 0);
98 cursor_mode_last = CURSOR_DEFAULT;
101 // skip mouse motion events without pressed button outside level editor
102 if (button_status == MB_RELEASED &&
103 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
109 // to prevent delay problems, skip mouse motion events if the very next
110 // event is also a mouse motion event (and therefore effectively only
111 // handling the last of a row of mouse motion events in the event queue)
113 static boolean SkipPressedMouseMotionEvent(const Event *event)
115 // nothing to do if the current event is not a mouse motion event
116 if (event->type != EVENT_MOTIONNOTIFY)
119 // only skip motion events with pressed button outside the game
120 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
127 PeekEvent(&next_event);
129 // if next event is also a mouse motion event, skip the current one
130 if (next_event.type == EVENT_MOTIONNOTIFY)
137 static boolean WaitValidEvent(Event *event)
141 if (!FilterEvents(event))
144 if (SkipPressedMouseMotionEvent(event))
150 /* this is especially needed for event modifications for the Android target:
151 if mouse coordinates should be modified in the event filter function,
152 using a properly installed SDL event filter does not work, because in
153 the event filter, mouse coordinates in the event structure are still
154 physical pixel positions, not logical (scaled) screen positions, so this
155 has to be handled at a later stage in the event processing functions
156 (when device pixel positions are already converted to screen positions) */
158 boolean NextValidEvent(Event *event)
160 while (PendingEvent())
161 if (WaitValidEvent(event))
167 static void HandleEvents(void)
170 unsigned int event_frame_delay = 0;
171 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
173 ResetDelayCounter(&event_frame_delay);
175 while (NextValidEvent(&event))
179 case EVENT_BUTTONPRESS:
180 case EVENT_BUTTONRELEASE:
181 HandleButtonEvent((ButtonEvent *) &event);
184 case EVENT_MOTIONNOTIFY:
185 HandleMotionEvent((MotionEvent *) &event);
188 case EVENT_WHEELMOTION:
189 HandleWheelEvent((WheelEvent *) &event);
192 case SDL_WINDOWEVENT:
193 HandleWindowEvent((WindowEvent *) &event);
196 case EVENT_FINGERPRESS:
197 case EVENT_FINGERRELEASE:
198 case EVENT_FINGERMOTION:
199 HandleFingerEvent((FingerEvent *) &event);
202 case EVENT_TEXTINPUT:
203 HandleTextEvent((TextEvent *) &event);
206 case SDL_APP_WILLENTERBACKGROUND:
207 case SDL_APP_DIDENTERBACKGROUND:
208 case SDL_APP_WILLENTERFOREGROUND:
209 case SDL_APP_DIDENTERFOREGROUND:
210 HandlePauseResumeEvent((PauseResumeEvent *) &event);
214 case EVENT_KEYRELEASE:
215 HandleKeyEvent((KeyEvent *) &event);
219 HandleOtherEvents(&event);
223 // do not handle events for longer than standard frame delay period
224 if (DelayReached(&event_frame_delay, event_frame_delay_value))
229 void HandleOtherEvents(Event *event)
234 HandleExposeEvent((ExposeEvent *) event);
237 case EVENT_UNMAPNOTIFY:
239 // This causes the game to stop not only when iconified, but also
240 // when on another virtual desktop, which might be not desired.
241 SleepWhileUnmapped();
247 HandleFocusEvent((FocusChangeEvent *) event);
250 case EVENT_CLIENTMESSAGE:
251 HandleClientMessageEvent((ClientMessageEvent *) event);
254 case SDL_CONTROLLERBUTTONDOWN:
255 case SDL_CONTROLLERBUTTONUP:
256 // for any game controller button event, disable overlay buttons
257 SetOverlayEnabled(FALSE);
259 HandleSpecialGameControllerButtons(event);
262 case SDL_CONTROLLERDEVICEADDED:
263 case SDL_CONTROLLERDEVICEREMOVED:
264 case SDL_CONTROLLERAXISMOTION:
265 case SDL_JOYAXISMOTION:
266 case SDL_JOYBUTTONDOWN:
267 case SDL_JOYBUTTONUP:
268 HandleJoystickEvent(event);
272 HandleWindowManagerEvent(event);
280 static void HandleMouseCursor(void)
282 if (game_status == GAME_MODE_TITLE)
284 // when showing title screens, hide mouse pointer (if not moved)
286 if (gfx.cursor_mode != CURSOR_NONE &&
287 DelayReached(&special_cursor_delay, special_cursor_delay_value))
289 SetMouseCursor(CURSOR_NONE);
292 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
295 // when playing, display a special mouse pointer inside the playfield
297 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
298 cursor_inside_playfield &&
299 DelayReached(&special_cursor_delay, special_cursor_delay_value))
301 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
303 SetMouseCursor(CURSOR_PLAYFIELD);
306 else if (gfx.cursor_mode != CURSOR_DEFAULT)
308 SetMouseCursor(CURSOR_DEFAULT);
311 // this is set after all pending events have been processed
312 cursor_mode_last = gfx.cursor_mode;
324 // execute event related actions after pending events have been processed
325 HandleEventActions();
327 // don't use all CPU time when idle; the main loop while playing
328 // has its own synchronization and is CPU friendly, too
330 if (game_status == GAME_MODE_PLAYING)
333 // always copy backbuffer to visible screen for every video frame
336 // reset video frame delay to default (may change again while playing)
337 SetVideoFrameDelay(MenuFrameDelay);
339 if (game_status == GAME_MODE_QUIT)
344 void ClearAutoRepeatKeyEvents(void)
346 while (PendingEvent())
350 PeekEvent(&next_event);
352 // if event is repeated key press event, remove it from event queue
353 if (next_event.type == EVENT_KEYPRESS &&
354 next_event.key.repeat)
355 WaitEvent(&next_event);
361 void ClearEventQueue(void)
365 while (NextValidEvent(&event))
369 case EVENT_BUTTONRELEASE:
370 button_status = MB_RELEASED;
373 case EVENT_KEYRELEASE:
377 case SDL_CONTROLLERBUTTONUP:
378 HandleJoystickEvent(&event);
383 HandleOtherEvents(&event);
389 static void ClearPlayerMouseAction(void)
391 local_player->mouse_action.lx = 0;
392 local_player->mouse_action.ly = 0;
393 local_player->mouse_action.button = 0;
396 void ClearPlayerAction(void)
400 // simulate key release events for still pressed keys
401 key_joystick_mapping = 0;
402 for (i = 0; i < MAX_PLAYERS; i++)
403 stored_player[i].action = 0;
405 ClearJoystickState();
406 ClearPlayerMouseAction();
409 static void SetPlayerMouseAction(int mx, int my, int button)
411 int lx = getLevelFromScreenX(mx);
412 int ly = getLevelFromScreenY(my);
413 int new_button = (!local_player->mouse_action.button && button);
415 if (local_player->mouse_action.button_hint)
416 button = local_player->mouse_action.button_hint;
418 ClearPlayerMouseAction();
420 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
423 local_player->mouse_action.lx = lx;
424 local_player->mouse_action.ly = ly;
425 local_player->mouse_action.button = button;
427 if (tape.recording && tape.pausing && tape.use_mouse)
429 // un-pause a paused game only if mouse button was newly pressed down
431 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
434 SetTileCursorXY(lx, ly);
437 void SleepWhileUnmapped(void)
439 boolean window_unmapped = TRUE;
441 KeyboardAutoRepeatOn();
443 while (window_unmapped)
447 if (!WaitValidEvent(&event))
452 case EVENT_BUTTONRELEASE:
453 button_status = MB_RELEASED;
456 case EVENT_KEYRELEASE:
457 key_joystick_mapping = 0;
460 case SDL_CONTROLLERBUTTONUP:
461 HandleJoystickEvent(&event);
462 key_joystick_mapping = 0;
465 case EVENT_MAPNOTIFY:
466 window_unmapped = FALSE;
469 case EVENT_UNMAPNOTIFY:
470 // this is only to surely prevent the 'should not happen' case
471 // of recursively looping between 'SleepWhileUnmapped()' and
472 // 'HandleOtherEvents()' which usually calls this funtion.
476 HandleOtherEvents(&event);
481 if (game_status == GAME_MODE_PLAYING)
482 KeyboardAutoRepeatOffUnlessAutoplay();
485 void HandleExposeEvent(ExposeEvent *event)
489 void HandleButtonEvent(ButtonEvent *event)
491 #if DEBUG_EVENTS_BUTTON
492 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
494 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
498 // for any mouse button event, disable playfield tile cursor
499 SetTileCursorEnabled(FALSE);
501 #if defined(HAS_SCREEN_KEYBOARD)
502 if (video.shifted_up)
503 event->y += video.shifted_up_pos;
506 motion_status = FALSE;
508 if (event->type == EVENT_BUTTONPRESS)
509 button_status = event->button;
511 button_status = MB_RELEASED;
513 HandleButton(event->x, event->y, button_status, event->button);
516 void HandleMotionEvent(MotionEvent *event)
518 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
521 motion_status = TRUE;
523 #if DEBUG_EVENTS_MOTION
524 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
525 button_status, event->x, event->y);
528 HandleButton(event->x, event->y, button_status, button_status);
531 void HandleWheelEvent(WheelEvent *event)
535 #if DEBUG_EVENTS_WHEEL
537 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
538 event->which, event->x, event->y);
540 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
541 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
542 event->which, event->x, event->y,
543 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
544 "SDL_MOUSEWHEEL_FLIPPED"));
548 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
549 event->x > 0 ? MB_WHEEL_RIGHT :
550 event->y < 0 ? MB_WHEEL_DOWN :
551 event->y > 0 ? MB_WHEEL_UP : 0);
553 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
554 // accelerated mouse wheel available on Mac and Windows
555 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
557 // no accelerated mouse wheel available on Unix/Linux
558 wheel_steps = DEFAULT_WHEEL_STEPS;
561 motion_status = FALSE;
563 button_status = button_nr;
564 HandleButton(0, 0, button_status, -button_nr);
566 button_status = MB_RELEASED;
567 HandleButton(0, 0, button_status, -button_nr);
570 void HandleWindowEvent(WindowEvent *event)
572 #if DEBUG_EVENTS_WINDOW
573 int subtype = event->event;
576 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
577 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
578 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
579 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
580 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
581 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
582 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
583 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
584 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
585 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
586 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
587 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
588 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
589 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
592 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
593 event_name, event->data1, event->data2);
597 // (not needed, as the screen gets redrawn every 20 ms anyway)
598 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
599 event->event == SDL_WINDOWEVENT_RESIZED ||
600 event->event == SDL_WINDOWEVENT_EXPOSED)
604 if (event->event == SDL_WINDOWEVENT_RESIZED)
606 if (!video.fullscreen_enabled)
608 int new_window_width = event->data1;
609 int new_window_height = event->data2;
611 // if window size has changed after resizing, calculate new scaling factor
612 if (new_window_width != video.window_width ||
613 new_window_height != video.window_height)
615 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
616 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
618 // (extreme window scaling allowed, but cannot be saved permanently)
619 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
620 setup.window_scaling_percent =
621 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
622 MAX_WINDOW_SCALING_PERCENT);
624 video.window_width = new_window_width;
625 video.window_height = new_window_height;
627 if (game_status == GAME_MODE_SETUP)
628 RedrawSetupScreenAfterFullscreenToggle();
633 #if defined(PLATFORM_ANDROID)
636 int new_display_width = event->data1;
637 int new_display_height = event->data2;
639 // if fullscreen display size has changed, device has been rotated
640 if (new_display_width != video.display_width ||
641 new_display_height != video.display_height)
643 int nr = GRID_ACTIVE_NR(); // previous screen orientation
645 video.display_width = new_display_width;
646 video.display_height = new_display_height;
648 SDLSetScreenProperties();
650 // check if screen orientation has changed (should always be true here)
651 if (nr != GRID_ACTIVE_NR())
655 if (game_status == GAME_MODE_SETUP)
656 RedrawSetupScreenAfterScreenRotation(nr);
658 nr = GRID_ACTIVE_NR();
660 overlay.grid_xsize = setup.touch.grid_xsize[nr];
661 overlay.grid_ysize = setup.touch.grid_ysize[nr];
663 for (x = 0; x < MAX_GRID_XSIZE; x++)
664 for (y = 0; y < MAX_GRID_YSIZE; y++)
665 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
673 #define NUM_TOUCH_FINGERS 3
678 SDL_FingerID finger_id;
681 } touch_info[NUM_TOUCH_FINGERS];
683 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
686 int x = event->x * overlay.grid_xsize;
687 int y = event->y * overlay.grid_ysize;
688 int grid_button = overlay.grid_button[x][y];
689 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
690 Key key = (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
691 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
692 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
693 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
694 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
695 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
698 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
699 float event_x = (event->x);
700 float event_y = (event->y - ypos) / (1 - ypos);
701 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
702 event_y > 2.0 / 3.0 && event_y < 1 ?
703 setup.input[0].key.snap :
704 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
705 event_y > 2.0 / 3.0 && event_y < 1 ?
706 setup.input[0].key.drop :
707 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
708 event_y > 0 && event_y < 1.0 / 3.0 ?
709 setup.input[0].key.up :
710 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
711 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
712 setup.input[0].key.left :
713 event_x > 8.0 / 9.0 && event_x < 1 &&
714 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
715 setup.input[0].key.right :
716 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
717 event_y > 2.0 / 3.0 && event_y < 1 ?
718 setup.input[0].key.down :
721 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
723 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
727 virtual_button_pressed = (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
729 // for any touch input event, enable overlay buttons (if activated)
730 SetOverlayEnabled(TRUE);
732 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
733 getKeyNameFromKey(key), key_status_name, event->fingerId);
735 if (key_status == KEY_PRESSED)
736 overlay.grid_button_action |= grid_button_action;
738 overlay.grid_button_action &= ~grid_button_action;
740 // check if we already know this touch event's finger id
741 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
743 if (touch_info[i].touched &&
744 touch_info[i].finger_id == event->fingerId)
746 // Error(ERR_DEBUG, "MARK 1: %d", i);
752 if (i >= NUM_TOUCH_FINGERS)
754 if (key_status == KEY_PRESSED)
756 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
758 // unknown finger id -- get new, empty slot, if available
759 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
761 if (touch_info[i].counter < oldest_counter)
764 oldest_counter = touch_info[i].counter;
766 // Error(ERR_DEBUG, "MARK 2: %d", i);
769 if (!touch_info[i].touched)
771 // Error(ERR_DEBUG, "MARK 3: %d", i);
777 if (i >= NUM_TOUCH_FINGERS)
779 // all slots allocated -- use oldest slot
782 // Error(ERR_DEBUG, "MARK 4: %d", i);
787 // release of previously unknown key (should not happen)
789 if (key != KSYM_UNDEFINED)
791 HandleKey(key, KEY_RELEASED);
793 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
794 getKeyNameFromKey(key), "KEY_RELEASED", i);
799 if (i < NUM_TOUCH_FINGERS)
801 if (key_status == KEY_PRESSED)
803 if (touch_info[i].key != key)
805 if (touch_info[i].key != KSYM_UNDEFINED)
807 HandleKey(touch_info[i].key, KEY_RELEASED);
809 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
810 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
813 if (key != KSYM_UNDEFINED)
815 HandleKey(key, KEY_PRESSED);
817 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
818 getKeyNameFromKey(key), "KEY_PRESSED", i);
822 touch_info[i].touched = TRUE;
823 touch_info[i].finger_id = event->fingerId;
824 touch_info[i].counter = Counter();
825 touch_info[i].key = key;
829 if (touch_info[i].key != KSYM_UNDEFINED)
831 HandleKey(touch_info[i].key, KEY_RELEASED);
833 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
834 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
837 touch_info[i].touched = FALSE;
838 touch_info[i].finger_id = 0;
839 touch_info[i].counter = 0;
840 touch_info[i].key = 0;
845 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
847 static Key motion_key_x = KSYM_UNDEFINED;
848 static Key motion_key_y = KSYM_UNDEFINED;
849 static Key button_key = KSYM_UNDEFINED;
850 static float motion_x1, motion_y1;
851 static float button_x1, button_y1;
852 static SDL_FingerID motion_id = -1;
853 static SDL_FingerID button_id = -1;
854 int move_trigger_distance_percent = setup.touch.move_distance;
855 int drop_trigger_distance_percent = setup.touch.drop_distance;
856 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
857 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
858 float event_x = event->x;
859 float event_y = event->y;
861 if (event->type == EVENT_FINGERPRESS)
863 if (event_x > 1.0 / 3.0)
867 motion_id = event->fingerId;
872 motion_key_x = KSYM_UNDEFINED;
873 motion_key_y = KSYM_UNDEFINED;
875 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
881 button_id = event->fingerId;
886 button_key = setup.input[0].key.snap;
888 HandleKey(button_key, KEY_PRESSED);
890 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
893 else if (event->type == EVENT_FINGERRELEASE)
895 if (event->fingerId == motion_id)
899 if (motion_key_x != KSYM_UNDEFINED)
900 HandleKey(motion_key_x, KEY_RELEASED);
901 if (motion_key_y != KSYM_UNDEFINED)
902 HandleKey(motion_key_y, KEY_RELEASED);
904 motion_key_x = KSYM_UNDEFINED;
905 motion_key_y = KSYM_UNDEFINED;
907 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
909 else if (event->fingerId == button_id)
913 if (button_key != KSYM_UNDEFINED)
914 HandleKey(button_key, KEY_RELEASED);
916 button_key = KSYM_UNDEFINED;
918 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
921 else if (event->type == EVENT_FINGERMOTION)
923 if (event->fingerId == motion_id)
925 float distance_x = ABS(event_x - motion_x1);
926 float distance_y = ABS(event_y - motion_y1);
927 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
928 event_x > motion_x1 ? setup.input[0].key.right :
930 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
931 event_y > motion_y1 ? setup.input[0].key.down :
934 if (distance_x < move_trigger_distance / 2 ||
935 distance_x < distance_y)
936 new_motion_key_x = KSYM_UNDEFINED;
938 if (distance_y < move_trigger_distance / 2 ||
939 distance_y < distance_x)
940 new_motion_key_y = KSYM_UNDEFINED;
942 if (distance_x > move_trigger_distance ||
943 distance_y > move_trigger_distance)
945 if (new_motion_key_x != motion_key_x)
947 if (motion_key_x != KSYM_UNDEFINED)
948 HandleKey(motion_key_x, KEY_RELEASED);
949 if (new_motion_key_x != KSYM_UNDEFINED)
950 HandleKey(new_motion_key_x, KEY_PRESSED);
953 if (new_motion_key_y != motion_key_y)
955 if (motion_key_y != KSYM_UNDEFINED)
956 HandleKey(motion_key_y, KEY_RELEASED);
957 if (new_motion_key_y != KSYM_UNDEFINED)
958 HandleKey(new_motion_key_y, KEY_PRESSED);
964 motion_key_x = new_motion_key_x;
965 motion_key_y = new_motion_key_y;
967 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
970 else if (event->fingerId == button_id)
972 float distance_x = ABS(event_x - button_x1);
973 float distance_y = ABS(event_y - button_y1);
975 if (distance_x < drop_trigger_distance / 2 &&
976 distance_y > drop_trigger_distance)
978 if (button_key == setup.input[0].key.snap)
979 HandleKey(button_key, KEY_RELEASED);
984 button_key = setup.input[0].key.drop;
986 HandleKey(button_key, KEY_PRESSED);
988 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
994 void HandleFingerEvent(FingerEvent *event)
996 #if DEBUG_EVENTS_FINGER
997 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
998 event->type == EVENT_FINGERPRESS ? "pressed" :
999 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
1003 event->dx, event->dy,
1007 if (game_status != GAME_MODE_PLAYING)
1010 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1012 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1013 local_player->mouse_action.button_hint =
1014 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1015 event->x < 0.5 ? MB_LEFTBUTTON :
1016 event->x > 0.5 ? MB_RIGHTBUTTON :
1022 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1023 HandleFingerEvent_VirtualButtons(event);
1024 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1025 HandleFingerEvent_WipeGestures(event);
1028 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1030 static int old_mx = 0, old_my = 0;
1031 static int last_button = MB_LEFTBUTTON;
1032 static boolean touched = FALSE;
1033 static boolean tapped = FALSE;
1035 // screen tile was tapped (but finger not touching the screen anymore)
1036 // (this point will also be reached without receiving a touch event)
1037 if (tapped && !touched)
1039 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1044 // stop here if this function was not triggered by a touch event
1048 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1050 // finger started touching the screen
1060 ClearPlayerMouseAction();
1062 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1065 else if (button == MB_RELEASED && touched)
1067 // finger stopped touching the screen
1072 SetPlayerMouseAction(old_mx, old_my, last_button);
1074 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1076 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1081 // finger moved while touching the screen
1083 int old_x = getLevelFromScreenX(old_mx);
1084 int old_y = getLevelFromScreenY(old_my);
1085 int new_x = getLevelFromScreenX(mx);
1086 int new_y = getLevelFromScreenY(my);
1088 if (new_x != old_x || new_y != old_y)
1093 // finger moved left or right from (horizontal) starting position
1095 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1097 SetPlayerMouseAction(old_mx, old_my, button_nr);
1099 last_button = button_nr;
1101 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1105 // finger stays at or returned to (horizontal) starting position
1107 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1109 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1114 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1116 static int old_mx = 0, old_my = 0;
1117 static int last_button = MB_LEFTBUTTON;
1118 static boolean touched = FALSE;
1119 static boolean tapped = FALSE;
1121 // screen tile was tapped (but finger not touching the screen anymore)
1122 // (this point will also be reached without receiving a touch event)
1123 if (tapped && !touched)
1125 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1130 // stop here if this function was not triggered by a touch event
1134 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1136 // finger started touching the screen
1146 ClearPlayerMouseAction();
1148 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1151 else if (button == MB_RELEASED && touched)
1153 // finger stopped touching the screen
1158 SetPlayerMouseAction(old_mx, old_my, last_button);
1160 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1162 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1167 // finger moved while touching the screen
1169 int old_x = getLevelFromScreenX(old_mx);
1170 int old_y = getLevelFromScreenY(old_my);
1171 int new_x = getLevelFromScreenX(mx);
1172 int new_y = getLevelFromScreenY(my);
1174 if (new_x != old_x || new_y != old_y)
1176 // finger moved away from starting position
1178 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1180 // quickly alternate between clicking and releasing for maximum speed
1181 if (FrameCounter % 2 == 0)
1182 button_nr = MB_RELEASED;
1184 SetPlayerMouseAction(old_mx, old_my, button_nr);
1187 last_button = button_nr;
1191 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1195 // finger stays at or returned to starting position
1197 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1199 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1204 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1206 static int old_mx = 0, old_my = 0;
1207 static Key motion_key_x = KSYM_UNDEFINED;
1208 static Key motion_key_y = KSYM_UNDEFINED;
1209 static boolean touched = FALSE;
1210 static boolean started_on_player = FALSE;
1211 static boolean player_is_dropping = FALSE;
1212 static int player_drop_count = 0;
1213 static int last_player_x = -1;
1214 static int last_player_y = -1;
1216 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1225 started_on_player = FALSE;
1226 player_is_dropping = FALSE;
1227 player_drop_count = 0;
1231 motion_key_x = KSYM_UNDEFINED;
1232 motion_key_y = KSYM_UNDEFINED;
1234 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1237 else if (button == MB_RELEASED && touched)
1244 if (motion_key_x != KSYM_UNDEFINED)
1245 HandleKey(motion_key_x, KEY_RELEASED);
1246 if (motion_key_y != KSYM_UNDEFINED)
1247 HandleKey(motion_key_y, KEY_RELEASED);
1249 if (started_on_player)
1251 if (player_is_dropping)
1253 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1255 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1259 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1261 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1265 motion_key_x = KSYM_UNDEFINED;
1266 motion_key_y = KSYM_UNDEFINED;
1268 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1273 int src_x = local_player->jx;
1274 int src_y = local_player->jy;
1275 int dst_x = getLevelFromScreenX(old_mx);
1276 int dst_y = getLevelFromScreenY(old_my);
1277 int dx = dst_x - src_x;
1278 int dy = dst_y - src_y;
1279 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1280 dx > 0 ? setup.input[0].key.right :
1282 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1283 dy > 0 ? setup.input[0].key.down :
1286 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1287 (last_player_x != local_player->jx ||
1288 last_player_y != local_player->jy))
1290 // in case of asymmetric diagonal movement, use "preferred" direction
1292 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1294 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1295 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1297 local_player->last_move_dir = last_move_dir;
1299 // (required to prevent accidentally forcing direction for next movement)
1300 last_player_x = local_player->jx;
1301 last_player_y = local_player->jy;
1304 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1306 started_on_player = TRUE;
1307 player_drop_count = getPlayerInventorySize(0);
1308 player_is_dropping = (player_drop_count > 0);
1310 if (player_is_dropping)
1312 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1314 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1318 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1320 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1323 else if (dx != 0 || dy != 0)
1325 if (player_is_dropping &&
1326 player_drop_count == getPlayerInventorySize(0))
1328 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1330 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1331 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1333 player_is_dropping = FALSE;
1337 if (new_motion_key_x != motion_key_x)
1339 Error(ERR_DEBUG, "---------- %s %s ----------",
1340 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1341 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1343 if (motion_key_x != KSYM_UNDEFINED)
1344 HandleKey(motion_key_x, KEY_RELEASED);
1345 if (new_motion_key_x != KSYM_UNDEFINED)
1346 HandleKey(new_motion_key_x, KEY_PRESSED);
1349 if (new_motion_key_y != motion_key_y)
1351 Error(ERR_DEBUG, "---------- %s %s ----------",
1352 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1353 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1355 if (motion_key_y != KSYM_UNDEFINED)
1356 HandleKey(motion_key_y, KEY_RELEASED);
1357 if (new_motion_key_y != KSYM_UNDEFINED)
1358 HandleKey(new_motion_key_y, KEY_PRESSED);
1361 motion_key_x = new_motion_key_x;
1362 motion_key_y = new_motion_key_y;
1366 static void HandleButtonOrFinger(int mx, int my, int button)
1368 if (game_status != GAME_MODE_PLAYING)
1371 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1373 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1374 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1375 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1376 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1377 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1378 SetPlayerMouseAction(mx, my, button); // special case
1382 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1383 HandleButtonOrFinger_FollowFinger(mx, my, button);
1387 static boolean checkTextInputKeyModState(void)
1389 // when playing, only handle raw key events and ignore text input
1390 if (game_status == GAME_MODE_PLAYING)
1393 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1396 void HandleTextEvent(TextEvent *event)
1398 char *text = event->text;
1399 Key key = getKeyFromKeyName(text);
1401 #if DEBUG_EVENTS_TEXT
1402 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1405 text[0], (int)(text[0]),
1407 getKeyNameFromKey(key),
1411 #if !defined(HAS_SCREEN_KEYBOARD)
1412 // non-mobile devices: only handle key input with modifier keys pressed here
1413 // (every other key input is handled directly as physical key input event)
1414 if (!checkTextInputKeyModState())
1418 // process text input as "classic" (with uppercase etc.) key input event
1419 HandleKey(key, KEY_PRESSED);
1420 HandleKey(key, KEY_RELEASED);
1423 void HandlePauseResumeEvent(PauseResumeEvent *event)
1425 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1429 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1435 void HandleKeyEvent(KeyEvent *event)
1437 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1438 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1439 Key key = GetEventKey(event, with_modifiers);
1440 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1442 #if DEBUG_EVENTS_KEY
1443 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1444 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1445 event->keysym.scancode,
1450 getKeyNameFromKey(key));
1453 #if defined(PLATFORM_ANDROID)
1454 if (key == KSYM_Back)
1456 // always map the "back" button to the "escape" key on Android devices
1459 else if (key == KSYM_Menu)
1461 // the "menu" button can be used to toggle displaying virtual buttons
1462 if (key_status == KEY_PRESSED)
1463 SetOverlayEnabled(!GetOverlayEnabled());
1467 // for any other "real" key event, disable virtual buttons
1468 SetOverlayEnabled(FALSE);
1472 HandleKeyModState(keymod, key_status);
1474 // only handle raw key input without text modifier keys pressed
1475 if (!checkTextInputKeyModState())
1476 HandleKey(key, key_status);
1479 void HandleFocusEvent(FocusChangeEvent *event)
1481 static int old_joystick_status = -1;
1483 if (event->type == EVENT_FOCUSOUT)
1485 KeyboardAutoRepeatOn();
1486 old_joystick_status = joystick.status;
1487 joystick.status = JOYSTICK_NOT_AVAILABLE;
1489 ClearPlayerAction();
1491 else if (event->type == EVENT_FOCUSIN)
1493 /* When there are two Rocks'n'Diamonds windows which overlap and
1494 the player moves the pointer from one game window to the other,
1495 a 'FocusOut' event is generated for the window the pointer is
1496 leaving and a 'FocusIn' event is generated for the window the
1497 pointer is entering. In some cases, it can happen that the
1498 'FocusIn' event is handled by the one game process before the
1499 'FocusOut' event by the other game process. In this case the
1500 X11 environment would end up with activated keyboard auto repeat,
1501 because unfortunately this is a global setting and not (which
1502 would be far better) set for each X11 window individually.
1503 The effect would be keyboard auto repeat while playing the game
1504 (game_status == GAME_MODE_PLAYING), which is not desired.
1505 To avoid this special case, we just wait 1/10 second before
1506 processing the 'FocusIn' event. */
1508 if (game_status == GAME_MODE_PLAYING)
1511 KeyboardAutoRepeatOffUnlessAutoplay();
1514 if (old_joystick_status != -1)
1515 joystick.status = old_joystick_status;
1519 void HandleClientMessageEvent(ClientMessageEvent *event)
1521 if (CheckCloseWindowEvent(event))
1525 void HandleWindowManagerEvent(Event *event)
1527 SDLHandleWindowManagerEvent(event);
1530 void HandleButton(int mx, int my, int button, int button_nr)
1532 static int old_mx = 0, old_my = 0;
1533 boolean button_hold = FALSE;
1534 boolean handle_gadgets = TRUE;
1540 button_nr = -button_nr;
1549 #if defined(PLATFORM_ANDROID)
1550 // when playing, only handle gadgets when using "follow finger" controls
1551 // or when using touch controls in combination with the MM game engine
1552 // or when using gadgets that do not overlap with virtual buttons
1554 (game_status != GAME_MODE_PLAYING ||
1555 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1556 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1557 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1558 !virtual_button_pressed));
1561 if (HandleGlobalAnimClicks(mx, my, button))
1563 // do not handle this button event anymore
1564 return; // force mouse event not to be handled at all
1567 if (handle_gadgets && HandleGadgets(mx, my, button))
1569 // do not handle this button event anymore
1570 mx = my = -32; // force mouse event to be outside screen tiles
1573 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1576 // do not use scroll wheel button events for anything other than gadgets
1577 if (IS_WHEEL_BUTTON(button_nr))
1580 switch (game_status)
1582 case GAME_MODE_TITLE:
1583 HandleTitleScreen(mx, my, 0, 0, button);
1586 case GAME_MODE_MAIN:
1587 HandleMainMenu(mx, my, 0, 0, button);
1590 case GAME_MODE_PSEUDO_TYPENAME:
1591 HandleTypeName(0, KSYM_Return);
1594 case GAME_MODE_LEVELS:
1595 HandleChooseLevelSet(mx, my, 0, 0, button);
1598 case GAME_MODE_LEVELNR:
1599 HandleChooseLevelNr(mx, my, 0, 0, button);
1602 case GAME_MODE_SCORES:
1603 HandleHallOfFame(0, 0, 0, 0, button);
1606 case GAME_MODE_EDITOR:
1607 HandleLevelEditorIdle();
1610 case GAME_MODE_INFO:
1611 HandleInfoScreen(mx, my, 0, 0, button);
1614 case GAME_MODE_SETUP:
1615 HandleSetupScreen(mx, my, 0, 0, button);
1618 case GAME_MODE_PLAYING:
1619 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1620 HandleButtonOrFinger(mx, my, button);
1622 SetPlayerMouseAction(mx, my, button);
1625 if (button == MB_PRESSED && !motion_status && !button_hold &&
1626 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1627 DumpTileFromScreen(mx, my);
1637 static boolean is_string_suffix(char *string, char *suffix)
1639 int string_len = strlen(string);
1640 int suffix_len = strlen(suffix);
1642 if (suffix_len > string_len)
1645 return (strEqual(&string[string_len - suffix_len], suffix));
1648 #define MAX_CHEAT_INPUT_LEN 32
1650 static void HandleKeysSpecial(Key key)
1652 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1653 char letter = getCharFromKey(key);
1654 int cheat_input_len = strlen(cheat_input);
1660 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1662 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1663 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1665 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1668 cheat_input[cheat_input_len++] = letter;
1669 cheat_input[cheat_input_len] = '\0';
1671 #if DEBUG_EVENTS_KEY
1672 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1675 if (game_status == GAME_MODE_MAIN)
1677 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1678 is_string_suffix(cheat_input, ":ist"))
1680 InsertSolutionTape();
1682 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1683 is_string_suffix(cheat_input, ":pst"))
1687 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1688 is_string_suffix(cheat_input, ":rg"))
1690 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1693 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1694 is_string_suffix(cheat_input, ":rs"))
1696 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1699 else if (is_string_suffix(cheat_input, ":reload-music") ||
1700 is_string_suffix(cheat_input, ":rm"))
1702 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1705 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1706 is_string_suffix(cheat_input, ":ra"))
1708 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1709 1 << ARTWORK_TYPE_SOUNDS |
1710 1 << ARTWORK_TYPE_MUSIC);
1713 else if (is_string_suffix(cheat_input, ":dump-level") ||
1714 is_string_suffix(cheat_input, ":dl"))
1718 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1719 is_string_suffix(cheat_input, ":dt"))
1723 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1724 is_string_suffix(cheat_input, ":ft"))
1726 /* fix single-player tapes that contain player input for more than one
1727 player (due to a bug in 3.3.1.2 and earlier versions), which results
1728 in playing levels with more than one player in multi-player mode,
1729 even though the tape was originally recorded in single-player mode */
1731 // remove player input actions for all players but the first one
1732 for (i = 1; i < MAX_PLAYERS; i++)
1733 tape.player_participates[i] = FALSE;
1735 tape.changed = TRUE;
1737 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1738 is_string_suffix(cheat_input, ":snl"))
1740 SaveNativeLevel(&level);
1742 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1743 is_string_suffix(cheat_input, ":fps"))
1745 global.show_frames_per_second = !global.show_frames_per_second;
1748 else if (game_status == GAME_MODE_PLAYING)
1751 if (is_string_suffix(cheat_input, ".q"))
1752 DEBUG_SetMaximumDynamite();
1755 else if (game_status == GAME_MODE_EDITOR)
1757 if (is_string_suffix(cheat_input, ":dump-brush") ||
1758 is_string_suffix(cheat_input, ":DB"))
1762 else if (is_string_suffix(cheat_input, ":DDB"))
1767 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1769 if (letter == 'x') // copy brush to clipboard (small size)
1771 CopyBrushToClipboard_Small();
1773 else if (letter == 'c') // copy brush to clipboard (normal size)
1775 CopyBrushToClipboard();
1777 else if (letter == 'v') // paste brush from Clipboard
1779 CopyClipboardToBrush();
1784 // special key shortcuts for all game modes
1785 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1786 is_string_suffix(cheat_input, ":dea") ||
1787 is_string_suffix(cheat_input, ":DEA"))
1789 DumpGadgetIdentifiers();
1790 DumpScreenIdentifiers();
1794 boolean HandleKeysDebug(Key key, int key_status)
1799 if (key_status != KEY_PRESSED)
1802 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1804 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1806 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1808 if (key == setup.debug.frame_delay_key[i] &&
1809 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1811 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1812 setup.debug.frame_delay[i] : setup.game_frame_delay);
1814 if (!setup.debug.frame_delay_game_only)
1815 MenuFrameDelay = GameFrameDelay;
1817 SetVideoFrameDelay(GameFrameDelay);
1819 if (GameFrameDelay > ONE_SECOND_DELAY)
1820 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1821 else if (GameFrameDelay != 0)
1822 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1823 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1824 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1826 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1833 if (game_status == GAME_MODE_PLAYING)
1837 options.debug = !options.debug;
1839 Error(ERR_DEBUG, "debug mode %s",
1840 (options.debug ? "enabled" : "disabled"));
1844 else if (key == KSYM_v)
1846 Error(ERR_DEBUG, "currently using game engine version %d",
1847 game.engine_version);
1857 void HandleKey(Key key, int key_status)
1859 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1860 static boolean ignore_repeated_key = FALSE;
1861 static struct SetupKeyboardInfo ski;
1862 static struct SetupShortcutInfo ssi;
1871 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1872 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1873 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1874 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1875 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1876 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1881 if (HandleKeysDebug(key, key_status))
1882 return; // do not handle already processed keys again
1884 // map special keys (media keys / remote control buttons) to default keys
1885 if (key == KSYM_PlayPause)
1887 else if (key == KSYM_Select)
1890 HandleSpecialGameControllerKeys(key, key_status);
1892 if (game_status == GAME_MODE_PLAYING)
1894 // only needed for single-step tape recording mode
1895 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1898 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1900 byte key_action = 0;
1902 if (setup.input[pnr].use_joystick)
1905 ski = setup.input[pnr].key;
1907 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1908 if (key == *key_info[i].key_custom)
1909 key_action |= key_info[i].action;
1911 // use combined snap+direction keys for the first player only
1914 ssi = setup.shortcut;
1916 for (i = 0; i < NUM_DIRECTIONS; i++)
1917 if (key == *key_info[i].key_snap)
1918 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1921 if (key_status == KEY_PRESSED)
1922 stored_player[pnr].action |= key_action;
1924 stored_player[pnr].action &= ~key_action;
1926 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
1928 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1930 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1932 // if snap key already pressed, keep pause mode when releasing
1933 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1934 has_snapped[pnr] = TRUE;
1936 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1938 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1940 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1941 getRedDiskReleaseFlag_SP() == 0)
1943 // add a single inactive frame before dropping starts
1944 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1945 stored_player[pnr].force_dropping = TRUE;
1948 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1950 // if snap key was pressed without direction, leave pause mode
1951 if (!has_snapped[pnr])
1952 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1954 has_snapped[pnr] = FALSE;
1957 else if (tape.recording && tape.pausing && !tape.use_mouse)
1959 // prevent key release events from un-pausing a paused game
1960 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1961 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1964 // for MM style levels, handle in-game keyboard input in HandleJoystick()
1965 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1971 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1972 if (key == key_info[i].key_default)
1973 joy |= key_info[i].action;
1978 if (key_status == KEY_PRESSED)
1979 key_joystick_mapping |= joy;
1981 key_joystick_mapping &= ~joy;
1986 if (game_status != GAME_MODE_PLAYING)
1987 key_joystick_mapping = 0;
1989 if (key_status == KEY_RELEASED)
1991 // reset flag to ignore repeated "key pressed" events after key release
1992 ignore_repeated_key = FALSE;
1997 if ((key == KSYM_F11 ||
1998 ((key == KSYM_Return ||
1999 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2000 video.fullscreen_available &&
2001 !ignore_repeated_key)
2003 setup.fullscreen = !setup.fullscreen;
2005 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2007 if (game_status == GAME_MODE_SETUP)
2008 RedrawSetupScreenAfterFullscreenToggle();
2010 // set flag to ignore repeated "key pressed" events
2011 ignore_repeated_key = TRUE;
2016 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2017 key == KSYM_minus || key == KSYM_KP_Subtract ||
2018 key == KSYM_plus || key == KSYM_KP_Add ||
2019 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2020 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2021 video.window_scaling_available &&
2022 !video.fullscreen_enabled)
2024 if (key == KSYM_0 || key == KSYM_KP_0)
2025 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2026 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2027 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2029 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2031 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2032 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2033 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2034 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2036 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2038 if (game_status == GAME_MODE_SETUP)
2039 RedrawSetupScreenAfterFullscreenToggle();
2044 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2045 key == KSYM_Return ||
2046 key == KSYM_Escape)))
2048 // do not handle this key event anymore
2049 if (key != KSYM_Escape) // always allow ESC key to be handled
2053 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2054 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2061 if (game_status == GAME_MODE_MAIN &&
2062 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2064 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2069 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2071 if (key == setup.shortcut.save_game)
2073 else if (key == setup.shortcut.load_game)
2075 else if (key == setup.shortcut.toggle_pause)
2076 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2078 HandleTapeButtonKeys(key);
2079 HandleSoundButtonKeys(key);
2082 if (game_status == GAME_MODE_PLAYING && !network_playing)
2084 int centered_player_nr_next = -999;
2086 if (key == setup.shortcut.focus_player_all)
2087 centered_player_nr_next = -1;
2089 for (i = 0; i < MAX_PLAYERS; i++)
2090 if (key == setup.shortcut.focus_player[i])
2091 centered_player_nr_next = i;
2093 if (centered_player_nr_next != -999)
2095 game.centered_player_nr_next = centered_player_nr_next;
2096 game.set_centered_player = TRUE;
2100 tape.centered_player_nr_next = game.centered_player_nr_next;
2101 tape.set_centered_player = TRUE;
2106 HandleKeysSpecial(key);
2108 if (HandleGadgetsKeyInput(key))
2109 return; // do not handle already processed keys again
2111 switch (game_status)
2113 case GAME_MODE_PSEUDO_TYPENAME:
2114 HandleTypeName(0, key);
2117 case GAME_MODE_TITLE:
2118 case GAME_MODE_MAIN:
2119 case GAME_MODE_LEVELS:
2120 case GAME_MODE_LEVELNR:
2121 case GAME_MODE_SETUP:
2122 case GAME_MODE_INFO:
2123 case GAME_MODE_SCORES:
2125 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2132 if (game_status == GAME_MODE_TITLE)
2133 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2134 else if (game_status == GAME_MODE_MAIN)
2135 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2136 else if (game_status == GAME_MODE_LEVELS)
2137 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2138 else if (game_status == GAME_MODE_LEVELNR)
2139 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2140 else if (game_status == GAME_MODE_SETUP)
2141 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2142 else if (game_status == GAME_MODE_INFO)
2143 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2144 else if (game_status == GAME_MODE_SCORES)
2145 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2149 if (game_status != GAME_MODE_MAIN)
2150 FadeSkipNextFadeIn();
2152 if (game_status == GAME_MODE_TITLE)
2153 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2154 else if (game_status == GAME_MODE_LEVELS)
2155 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2156 else if (game_status == GAME_MODE_LEVELNR)
2157 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2158 else if (game_status == GAME_MODE_SETUP)
2159 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2160 else if (game_status == GAME_MODE_INFO)
2161 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2162 else if (game_status == GAME_MODE_SCORES)
2163 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2167 if (game_status == GAME_MODE_LEVELS)
2168 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2169 else if (game_status == GAME_MODE_LEVELNR)
2170 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2171 else if (game_status == GAME_MODE_SETUP)
2172 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2173 else if (game_status == GAME_MODE_INFO)
2174 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2175 else if (game_status == GAME_MODE_SCORES)
2176 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2179 case KSYM_Page_Down:
2180 if (game_status == GAME_MODE_LEVELS)
2181 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2182 else if (game_status == GAME_MODE_LEVELNR)
2183 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2184 else if (game_status == GAME_MODE_SETUP)
2185 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2186 else if (game_status == GAME_MODE_INFO)
2187 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2188 else if (game_status == GAME_MODE_SCORES)
2189 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2197 case GAME_MODE_EDITOR:
2198 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2199 HandleLevelEditorKeyInput(key);
2202 case GAME_MODE_PLAYING:
2207 RequestQuitGame(setup.ask_on_escape);
2217 if (key == KSYM_Escape)
2219 SetGameStatus(GAME_MODE_MAIN);
2228 void HandleNoEvent(void)
2230 HandleMouseCursor();
2232 switch (game_status)
2234 case GAME_MODE_PLAYING:
2235 HandleButtonOrFinger(-1, -1, -1);
2240 void HandleEventActions(void)
2242 // if (button_status && game_status != GAME_MODE_PLAYING)
2243 if (button_status && (game_status != GAME_MODE_PLAYING ||
2245 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2247 HandleButton(0, 0, button_status, -button_status);
2254 if (network.enabled)
2257 switch (game_status)
2259 case GAME_MODE_MAIN:
2260 DrawPreviewLevelAnimation();
2263 case GAME_MODE_EDITOR:
2264 HandleLevelEditorIdle();
2272 static void HandleTileCursor(int dx, int dy, int button)
2275 ClearPlayerMouseAction();
2282 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2283 (dx < 0 ? MB_LEFTBUTTON :
2284 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2286 else if (!tile_cursor.moving)
2288 int old_xpos = tile_cursor.xpos;
2289 int old_ypos = tile_cursor.ypos;
2290 int new_xpos = old_xpos;
2291 int new_ypos = old_ypos;
2293 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2294 new_xpos = old_xpos + dx;
2296 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2297 new_ypos = old_ypos + dy;
2299 SetTileCursorTargetXY(new_xpos, new_ypos);
2303 static int HandleJoystickForAllPlayers(void)
2307 boolean no_joysticks_configured = TRUE;
2308 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2309 static byte joy_action_last[MAX_PLAYERS];
2311 for (i = 0; i < MAX_PLAYERS; i++)
2312 if (setup.input[i].use_joystick)
2313 no_joysticks_configured = FALSE;
2315 // if no joysticks configured, map connected joysticks to players
2316 if (no_joysticks_configured)
2317 use_as_joystick_nr = TRUE;
2319 for (i = 0; i < MAX_PLAYERS; i++)
2321 byte joy_action = 0;
2323 joy_action = JoystickExt(i, use_as_joystick_nr);
2324 result |= joy_action;
2326 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2327 joy_action != joy_action_last[i])
2328 stored_player[i].action = joy_action;
2330 joy_action_last[i] = joy_action;
2336 void HandleJoystick(void)
2338 static unsigned int joytest_delay = 0;
2339 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2340 static int joytest_last = 0;
2341 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2342 int delay_value = GADGET_FRAME_DELAY;
2343 int joystick = HandleJoystickForAllPlayers();
2344 int keyboard = key_joystick_mapping;
2345 int joy = (joystick | keyboard);
2346 int joytest = joystick;
2347 int left = joy & JOY_LEFT;
2348 int right = joy & JOY_RIGHT;
2349 int up = joy & JOY_UP;
2350 int down = joy & JOY_DOWN;
2351 int button = joy & JOY_BUTTON;
2352 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2353 int dx = (left ? -1 : right ? 1 : 0);
2354 int dy = (up ? -1 : down ? 1 : 0);
2355 boolean use_delay_value_first = (joytest != joytest_last);
2357 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2359 // do not handle this button event anymore
2363 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2365 if (game_status == GAME_MODE_PLAYING)
2367 // when playing MM style levels, also use delay for keyboard events
2368 joytest |= keyboard;
2370 // only use first delay value for new events, but not for changed events
2371 use_delay_value_first = (!joytest != !joytest_last);
2373 // only use delay after the initial keyboard event
2377 // for any joystick or keyboard event, enable playfield tile cursor
2378 if (dx || dy || button)
2379 SetTileCursorEnabled(TRUE);
2382 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2384 // delay joystick/keyboard actions if axes/keys continually pressed
2385 newbutton = dx = dy = 0;
2389 // first start with longer delay, then continue with shorter delay
2390 joytest_delay_value =
2391 (use_delay_value_first ? delay_value_first : delay_value);
2394 joytest_last = joytest;
2396 switch (game_status)
2398 case GAME_MODE_TITLE:
2399 case GAME_MODE_MAIN:
2400 case GAME_MODE_LEVELS:
2401 case GAME_MODE_LEVELNR:
2402 case GAME_MODE_SETUP:
2403 case GAME_MODE_INFO:
2404 case GAME_MODE_SCORES:
2406 if (anyTextGadgetActive())
2409 if (game_status == GAME_MODE_TITLE)
2410 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2411 else if (game_status == GAME_MODE_MAIN)
2412 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2413 else if (game_status == GAME_MODE_LEVELS)
2414 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2415 else if (game_status == GAME_MODE_LEVELNR)
2416 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2417 else if (game_status == GAME_MODE_SETUP)
2418 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2419 else if (game_status == GAME_MODE_INFO)
2420 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2421 else if (game_status == GAME_MODE_SCORES)
2422 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2427 case GAME_MODE_PLAYING:
2429 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2430 if (tape.playing || keyboard)
2431 newbutton = ((joy & JOY_BUTTON) != 0);
2434 if (newbutton && game.all_players_gone)
2441 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2443 if (joystick & JOY_ACTION)
2444 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2446 else if (tape.recording && tape.pausing && !tape.use_mouse)
2448 if (joystick & JOY_ACTION)
2449 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2452 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2453 HandleTileCursor(dx, dy, button);
2462 void HandleSpecialGameControllerButtons(Event *event)
2467 switch (event->type)
2469 case SDL_CONTROLLERBUTTONDOWN:
2470 key_status = KEY_PRESSED;
2473 case SDL_CONTROLLERBUTTONUP:
2474 key_status = KEY_RELEASED;
2481 switch (event->cbutton.button)
2483 case SDL_CONTROLLER_BUTTON_START:
2487 case SDL_CONTROLLER_BUTTON_BACK:
2495 HandleKey(key, key_status);
2498 void HandleSpecialGameControllerKeys(Key key, int key_status)
2500 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2501 int button = SDL_CONTROLLER_BUTTON_INVALID;
2503 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2504 if (key == KSYM_Rewind)
2505 button = SDL_CONTROLLER_BUTTON_A;
2506 else if (key == KSYM_FastForward || key == KSYM_Menu)
2507 button = SDL_CONTROLLER_BUTTON_B;
2509 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2513 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2514 SDL_CONTROLLERBUTTONUP);
2516 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2517 event.cbutton.button = button;
2518 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2521 HandleJoystickEvent(&event);
2526 boolean DoKeysymAction(int keysym)
2530 Key key = (Key)(-keysym);
2532 HandleKey(key, KEY_PRESSED);
2533 HandleKey(key, KEY_RELEASED);