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 HandleDropFileEvent(event);
276 HandleDropTextEvent(event);
284 static void HandleMouseCursor(void)
286 if (game_status == GAME_MODE_TITLE)
288 // when showing title screens, hide mouse pointer (if not moved)
290 if (gfx.cursor_mode != CURSOR_NONE &&
291 DelayReached(&special_cursor_delay, special_cursor_delay_value))
293 SetMouseCursor(CURSOR_NONE);
296 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
299 // when playing, display a special mouse pointer inside the playfield
301 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
302 cursor_inside_playfield &&
303 DelayReached(&special_cursor_delay, special_cursor_delay_value))
305 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
307 SetMouseCursor(CURSOR_PLAYFIELD);
310 else if (gfx.cursor_mode != CURSOR_DEFAULT)
312 SetMouseCursor(CURSOR_DEFAULT);
315 // this is set after all pending events have been processed
316 cursor_mode_last = gfx.cursor_mode;
328 // execute event related actions after pending events have been processed
329 HandleEventActions();
331 // don't use all CPU time when idle; the main loop while playing
332 // has its own synchronization and is CPU friendly, too
334 if (game_status == GAME_MODE_PLAYING)
337 // always copy backbuffer to visible screen for every video frame
340 // reset video frame delay to default (may change again while playing)
341 SetVideoFrameDelay(MenuFrameDelay);
343 if (game_status == GAME_MODE_QUIT)
348 void ClearAutoRepeatKeyEvents(void)
350 while (PendingEvent())
354 PeekEvent(&next_event);
356 // if event is repeated key press event, remove it from event queue
357 if (next_event.type == EVENT_KEYPRESS &&
358 next_event.key.repeat)
359 WaitEvent(&next_event);
365 void ClearEventQueue(void)
369 while (NextValidEvent(&event))
373 case EVENT_BUTTONRELEASE:
374 button_status = MB_RELEASED;
377 case EVENT_KEYRELEASE:
381 case SDL_CONTROLLERBUTTONUP:
382 HandleJoystickEvent(&event);
387 HandleOtherEvents(&event);
393 static void ClearPlayerMouseAction(void)
395 local_player->mouse_action.lx = 0;
396 local_player->mouse_action.ly = 0;
397 local_player->mouse_action.button = 0;
400 void ClearPlayerAction(void)
404 // simulate key release events for still pressed keys
405 key_joystick_mapping = 0;
406 for (i = 0; i < MAX_PLAYERS; i++)
407 stored_player[i].action = 0;
409 ClearJoystickState();
410 ClearPlayerMouseAction();
413 static void SetPlayerMouseAction(int mx, int my, int button)
415 int lx = getLevelFromScreenX(mx);
416 int ly = getLevelFromScreenY(my);
417 int new_button = (!local_player->mouse_action.button && button);
419 if (local_player->mouse_action.button_hint)
420 button = local_player->mouse_action.button_hint;
422 ClearPlayerMouseAction();
424 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
427 local_player->mouse_action.lx = lx;
428 local_player->mouse_action.ly = ly;
429 local_player->mouse_action.button = button;
431 if (tape.recording && tape.pausing && tape.use_mouse)
433 // un-pause a paused game only if mouse button was newly pressed down
435 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
438 SetTileCursorXY(lx, ly);
441 void SleepWhileUnmapped(void)
443 boolean window_unmapped = TRUE;
445 KeyboardAutoRepeatOn();
447 while (window_unmapped)
451 if (!WaitValidEvent(&event))
456 case EVENT_BUTTONRELEASE:
457 button_status = MB_RELEASED;
460 case EVENT_KEYRELEASE:
461 key_joystick_mapping = 0;
464 case SDL_CONTROLLERBUTTONUP:
465 HandleJoystickEvent(&event);
466 key_joystick_mapping = 0;
469 case EVENT_MAPNOTIFY:
470 window_unmapped = FALSE;
473 case EVENT_UNMAPNOTIFY:
474 // this is only to surely prevent the 'should not happen' case
475 // of recursively looping between 'SleepWhileUnmapped()' and
476 // 'HandleOtherEvents()' which usually calls this funtion.
480 HandleOtherEvents(&event);
485 if (game_status == GAME_MODE_PLAYING)
486 KeyboardAutoRepeatOffUnlessAutoplay();
489 void HandleExposeEvent(ExposeEvent *event)
493 void HandleButtonEvent(ButtonEvent *event)
495 #if DEBUG_EVENTS_BUTTON
496 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
498 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
502 // for any mouse button event, disable playfield tile cursor
503 SetTileCursorEnabled(FALSE);
505 #if defined(HAS_SCREEN_KEYBOARD)
506 if (video.shifted_up)
507 event->y += video.shifted_up_pos;
510 motion_status = FALSE;
512 if (event->type == EVENT_BUTTONPRESS)
513 button_status = event->button;
515 button_status = MB_RELEASED;
517 HandleButton(event->x, event->y, button_status, event->button);
520 void HandleMotionEvent(MotionEvent *event)
522 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
525 motion_status = TRUE;
527 #if DEBUG_EVENTS_MOTION
528 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
529 button_status, event->x, event->y);
532 HandleButton(event->x, event->y, button_status, button_status);
535 void HandleWheelEvent(WheelEvent *event)
539 #if DEBUG_EVENTS_WHEEL
541 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
542 event->which, event->x, event->y);
544 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
545 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
546 event->which, event->x, event->y,
547 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
548 "SDL_MOUSEWHEEL_FLIPPED"));
552 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
553 event->x > 0 ? MB_WHEEL_RIGHT :
554 event->y < 0 ? MB_WHEEL_DOWN :
555 event->y > 0 ? MB_WHEEL_UP : 0);
557 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
558 // accelerated mouse wheel available on Mac and Windows
559 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
561 // no accelerated mouse wheel available on Unix/Linux
562 wheel_steps = DEFAULT_WHEEL_STEPS;
565 motion_status = FALSE;
567 button_status = button_nr;
568 HandleButton(0, 0, button_status, -button_nr);
570 button_status = MB_RELEASED;
571 HandleButton(0, 0, button_status, -button_nr);
574 void HandleWindowEvent(WindowEvent *event)
576 #if DEBUG_EVENTS_WINDOW
577 int subtype = event->event;
580 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
581 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
582 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
583 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
584 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
585 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
586 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
587 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
588 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
589 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
590 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
591 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
592 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
593 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
596 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
597 event_name, event->data1, event->data2);
601 // (not needed, as the screen gets redrawn every 20 ms anyway)
602 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
603 event->event == SDL_WINDOWEVENT_RESIZED ||
604 event->event == SDL_WINDOWEVENT_EXPOSED)
608 if (event->event == SDL_WINDOWEVENT_RESIZED)
610 if (!video.fullscreen_enabled)
612 int new_window_width = event->data1;
613 int new_window_height = event->data2;
615 // if window size has changed after resizing, calculate new scaling factor
616 if (new_window_width != video.window_width ||
617 new_window_height != video.window_height)
619 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
620 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
622 // (extreme window scaling allowed, but cannot be saved permanently)
623 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
624 setup.window_scaling_percent =
625 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
626 MAX_WINDOW_SCALING_PERCENT);
628 video.window_width = new_window_width;
629 video.window_height = new_window_height;
631 if (game_status == GAME_MODE_SETUP)
632 RedrawSetupScreenAfterFullscreenToggle();
637 #if defined(PLATFORM_ANDROID)
640 int new_display_width = event->data1;
641 int new_display_height = event->data2;
643 // if fullscreen display size has changed, device has been rotated
644 if (new_display_width != video.display_width ||
645 new_display_height != video.display_height)
647 int nr = GRID_ACTIVE_NR(); // previous screen orientation
649 video.display_width = new_display_width;
650 video.display_height = new_display_height;
652 SDLSetScreenProperties();
654 // check if screen orientation has changed (should always be true here)
655 if (nr != GRID_ACTIVE_NR())
659 if (game_status == GAME_MODE_SETUP)
660 RedrawSetupScreenAfterScreenRotation(nr);
662 nr = GRID_ACTIVE_NR();
664 overlay.grid_xsize = setup.touch.grid_xsize[nr];
665 overlay.grid_ysize = setup.touch.grid_ysize[nr];
667 for (x = 0; x < MAX_GRID_XSIZE; x++)
668 for (y = 0; y < MAX_GRID_YSIZE; y++)
669 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
677 #define NUM_TOUCH_FINGERS 3
682 SDL_FingerID finger_id;
685 } touch_info[NUM_TOUCH_FINGERS];
687 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
690 int x = event->x * overlay.grid_xsize;
691 int y = event->y * overlay.grid_ysize;
692 int grid_button = overlay.grid_button[x][y];
693 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
694 Key key = (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
695 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
696 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
697 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
698 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
699 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
702 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
703 float event_x = (event->x);
704 float event_y = (event->y - ypos) / (1 - ypos);
705 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
706 event_y > 2.0 / 3.0 && event_y < 1 ?
707 setup.input[0].key.snap :
708 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
709 event_y > 2.0 / 3.0 && event_y < 1 ?
710 setup.input[0].key.drop :
711 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
712 event_y > 0 && event_y < 1.0 / 3.0 ?
713 setup.input[0].key.up :
714 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
715 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
716 setup.input[0].key.left :
717 event_x > 8.0 / 9.0 && event_x < 1 &&
718 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
719 setup.input[0].key.right :
720 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
721 event_y > 2.0 / 3.0 && event_y < 1 ?
722 setup.input[0].key.down :
725 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
727 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
731 virtual_button_pressed = (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
733 // for any touch input event, enable overlay buttons (if activated)
734 SetOverlayEnabled(TRUE);
736 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
737 getKeyNameFromKey(key), key_status_name, event->fingerId);
739 if (key_status == KEY_PRESSED)
740 overlay.grid_button_action |= grid_button_action;
742 overlay.grid_button_action &= ~grid_button_action;
744 // check if we already know this touch event's finger id
745 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
747 if (touch_info[i].touched &&
748 touch_info[i].finger_id == event->fingerId)
750 // Error(ERR_DEBUG, "MARK 1: %d", i);
756 if (i >= NUM_TOUCH_FINGERS)
758 if (key_status == KEY_PRESSED)
760 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
762 // unknown finger id -- get new, empty slot, if available
763 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
765 if (touch_info[i].counter < oldest_counter)
768 oldest_counter = touch_info[i].counter;
770 // Error(ERR_DEBUG, "MARK 2: %d", i);
773 if (!touch_info[i].touched)
775 // Error(ERR_DEBUG, "MARK 3: %d", i);
781 if (i >= NUM_TOUCH_FINGERS)
783 // all slots allocated -- use oldest slot
786 // Error(ERR_DEBUG, "MARK 4: %d", i);
791 // release of previously unknown key (should not happen)
793 if (key != KSYM_UNDEFINED)
795 HandleKey(key, KEY_RELEASED);
797 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
798 getKeyNameFromKey(key), "KEY_RELEASED", i);
803 if (i < NUM_TOUCH_FINGERS)
805 if (key_status == KEY_PRESSED)
807 if (touch_info[i].key != key)
809 if (touch_info[i].key != KSYM_UNDEFINED)
811 HandleKey(touch_info[i].key, KEY_RELEASED);
813 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
814 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
817 if (key != KSYM_UNDEFINED)
819 HandleKey(key, KEY_PRESSED);
821 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
822 getKeyNameFromKey(key), "KEY_PRESSED", i);
826 touch_info[i].touched = TRUE;
827 touch_info[i].finger_id = event->fingerId;
828 touch_info[i].counter = Counter();
829 touch_info[i].key = key;
833 if (touch_info[i].key != KSYM_UNDEFINED)
835 HandleKey(touch_info[i].key, KEY_RELEASED);
837 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
838 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
841 touch_info[i].touched = FALSE;
842 touch_info[i].finger_id = 0;
843 touch_info[i].counter = 0;
844 touch_info[i].key = 0;
849 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
851 static Key motion_key_x = KSYM_UNDEFINED;
852 static Key motion_key_y = KSYM_UNDEFINED;
853 static Key button_key = KSYM_UNDEFINED;
854 static float motion_x1, motion_y1;
855 static float button_x1, button_y1;
856 static SDL_FingerID motion_id = -1;
857 static SDL_FingerID button_id = -1;
858 int move_trigger_distance_percent = setup.touch.move_distance;
859 int drop_trigger_distance_percent = setup.touch.drop_distance;
860 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
861 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
862 float event_x = event->x;
863 float event_y = event->y;
865 if (event->type == EVENT_FINGERPRESS)
867 if (event_x > 1.0 / 3.0)
871 motion_id = event->fingerId;
876 motion_key_x = KSYM_UNDEFINED;
877 motion_key_y = KSYM_UNDEFINED;
879 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
885 button_id = event->fingerId;
890 button_key = setup.input[0].key.snap;
892 HandleKey(button_key, KEY_PRESSED);
894 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
897 else if (event->type == EVENT_FINGERRELEASE)
899 if (event->fingerId == motion_id)
903 if (motion_key_x != KSYM_UNDEFINED)
904 HandleKey(motion_key_x, KEY_RELEASED);
905 if (motion_key_y != KSYM_UNDEFINED)
906 HandleKey(motion_key_y, KEY_RELEASED);
908 motion_key_x = KSYM_UNDEFINED;
909 motion_key_y = KSYM_UNDEFINED;
911 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
913 else if (event->fingerId == button_id)
917 if (button_key != KSYM_UNDEFINED)
918 HandleKey(button_key, KEY_RELEASED);
920 button_key = KSYM_UNDEFINED;
922 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
925 else if (event->type == EVENT_FINGERMOTION)
927 if (event->fingerId == motion_id)
929 float distance_x = ABS(event_x - motion_x1);
930 float distance_y = ABS(event_y - motion_y1);
931 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
932 event_x > motion_x1 ? setup.input[0].key.right :
934 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
935 event_y > motion_y1 ? setup.input[0].key.down :
938 if (distance_x < move_trigger_distance / 2 ||
939 distance_x < distance_y)
940 new_motion_key_x = KSYM_UNDEFINED;
942 if (distance_y < move_trigger_distance / 2 ||
943 distance_y < distance_x)
944 new_motion_key_y = KSYM_UNDEFINED;
946 if (distance_x > move_trigger_distance ||
947 distance_y > move_trigger_distance)
949 if (new_motion_key_x != motion_key_x)
951 if (motion_key_x != KSYM_UNDEFINED)
952 HandleKey(motion_key_x, KEY_RELEASED);
953 if (new_motion_key_x != KSYM_UNDEFINED)
954 HandleKey(new_motion_key_x, KEY_PRESSED);
957 if (new_motion_key_y != motion_key_y)
959 if (motion_key_y != KSYM_UNDEFINED)
960 HandleKey(motion_key_y, KEY_RELEASED);
961 if (new_motion_key_y != KSYM_UNDEFINED)
962 HandleKey(new_motion_key_y, KEY_PRESSED);
968 motion_key_x = new_motion_key_x;
969 motion_key_y = new_motion_key_y;
971 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
974 else if (event->fingerId == button_id)
976 float distance_x = ABS(event_x - button_x1);
977 float distance_y = ABS(event_y - button_y1);
979 if (distance_x < drop_trigger_distance / 2 &&
980 distance_y > drop_trigger_distance)
982 if (button_key == setup.input[0].key.snap)
983 HandleKey(button_key, KEY_RELEASED);
988 button_key = setup.input[0].key.drop;
990 HandleKey(button_key, KEY_PRESSED);
992 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
998 void HandleFingerEvent(FingerEvent *event)
1000 #if DEBUG_EVENTS_FINGER
1001 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
1002 event->type == EVENT_FINGERPRESS ? "pressed" :
1003 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
1007 event->dx, event->dy,
1011 if (game_status != GAME_MODE_PLAYING)
1014 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1016 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1017 local_player->mouse_action.button_hint =
1018 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1019 event->x < 0.5 ? MB_LEFTBUTTON :
1020 event->x > 0.5 ? MB_RIGHTBUTTON :
1026 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1027 HandleFingerEvent_VirtualButtons(event);
1028 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1029 HandleFingerEvent_WipeGestures(event);
1032 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1034 static int old_mx = 0, old_my = 0;
1035 static int last_button = MB_LEFTBUTTON;
1036 static boolean touched = FALSE;
1037 static boolean tapped = FALSE;
1039 // screen tile was tapped (but finger not touching the screen anymore)
1040 // (this point will also be reached without receiving a touch event)
1041 if (tapped && !touched)
1043 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1048 // stop here if this function was not triggered by a touch event
1052 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1054 // finger started touching the screen
1064 ClearPlayerMouseAction();
1066 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1069 else if (button == MB_RELEASED && touched)
1071 // finger stopped touching the screen
1076 SetPlayerMouseAction(old_mx, old_my, last_button);
1078 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1080 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1085 // finger moved while touching the screen
1087 int old_x = getLevelFromScreenX(old_mx);
1088 int old_y = getLevelFromScreenY(old_my);
1089 int new_x = getLevelFromScreenX(mx);
1090 int new_y = getLevelFromScreenY(my);
1092 if (new_x != old_x || new_y != old_y)
1097 // finger moved left or right from (horizontal) starting position
1099 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1101 SetPlayerMouseAction(old_mx, old_my, button_nr);
1103 last_button = button_nr;
1105 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1109 // finger stays at or returned to (horizontal) starting position
1111 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1113 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1118 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1120 static int old_mx = 0, old_my = 0;
1121 static int last_button = MB_LEFTBUTTON;
1122 static boolean touched = FALSE;
1123 static boolean tapped = FALSE;
1125 // screen tile was tapped (but finger not touching the screen anymore)
1126 // (this point will also be reached without receiving a touch event)
1127 if (tapped && !touched)
1129 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1134 // stop here if this function was not triggered by a touch event
1138 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1140 // finger started touching the screen
1150 ClearPlayerMouseAction();
1152 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1155 else if (button == MB_RELEASED && touched)
1157 // finger stopped touching the screen
1162 SetPlayerMouseAction(old_mx, old_my, last_button);
1164 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1166 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1171 // finger moved while touching the screen
1173 int old_x = getLevelFromScreenX(old_mx);
1174 int old_y = getLevelFromScreenY(old_my);
1175 int new_x = getLevelFromScreenX(mx);
1176 int new_y = getLevelFromScreenY(my);
1178 if (new_x != old_x || new_y != old_y)
1180 // finger moved away from starting position
1182 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1184 // quickly alternate between clicking and releasing for maximum speed
1185 if (FrameCounter % 2 == 0)
1186 button_nr = MB_RELEASED;
1188 SetPlayerMouseAction(old_mx, old_my, button_nr);
1191 last_button = button_nr;
1195 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1199 // finger stays at or returned to starting position
1201 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1203 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1208 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1210 static int old_mx = 0, old_my = 0;
1211 static Key motion_key_x = KSYM_UNDEFINED;
1212 static Key motion_key_y = KSYM_UNDEFINED;
1213 static boolean touched = FALSE;
1214 static boolean started_on_player = FALSE;
1215 static boolean player_is_dropping = FALSE;
1216 static int player_drop_count = 0;
1217 static int last_player_x = -1;
1218 static int last_player_y = -1;
1220 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1229 started_on_player = FALSE;
1230 player_is_dropping = FALSE;
1231 player_drop_count = 0;
1235 motion_key_x = KSYM_UNDEFINED;
1236 motion_key_y = KSYM_UNDEFINED;
1238 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1241 else if (button == MB_RELEASED && touched)
1248 if (motion_key_x != KSYM_UNDEFINED)
1249 HandleKey(motion_key_x, KEY_RELEASED);
1250 if (motion_key_y != KSYM_UNDEFINED)
1251 HandleKey(motion_key_y, KEY_RELEASED);
1253 if (started_on_player)
1255 if (player_is_dropping)
1257 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1259 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1263 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1265 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1269 motion_key_x = KSYM_UNDEFINED;
1270 motion_key_y = KSYM_UNDEFINED;
1272 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1277 int src_x = local_player->jx;
1278 int src_y = local_player->jy;
1279 int dst_x = getLevelFromScreenX(old_mx);
1280 int dst_y = getLevelFromScreenY(old_my);
1281 int dx = dst_x - src_x;
1282 int dy = dst_y - src_y;
1283 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1284 dx > 0 ? setup.input[0].key.right :
1286 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1287 dy > 0 ? setup.input[0].key.down :
1290 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1291 (last_player_x != local_player->jx ||
1292 last_player_y != local_player->jy))
1294 // in case of asymmetric diagonal movement, use "preferred" direction
1296 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1298 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1299 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1301 local_player->last_move_dir = last_move_dir;
1303 // (required to prevent accidentally forcing direction for next movement)
1304 last_player_x = local_player->jx;
1305 last_player_y = local_player->jy;
1308 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1310 started_on_player = TRUE;
1311 player_drop_count = getPlayerInventorySize(0);
1312 player_is_dropping = (player_drop_count > 0);
1314 if (player_is_dropping)
1316 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1318 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1322 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1324 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1327 else if (dx != 0 || dy != 0)
1329 if (player_is_dropping &&
1330 player_drop_count == getPlayerInventorySize(0))
1332 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1334 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1335 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1337 player_is_dropping = FALSE;
1341 if (new_motion_key_x != motion_key_x)
1343 Error(ERR_DEBUG, "---------- %s %s ----------",
1344 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1345 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1347 if (motion_key_x != KSYM_UNDEFINED)
1348 HandleKey(motion_key_x, KEY_RELEASED);
1349 if (new_motion_key_x != KSYM_UNDEFINED)
1350 HandleKey(new_motion_key_x, KEY_PRESSED);
1353 if (new_motion_key_y != motion_key_y)
1355 Error(ERR_DEBUG, "---------- %s %s ----------",
1356 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1357 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1359 if (motion_key_y != KSYM_UNDEFINED)
1360 HandleKey(motion_key_y, KEY_RELEASED);
1361 if (new_motion_key_y != KSYM_UNDEFINED)
1362 HandleKey(new_motion_key_y, KEY_PRESSED);
1365 motion_key_x = new_motion_key_x;
1366 motion_key_y = new_motion_key_y;
1370 static void HandleButtonOrFinger(int mx, int my, int button)
1372 if (game_status != GAME_MODE_PLAYING)
1375 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1377 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1378 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1379 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1380 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1381 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1382 SetPlayerMouseAction(mx, my, button); // special case
1386 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1387 HandleButtonOrFinger_FollowFinger(mx, my, button);
1391 static boolean checkTextInputKeyModState(void)
1393 // when playing, only handle raw key events and ignore text input
1394 if (game_status == GAME_MODE_PLAYING)
1397 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1400 void HandleTextEvent(TextEvent *event)
1402 char *text = event->text;
1403 Key key = getKeyFromKeyName(text);
1405 #if DEBUG_EVENTS_TEXT
1406 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1409 text[0], (int)(text[0]),
1411 getKeyNameFromKey(key),
1415 #if !defined(HAS_SCREEN_KEYBOARD)
1416 // non-mobile devices: only handle key input with modifier keys pressed here
1417 // (every other key input is handled directly as physical key input event)
1418 if (!checkTextInputKeyModState())
1422 // process text input as "classic" (with uppercase etc.) key input event
1423 HandleKey(key, KEY_PRESSED);
1424 HandleKey(key, KEY_RELEASED);
1427 void HandlePauseResumeEvent(PauseResumeEvent *event)
1429 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1433 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1439 void HandleKeyEvent(KeyEvent *event)
1441 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1442 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1443 Key key = GetEventKey(event, with_modifiers);
1444 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1446 #if DEBUG_EVENTS_KEY
1447 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1448 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1449 event->keysym.scancode,
1454 getKeyNameFromKey(key));
1457 #if defined(PLATFORM_ANDROID)
1458 if (key == KSYM_Back)
1460 // always map the "back" button to the "escape" key on Android devices
1463 else if (key == KSYM_Menu)
1465 // the "menu" button can be used to toggle displaying virtual buttons
1466 if (key_status == KEY_PRESSED)
1467 SetOverlayEnabled(!GetOverlayEnabled());
1471 // for any other "real" key event, disable virtual buttons
1472 SetOverlayEnabled(FALSE);
1476 HandleKeyModState(keymod, key_status);
1478 // only handle raw key input without text modifier keys pressed
1479 if (!checkTextInputKeyModState())
1480 HandleKey(key, key_status);
1483 void HandleFocusEvent(FocusChangeEvent *event)
1485 static int old_joystick_status = -1;
1487 if (event->type == EVENT_FOCUSOUT)
1489 KeyboardAutoRepeatOn();
1490 old_joystick_status = joystick.status;
1491 joystick.status = JOYSTICK_NOT_AVAILABLE;
1493 ClearPlayerAction();
1495 else if (event->type == EVENT_FOCUSIN)
1497 /* When there are two Rocks'n'Diamonds windows which overlap and
1498 the player moves the pointer from one game window to the other,
1499 a 'FocusOut' event is generated for the window the pointer is
1500 leaving and a 'FocusIn' event is generated for the window the
1501 pointer is entering. In some cases, it can happen that the
1502 'FocusIn' event is handled by the one game process before the
1503 'FocusOut' event by the other game process. In this case the
1504 X11 environment would end up with activated keyboard auto repeat,
1505 because unfortunately this is a global setting and not (which
1506 would be far better) set for each X11 window individually.
1507 The effect would be keyboard auto repeat while playing the game
1508 (game_status == GAME_MODE_PLAYING), which is not desired.
1509 To avoid this special case, we just wait 1/10 second before
1510 processing the 'FocusIn' event. */
1512 if (game_status == GAME_MODE_PLAYING)
1515 KeyboardAutoRepeatOffUnlessAutoplay();
1518 if (old_joystick_status != -1)
1519 joystick.status = old_joystick_status;
1523 void HandleClientMessageEvent(ClientMessageEvent *event)
1525 if (CheckCloseWindowEvent(event))
1529 static void HandleDropFileEventExt(char *filename)
1531 Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
1533 // check and extract dropped zip files into correct user data directory
1534 if (strSuffixLower(filename, ".zip"))
1536 int tree_type = GetZipFileTreeType(filename);
1537 char *directory = TREE_USERDIR(tree_type);
1539 if (directory == NULL)
1541 Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1546 ExtractZipFileIntoDirectory(filename, directory, tree_type);
1549 static void HandleDropTextEventExt(char *text)
1551 Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1554 void HandleDropFileEvent(Event *event)
1556 HandleDropFileEventExt(event->drop.file);
1558 SDL_free(event->drop.file);
1561 void HandleDropTextEvent(Event *event)
1563 HandleDropTextEventExt(event->drop.file);
1565 SDL_free(event->drop.file);
1568 void HandleButton(int mx, int my, int button, int button_nr)
1570 static int old_mx = 0, old_my = 0;
1571 boolean button_hold = FALSE;
1572 boolean handle_gadgets = TRUE;
1578 button_nr = -button_nr;
1587 #if defined(PLATFORM_ANDROID)
1588 // when playing, only handle gadgets when using "follow finger" controls
1589 // or when using touch controls in combination with the MM game engine
1590 // or when using gadgets that do not overlap with virtual buttons
1592 (game_status != GAME_MODE_PLAYING ||
1593 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1594 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1595 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1596 !virtual_button_pressed));
1599 if (HandleGlobalAnimClicks(mx, my, button))
1601 // do not handle this button event anymore
1602 return; // force mouse event not to be handled at all
1605 if (handle_gadgets && HandleGadgets(mx, my, button))
1607 // do not handle this button event anymore
1608 mx = my = -32; // force mouse event to be outside screen tiles
1611 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1614 // do not use scroll wheel button events for anything other than gadgets
1615 if (IS_WHEEL_BUTTON(button_nr))
1618 switch (game_status)
1620 case GAME_MODE_TITLE:
1621 HandleTitleScreen(mx, my, 0, 0, button);
1624 case GAME_MODE_MAIN:
1625 HandleMainMenu(mx, my, 0, 0, button);
1628 case GAME_MODE_PSEUDO_TYPENAME:
1629 HandleTypeName(0, KSYM_Return);
1632 case GAME_MODE_LEVELS:
1633 HandleChooseLevelSet(mx, my, 0, 0, button);
1636 case GAME_MODE_LEVELNR:
1637 HandleChooseLevelNr(mx, my, 0, 0, button);
1640 case GAME_MODE_SCORES:
1641 HandleHallOfFame(0, 0, 0, 0, button);
1644 case GAME_MODE_EDITOR:
1645 HandleLevelEditorIdle();
1648 case GAME_MODE_INFO:
1649 HandleInfoScreen(mx, my, 0, 0, button);
1652 case GAME_MODE_SETUP:
1653 HandleSetupScreen(mx, my, 0, 0, button);
1656 case GAME_MODE_PLAYING:
1657 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1658 HandleButtonOrFinger(mx, my, button);
1660 SetPlayerMouseAction(mx, my, button);
1663 if (button == MB_PRESSED && !motion_status && !button_hold &&
1664 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1665 DumpTileFromScreen(mx, my);
1675 static boolean is_string_suffix(char *string, char *suffix)
1677 int string_len = strlen(string);
1678 int suffix_len = strlen(suffix);
1680 if (suffix_len > string_len)
1683 return (strEqual(&string[string_len - suffix_len], suffix));
1686 #define MAX_CHEAT_INPUT_LEN 32
1688 static void HandleKeysSpecial(Key key)
1690 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1691 char letter = getCharFromKey(key);
1692 int cheat_input_len = strlen(cheat_input);
1698 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1700 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1701 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1703 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1706 cheat_input[cheat_input_len++] = letter;
1707 cheat_input[cheat_input_len] = '\0';
1709 #if DEBUG_EVENTS_KEY
1710 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1713 if (game_status == GAME_MODE_MAIN)
1715 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1716 is_string_suffix(cheat_input, ":ist"))
1718 InsertSolutionTape();
1720 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1721 is_string_suffix(cheat_input, ":pst"))
1725 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1726 is_string_suffix(cheat_input, ":rg"))
1728 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1731 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1732 is_string_suffix(cheat_input, ":rs"))
1734 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1737 else if (is_string_suffix(cheat_input, ":reload-music") ||
1738 is_string_suffix(cheat_input, ":rm"))
1740 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1743 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1744 is_string_suffix(cheat_input, ":ra"))
1746 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1747 1 << ARTWORK_TYPE_SOUNDS |
1748 1 << ARTWORK_TYPE_MUSIC);
1751 else if (is_string_suffix(cheat_input, ":dump-level") ||
1752 is_string_suffix(cheat_input, ":dl"))
1756 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1757 is_string_suffix(cheat_input, ":dt"))
1761 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1762 is_string_suffix(cheat_input, ":ft"))
1764 /* fix single-player tapes that contain player input for more than one
1765 player (due to a bug in 3.3.1.2 and earlier versions), which results
1766 in playing levels with more than one player in multi-player mode,
1767 even though the tape was originally recorded in single-player mode */
1769 // remove player input actions for all players but the first one
1770 for (i = 1; i < MAX_PLAYERS; i++)
1771 tape.player_participates[i] = FALSE;
1773 tape.changed = TRUE;
1775 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1776 is_string_suffix(cheat_input, ":snl"))
1778 SaveNativeLevel(&level);
1780 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1781 is_string_suffix(cheat_input, ":fps"))
1783 global.show_frames_per_second = !global.show_frames_per_second;
1786 else if (game_status == GAME_MODE_PLAYING)
1789 if (is_string_suffix(cheat_input, ".q"))
1790 DEBUG_SetMaximumDynamite();
1793 else if (game_status == GAME_MODE_EDITOR)
1795 if (is_string_suffix(cheat_input, ":dump-brush") ||
1796 is_string_suffix(cheat_input, ":DB"))
1800 else if (is_string_suffix(cheat_input, ":DDB"))
1805 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1807 if (letter == 'x') // copy brush to clipboard (small size)
1809 CopyBrushToClipboard_Small();
1811 else if (letter == 'c') // copy brush to clipboard (normal size)
1813 CopyBrushToClipboard();
1815 else if (letter == 'v') // paste brush from Clipboard
1817 CopyClipboardToBrush();
1822 // special key shortcuts for all game modes
1823 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1824 is_string_suffix(cheat_input, ":dea") ||
1825 is_string_suffix(cheat_input, ":DEA"))
1827 DumpGadgetIdentifiers();
1828 DumpScreenIdentifiers();
1832 boolean HandleKeysDebug(Key key, int key_status)
1837 if (key_status != KEY_PRESSED)
1840 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1842 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1844 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1846 if (key == setup.debug.frame_delay_key[i] &&
1847 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1849 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1850 setup.debug.frame_delay[i] : setup.game_frame_delay);
1852 if (!setup.debug.frame_delay_game_only)
1853 MenuFrameDelay = GameFrameDelay;
1855 SetVideoFrameDelay(GameFrameDelay);
1857 if (GameFrameDelay > ONE_SECOND_DELAY)
1858 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1859 else if (GameFrameDelay != 0)
1860 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1861 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1862 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1864 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1871 if (game_status == GAME_MODE_PLAYING)
1875 options.debug = !options.debug;
1877 Error(ERR_DEBUG, "debug mode %s",
1878 (options.debug ? "enabled" : "disabled"));
1882 else if (key == KSYM_v)
1884 Error(ERR_DEBUG, "currently using game engine version %d",
1885 game.engine_version);
1895 void HandleKey(Key key, int key_status)
1897 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1898 static boolean ignore_repeated_key = FALSE;
1899 static struct SetupKeyboardInfo ski;
1900 static struct SetupShortcutInfo ssi;
1909 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1910 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1911 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1912 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1913 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1914 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1919 if (HandleKeysDebug(key, key_status))
1920 return; // do not handle already processed keys again
1922 // map special keys (media keys / remote control buttons) to default keys
1923 if (key == KSYM_PlayPause)
1925 else if (key == KSYM_Select)
1928 HandleSpecialGameControllerKeys(key, key_status);
1930 if (game_status == GAME_MODE_PLAYING)
1932 // only needed for single-step tape recording mode
1933 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1936 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1938 byte key_action = 0;
1940 if (setup.input[pnr].use_joystick)
1943 ski = setup.input[pnr].key;
1945 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1946 if (key == *key_info[i].key_custom)
1947 key_action |= key_info[i].action;
1949 // use combined snap+direction keys for the first player only
1952 ssi = setup.shortcut;
1954 for (i = 0; i < NUM_DIRECTIONS; i++)
1955 if (key == *key_info[i].key_snap)
1956 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1959 if (key_status == KEY_PRESSED)
1960 stored_player[pnr].action |= key_action;
1962 stored_player[pnr].action &= ~key_action;
1964 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
1966 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1968 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1970 // if snap key already pressed, keep pause mode when releasing
1971 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1972 has_snapped[pnr] = TRUE;
1974 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1976 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1978 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1979 getRedDiskReleaseFlag_SP() == 0)
1981 // add a single inactive frame before dropping starts
1982 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1983 stored_player[pnr].force_dropping = TRUE;
1986 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1988 // if snap key was pressed without direction, leave pause mode
1989 if (!has_snapped[pnr])
1990 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1992 has_snapped[pnr] = FALSE;
1995 else if (tape.recording && tape.pausing && !tape.use_mouse)
1997 // prevent key release events from un-pausing a paused game
1998 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1999 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2002 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2003 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2009 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2010 if (key == key_info[i].key_default)
2011 joy |= key_info[i].action;
2016 if (key_status == KEY_PRESSED)
2017 key_joystick_mapping |= joy;
2019 key_joystick_mapping &= ~joy;
2024 if (game_status != GAME_MODE_PLAYING)
2025 key_joystick_mapping = 0;
2027 if (key_status == KEY_RELEASED)
2029 // reset flag to ignore repeated "key pressed" events after key release
2030 ignore_repeated_key = FALSE;
2035 if ((key == KSYM_F11 ||
2036 ((key == KSYM_Return ||
2037 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2038 video.fullscreen_available &&
2039 !ignore_repeated_key)
2041 setup.fullscreen = !setup.fullscreen;
2043 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2045 if (game_status == GAME_MODE_SETUP)
2046 RedrawSetupScreenAfterFullscreenToggle();
2048 // set flag to ignore repeated "key pressed" events
2049 ignore_repeated_key = TRUE;
2054 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2055 key == KSYM_minus || key == KSYM_KP_Subtract ||
2056 key == KSYM_plus || key == KSYM_KP_Add ||
2057 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2058 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2059 video.window_scaling_available &&
2060 !video.fullscreen_enabled)
2062 if (key == KSYM_0 || key == KSYM_KP_0)
2063 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2064 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2065 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2067 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2069 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2070 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2071 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2072 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2074 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2076 if (game_status == GAME_MODE_SETUP)
2077 RedrawSetupScreenAfterFullscreenToggle();
2082 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2083 key == KSYM_Return ||
2084 key == KSYM_Escape)))
2086 // do not handle this key event anymore
2087 if (key != KSYM_Escape) // always allow ESC key to be handled
2091 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2092 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2099 if (game_status == GAME_MODE_MAIN &&
2100 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2102 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2107 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2109 if (key == setup.shortcut.save_game)
2111 else if (key == setup.shortcut.load_game)
2113 else if (key == setup.shortcut.toggle_pause)
2114 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2116 HandleTapeButtonKeys(key);
2117 HandleSoundButtonKeys(key);
2120 if (game_status == GAME_MODE_PLAYING && !network_playing)
2122 int centered_player_nr_next = -999;
2124 if (key == setup.shortcut.focus_player_all)
2125 centered_player_nr_next = -1;
2127 for (i = 0; i < MAX_PLAYERS; i++)
2128 if (key == setup.shortcut.focus_player[i])
2129 centered_player_nr_next = i;
2131 if (centered_player_nr_next != -999)
2133 game.centered_player_nr_next = centered_player_nr_next;
2134 game.set_centered_player = TRUE;
2138 tape.centered_player_nr_next = game.centered_player_nr_next;
2139 tape.set_centered_player = TRUE;
2144 HandleKeysSpecial(key);
2146 if (HandleGadgetsKeyInput(key))
2147 return; // do not handle already processed keys again
2149 switch (game_status)
2151 case GAME_MODE_PSEUDO_TYPENAME:
2152 HandleTypeName(0, key);
2155 case GAME_MODE_TITLE:
2156 case GAME_MODE_MAIN:
2157 case GAME_MODE_LEVELS:
2158 case GAME_MODE_LEVELNR:
2159 case GAME_MODE_SETUP:
2160 case GAME_MODE_INFO:
2161 case GAME_MODE_SCORES:
2163 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2170 if (game_status == GAME_MODE_TITLE)
2171 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2172 else if (game_status == GAME_MODE_MAIN)
2173 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2174 else if (game_status == GAME_MODE_LEVELS)
2175 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2176 else if (game_status == GAME_MODE_LEVELNR)
2177 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2178 else if (game_status == GAME_MODE_SETUP)
2179 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2180 else if (game_status == GAME_MODE_INFO)
2181 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2182 else if (game_status == GAME_MODE_SCORES)
2183 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2187 if (game_status != GAME_MODE_MAIN)
2188 FadeSkipNextFadeIn();
2190 if (game_status == GAME_MODE_TITLE)
2191 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2192 else if (game_status == GAME_MODE_LEVELS)
2193 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2194 else if (game_status == GAME_MODE_LEVELNR)
2195 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2196 else if (game_status == GAME_MODE_SETUP)
2197 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2198 else if (game_status == GAME_MODE_INFO)
2199 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2200 else if (game_status == GAME_MODE_SCORES)
2201 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2205 if (game_status == GAME_MODE_LEVELS)
2206 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2207 else if (game_status == GAME_MODE_LEVELNR)
2208 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2209 else if (game_status == GAME_MODE_SETUP)
2210 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2211 else if (game_status == GAME_MODE_INFO)
2212 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2213 else if (game_status == GAME_MODE_SCORES)
2214 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2217 case KSYM_Page_Down:
2218 if (game_status == GAME_MODE_LEVELS)
2219 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2220 else if (game_status == GAME_MODE_LEVELNR)
2221 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2222 else if (game_status == GAME_MODE_SETUP)
2223 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2224 else if (game_status == GAME_MODE_INFO)
2225 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2226 else if (game_status == GAME_MODE_SCORES)
2227 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2235 case GAME_MODE_EDITOR:
2236 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2237 HandleLevelEditorKeyInput(key);
2240 case GAME_MODE_PLAYING:
2245 RequestQuitGame(setup.ask_on_escape);
2255 if (key == KSYM_Escape)
2257 SetGameStatus(GAME_MODE_MAIN);
2266 void HandleNoEvent(void)
2268 HandleMouseCursor();
2270 switch (game_status)
2272 case GAME_MODE_PLAYING:
2273 HandleButtonOrFinger(-1, -1, -1);
2278 void HandleEventActions(void)
2280 // if (button_status && game_status != GAME_MODE_PLAYING)
2281 if (button_status && (game_status != GAME_MODE_PLAYING ||
2283 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2285 HandleButton(0, 0, button_status, -button_status);
2292 if (network.enabled)
2295 switch (game_status)
2297 case GAME_MODE_MAIN:
2298 DrawPreviewLevelAnimation();
2301 case GAME_MODE_EDITOR:
2302 HandleLevelEditorIdle();
2310 static void HandleTileCursor(int dx, int dy, int button)
2313 ClearPlayerMouseAction();
2320 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2321 (dx < 0 ? MB_LEFTBUTTON :
2322 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2324 else if (!tile_cursor.moving)
2326 int old_xpos = tile_cursor.xpos;
2327 int old_ypos = tile_cursor.ypos;
2328 int new_xpos = old_xpos;
2329 int new_ypos = old_ypos;
2331 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2332 new_xpos = old_xpos + dx;
2334 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2335 new_ypos = old_ypos + dy;
2337 SetTileCursorTargetXY(new_xpos, new_ypos);
2341 static int HandleJoystickForAllPlayers(void)
2345 boolean no_joysticks_configured = TRUE;
2346 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2347 static byte joy_action_last[MAX_PLAYERS];
2349 for (i = 0; i < MAX_PLAYERS; i++)
2350 if (setup.input[i].use_joystick)
2351 no_joysticks_configured = FALSE;
2353 // if no joysticks configured, map connected joysticks to players
2354 if (no_joysticks_configured)
2355 use_as_joystick_nr = TRUE;
2357 for (i = 0; i < MAX_PLAYERS; i++)
2359 byte joy_action = 0;
2361 joy_action = JoystickExt(i, use_as_joystick_nr);
2362 result |= joy_action;
2364 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2365 joy_action != joy_action_last[i])
2366 stored_player[i].action = joy_action;
2368 joy_action_last[i] = joy_action;
2374 void HandleJoystick(void)
2376 static unsigned int joytest_delay = 0;
2377 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2378 static int joytest_last = 0;
2379 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2380 int delay_value = GADGET_FRAME_DELAY;
2381 int joystick = HandleJoystickForAllPlayers();
2382 int keyboard = key_joystick_mapping;
2383 int joy = (joystick | keyboard);
2384 int joytest = joystick;
2385 int left = joy & JOY_LEFT;
2386 int right = joy & JOY_RIGHT;
2387 int up = joy & JOY_UP;
2388 int down = joy & JOY_DOWN;
2389 int button = joy & JOY_BUTTON;
2390 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2391 int dx = (left ? -1 : right ? 1 : 0);
2392 int dy = (up ? -1 : down ? 1 : 0);
2393 boolean use_delay_value_first = (joytest != joytest_last);
2395 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2397 // do not handle this button event anymore
2401 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2402 anyTextGadgetActive()))
2404 // leave name input in main menu or text input gadget
2405 HandleKey(KSYM_Escape, KEY_PRESSED);
2406 HandleKey(KSYM_Escape, KEY_RELEASED);
2411 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2413 if (game_status == GAME_MODE_PLAYING)
2415 // when playing MM style levels, also use delay for keyboard events
2416 joytest |= keyboard;
2418 // only use first delay value for new events, but not for changed events
2419 use_delay_value_first = (!joytest != !joytest_last);
2421 // only use delay after the initial keyboard event
2425 // for any joystick or keyboard event, enable playfield tile cursor
2426 if (dx || dy || button)
2427 SetTileCursorEnabled(TRUE);
2430 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2432 // delay joystick/keyboard actions if axes/keys continually pressed
2433 newbutton = dx = dy = 0;
2437 // first start with longer delay, then continue with shorter delay
2438 joytest_delay_value =
2439 (use_delay_value_first ? delay_value_first : delay_value);
2442 joytest_last = joytest;
2444 switch (game_status)
2446 case GAME_MODE_TITLE:
2447 case GAME_MODE_MAIN:
2448 case GAME_MODE_LEVELS:
2449 case GAME_MODE_LEVELNR:
2450 case GAME_MODE_SETUP:
2451 case GAME_MODE_INFO:
2452 case GAME_MODE_SCORES:
2454 if (anyTextGadgetActive())
2457 if (game_status == GAME_MODE_TITLE)
2458 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2459 else if (game_status == GAME_MODE_MAIN)
2460 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2461 else if (game_status == GAME_MODE_LEVELS)
2462 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2463 else if (game_status == GAME_MODE_LEVELNR)
2464 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2465 else if (game_status == GAME_MODE_SETUP)
2466 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2467 else if (game_status == GAME_MODE_INFO)
2468 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2469 else if (game_status == GAME_MODE_SCORES)
2470 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2475 case GAME_MODE_PLAYING:
2477 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2478 if (tape.playing || keyboard)
2479 newbutton = ((joy & JOY_BUTTON) != 0);
2482 if (newbutton && game.all_players_gone)
2489 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2491 if (joystick & JOY_ACTION)
2492 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2494 else if (tape.recording && tape.pausing && !tape.use_mouse)
2496 if (joystick & JOY_ACTION)
2497 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2500 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2501 HandleTileCursor(dx, dy, button);
2510 void HandleSpecialGameControllerButtons(Event *event)
2515 switch (event->type)
2517 case SDL_CONTROLLERBUTTONDOWN:
2518 key_status = KEY_PRESSED;
2521 case SDL_CONTROLLERBUTTONUP:
2522 key_status = KEY_RELEASED;
2529 switch (event->cbutton.button)
2531 case SDL_CONTROLLER_BUTTON_START:
2535 case SDL_CONTROLLER_BUTTON_BACK:
2543 HandleKey(key, key_status);
2546 void HandleSpecialGameControllerKeys(Key key, int key_status)
2548 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2549 int button = SDL_CONTROLLER_BUTTON_INVALID;
2551 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2552 if (key == KSYM_Rewind)
2553 button = SDL_CONTROLLER_BUTTON_A;
2554 else if (key == KSYM_FastForward || key == KSYM_Menu)
2555 button = SDL_CONTROLLER_BUTTON_B;
2557 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2561 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2562 SDL_CONTROLLERBUTTONUP);
2564 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2565 event.cbutton.button = button;
2566 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2569 HandleJoystickEvent(&event);
2574 boolean DoKeysymAction(int keysym)
2578 Key key = (Key)(-keysym);
2580 HandleKey(key, KEY_PRESSED);
2581 HandleKey(key, KEY_RELEASED);