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);
271 #if defined(USE_DRAG_AND_DROP)
273 case SDL_DROPCOMPLETE:
276 HandleDropEvent(event);
285 static void HandleMouseCursor(void)
287 if (game_status == GAME_MODE_TITLE)
289 // when showing title screens, hide mouse pointer (if not moved)
291 if (gfx.cursor_mode != CURSOR_NONE &&
292 DelayReached(&special_cursor_delay, special_cursor_delay_value))
294 SetMouseCursor(CURSOR_NONE);
297 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
300 // when playing, display a special mouse pointer inside the playfield
302 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
303 cursor_inside_playfield &&
304 DelayReached(&special_cursor_delay, special_cursor_delay_value))
306 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
308 SetMouseCursor(CURSOR_PLAYFIELD);
311 else if (gfx.cursor_mode != CURSOR_DEFAULT)
313 SetMouseCursor(CURSOR_DEFAULT);
316 // this is set after all pending events have been processed
317 cursor_mode_last = gfx.cursor_mode;
329 // execute event related actions after pending events have been processed
330 HandleEventActions();
332 // don't use all CPU time when idle; the main loop while playing
333 // has its own synchronization and is CPU friendly, too
335 if (game_status == GAME_MODE_PLAYING)
338 // always copy backbuffer to visible screen for every video frame
341 // reset video frame delay to default (may change again while playing)
342 SetVideoFrameDelay(MenuFrameDelay);
344 if (game_status == GAME_MODE_QUIT)
349 void ClearAutoRepeatKeyEvents(void)
351 while (PendingEvent())
355 PeekEvent(&next_event);
357 // if event is repeated key press event, remove it from event queue
358 if (next_event.type == EVENT_KEYPRESS &&
359 next_event.key.repeat)
360 WaitEvent(&next_event);
366 void ClearEventQueue(void)
370 while (NextValidEvent(&event))
374 case EVENT_BUTTONRELEASE:
375 button_status = MB_RELEASED;
378 case EVENT_KEYRELEASE:
382 case SDL_CONTROLLERBUTTONUP:
383 HandleJoystickEvent(&event);
388 HandleOtherEvents(&event);
394 static void ClearPlayerMouseAction(void)
396 local_player->mouse_action.lx = 0;
397 local_player->mouse_action.ly = 0;
398 local_player->mouse_action.button = 0;
401 void ClearPlayerAction(void)
405 // simulate key release events for still pressed keys
406 key_joystick_mapping = 0;
407 for (i = 0; i < MAX_PLAYERS; i++)
408 stored_player[i].action = 0;
410 ClearJoystickState();
411 ClearPlayerMouseAction();
414 static void SetPlayerMouseAction(int mx, int my, int button)
416 int lx = getLevelFromScreenX(mx);
417 int ly = getLevelFromScreenY(my);
418 int new_button = (!local_player->mouse_action.button && button);
420 if (local_player->mouse_action.button_hint)
421 button = local_player->mouse_action.button_hint;
423 ClearPlayerMouseAction();
425 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
428 local_player->mouse_action.lx = lx;
429 local_player->mouse_action.ly = ly;
430 local_player->mouse_action.button = button;
432 if (tape.recording && tape.pausing && tape.use_mouse)
434 // un-pause a paused game only if mouse button was newly pressed down
436 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
439 SetTileCursorXY(lx, ly);
442 void SleepWhileUnmapped(void)
444 boolean window_unmapped = TRUE;
446 KeyboardAutoRepeatOn();
448 while (window_unmapped)
452 if (!WaitValidEvent(&event))
457 case EVENT_BUTTONRELEASE:
458 button_status = MB_RELEASED;
461 case EVENT_KEYRELEASE:
462 key_joystick_mapping = 0;
465 case SDL_CONTROLLERBUTTONUP:
466 HandleJoystickEvent(&event);
467 key_joystick_mapping = 0;
470 case EVENT_MAPNOTIFY:
471 window_unmapped = FALSE;
474 case EVENT_UNMAPNOTIFY:
475 // this is only to surely prevent the 'should not happen' case
476 // of recursively looping between 'SleepWhileUnmapped()' and
477 // 'HandleOtherEvents()' which usually calls this funtion.
481 HandleOtherEvents(&event);
486 if (game_status == GAME_MODE_PLAYING)
487 KeyboardAutoRepeatOffUnlessAutoplay();
490 void HandleExposeEvent(ExposeEvent *event)
494 void HandleButtonEvent(ButtonEvent *event)
496 #if DEBUG_EVENTS_BUTTON
497 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
499 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
503 // for any mouse button event, disable playfield tile cursor
504 SetTileCursorEnabled(FALSE);
506 #if defined(HAS_SCREEN_KEYBOARD)
507 if (video.shifted_up)
508 event->y += video.shifted_up_pos;
511 motion_status = FALSE;
513 if (event->type == EVENT_BUTTONPRESS)
514 button_status = event->button;
516 button_status = MB_RELEASED;
518 HandleButton(event->x, event->y, button_status, event->button);
521 void HandleMotionEvent(MotionEvent *event)
523 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
526 motion_status = TRUE;
528 #if DEBUG_EVENTS_MOTION
529 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
530 button_status, event->x, event->y);
533 HandleButton(event->x, event->y, button_status, button_status);
536 void HandleWheelEvent(WheelEvent *event)
540 #if DEBUG_EVENTS_WHEEL
542 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
543 event->which, event->x, event->y);
545 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
546 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
547 event->which, event->x, event->y,
548 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
549 "SDL_MOUSEWHEEL_FLIPPED"));
553 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
554 event->x > 0 ? MB_WHEEL_RIGHT :
555 event->y < 0 ? MB_WHEEL_DOWN :
556 event->y > 0 ? MB_WHEEL_UP : 0);
558 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
559 // accelerated mouse wheel available on Mac and Windows
560 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
562 // no accelerated mouse wheel available on Unix/Linux
563 wheel_steps = DEFAULT_WHEEL_STEPS;
566 motion_status = FALSE;
568 button_status = button_nr;
569 HandleButton(0, 0, button_status, -button_nr);
571 button_status = MB_RELEASED;
572 HandleButton(0, 0, button_status, -button_nr);
575 void HandleWindowEvent(WindowEvent *event)
577 #if DEBUG_EVENTS_WINDOW
578 int subtype = event->event;
581 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
582 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
583 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
584 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
585 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
586 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
587 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
588 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
589 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
590 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
591 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
592 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
593 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
594 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
597 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
598 event_name, event->data1, event->data2);
602 // (not needed, as the screen gets redrawn every 20 ms anyway)
603 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
604 event->event == SDL_WINDOWEVENT_RESIZED ||
605 event->event == SDL_WINDOWEVENT_EXPOSED)
609 if (event->event == SDL_WINDOWEVENT_RESIZED)
611 if (!video.fullscreen_enabled)
613 int new_window_width = event->data1;
614 int new_window_height = event->data2;
616 // if window size has changed after resizing, calculate new scaling factor
617 if (new_window_width != video.window_width ||
618 new_window_height != video.window_height)
620 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
621 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
623 // (extreme window scaling allowed, but cannot be saved permanently)
624 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
625 setup.window_scaling_percent =
626 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
627 MAX_WINDOW_SCALING_PERCENT);
629 video.window_width = new_window_width;
630 video.window_height = new_window_height;
632 if (game_status == GAME_MODE_SETUP)
633 RedrawSetupScreenAfterFullscreenToggle();
638 #if defined(PLATFORM_ANDROID)
641 int new_display_width = event->data1;
642 int new_display_height = event->data2;
644 // if fullscreen display size has changed, device has been rotated
645 if (new_display_width != video.display_width ||
646 new_display_height != video.display_height)
648 int nr = GRID_ACTIVE_NR(); // previous screen orientation
650 video.display_width = new_display_width;
651 video.display_height = new_display_height;
653 SDLSetScreenProperties();
655 // check if screen orientation has changed (should always be true here)
656 if (nr != GRID_ACTIVE_NR())
660 if (game_status == GAME_MODE_SETUP)
661 RedrawSetupScreenAfterScreenRotation(nr);
663 nr = GRID_ACTIVE_NR();
665 overlay.grid_xsize = setup.touch.grid_xsize[nr];
666 overlay.grid_ysize = setup.touch.grid_ysize[nr];
668 for (x = 0; x < MAX_GRID_XSIZE; x++)
669 for (y = 0; y < MAX_GRID_YSIZE; y++)
670 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
678 #define NUM_TOUCH_FINGERS 3
683 SDL_FingerID finger_id;
686 } touch_info[NUM_TOUCH_FINGERS];
688 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
691 int x = event->x * overlay.grid_xsize;
692 int y = event->y * overlay.grid_ysize;
693 int grid_button = overlay.grid_button[x][y];
694 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
695 Key key = (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
696 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
697 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
698 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
699 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
700 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
703 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
704 float event_x = (event->x);
705 float event_y = (event->y - ypos) / (1 - ypos);
706 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
707 event_y > 2.0 / 3.0 && event_y < 1 ?
708 setup.input[0].key.snap :
709 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
710 event_y > 2.0 / 3.0 && event_y < 1 ?
711 setup.input[0].key.drop :
712 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
713 event_y > 0 && event_y < 1.0 / 3.0 ?
714 setup.input[0].key.up :
715 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
716 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
717 setup.input[0].key.left :
718 event_x > 8.0 / 9.0 && event_x < 1 &&
719 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
720 setup.input[0].key.right :
721 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
722 event_y > 2.0 / 3.0 && event_y < 1 ?
723 setup.input[0].key.down :
726 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
728 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
732 virtual_button_pressed = (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
734 // for any touch input event, enable overlay buttons (if activated)
735 SetOverlayEnabled(TRUE);
737 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
738 getKeyNameFromKey(key), key_status_name, event->fingerId);
740 if (key_status == KEY_PRESSED)
741 overlay.grid_button_action |= grid_button_action;
743 overlay.grid_button_action &= ~grid_button_action;
745 // check if we already know this touch event's finger id
746 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
748 if (touch_info[i].touched &&
749 touch_info[i].finger_id == event->fingerId)
751 // Error(ERR_DEBUG, "MARK 1: %d", i);
757 if (i >= NUM_TOUCH_FINGERS)
759 if (key_status == KEY_PRESSED)
761 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
763 // unknown finger id -- get new, empty slot, if available
764 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
766 if (touch_info[i].counter < oldest_counter)
769 oldest_counter = touch_info[i].counter;
771 // Error(ERR_DEBUG, "MARK 2: %d", i);
774 if (!touch_info[i].touched)
776 // Error(ERR_DEBUG, "MARK 3: %d", i);
782 if (i >= NUM_TOUCH_FINGERS)
784 // all slots allocated -- use oldest slot
787 // Error(ERR_DEBUG, "MARK 4: %d", i);
792 // release of previously unknown key (should not happen)
794 if (key != KSYM_UNDEFINED)
796 HandleKey(key, KEY_RELEASED);
798 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
799 getKeyNameFromKey(key), "KEY_RELEASED", i);
804 if (i < NUM_TOUCH_FINGERS)
806 if (key_status == KEY_PRESSED)
808 if (touch_info[i].key != key)
810 if (touch_info[i].key != KSYM_UNDEFINED)
812 HandleKey(touch_info[i].key, KEY_RELEASED);
814 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
815 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
818 if (key != KSYM_UNDEFINED)
820 HandleKey(key, KEY_PRESSED);
822 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
823 getKeyNameFromKey(key), "KEY_PRESSED", i);
827 touch_info[i].touched = TRUE;
828 touch_info[i].finger_id = event->fingerId;
829 touch_info[i].counter = Counter();
830 touch_info[i].key = key;
834 if (touch_info[i].key != KSYM_UNDEFINED)
836 HandleKey(touch_info[i].key, KEY_RELEASED);
838 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
839 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
842 touch_info[i].touched = FALSE;
843 touch_info[i].finger_id = 0;
844 touch_info[i].counter = 0;
845 touch_info[i].key = 0;
850 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
852 static Key motion_key_x = KSYM_UNDEFINED;
853 static Key motion_key_y = KSYM_UNDEFINED;
854 static Key button_key = KSYM_UNDEFINED;
855 static float motion_x1, motion_y1;
856 static float button_x1, button_y1;
857 static SDL_FingerID motion_id = -1;
858 static SDL_FingerID button_id = -1;
859 int move_trigger_distance_percent = setup.touch.move_distance;
860 int drop_trigger_distance_percent = setup.touch.drop_distance;
861 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
862 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
863 float event_x = event->x;
864 float event_y = event->y;
866 if (event->type == EVENT_FINGERPRESS)
868 if (event_x > 1.0 / 3.0)
872 motion_id = event->fingerId;
877 motion_key_x = KSYM_UNDEFINED;
878 motion_key_y = KSYM_UNDEFINED;
880 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
886 button_id = event->fingerId;
891 button_key = setup.input[0].key.snap;
893 HandleKey(button_key, KEY_PRESSED);
895 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
898 else if (event->type == EVENT_FINGERRELEASE)
900 if (event->fingerId == motion_id)
904 if (motion_key_x != KSYM_UNDEFINED)
905 HandleKey(motion_key_x, KEY_RELEASED);
906 if (motion_key_y != KSYM_UNDEFINED)
907 HandleKey(motion_key_y, KEY_RELEASED);
909 motion_key_x = KSYM_UNDEFINED;
910 motion_key_y = KSYM_UNDEFINED;
912 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
914 else if (event->fingerId == button_id)
918 if (button_key != KSYM_UNDEFINED)
919 HandleKey(button_key, KEY_RELEASED);
921 button_key = KSYM_UNDEFINED;
923 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
926 else if (event->type == EVENT_FINGERMOTION)
928 if (event->fingerId == motion_id)
930 float distance_x = ABS(event_x - motion_x1);
931 float distance_y = ABS(event_y - motion_y1);
932 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
933 event_x > motion_x1 ? setup.input[0].key.right :
935 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
936 event_y > motion_y1 ? setup.input[0].key.down :
939 if (distance_x < move_trigger_distance / 2 ||
940 distance_x < distance_y)
941 new_motion_key_x = KSYM_UNDEFINED;
943 if (distance_y < move_trigger_distance / 2 ||
944 distance_y < distance_x)
945 new_motion_key_y = KSYM_UNDEFINED;
947 if (distance_x > move_trigger_distance ||
948 distance_y > move_trigger_distance)
950 if (new_motion_key_x != motion_key_x)
952 if (motion_key_x != KSYM_UNDEFINED)
953 HandleKey(motion_key_x, KEY_RELEASED);
954 if (new_motion_key_x != KSYM_UNDEFINED)
955 HandleKey(new_motion_key_x, KEY_PRESSED);
958 if (new_motion_key_y != motion_key_y)
960 if (motion_key_y != KSYM_UNDEFINED)
961 HandleKey(motion_key_y, KEY_RELEASED);
962 if (new_motion_key_y != KSYM_UNDEFINED)
963 HandleKey(new_motion_key_y, KEY_PRESSED);
969 motion_key_x = new_motion_key_x;
970 motion_key_y = new_motion_key_y;
972 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
975 else if (event->fingerId == button_id)
977 float distance_x = ABS(event_x - button_x1);
978 float distance_y = ABS(event_y - button_y1);
980 if (distance_x < drop_trigger_distance / 2 &&
981 distance_y > drop_trigger_distance)
983 if (button_key == setup.input[0].key.snap)
984 HandleKey(button_key, KEY_RELEASED);
989 button_key = setup.input[0].key.drop;
991 HandleKey(button_key, KEY_PRESSED);
993 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
999 void HandleFingerEvent(FingerEvent *event)
1001 #if DEBUG_EVENTS_FINGER
1002 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
1003 event->type == EVENT_FINGERPRESS ? "pressed" :
1004 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
1008 event->dx, event->dy,
1012 if (game_status != GAME_MODE_PLAYING)
1015 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1017 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1018 local_player->mouse_action.button_hint =
1019 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1020 event->x < 0.5 ? MB_LEFTBUTTON :
1021 event->x > 0.5 ? MB_RIGHTBUTTON :
1027 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1028 HandleFingerEvent_VirtualButtons(event);
1029 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1030 HandleFingerEvent_WipeGestures(event);
1033 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1035 static int old_mx = 0, old_my = 0;
1036 static int last_button = MB_LEFTBUTTON;
1037 static boolean touched = FALSE;
1038 static boolean tapped = FALSE;
1040 // screen tile was tapped (but finger not touching the screen anymore)
1041 // (this point will also be reached without receiving a touch event)
1042 if (tapped && !touched)
1044 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1049 // stop here if this function was not triggered by a touch event
1053 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1055 // finger started touching the screen
1065 ClearPlayerMouseAction();
1067 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1070 else if (button == MB_RELEASED && touched)
1072 // finger stopped touching the screen
1077 SetPlayerMouseAction(old_mx, old_my, last_button);
1079 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1081 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1086 // finger moved while touching the screen
1088 int old_x = getLevelFromScreenX(old_mx);
1089 int old_y = getLevelFromScreenY(old_my);
1090 int new_x = getLevelFromScreenX(mx);
1091 int new_y = getLevelFromScreenY(my);
1093 if (new_x != old_x || new_y != old_y)
1098 // finger moved left or right from (horizontal) starting position
1100 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1102 SetPlayerMouseAction(old_mx, old_my, button_nr);
1104 last_button = button_nr;
1106 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1110 // finger stays at or returned to (horizontal) starting position
1112 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1114 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1119 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1121 static int old_mx = 0, old_my = 0;
1122 static int last_button = MB_LEFTBUTTON;
1123 static boolean touched = FALSE;
1124 static boolean tapped = FALSE;
1126 // screen tile was tapped (but finger not touching the screen anymore)
1127 // (this point will also be reached without receiving a touch event)
1128 if (tapped && !touched)
1130 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1135 // stop here if this function was not triggered by a touch event
1139 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1141 // finger started touching the screen
1151 ClearPlayerMouseAction();
1153 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1156 else if (button == MB_RELEASED && touched)
1158 // finger stopped touching the screen
1163 SetPlayerMouseAction(old_mx, old_my, last_button);
1165 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1167 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1172 // finger moved while touching the screen
1174 int old_x = getLevelFromScreenX(old_mx);
1175 int old_y = getLevelFromScreenY(old_my);
1176 int new_x = getLevelFromScreenX(mx);
1177 int new_y = getLevelFromScreenY(my);
1179 if (new_x != old_x || new_y != old_y)
1181 // finger moved away from starting position
1183 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1185 // quickly alternate between clicking and releasing for maximum speed
1186 if (FrameCounter % 2 == 0)
1187 button_nr = MB_RELEASED;
1189 SetPlayerMouseAction(old_mx, old_my, button_nr);
1192 last_button = button_nr;
1196 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1200 // finger stays at or returned to starting position
1202 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1204 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1209 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1211 static int old_mx = 0, old_my = 0;
1212 static Key motion_key_x = KSYM_UNDEFINED;
1213 static Key motion_key_y = KSYM_UNDEFINED;
1214 static boolean touched = FALSE;
1215 static boolean started_on_player = FALSE;
1216 static boolean player_is_dropping = FALSE;
1217 static int player_drop_count = 0;
1218 static int last_player_x = -1;
1219 static int last_player_y = -1;
1221 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1230 started_on_player = FALSE;
1231 player_is_dropping = FALSE;
1232 player_drop_count = 0;
1236 motion_key_x = KSYM_UNDEFINED;
1237 motion_key_y = KSYM_UNDEFINED;
1239 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1242 else if (button == MB_RELEASED && touched)
1249 if (motion_key_x != KSYM_UNDEFINED)
1250 HandleKey(motion_key_x, KEY_RELEASED);
1251 if (motion_key_y != KSYM_UNDEFINED)
1252 HandleKey(motion_key_y, KEY_RELEASED);
1254 if (started_on_player)
1256 if (player_is_dropping)
1258 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1260 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1264 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1266 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1270 motion_key_x = KSYM_UNDEFINED;
1271 motion_key_y = KSYM_UNDEFINED;
1273 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1278 int src_x = local_player->jx;
1279 int src_y = local_player->jy;
1280 int dst_x = getLevelFromScreenX(old_mx);
1281 int dst_y = getLevelFromScreenY(old_my);
1282 int dx = dst_x - src_x;
1283 int dy = dst_y - src_y;
1284 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1285 dx > 0 ? setup.input[0].key.right :
1287 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1288 dy > 0 ? setup.input[0].key.down :
1291 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1292 (last_player_x != local_player->jx ||
1293 last_player_y != local_player->jy))
1295 // in case of asymmetric diagonal movement, use "preferred" direction
1297 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1299 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1300 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1302 local_player->last_move_dir = last_move_dir;
1304 // (required to prevent accidentally forcing direction for next movement)
1305 last_player_x = local_player->jx;
1306 last_player_y = local_player->jy;
1309 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1311 started_on_player = TRUE;
1312 player_drop_count = getPlayerInventorySize(0);
1313 player_is_dropping = (player_drop_count > 0);
1315 if (player_is_dropping)
1317 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1319 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1323 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1325 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1328 else if (dx != 0 || dy != 0)
1330 if (player_is_dropping &&
1331 player_drop_count == getPlayerInventorySize(0))
1333 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1335 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1336 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1338 player_is_dropping = FALSE;
1342 if (new_motion_key_x != motion_key_x)
1344 Error(ERR_DEBUG, "---------- %s %s ----------",
1345 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1346 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1348 if (motion_key_x != KSYM_UNDEFINED)
1349 HandleKey(motion_key_x, KEY_RELEASED);
1350 if (new_motion_key_x != KSYM_UNDEFINED)
1351 HandleKey(new_motion_key_x, KEY_PRESSED);
1354 if (new_motion_key_y != motion_key_y)
1356 Error(ERR_DEBUG, "---------- %s %s ----------",
1357 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1358 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1360 if (motion_key_y != KSYM_UNDEFINED)
1361 HandleKey(motion_key_y, KEY_RELEASED);
1362 if (new_motion_key_y != KSYM_UNDEFINED)
1363 HandleKey(new_motion_key_y, KEY_PRESSED);
1366 motion_key_x = new_motion_key_x;
1367 motion_key_y = new_motion_key_y;
1371 static void HandleButtonOrFinger(int mx, int my, int button)
1373 if (game_status != GAME_MODE_PLAYING)
1376 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1378 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1379 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1380 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1381 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1382 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1383 SetPlayerMouseAction(mx, my, button); // special case
1387 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1388 HandleButtonOrFinger_FollowFinger(mx, my, button);
1392 static boolean checkTextInputKeyModState(void)
1394 // when playing, only handle raw key events and ignore text input
1395 if (game_status == GAME_MODE_PLAYING)
1398 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1401 void HandleTextEvent(TextEvent *event)
1403 char *text = event->text;
1404 Key key = getKeyFromKeyName(text);
1406 #if DEBUG_EVENTS_TEXT
1407 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1410 text[0], (int)(text[0]),
1412 getKeyNameFromKey(key),
1416 #if !defined(HAS_SCREEN_KEYBOARD)
1417 // non-mobile devices: only handle key input with modifier keys pressed here
1418 // (every other key input is handled directly as physical key input event)
1419 if (!checkTextInputKeyModState())
1423 // process text input as "classic" (with uppercase etc.) key input event
1424 HandleKey(key, KEY_PRESSED);
1425 HandleKey(key, KEY_RELEASED);
1428 void HandlePauseResumeEvent(PauseResumeEvent *event)
1430 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1434 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1440 void HandleKeyEvent(KeyEvent *event)
1442 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1443 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1444 Key key = GetEventKey(event, with_modifiers);
1445 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1447 #if DEBUG_EVENTS_KEY
1448 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1449 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1450 event->keysym.scancode,
1455 getKeyNameFromKey(key));
1458 #if defined(PLATFORM_ANDROID)
1459 if (key == KSYM_Back)
1461 // always map the "back" button to the "escape" key on Android devices
1464 else if (key == KSYM_Menu)
1466 // the "menu" button can be used to toggle displaying virtual buttons
1467 if (key_status == KEY_PRESSED)
1468 SetOverlayEnabled(!GetOverlayEnabled());
1472 // for any other "real" key event, disable virtual buttons
1473 SetOverlayEnabled(FALSE);
1477 HandleKeyModState(keymod, key_status);
1479 // only handle raw key input without text modifier keys pressed
1480 if (!checkTextInputKeyModState())
1481 HandleKey(key, key_status);
1484 void HandleFocusEvent(FocusChangeEvent *event)
1486 static int old_joystick_status = -1;
1488 if (event->type == EVENT_FOCUSOUT)
1490 KeyboardAutoRepeatOn();
1491 old_joystick_status = joystick.status;
1492 joystick.status = JOYSTICK_NOT_AVAILABLE;
1494 ClearPlayerAction();
1496 else if (event->type == EVENT_FOCUSIN)
1498 /* When there are two Rocks'n'Diamonds windows which overlap and
1499 the player moves the pointer from one game window to the other,
1500 a 'FocusOut' event is generated for the window the pointer is
1501 leaving and a 'FocusIn' event is generated for the window the
1502 pointer is entering. In some cases, it can happen that the
1503 'FocusIn' event is handled by the one game process before the
1504 'FocusOut' event by the other game process. In this case the
1505 X11 environment would end up with activated keyboard auto repeat,
1506 because unfortunately this is a global setting and not (which
1507 would be far better) set for each X11 window individually.
1508 The effect would be keyboard auto repeat while playing the game
1509 (game_status == GAME_MODE_PLAYING), which is not desired.
1510 To avoid this special case, we just wait 1/10 second before
1511 processing the 'FocusIn' event. */
1513 if (game_status == GAME_MODE_PLAYING)
1516 KeyboardAutoRepeatOffUnlessAutoplay();
1519 if (old_joystick_status != -1)
1520 joystick.status = old_joystick_status;
1524 void HandleClientMessageEvent(ClientMessageEvent *event)
1526 if (CheckCloseWindowEvent(event))
1530 #if defined(USE_DRAG_AND_DROP)
1531 static boolean HandleDropFileEvent(char *filename)
1533 Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
1535 // check and extract dropped zip files into correct user data directory
1536 if (!strSuffixLower(filename, ".zip"))
1538 Error(ERR_WARN, "file '%s' not supported", filename);
1543 TreeInfo *tree_node = NULL;
1544 int tree_type = GetZipFileTreeType(filename);
1545 char *directory = TREE_USERDIR(tree_type);
1547 if (directory == NULL)
1549 Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1554 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1555 game_status == GAME_MODE_LEVELS &&
1556 leveldir_current->node_parent != NULL)
1558 // extract new level set next to currently selected level set
1559 tree_node = leveldir_current;
1561 // get parent directory of currently selected level set directory
1562 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1564 // use private level directory instead of top-level package level directory
1565 if (strPrefix(directory, options.level_directory) &&
1566 strEqual(leveldir_current->node_parent->fullpath, "."))
1567 directory = getUserLevelDir(NULL);
1570 // extract level or artwork set from zip file to target directory
1571 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1573 if (top_dir == NULL)
1575 // error message already issued by "ExtractZipFileIntoDirectory()"
1580 // add extracted level or artwork set to tree info structure
1581 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1583 // update menu screen (and possibly change current level set)
1584 DrawScreenAfterAddingSet(top_dir, tree_type);
1589 static void HandleDropTextEvent(char *text)
1591 Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1594 void HandleDropEvent(Event *event)
1596 static int files_succeeded = 0;
1597 static int files_failed = 0;
1599 switch (event->type)
1603 files_succeeded = 0;
1611 boolean success = HandleDropFileEvent(event->drop.file);
1623 HandleDropTextEvent(event->drop.file);
1628 case SDL_DROPCOMPLETE:
1630 // only show request dialog if no other request dialog already active
1631 if (!game.request_active)
1633 if (files_succeeded > 0 && files_failed > 0)
1634 Request("New level or artwork set(s) added, "
1635 "but some dropped file(s) failed!", REQ_CONFIRM);
1636 else if (files_succeeded > 0)
1637 Request("New level or artwork set(s) added!", REQ_CONFIRM);
1638 else if (files_failed > 0)
1639 Request("Failed to process dropped file(s)!", REQ_CONFIRM);
1646 if (event->drop.file != NULL)
1647 SDL_free(event->drop.file);
1651 void HandleButton(int mx, int my, int button, int button_nr)
1653 static int old_mx = 0, old_my = 0;
1654 boolean button_hold = FALSE;
1655 boolean handle_gadgets = TRUE;
1661 button_nr = -button_nr;
1670 #if defined(PLATFORM_ANDROID)
1671 // when playing, only handle gadgets when using "follow finger" controls
1672 // or when using touch controls in combination with the MM game engine
1673 // or when using gadgets that do not overlap with virtual buttons
1675 (game_status != GAME_MODE_PLAYING ||
1676 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1677 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1678 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1679 !virtual_button_pressed));
1682 if (HandleGlobalAnimClicks(mx, my, button))
1684 // do not handle this button event anymore
1685 return; // force mouse event not to be handled at all
1688 if (handle_gadgets && HandleGadgets(mx, my, button))
1690 // do not handle this button event anymore
1691 mx = my = -32; // force mouse event to be outside screen tiles
1694 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1697 // do not use scroll wheel button events for anything other than gadgets
1698 if (IS_WHEEL_BUTTON(button_nr))
1701 switch (game_status)
1703 case GAME_MODE_TITLE:
1704 HandleTitleScreen(mx, my, 0, 0, button);
1707 case GAME_MODE_MAIN:
1708 HandleMainMenu(mx, my, 0, 0, button);
1711 case GAME_MODE_PSEUDO_TYPENAME:
1712 HandleTypeName(0, KSYM_Return);
1715 case GAME_MODE_LEVELS:
1716 HandleChooseLevelSet(mx, my, 0, 0, button);
1719 case GAME_MODE_LEVELNR:
1720 HandleChooseLevelNr(mx, my, 0, 0, button);
1723 case GAME_MODE_SCORES:
1724 HandleHallOfFame(0, 0, 0, 0, button);
1727 case GAME_MODE_EDITOR:
1728 HandleLevelEditorIdle();
1731 case GAME_MODE_INFO:
1732 HandleInfoScreen(mx, my, 0, 0, button);
1735 case GAME_MODE_SETUP:
1736 HandleSetupScreen(mx, my, 0, 0, button);
1739 case GAME_MODE_PLAYING:
1740 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1741 HandleButtonOrFinger(mx, my, button);
1743 SetPlayerMouseAction(mx, my, button);
1746 if (button == MB_PRESSED && !motion_status && !button_hold &&
1747 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1748 DumpTileFromScreen(mx, my);
1758 static boolean is_string_suffix(char *string, char *suffix)
1760 int string_len = strlen(string);
1761 int suffix_len = strlen(suffix);
1763 if (suffix_len > string_len)
1766 return (strEqual(&string[string_len - suffix_len], suffix));
1769 #define MAX_CHEAT_INPUT_LEN 32
1771 static void HandleKeysSpecial(Key key)
1773 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1774 char letter = getCharFromKey(key);
1775 int cheat_input_len = strlen(cheat_input);
1781 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1783 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1784 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1786 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1789 cheat_input[cheat_input_len++] = letter;
1790 cheat_input[cheat_input_len] = '\0';
1792 #if DEBUG_EVENTS_KEY
1793 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1796 if (game_status == GAME_MODE_MAIN)
1798 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1799 is_string_suffix(cheat_input, ":ist"))
1801 InsertSolutionTape();
1803 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1804 is_string_suffix(cheat_input, ":pst"))
1808 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1809 is_string_suffix(cheat_input, ":rg"))
1811 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1814 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1815 is_string_suffix(cheat_input, ":rs"))
1817 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1820 else if (is_string_suffix(cheat_input, ":reload-music") ||
1821 is_string_suffix(cheat_input, ":rm"))
1823 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1826 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1827 is_string_suffix(cheat_input, ":ra"))
1829 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1830 1 << ARTWORK_TYPE_SOUNDS |
1831 1 << ARTWORK_TYPE_MUSIC);
1834 else if (is_string_suffix(cheat_input, ":dump-level") ||
1835 is_string_suffix(cheat_input, ":dl"))
1839 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1840 is_string_suffix(cheat_input, ":dt"))
1844 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1845 is_string_suffix(cheat_input, ":ft"))
1847 /* fix single-player tapes that contain player input for more than one
1848 player (due to a bug in 3.3.1.2 and earlier versions), which results
1849 in playing levels with more than one player in multi-player mode,
1850 even though the tape was originally recorded in single-player mode */
1852 // remove player input actions for all players but the first one
1853 for (i = 1; i < MAX_PLAYERS; i++)
1854 tape.player_participates[i] = FALSE;
1856 tape.changed = TRUE;
1858 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1859 is_string_suffix(cheat_input, ":snl"))
1861 SaveNativeLevel(&level);
1863 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1864 is_string_suffix(cheat_input, ":fps"))
1866 global.show_frames_per_second = !global.show_frames_per_second;
1869 else if (game_status == GAME_MODE_PLAYING)
1872 if (is_string_suffix(cheat_input, ".q"))
1873 DEBUG_SetMaximumDynamite();
1876 else if (game_status == GAME_MODE_EDITOR)
1878 if (is_string_suffix(cheat_input, ":dump-brush") ||
1879 is_string_suffix(cheat_input, ":DB"))
1883 else if (is_string_suffix(cheat_input, ":DDB"))
1888 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1890 if (letter == 'x') // copy brush to clipboard (small size)
1892 CopyBrushToClipboard_Small();
1894 else if (letter == 'c') // copy brush to clipboard (normal size)
1896 CopyBrushToClipboard();
1898 else if (letter == 'v') // paste brush from Clipboard
1900 CopyClipboardToBrush();
1905 // special key shortcuts for all game modes
1906 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1907 is_string_suffix(cheat_input, ":dea") ||
1908 is_string_suffix(cheat_input, ":DEA"))
1910 DumpGadgetIdentifiers();
1911 DumpScreenIdentifiers();
1915 boolean HandleKeysDebug(Key key, int key_status)
1920 if (key_status != KEY_PRESSED)
1923 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1925 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1927 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1929 if (key == setup.debug.frame_delay_key[i] &&
1930 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1932 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1933 setup.debug.frame_delay[i] : setup.game_frame_delay);
1935 if (!setup.debug.frame_delay_game_only)
1936 MenuFrameDelay = GameFrameDelay;
1938 SetVideoFrameDelay(GameFrameDelay);
1940 if (GameFrameDelay > ONE_SECOND_DELAY)
1941 Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
1942 else if (GameFrameDelay != 0)
1943 Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
1944 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1945 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1947 Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
1954 if (game_status == GAME_MODE_PLAYING)
1958 options.debug = !options.debug;
1960 Error(ERR_INFO, "debug mode %s",
1961 (options.debug ? "enabled" : "disabled"));
1965 else if (key == KSYM_v)
1967 Error(ERR_INFO, "currently using game engine version %d",
1968 game.engine_version);
1978 void HandleKey(Key key, int key_status)
1980 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1981 static boolean ignore_repeated_key = FALSE;
1982 static struct SetupKeyboardInfo ski;
1983 static struct SetupShortcutInfo ssi;
1992 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1993 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1994 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1995 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1996 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1997 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2002 if (HandleKeysDebug(key, key_status))
2003 return; // do not handle already processed keys again
2005 // map special keys (media keys / remote control buttons) to default keys
2006 if (key == KSYM_PlayPause)
2008 else if (key == KSYM_Select)
2011 HandleSpecialGameControllerKeys(key, key_status);
2013 if (game_status == GAME_MODE_PLAYING)
2015 // only needed for single-step tape recording mode
2016 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2019 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2021 byte key_action = 0;
2022 byte key_snap_action = 0;
2024 if (setup.input[pnr].use_joystick)
2027 ski = setup.input[pnr].key;
2029 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2030 if (key == *key_info[i].key_custom)
2031 key_action |= key_info[i].action;
2033 // use combined snap+direction keys for the first player only
2036 ssi = setup.shortcut;
2038 // also remember normal snap key when handling snap+direction keys
2039 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2041 for (i = 0; i < NUM_DIRECTIONS; i++)
2043 if (key == *key_info[i].key_snap)
2045 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2046 key_snap_action |= key_info[i].action;
2051 if (key_status == KEY_PRESSED)
2053 stored_player[pnr].action |= key_action;
2054 stored_player[pnr].snap_action |= key_snap_action;
2058 stored_player[pnr].action &= ~key_action;
2059 stored_player[pnr].snap_action &= ~key_snap_action;
2062 // restore snap action if one of several pressed snap keys was released
2063 if (stored_player[pnr].snap_action)
2064 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2066 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2068 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2070 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2072 // if snap key already pressed, keep pause mode when releasing
2073 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2074 has_snapped[pnr] = TRUE;
2076 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2078 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2080 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2081 getRedDiskReleaseFlag_SP() == 0)
2083 // add a single inactive frame before dropping starts
2084 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2085 stored_player[pnr].force_dropping = TRUE;
2088 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2090 // if snap key was pressed without direction, leave pause mode
2091 if (!has_snapped[pnr])
2092 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2094 has_snapped[pnr] = FALSE;
2097 else if (tape.recording && tape.pausing && !tape.use_mouse)
2099 // prevent key release events from un-pausing a paused game
2100 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2101 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2104 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2105 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2111 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2112 if (key == key_info[i].key_default)
2113 joy |= key_info[i].action;
2118 if (key_status == KEY_PRESSED)
2119 key_joystick_mapping |= joy;
2121 key_joystick_mapping &= ~joy;
2126 if (game_status != GAME_MODE_PLAYING)
2127 key_joystick_mapping = 0;
2129 if (key_status == KEY_RELEASED)
2131 // reset flag to ignore repeated "key pressed" events after key release
2132 ignore_repeated_key = FALSE;
2137 if ((key == KSYM_F11 ||
2138 ((key == KSYM_Return ||
2139 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2140 video.fullscreen_available &&
2141 !ignore_repeated_key)
2143 setup.fullscreen = !setup.fullscreen;
2145 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2147 if (game_status == GAME_MODE_SETUP)
2148 RedrawSetupScreenAfterFullscreenToggle();
2150 // set flag to ignore repeated "key pressed" events
2151 ignore_repeated_key = TRUE;
2156 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2157 key == KSYM_minus || key == KSYM_KP_Subtract ||
2158 key == KSYM_plus || key == KSYM_KP_Add ||
2159 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2160 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2161 video.window_scaling_available &&
2162 !video.fullscreen_enabled)
2164 if (key == KSYM_0 || key == KSYM_KP_0)
2165 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2166 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2167 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2169 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2171 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2172 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2173 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2174 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2176 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2178 if (game_status == GAME_MODE_SETUP)
2179 RedrawSetupScreenAfterFullscreenToggle();
2184 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2185 key == KSYM_Return ||
2186 key == KSYM_Escape)))
2188 // do not handle this key event anymore
2189 if (key != KSYM_Escape) // always allow ESC key to be handled
2193 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2194 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2201 if (game_status == GAME_MODE_MAIN &&
2202 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2204 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2209 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2211 if (key == setup.shortcut.save_game)
2213 else if (key == setup.shortcut.load_game)
2215 else if (key == setup.shortcut.toggle_pause)
2216 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2218 HandleTapeButtonKeys(key);
2219 HandleSoundButtonKeys(key);
2222 if (game_status == GAME_MODE_PLAYING && !network_playing)
2224 int centered_player_nr_next = -999;
2226 if (key == setup.shortcut.focus_player_all)
2227 centered_player_nr_next = -1;
2229 for (i = 0; i < MAX_PLAYERS; i++)
2230 if (key == setup.shortcut.focus_player[i])
2231 centered_player_nr_next = i;
2233 if (centered_player_nr_next != -999)
2235 game.centered_player_nr_next = centered_player_nr_next;
2236 game.set_centered_player = TRUE;
2240 tape.centered_player_nr_next = game.centered_player_nr_next;
2241 tape.set_centered_player = TRUE;
2246 HandleKeysSpecial(key);
2248 if (HandleGadgetsKeyInput(key))
2249 return; // do not handle already processed keys again
2251 switch (game_status)
2253 case GAME_MODE_PSEUDO_TYPENAME:
2254 HandleTypeName(0, key);
2257 case GAME_MODE_TITLE:
2258 case GAME_MODE_MAIN:
2259 case GAME_MODE_LEVELS:
2260 case GAME_MODE_LEVELNR:
2261 case GAME_MODE_SETUP:
2262 case GAME_MODE_INFO:
2263 case GAME_MODE_SCORES:
2265 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2272 if (game_status == GAME_MODE_TITLE)
2273 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2274 else if (game_status == GAME_MODE_MAIN)
2275 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2276 else if (game_status == GAME_MODE_LEVELS)
2277 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2278 else if (game_status == GAME_MODE_LEVELNR)
2279 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2280 else if (game_status == GAME_MODE_SETUP)
2281 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2282 else if (game_status == GAME_MODE_INFO)
2283 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2284 else if (game_status == GAME_MODE_SCORES)
2285 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2289 if (game_status != GAME_MODE_MAIN)
2290 FadeSkipNextFadeIn();
2292 if (game_status == GAME_MODE_TITLE)
2293 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2294 else if (game_status == GAME_MODE_LEVELS)
2295 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2296 else if (game_status == GAME_MODE_LEVELNR)
2297 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2298 else if (game_status == GAME_MODE_SETUP)
2299 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2300 else if (game_status == GAME_MODE_INFO)
2301 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2302 else if (game_status == GAME_MODE_SCORES)
2303 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2307 if (game_status == GAME_MODE_LEVELS)
2308 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2309 else if (game_status == GAME_MODE_LEVELNR)
2310 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2311 else if (game_status == GAME_MODE_SETUP)
2312 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2313 else if (game_status == GAME_MODE_INFO)
2314 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2315 else if (game_status == GAME_MODE_SCORES)
2316 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2319 case KSYM_Page_Down:
2320 if (game_status == GAME_MODE_LEVELS)
2321 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2322 else if (game_status == GAME_MODE_LEVELNR)
2323 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2324 else if (game_status == GAME_MODE_SETUP)
2325 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2326 else if (game_status == GAME_MODE_INFO)
2327 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2328 else if (game_status == GAME_MODE_SCORES)
2329 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2337 case GAME_MODE_EDITOR:
2338 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2339 HandleLevelEditorKeyInput(key);
2342 case GAME_MODE_PLAYING:
2347 RequestQuitGame(setup.ask_on_escape);
2357 if (key == KSYM_Escape)
2359 SetGameStatus(GAME_MODE_MAIN);
2368 void HandleNoEvent(void)
2370 HandleMouseCursor();
2372 switch (game_status)
2374 case GAME_MODE_PLAYING:
2375 HandleButtonOrFinger(-1, -1, -1);
2380 void HandleEventActions(void)
2382 // if (button_status && game_status != GAME_MODE_PLAYING)
2383 if (button_status && (game_status != GAME_MODE_PLAYING ||
2385 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2387 HandleButton(0, 0, button_status, -button_status);
2394 if (network.enabled)
2397 switch (game_status)
2399 case GAME_MODE_MAIN:
2400 DrawPreviewLevelAnimation();
2403 case GAME_MODE_EDITOR:
2404 HandleLevelEditorIdle();
2412 static void HandleTileCursor(int dx, int dy, int button)
2415 ClearPlayerMouseAction();
2422 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2423 (dx < 0 ? MB_LEFTBUTTON :
2424 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2426 else if (!tile_cursor.moving)
2428 int old_xpos = tile_cursor.xpos;
2429 int old_ypos = tile_cursor.ypos;
2430 int new_xpos = old_xpos;
2431 int new_ypos = old_ypos;
2433 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2434 new_xpos = old_xpos + dx;
2436 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2437 new_ypos = old_ypos + dy;
2439 SetTileCursorTargetXY(new_xpos, new_ypos);
2443 static int HandleJoystickForAllPlayers(void)
2447 boolean no_joysticks_configured = TRUE;
2448 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2449 static byte joy_action_last[MAX_PLAYERS];
2451 for (i = 0; i < MAX_PLAYERS; i++)
2452 if (setup.input[i].use_joystick)
2453 no_joysticks_configured = FALSE;
2455 // if no joysticks configured, map connected joysticks to players
2456 if (no_joysticks_configured)
2457 use_as_joystick_nr = TRUE;
2459 for (i = 0; i < MAX_PLAYERS; i++)
2461 byte joy_action = 0;
2463 joy_action = JoystickExt(i, use_as_joystick_nr);
2464 result |= joy_action;
2466 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2467 joy_action != joy_action_last[i])
2468 stored_player[i].action = joy_action;
2470 joy_action_last[i] = joy_action;
2476 void HandleJoystick(void)
2478 static unsigned int joytest_delay = 0;
2479 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2480 static int joytest_last = 0;
2481 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2482 int delay_value = GADGET_FRAME_DELAY;
2483 int joystick = HandleJoystickForAllPlayers();
2484 int keyboard = key_joystick_mapping;
2485 int joy = (joystick | keyboard);
2486 int joytest = joystick;
2487 int left = joy & JOY_LEFT;
2488 int right = joy & JOY_RIGHT;
2489 int up = joy & JOY_UP;
2490 int down = joy & JOY_DOWN;
2491 int button = joy & JOY_BUTTON;
2492 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2493 int dx = (left ? -1 : right ? 1 : 0);
2494 int dy = (up ? -1 : down ? 1 : 0);
2495 boolean use_delay_value_first = (joytest != joytest_last);
2497 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2499 // do not handle this button event anymore
2503 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2504 anyTextGadgetActive()))
2506 // leave name input in main menu or text input gadget
2507 HandleKey(KSYM_Escape, KEY_PRESSED);
2508 HandleKey(KSYM_Escape, KEY_RELEASED);
2513 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2515 if (game_status == GAME_MODE_PLAYING)
2517 // when playing MM style levels, also use delay for keyboard events
2518 joytest |= keyboard;
2520 // only use first delay value for new events, but not for changed events
2521 use_delay_value_first = (!joytest != !joytest_last);
2523 // only use delay after the initial keyboard event
2527 // for any joystick or keyboard event, enable playfield tile cursor
2528 if (dx || dy || button)
2529 SetTileCursorEnabled(TRUE);
2532 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2534 // delay joystick/keyboard actions if axes/keys continually pressed
2535 newbutton = dx = dy = 0;
2539 // first start with longer delay, then continue with shorter delay
2540 joytest_delay_value =
2541 (use_delay_value_first ? delay_value_first : delay_value);
2544 joytest_last = joytest;
2546 switch (game_status)
2548 case GAME_MODE_TITLE:
2549 case GAME_MODE_MAIN:
2550 case GAME_MODE_LEVELS:
2551 case GAME_MODE_LEVELNR:
2552 case GAME_MODE_SETUP:
2553 case GAME_MODE_INFO:
2554 case GAME_MODE_SCORES:
2556 if (anyTextGadgetActive())
2559 if (game_status == GAME_MODE_TITLE)
2560 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2561 else if (game_status == GAME_MODE_MAIN)
2562 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2563 else if (game_status == GAME_MODE_LEVELS)
2564 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2565 else if (game_status == GAME_MODE_LEVELNR)
2566 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2567 else if (game_status == GAME_MODE_SETUP)
2568 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2569 else if (game_status == GAME_MODE_INFO)
2570 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2571 else if (game_status == GAME_MODE_SCORES)
2572 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2577 case GAME_MODE_PLAYING:
2579 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2580 if (tape.playing || keyboard)
2581 newbutton = ((joy & JOY_BUTTON) != 0);
2584 if (newbutton && game.all_players_gone)
2591 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2593 if (joystick & JOY_ACTION)
2594 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2596 else if (tape.recording && tape.pausing && !tape.use_mouse)
2598 if (joystick & JOY_ACTION)
2599 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2602 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2603 HandleTileCursor(dx, dy, button);
2612 void HandleSpecialGameControllerButtons(Event *event)
2617 switch (event->type)
2619 case SDL_CONTROLLERBUTTONDOWN:
2620 key_status = KEY_PRESSED;
2623 case SDL_CONTROLLERBUTTONUP:
2624 key_status = KEY_RELEASED;
2631 switch (event->cbutton.button)
2633 case SDL_CONTROLLER_BUTTON_START:
2637 case SDL_CONTROLLER_BUTTON_BACK:
2645 HandleKey(key, key_status);
2648 void HandleSpecialGameControllerKeys(Key key, int key_status)
2650 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2651 int button = SDL_CONTROLLER_BUTTON_INVALID;
2653 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2654 if (key == KSYM_Rewind)
2655 button = SDL_CONTROLLER_BUTTON_A;
2656 else if (key == KSYM_FastForward || key == KSYM_Menu)
2657 button = SDL_CONTROLLER_BUTTON_B;
2659 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2663 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2664 SDL_CONTROLLERBUTTONUP);
2666 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2667 event.cbutton.button = button;
2668 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2671 HandleJoystickEvent(&event);
2676 boolean DoKeysymAction(int keysym)
2680 Key key = (Key)(-keysym);
2682 HandleKey(key, KEY_PRESSED);
2683 HandleKey(key, KEY_RELEASED);