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 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1548 if (top_dir != NULL)
1549 AddUserTreeSetToTreeInfo(top_dir, tree_type);
1553 static void HandleDropTextEventExt(char *text)
1555 Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1558 void HandleDropFileEvent(Event *event)
1560 HandleDropFileEventExt(event->drop.file);
1562 SDL_free(event->drop.file);
1565 void HandleDropTextEvent(Event *event)
1567 HandleDropTextEventExt(event->drop.file);
1569 SDL_free(event->drop.file);
1572 void HandleButton(int mx, int my, int button, int button_nr)
1574 static int old_mx = 0, old_my = 0;
1575 boolean button_hold = FALSE;
1576 boolean handle_gadgets = TRUE;
1582 button_nr = -button_nr;
1591 #if defined(PLATFORM_ANDROID)
1592 // when playing, only handle gadgets when using "follow finger" controls
1593 // or when using touch controls in combination with the MM game engine
1594 // or when using gadgets that do not overlap with virtual buttons
1596 (game_status != GAME_MODE_PLAYING ||
1597 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1598 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1599 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1600 !virtual_button_pressed));
1603 if (HandleGlobalAnimClicks(mx, my, button))
1605 // do not handle this button event anymore
1606 return; // force mouse event not to be handled at all
1609 if (handle_gadgets && HandleGadgets(mx, my, button))
1611 // do not handle this button event anymore
1612 mx = my = -32; // force mouse event to be outside screen tiles
1615 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1618 // do not use scroll wheel button events for anything other than gadgets
1619 if (IS_WHEEL_BUTTON(button_nr))
1622 switch (game_status)
1624 case GAME_MODE_TITLE:
1625 HandleTitleScreen(mx, my, 0, 0, button);
1628 case GAME_MODE_MAIN:
1629 HandleMainMenu(mx, my, 0, 0, button);
1632 case GAME_MODE_PSEUDO_TYPENAME:
1633 HandleTypeName(0, KSYM_Return);
1636 case GAME_MODE_LEVELS:
1637 HandleChooseLevelSet(mx, my, 0, 0, button);
1640 case GAME_MODE_LEVELNR:
1641 HandleChooseLevelNr(mx, my, 0, 0, button);
1644 case GAME_MODE_SCORES:
1645 HandleHallOfFame(0, 0, 0, 0, button);
1648 case GAME_MODE_EDITOR:
1649 HandleLevelEditorIdle();
1652 case GAME_MODE_INFO:
1653 HandleInfoScreen(mx, my, 0, 0, button);
1656 case GAME_MODE_SETUP:
1657 HandleSetupScreen(mx, my, 0, 0, button);
1660 case GAME_MODE_PLAYING:
1661 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1662 HandleButtonOrFinger(mx, my, button);
1664 SetPlayerMouseAction(mx, my, button);
1667 if (button == MB_PRESSED && !motion_status && !button_hold &&
1668 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1669 DumpTileFromScreen(mx, my);
1679 static boolean is_string_suffix(char *string, char *suffix)
1681 int string_len = strlen(string);
1682 int suffix_len = strlen(suffix);
1684 if (suffix_len > string_len)
1687 return (strEqual(&string[string_len - suffix_len], suffix));
1690 #define MAX_CHEAT_INPUT_LEN 32
1692 static void HandleKeysSpecial(Key key)
1694 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1695 char letter = getCharFromKey(key);
1696 int cheat_input_len = strlen(cheat_input);
1702 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1704 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1705 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1707 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1710 cheat_input[cheat_input_len++] = letter;
1711 cheat_input[cheat_input_len] = '\0';
1713 #if DEBUG_EVENTS_KEY
1714 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1717 if (game_status == GAME_MODE_MAIN)
1719 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1720 is_string_suffix(cheat_input, ":ist"))
1722 InsertSolutionTape();
1724 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1725 is_string_suffix(cheat_input, ":pst"))
1729 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1730 is_string_suffix(cheat_input, ":rg"))
1732 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1735 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1736 is_string_suffix(cheat_input, ":rs"))
1738 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1741 else if (is_string_suffix(cheat_input, ":reload-music") ||
1742 is_string_suffix(cheat_input, ":rm"))
1744 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1747 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1748 is_string_suffix(cheat_input, ":ra"))
1750 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1751 1 << ARTWORK_TYPE_SOUNDS |
1752 1 << ARTWORK_TYPE_MUSIC);
1755 else if (is_string_suffix(cheat_input, ":dump-level") ||
1756 is_string_suffix(cheat_input, ":dl"))
1760 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1761 is_string_suffix(cheat_input, ":dt"))
1765 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1766 is_string_suffix(cheat_input, ":ft"))
1768 /* fix single-player tapes that contain player input for more than one
1769 player (due to a bug in 3.3.1.2 and earlier versions), which results
1770 in playing levels with more than one player in multi-player mode,
1771 even though the tape was originally recorded in single-player mode */
1773 // remove player input actions for all players but the first one
1774 for (i = 1; i < MAX_PLAYERS; i++)
1775 tape.player_participates[i] = FALSE;
1777 tape.changed = TRUE;
1779 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1780 is_string_suffix(cheat_input, ":snl"))
1782 SaveNativeLevel(&level);
1784 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1785 is_string_suffix(cheat_input, ":fps"))
1787 global.show_frames_per_second = !global.show_frames_per_second;
1790 else if (game_status == GAME_MODE_PLAYING)
1793 if (is_string_suffix(cheat_input, ".q"))
1794 DEBUG_SetMaximumDynamite();
1797 else if (game_status == GAME_MODE_EDITOR)
1799 if (is_string_suffix(cheat_input, ":dump-brush") ||
1800 is_string_suffix(cheat_input, ":DB"))
1804 else if (is_string_suffix(cheat_input, ":DDB"))
1809 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1811 if (letter == 'x') // copy brush to clipboard (small size)
1813 CopyBrushToClipboard_Small();
1815 else if (letter == 'c') // copy brush to clipboard (normal size)
1817 CopyBrushToClipboard();
1819 else if (letter == 'v') // paste brush from Clipboard
1821 CopyClipboardToBrush();
1826 // special key shortcuts for all game modes
1827 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1828 is_string_suffix(cheat_input, ":dea") ||
1829 is_string_suffix(cheat_input, ":DEA"))
1831 DumpGadgetIdentifiers();
1832 DumpScreenIdentifiers();
1836 boolean HandleKeysDebug(Key key, int key_status)
1841 if (key_status != KEY_PRESSED)
1844 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1846 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1848 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1850 if (key == setup.debug.frame_delay_key[i] &&
1851 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1853 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1854 setup.debug.frame_delay[i] : setup.game_frame_delay);
1856 if (!setup.debug.frame_delay_game_only)
1857 MenuFrameDelay = GameFrameDelay;
1859 SetVideoFrameDelay(GameFrameDelay);
1861 if (GameFrameDelay > ONE_SECOND_DELAY)
1862 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1863 else if (GameFrameDelay != 0)
1864 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1865 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1866 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1868 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1875 if (game_status == GAME_MODE_PLAYING)
1879 options.debug = !options.debug;
1881 Error(ERR_DEBUG, "debug mode %s",
1882 (options.debug ? "enabled" : "disabled"));
1886 else if (key == KSYM_v)
1888 Error(ERR_DEBUG, "currently using game engine version %d",
1889 game.engine_version);
1899 void HandleKey(Key key, int key_status)
1901 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1902 static boolean ignore_repeated_key = FALSE;
1903 static struct SetupKeyboardInfo ski;
1904 static struct SetupShortcutInfo ssi;
1913 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1914 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1915 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1916 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1917 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1918 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1923 if (HandleKeysDebug(key, key_status))
1924 return; // do not handle already processed keys again
1926 // map special keys (media keys / remote control buttons) to default keys
1927 if (key == KSYM_PlayPause)
1929 else if (key == KSYM_Select)
1932 HandleSpecialGameControllerKeys(key, key_status);
1934 if (game_status == GAME_MODE_PLAYING)
1936 // only needed for single-step tape recording mode
1937 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1940 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1942 byte key_action = 0;
1944 if (setup.input[pnr].use_joystick)
1947 ski = setup.input[pnr].key;
1949 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1950 if (key == *key_info[i].key_custom)
1951 key_action |= key_info[i].action;
1953 // use combined snap+direction keys for the first player only
1956 ssi = setup.shortcut;
1958 for (i = 0; i < NUM_DIRECTIONS; i++)
1959 if (key == *key_info[i].key_snap)
1960 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1963 if (key_status == KEY_PRESSED)
1964 stored_player[pnr].action |= key_action;
1966 stored_player[pnr].action &= ~key_action;
1968 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
1970 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1972 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1974 // if snap key already pressed, keep pause mode when releasing
1975 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1976 has_snapped[pnr] = TRUE;
1978 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1980 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1982 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1983 getRedDiskReleaseFlag_SP() == 0)
1985 // add a single inactive frame before dropping starts
1986 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1987 stored_player[pnr].force_dropping = TRUE;
1990 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1992 // if snap key was pressed without direction, leave pause mode
1993 if (!has_snapped[pnr])
1994 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1996 has_snapped[pnr] = FALSE;
1999 else if (tape.recording && tape.pausing && !tape.use_mouse)
2001 // prevent key release events from un-pausing a paused game
2002 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2003 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2006 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2007 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2013 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2014 if (key == key_info[i].key_default)
2015 joy |= key_info[i].action;
2020 if (key_status == KEY_PRESSED)
2021 key_joystick_mapping |= joy;
2023 key_joystick_mapping &= ~joy;
2028 if (game_status != GAME_MODE_PLAYING)
2029 key_joystick_mapping = 0;
2031 if (key_status == KEY_RELEASED)
2033 // reset flag to ignore repeated "key pressed" events after key release
2034 ignore_repeated_key = FALSE;
2039 if ((key == KSYM_F11 ||
2040 ((key == KSYM_Return ||
2041 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2042 video.fullscreen_available &&
2043 !ignore_repeated_key)
2045 setup.fullscreen = !setup.fullscreen;
2047 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2049 if (game_status == GAME_MODE_SETUP)
2050 RedrawSetupScreenAfterFullscreenToggle();
2052 // set flag to ignore repeated "key pressed" events
2053 ignore_repeated_key = TRUE;
2058 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2059 key == KSYM_minus || key == KSYM_KP_Subtract ||
2060 key == KSYM_plus || key == KSYM_KP_Add ||
2061 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2062 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2063 video.window_scaling_available &&
2064 !video.fullscreen_enabled)
2066 if (key == KSYM_0 || key == KSYM_KP_0)
2067 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2068 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2069 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2071 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2073 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2074 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2075 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2076 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2078 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2080 if (game_status == GAME_MODE_SETUP)
2081 RedrawSetupScreenAfterFullscreenToggle();
2086 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2087 key == KSYM_Return ||
2088 key == KSYM_Escape)))
2090 // do not handle this key event anymore
2091 if (key != KSYM_Escape) // always allow ESC key to be handled
2095 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2096 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2103 if (game_status == GAME_MODE_MAIN &&
2104 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2106 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2111 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2113 if (key == setup.shortcut.save_game)
2115 else if (key == setup.shortcut.load_game)
2117 else if (key == setup.shortcut.toggle_pause)
2118 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2120 HandleTapeButtonKeys(key);
2121 HandleSoundButtonKeys(key);
2124 if (game_status == GAME_MODE_PLAYING && !network_playing)
2126 int centered_player_nr_next = -999;
2128 if (key == setup.shortcut.focus_player_all)
2129 centered_player_nr_next = -1;
2131 for (i = 0; i < MAX_PLAYERS; i++)
2132 if (key == setup.shortcut.focus_player[i])
2133 centered_player_nr_next = i;
2135 if (centered_player_nr_next != -999)
2137 game.centered_player_nr_next = centered_player_nr_next;
2138 game.set_centered_player = TRUE;
2142 tape.centered_player_nr_next = game.centered_player_nr_next;
2143 tape.set_centered_player = TRUE;
2148 HandleKeysSpecial(key);
2150 if (HandleGadgetsKeyInput(key))
2151 return; // do not handle already processed keys again
2153 switch (game_status)
2155 case GAME_MODE_PSEUDO_TYPENAME:
2156 HandleTypeName(0, key);
2159 case GAME_MODE_TITLE:
2160 case GAME_MODE_MAIN:
2161 case GAME_MODE_LEVELS:
2162 case GAME_MODE_LEVELNR:
2163 case GAME_MODE_SETUP:
2164 case GAME_MODE_INFO:
2165 case GAME_MODE_SCORES:
2167 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2174 if (game_status == GAME_MODE_TITLE)
2175 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2176 else if (game_status == GAME_MODE_MAIN)
2177 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2178 else if (game_status == GAME_MODE_LEVELS)
2179 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2180 else if (game_status == GAME_MODE_LEVELNR)
2181 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2182 else if (game_status == GAME_MODE_SETUP)
2183 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2184 else if (game_status == GAME_MODE_INFO)
2185 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2186 else if (game_status == GAME_MODE_SCORES)
2187 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2191 if (game_status != GAME_MODE_MAIN)
2192 FadeSkipNextFadeIn();
2194 if (game_status == GAME_MODE_TITLE)
2195 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2196 else if (game_status == GAME_MODE_LEVELS)
2197 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2198 else if (game_status == GAME_MODE_LEVELNR)
2199 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2200 else if (game_status == GAME_MODE_SETUP)
2201 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2202 else if (game_status == GAME_MODE_INFO)
2203 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2204 else if (game_status == GAME_MODE_SCORES)
2205 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2209 if (game_status == GAME_MODE_LEVELS)
2210 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2211 else if (game_status == GAME_MODE_LEVELNR)
2212 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2213 else if (game_status == GAME_MODE_SETUP)
2214 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2215 else if (game_status == GAME_MODE_INFO)
2216 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2217 else if (game_status == GAME_MODE_SCORES)
2218 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2221 case KSYM_Page_Down:
2222 if (game_status == GAME_MODE_LEVELS)
2223 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2224 else if (game_status == GAME_MODE_LEVELNR)
2225 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2226 else if (game_status == GAME_MODE_SETUP)
2227 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2228 else if (game_status == GAME_MODE_INFO)
2229 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2230 else if (game_status == GAME_MODE_SCORES)
2231 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2239 case GAME_MODE_EDITOR:
2240 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2241 HandleLevelEditorKeyInput(key);
2244 case GAME_MODE_PLAYING:
2249 RequestQuitGame(setup.ask_on_escape);
2259 if (key == KSYM_Escape)
2261 SetGameStatus(GAME_MODE_MAIN);
2270 void HandleNoEvent(void)
2272 HandleMouseCursor();
2274 switch (game_status)
2276 case GAME_MODE_PLAYING:
2277 HandleButtonOrFinger(-1, -1, -1);
2282 void HandleEventActions(void)
2284 // if (button_status && game_status != GAME_MODE_PLAYING)
2285 if (button_status && (game_status != GAME_MODE_PLAYING ||
2287 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2289 HandleButton(0, 0, button_status, -button_status);
2296 if (network.enabled)
2299 switch (game_status)
2301 case GAME_MODE_MAIN:
2302 DrawPreviewLevelAnimation();
2305 case GAME_MODE_EDITOR:
2306 HandleLevelEditorIdle();
2314 static void HandleTileCursor(int dx, int dy, int button)
2317 ClearPlayerMouseAction();
2324 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2325 (dx < 0 ? MB_LEFTBUTTON :
2326 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2328 else if (!tile_cursor.moving)
2330 int old_xpos = tile_cursor.xpos;
2331 int old_ypos = tile_cursor.ypos;
2332 int new_xpos = old_xpos;
2333 int new_ypos = old_ypos;
2335 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2336 new_xpos = old_xpos + dx;
2338 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2339 new_ypos = old_ypos + dy;
2341 SetTileCursorTargetXY(new_xpos, new_ypos);
2345 static int HandleJoystickForAllPlayers(void)
2349 boolean no_joysticks_configured = TRUE;
2350 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2351 static byte joy_action_last[MAX_PLAYERS];
2353 for (i = 0; i < MAX_PLAYERS; i++)
2354 if (setup.input[i].use_joystick)
2355 no_joysticks_configured = FALSE;
2357 // if no joysticks configured, map connected joysticks to players
2358 if (no_joysticks_configured)
2359 use_as_joystick_nr = TRUE;
2361 for (i = 0; i < MAX_PLAYERS; i++)
2363 byte joy_action = 0;
2365 joy_action = JoystickExt(i, use_as_joystick_nr);
2366 result |= joy_action;
2368 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2369 joy_action != joy_action_last[i])
2370 stored_player[i].action = joy_action;
2372 joy_action_last[i] = joy_action;
2378 void HandleJoystick(void)
2380 static unsigned int joytest_delay = 0;
2381 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2382 static int joytest_last = 0;
2383 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2384 int delay_value = GADGET_FRAME_DELAY;
2385 int joystick = HandleJoystickForAllPlayers();
2386 int keyboard = key_joystick_mapping;
2387 int joy = (joystick | keyboard);
2388 int joytest = joystick;
2389 int left = joy & JOY_LEFT;
2390 int right = joy & JOY_RIGHT;
2391 int up = joy & JOY_UP;
2392 int down = joy & JOY_DOWN;
2393 int button = joy & JOY_BUTTON;
2394 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2395 int dx = (left ? -1 : right ? 1 : 0);
2396 int dy = (up ? -1 : down ? 1 : 0);
2397 boolean use_delay_value_first = (joytest != joytest_last);
2399 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2401 // do not handle this button event anymore
2405 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2406 anyTextGadgetActive()))
2408 // leave name input in main menu or text input gadget
2409 HandleKey(KSYM_Escape, KEY_PRESSED);
2410 HandleKey(KSYM_Escape, KEY_RELEASED);
2415 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2417 if (game_status == GAME_MODE_PLAYING)
2419 // when playing MM style levels, also use delay for keyboard events
2420 joytest |= keyboard;
2422 // only use first delay value for new events, but not for changed events
2423 use_delay_value_first = (!joytest != !joytest_last);
2425 // only use delay after the initial keyboard event
2429 // for any joystick or keyboard event, enable playfield tile cursor
2430 if (dx || dy || button)
2431 SetTileCursorEnabled(TRUE);
2434 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2436 // delay joystick/keyboard actions if axes/keys continually pressed
2437 newbutton = dx = dy = 0;
2441 // first start with longer delay, then continue with shorter delay
2442 joytest_delay_value =
2443 (use_delay_value_first ? delay_value_first : delay_value);
2446 joytest_last = joytest;
2448 switch (game_status)
2450 case GAME_MODE_TITLE:
2451 case GAME_MODE_MAIN:
2452 case GAME_MODE_LEVELS:
2453 case GAME_MODE_LEVELNR:
2454 case GAME_MODE_SETUP:
2455 case GAME_MODE_INFO:
2456 case GAME_MODE_SCORES:
2458 if (anyTextGadgetActive())
2461 if (game_status == GAME_MODE_TITLE)
2462 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2463 else if (game_status == GAME_MODE_MAIN)
2464 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2465 else if (game_status == GAME_MODE_LEVELS)
2466 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2467 else if (game_status == GAME_MODE_LEVELNR)
2468 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2469 else if (game_status == GAME_MODE_SETUP)
2470 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2471 else if (game_status == GAME_MODE_INFO)
2472 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2473 else if (game_status == GAME_MODE_SCORES)
2474 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2479 case GAME_MODE_PLAYING:
2481 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2482 if (tape.playing || keyboard)
2483 newbutton = ((joy & JOY_BUTTON) != 0);
2486 if (newbutton && game.all_players_gone)
2493 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2495 if (joystick & JOY_ACTION)
2496 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2498 else if (tape.recording && tape.pausing && !tape.use_mouse)
2500 if (joystick & JOY_ACTION)
2501 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2504 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2505 HandleTileCursor(dx, dy, button);
2514 void HandleSpecialGameControllerButtons(Event *event)
2519 switch (event->type)
2521 case SDL_CONTROLLERBUTTONDOWN:
2522 key_status = KEY_PRESSED;
2525 case SDL_CONTROLLERBUTTONUP:
2526 key_status = KEY_RELEASED;
2533 switch (event->cbutton.button)
2535 case SDL_CONTROLLER_BUTTON_START:
2539 case SDL_CONTROLLER_BUTTON_BACK:
2547 HandleKey(key, key_status);
2550 void HandleSpecialGameControllerKeys(Key key, int key_status)
2552 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2553 int button = SDL_CONTROLLER_BUTTON_INVALID;
2555 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2556 if (key == KSYM_Rewind)
2557 button = SDL_CONTROLLER_BUTTON_A;
2558 else if (key == KSYM_FastForward || key == KSYM_Menu)
2559 button = SDL_CONTROLLER_BUTTON_B;
2561 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2565 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2566 SDL_CONTROLLERBUTTONUP);
2568 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2569 event.cbutton.button = button;
2570 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2573 HandleJoystickEvent(&event);
2578 boolean DoKeysymAction(int keysym)
2582 Key key = (Key)(-keysym);
2584 HandleKey(key, KEY_PRESSED);
2585 HandleKey(key, KEY_RELEASED);