1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://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 stop_processing_events = FALSE;
45 // forward declarations for internal use
46 static void ClearTouchInfo(void);
47 static void HandleNoEvent(void);
48 static void HandleEventActions(void);
51 // event filter to set mouse x/y position (for pointer class global animations)
52 // (this is especially required to ensure smooth global animation mouse pointer
53 // movement when the screen is updated without handling events; this can happen
54 // when drawing door/envelope request animations, for example)
56 int FilterMouseMotionEvents(void *userdata, Event *event)
58 if (event->type == EVENT_MOTIONNOTIFY)
60 int mouse_x = ((MotionEvent *)event)->x;
61 int mouse_y = ((MotionEvent *)event)->y;
63 UpdateRawMousePosition(mouse_x, mouse_y);
69 // event filter especially needed for SDL event filtering due to
70 // delay problems with lots of mouse motion events when mouse button
71 // not pressed (X11 can handle this with 'PointerMotionHintMask')
73 // event filter addition for SDL2: as SDL2 does not have a function to enable
74 // or disable keyboard auto-repeat, filter repeated keyboard events instead
76 static int FilterEvents(const Event *event)
80 // skip repeated key press events if keyboard auto-repeat is disabled
81 if (event->type == EVENT_KEYPRESS &&
86 if (event->type == EVENT_BUTTONPRESS ||
87 event->type == EVENT_BUTTONRELEASE)
89 ((ButtonEvent *)event)->x -= video.screen_xoffset;
90 ((ButtonEvent *)event)->y -= video.screen_yoffset;
92 else if (event->type == EVENT_MOTIONNOTIFY)
94 ((MotionEvent *)event)->x -= video.screen_xoffset;
95 ((MotionEvent *)event)->y -= video.screen_yoffset;
98 if (event->type == EVENT_BUTTONPRESS ||
99 event->type == EVENT_BUTTONRELEASE ||
100 event->type == EVENT_MOTIONNOTIFY)
102 // do not reset mouse cursor before all pending events have been processed
103 if (gfx.cursor_mode == cursor_mode_last &&
104 ((game_status == GAME_MODE_TITLE &&
105 gfx.cursor_mode == CURSOR_NONE) ||
106 (game_status == GAME_MODE_PLAYING &&
107 gfx.cursor_mode == CURSOR_PLAYFIELD)))
109 SetMouseCursor(CURSOR_DEFAULT);
111 DelayReached(&special_cursor_delay, 0);
113 cursor_mode_last = CURSOR_DEFAULT;
117 // non-motion events are directly passed to event handler functions
118 if (event->type != EVENT_MOTIONNOTIFY)
121 motion = (MotionEvent *)event;
122 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
123 motion->y >= SY && motion->y < SY + SYSIZE);
125 // set correct mouse x/y position (for pointer class global animations)
126 // (this is required in rare cases where the mouse x/y position calculated
127 // from raw values (to apply logical screen size scaling corrections) does
128 // not match the final mouse event x/y position -- this may happen because
129 // the SDL renderer's viewport position is internally represented as float,
130 // but only accessible as integer, which may lead to rounding errors)
131 gfx.mouse_x = motion->x;
132 gfx.mouse_y = motion->y;
134 // skip mouse motion events without pressed button outside level editor
135 if (button_status == MB_RELEASED &&
136 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
142 // to prevent delay problems, skip mouse motion events if the very next
143 // event is also a mouse motion event (and therefore effectively only
144 // handling the last of a row of mouse motion events in the event queue)
146 static boolean SkipPressedMouseMotionEvent(const Event *event)
148 // nothing to do if the current event is not a mouse motion event
149 if (event->type != EVENT_MOTIONNOTIFY)
152 // only skip motion events with pressed button outside the game
153 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
160 PeekEvent(&next_event);
162 // if next event is also a mouse motion event, skip the current one
163 if (next_event.type == EVENT_MOTIONNOTIFY)
170 static boolean WaitValidEvent(Event *event)
174 if (!FilterEvents(event))
177 if (SkipPressedMouseMotionEvent(event))
183 /* this is especially needed for event modifications for the Android target:
184 if mouse coordinates should be modified in the event filter function,
185 using a properly installed SDL event filter does not work, because in
186 the event filter, mouse coordinates in the event structure are still
187 physical pixel positions, not logical (scaled) screen positions, so this
188 has to be handled at a later stage in the event processing functions
189 (when device pixel positions are already converted to screen positions) */
191 boolean NextValidEvent(Event *event)
193 while (PendingEvent())
194 if (WaitValidEvent(event))
200 void StopProcessingEvents(void)
202 stop_processing_events = TRUE;
205 static void HandleEvents(void)
208 unsigned int event_frame_delay = 0;
209 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
211 ResetDelayCounter(&event_frame_delay);
213 stop_processing_events = FALSE;
215 while (NextValidEvent(&event))
219 case EVENT_BUTTONPRESS:
220 case EVENT_BUTTONRELEASE:
221 HandleButtonEvent((ButtonEvent *) &event);
224 case EVENT_MOTIONNOTIFY:
225 HandleMotionEvent((MotionEvent *) &event);
228 case EVENT_WHEELMOTION:
229 HandleWheelEvent((WheelEvent *) &event);
232 case SDL_WINDOWEVENT:
233 HandleWindowEvent((WindowEvent *) &event);
236 case EVENT_FINGERPRESS:
237 case EVENT_FINGERRELEASE:
238 case EVENT_FINGERMOTION:
239 HandleFingerEvent((FingerEvent *) &event);
242 case EVENT_TEXTINPUT:
243 HandleTextEvent((TextEvent *) &event);
246 case SDL_APP_WILLENTERBACKGROUND:
247 case SDL_APP_DIDENTERBACKGROUND:
248 case SDL_APP_WILLENTERFOREGROUND:
249 case SDL_APP_DIDENTERFOREGROUND:
250 HandlePauseResumeEvent((PauseResumeEvent *) &event);
254 case EVENT_KEYRELEASE:
255 HandleKeyEvent((KeyEvent *) &event);
259 HandleUserEvent((UserEvent *) &event);
263 HandleOtherEvents(&event);
267 // do not handle events for longer than standard frame delay period
268 if (DelayReached(&event_frame_delay, event_frame_delay_value))
271 // do not handle any further events if triggered by a special flag
272 if (stop_processing_events)
277 void HandleOtherEvents(Event *event)
281 case SDL_CONTROLLERBUTTONDOWN:
282 case SDL_CONTROLLERBUTTONUP:
283 // for any game controller button event, disable overlay buttons
284 SetOverlayEnabled(FALSE);
286 HandleSpecialGameControllerButtons(event);
289 case SDL_CONTROLLERDEVICEADDED:
290 case SDL_CONTROLLERDEVICEREMOVED:
291 case SDL_CONTROLLERAXISMOTION:
292 case SDL_JOYAXISMOTION:
293 case SDL_JOYBUTTONDOWN:
294 case SDL_JOYBUTTONUP:
295 HandleJoystickEvent(event);
299 case SDL_DROPCOMPLETE:
302 HandleDropEvent(event);
314 static void HandleMouseCursor(void)
316 if (game_status == GAME_MODE_TITLE)
318 // when showing title screens, hide mouse pointer (if not moved)
320 if (gfx.cursor_mode != CURSOR_NONE &&
321 DelayReached(&special_cursor_delay, special_cursor_delay_value))
323 SetMouseCursor(CURSOR_NONE);
326 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
329 // when playing, display a special mouse pointer inside the playfield
331 // display normal pointer if mouse pressed
332 if (button_status != MB_RELEASED)
333 DelayReached(&special_cursor_delay, 0);
335 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
336 cursor_inside_playfield &&
337 DelayReached(&special_cursor_delay, special_cursor_delay_value))
339 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
341 SetMouseCursor(CURSOR_PLAYFIELD);
344 else if (gfx.cursor_mode != CURSOR_DEFAULT)
346 SetMouseCursor(CURSOR_DEFAULT);
349 // this is set after all pending events have been processed
350 cursor_mode_last = gfx.cursor_mode;
362 // execute event related actions after pending events have been processed
363 HandleEventActions();
365 // don't use all CPU time when idle; the main loop while playing
366 // has its own synchronization and is CPU friendly, too
368 if (game_status == GAME_MODE_PLAYING)
371 // always copy backbuffer to visible screen for every video frame
374 // reset video frame delay to default (may change again while playing)
375 SetVideoFrameDelay(MenuFrameDelay);
377 if (game_status == GAME_MODE_QUIT)
382 void ClearAutoRepeatKeyEvents(void)
384 while (PendingEvent())
388 PeekEvent(&next_event);
390 // if event is repeated key press event, remove it from event queue
391 if (next_event.type == EVENT_KEYPRESS &&
392 next_event.key.repeat)
393 WaitEvent(&next_event);
399 void ClearEventQueue(void)
403 while (NextValidEvent(&event))
407 case EVENT_BUTTONRELEASE:
408 button_status = MB_RELEASED;
411 case EVENT_FINGERRELEASE:
412 case EVENT_KEYRELEASE:
416 case SDL_CONTROLLERBUTTONUP:
417 HandleJoystickEvent(&event);
422 HandleOtherEvents(&event);
428 static void ClearPlayerMouseAction(void)
430 local_player->mouse_action.lx = 0;
431 local_player->mouse_action.ly = 0;
432 local_player->mouse_action.button = 0;
435 void ClearPlayerAction(void)
439 // simulate key release events for still pressed keys
440 key_joystick_mapping = 0;
441 for (i = 0; i < MAX_PLAYERS; i++)
443 stored_player[i].action = 0;
444 stored_player[i].snap_action = 0;
447 // simulate finger release events for still pressed virtual buttons
448 overlay.grid_button_action = JOY_NO_ACTION;
451 ClearJoystickState();
452 ClearPlayerMouseAction();
455 static void SetPlayerMouseAction(int mx, int my, int button)
457 int lx = getLevelFromScreenX(mx);
458 int ly = getLevelFromScreenY(my);
459 int new_button = (!local_player->mouse_action.button && button);
461 if (local_player->mouse_action.button_hint)
462 button = local_player->mouse_action.button_hint;
464 ClearPlayerMouseAction();
466 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
469 local_player->mouse_action.lx = lx;
470 local_player->mouse_action.ly = ly;
471 local_player->mouse_action.button = button;
473 if (tape.recording && tape.pausing && tape.use_mouse_actions)
475 // un-pause a paused game only if mouse button was newly pressed down
477 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
480 SetTileCursorXY(lx, ly);
483 static Key GetKeyFromGridButton(int grid_button)
485 return (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
486 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
487 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
488 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
489 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
490 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
494 #if defined(PLATFORM_ANDROID)
495 static boolean CheckVirtualButtonPressed(int mx, int my, int button)
497 float touch_x = (float)(mx + video.screen_xoffset) / video.screen_width;
498 float touch_y = (float)(my + video.screen_yoffset) / video.screen_height;
499 int x = touch_x * overlay.grid_xsize;
500 int y = touch_y * overlay.grid_ysize;
501 int grid_button = overlay.grid_button[x][y];
502 Key key = GetKeyFromGridButton(grid_button);
503 int key_status = (button == MB_RELEASED ? KEY_RELEASED : KEY_PRESSED);
505 return (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
509 void HandleButtonEvent(ButtonEvent *event)
511 #if DEBUG_EVENTS_BUTTON
512 Debug("event:button", "button %d %s, x/y %d/%d\n",
514 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
518 // for any mouse button event, disable playfield tile cursor
519 SetTileCursorEnabled(FALSE);
521 #if defined(HAS_SCREEN_KEYBOARD)
522 if (video.shifted_up)
523 event->y += video.shifted_up_pos;
526 motion_status = FALSE;
528 if (event->type == EVENT_BUTTONPRESS)
529 button_status = event->button;
531 button_status = MB_RELEASED;
533 HandleButton(event->x, event->y, button_status, event->button);
536 void HandleMotionEvent(MotionEvent *event)
538 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
541 motion_status = TRUE;
543 #if DEBUG_EVENTS_MOTION
544 Debug("event:motion", "button %d moved, x/y %d/%d\n",
545 button_status, event->x, event->y);
548 HandleButton(event->x, event->y, button_status, button_status);
551 void HandleWheelEvent(WheelEvent *event)
555 #if DEBUG_EVENTS_WHEEL
557 Debug("event:wheel", "mouse == %d, x/y == %d/%d\n",
558 event->which, event->x, event->y);
560 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
561 Debug("event:wheel", "mouse == %d, x/y == %d/%d, direction == %s\n",
562 event->which, event->x, event->y,
563 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
564 "SDL_MOUSEWHEEL_FLIPPED"));
568 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
569 event->x > 0 ? MB_WHEEL_RIGHT :
570 event->y < 0 ? MB_WHEEL_DOWN :
571 event->y > 0 ? MB_WHEEL_UP : 0);
573 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
574 // accelerated mouse wheel available on Mac and Windows
575 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
577 // no accelerated mouse wheel available on Unix/Linux
578 wheel_steps = DEFAULT_WHEEL_STEPS;
581 motion_status = FALSE;
583 button_status = button_nr;
584 HandleButton(0, 0, button_status, -button_nr);
586 button_status = MB_RELEASED;
587 HandleButton(0, 0, button_status, -button_nr);
590 void HandleWindowEvent(WindowEvent *event)
592 #if DEBUG_EVENTS_WINDOW
593 int subtype = event->event;
596 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
597 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
598 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
599 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
600 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
601 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
602 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
603 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
604 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
605 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
606 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
607 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
608 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
609 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
612 Debug("event:window", "name: '%s', data1: %ld, data2: %ld",
613 event_name, event->data1, event->data2);
617 // (not needed, as the screen gets redrawn every 20 ms anyway)
618 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
619 event->event == SDL_WINDOWEVENT_RESIZED ||
620 event->event == SDL_WINDOWEVENT_EXPOSED)
624 if (event->event == SDL_WINDOWEVENT_RESIZED)
626 if (!video.fullscreen_enabled)
628 int new_window_width = event->data1;
629 int new_window_height = event->data2;
631 // if window size has changed after resizing, calculate new scaling factor
632 if (new_window_width != video.window_width ||
633 new_window_height != video.window_height)
635 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
636 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
638 // (extreme window scaling allowed, but cannot be saved permanently)
639 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
640 setup.window_scaling_percent =
641 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
642 MAX_WINDOW_SCALING_PERCENT);
644 video.window_width = new_window_width;
645 video.window_height = new_window_height;
647 if (game_status == GAME_MODE_SETUP)
648 RedrawSetupScreenAfterFullscreenToggle();
650 UpdateMousePosition();
655 #if defined(PLATFORM_ANDROID)
658 int new_display_width = event->data1;
659 int new_display_height = event->data2;
661 // if fullscreen display size has changed, device has been rotated
662 if (new_display_width != video.display_width ||
663 new_display_height != video.display_height)
665 int nr = GRID_ACTIVE_NR(); // previous screen orientation
667 video.display_width = new_display_width;
668 video.display_height = new_display_height;
670 SDLSetScreenProperties();
671 SetGadgetsPosition_OverlayTouchButtons();
673 // check if screen orientation has changed (should always be true here)
674 if (nr != GRID_ACTIVE_NR())
676 if (game_status == GAME_MODE_SETUP)
677 RedrawSetupScreenAfterScreenRotation(nr);
679 SetOverlayGridSizeAndButtons();
687 #define NUM_TOUCH_FINGERS 3
692 SDL_FingerID finger_id;
696 } touch_info[NUM_TOUCH_FINGERS];
698 static void SetTouchInfo(int pos, SDL_FingerID finger_id, int counter,
699 Key key, byte action)
701 touch_info[pos].touched = (action != JOY_NO_ACTION);
702 touch_info[pos].finger_id = finger_id;
703 touch_info[pos].counter = counter;
704 touch_info[pos].key = key;
705 touch_info[pos].action = action;
708 static void ClearTouchInfo(void)
712 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
713 SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
716 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
718 int x = event->x * overlay.grid_xsize;
719 int y = event->y * overlay.grid_ysize;
720 int grid_button = overlay.grid_button[x][y];
721 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
722 Key key = GetKeyFromGridButton(grid_button);
723 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
725 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
729 // for any touch input event, enable overlay buttons (if activated)
730 SetOverlayEnabled(TRUE);
732 Debug("event:finger", "key '%s' was '%s' [fingerId: %lld]",
733 getKeyNameFromKey(key), key_status_name, event->fingerId);
735 if (key_status == KEY_PRESSED)
736 overlay.grid_button_action |= grid_button_action;
738 overlay.grid_button_action &= ~grid_button_action;
740 // check if we already know this touch event's finger id
741 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
743 if (touch_info[i].touched &&
744 touch_info[i].finger_id == event->fingerId)
746 // Debug("event:finger", "MARK 1: %d", i);
752 if (i >= NUM_TOUCH_FINGERS)
754 if (key_status == KEY_PRESSED)
756 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
758 // unknown finger id -- get new, empty slot, if available
759 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
761 if (touch_info[i].counter < oldest_counter)
764 oldest_counter = touch_info[i].counter;
766 // Debug("event:finger", "MARK 2: %d", i);
769 if (!touch_info[i].touched)
771 // Debug("event:finger", "MARK 3: %d", i);
777 if (i >= NUM_TOUCH_FINGERS)
779 // all slots allocated -- use oldest slot
782 // Debug("event:finger", "MARK 4: %d", i);
787 // release of previously unknown key (should not happen)
789 if (key != KSYM_UNDEFINED)
791 HandleKey(key, KEY_RELEASED);
793 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [1]",
794 getKeyNameFromKey(key), "KEY_RELEASED", i);
799 if (i < NUM_TOUCH_FINGERS)
801 if (key_status == KEY_PRESSED)
803 if (touch_info[i].key != key)
805 if (touch_info[i].key != KSYM_UNDEFINED)
807 HandleKey(touch_info[i].key, KEY_RELEASED);
809 // undraw previous grid button when moving finger away
810 overlay.grid_button_action &= ~touch_info[i].action;
812 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [2]",
813 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
816 if (key != KSYM_UNDEFINED)
818 HandleKey(key, KEY_PRESSED);
820 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [3]",
821 getKeyNameFromKey(key), "KEY_PRESSED", i);
825 SetTouchInfo(i, event->fingerId, Counter(), key, grid_button_action);
829 if (touch_info[i].key != KSYM_UNDEFINED)
831 HandleKey(touch_info[i].key, KEY_RELEASED);
833 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [4]",
834 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
837 SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
842 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
844 static Key motion_key_x = KSYM_UNDEFINED;
845 static Key motion_key_y = KSYM_UNDEFINED;
846 static Key button_key = KSYM_UNDEFINED;
847 static float motion_x1, motion_y1;
848 static float button_x1, button_y1;
849 static SDL_FingerID motion_id = -1;
850 static SDL_FingerID button_id = -1;
851 int move_trigger_distance_percent = setup.touch.move_distance;
852 int drop_trigger_distance_percent = setup.touch.drop_distance;
853 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
854 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
855 float event_x = event->x;
856 float event_y = event->y;
858 if (event->type == EVENT_FINGERPRESS)
860 if (event_x > 1.0 / 3.0)
864 motion_id = event->fingerId;
869 motion_key_x = KSYM_UNDEFINED;
870 motion_key_y = KSYM_UNDEFINED;
872 Debug("event:finger", "---------- MOVE STARTED (WAIT) ----------");
878 button_id = event->fingerId;
883 button_key = setup.input[0].key.snap;
885 HandleKey(button_key, KEY_PRESSED);
887 Debug("event:finger", "---------- SNAP STARTED ----------");
890 else if (event->type == EVENT_FINGERRELEASE)
892 if (event->fingerId == motion_id)
896 if (motion_key_x != KSYM_UNDEFINED)
897 HandleKey(motion_key_x, KEY_RELEASED);
898 if (motion_key_y != KSYM_UNDEFINED)
899 HandleKey(motion_key_y, KEY_RELEASED);
901 motion_key_x = KSYM_UNDEFINED;
902 motion_key_y = KSYM_UNDEFINED;
904 Debug("event:finger", "---------- MOVE STOPPED ----------");
906 else if (event->fingerId == button_id)
910 if (button_key != KSYM_UNDEFINED)
911 HandleKey(button_key, KEY_RELEASED);
913 button_key = KSYM_UNDEFINED;
915 Debug("event:finger", "---------- SNAP STOPPED ----------");
918 else if (event->type == EVENT_FINGERMOTION)
920 if (event->fingerId == motion_id)
922 float distance_x = ABS(event_x - motion_x1);
923 float distance_y = ABS(event_y - motion_y1);
924 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
925 event_x > motion_x1 ? setup.input[0].key.right :
927 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
928 event_y > motion_y1 ? setup.input[0].key.down :
931 if (distance_x < move_trigger_distance / 2 ||
932 distance_x < distance_y)
933 new_motion_key_x = KSYM_UNDEFINED;
935 if (distance_y < move_trigger_distance / 2 ||
936 distance_y < distance_x)
937 new_motion_key_y = KSYM_UNDEFINED;
939 if (distance_x > move_trigger_distance ||
940 distance_y > move_trigger_distance)
942 if (new_motion_key_x != motion_key_x)
944 if (motion_key_x != KSYM_UNDEFINED)
945 HandleKey(motion_key_x, KEY_RELEASED);
946 if (new_motion_key_x != KSYM_UNDEFINED)
947 HandleKey(new_motion_key_x, KEY_PRESSED);
950 if (new_motion_key_y != motion_key_y)
952 if (motion_key_y != KSYM_UNDEFINED)
953 HandleKey(motion_key_y, KEY_RELEASED);
954 if (new_motion_key_y != KSYM_UNDEFINED)
955 HandleKey(new_motion_key_y, KEY_PRESSED);
961 motion_key_x = new_motion_key_x;
962 motion_key_y = new_motion_key_y;
964 Debug("event:finger", "---------- MOVE STARTED (MOVE) ----------");
967 else if (event->fingerId == button_id)
969 float distance_x = ABS(event_x - button_x1);
970 float distance_y = ABS(event_y - button_y1);
972 if (distance_x < drop_trigger_distance / 2 &&
973 distance_y > drop_trigger_distance)
975 if (button_key == setup.input[0].key.snap)
976 HandleKey(button_key, KEY_RELEASED);
981 button_key = setup.input[0].key.drop;
983 HandleKey(button_key, KEY_PRESSED);
985 Debug("event:finger", "---------- DROP STARTED ----------");
991 void HandleFingerEvent(FingerEvent *event)
993 #if DEBUG_EVENTS_FINGER
994 Debug("event:finger", "finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
995 event->type == EVENT_FINGERPRESS ? "pressed" :
996 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
1000 event->dx, event->dy,
1004 runtime.uses_touch_device = TRUE;
1006 if (game_status != GAME_MODE_PLAYING)
1009 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1011 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1012 local_player->mouse_action.button_hint =
1013 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1014 event->x < 0.5 ? MB_LEFTBUTTON :
1015 event->x > 0.5 ? MB_RIGHTBUTTON :
1021 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1022 HandleFingerEvent_VirtualButtons(event);
1023 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1024 HandleFingerEvent_WipeGestures(event);
1027 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1029 static int old_mx = 0, old_my = 0;
1030 static int last_button = MB_LEFTBUTTON;
1031 static boolean touched = FALSE;
1032 static boolean tapped = FALSE;
1034 // screen tile was tapped (but finger not touching the screen anymore)
1035 // (this point will also be reached without receiving a touch event)
1036 if (tapped && !touched)
1038 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1043 // stop here if this function was not triggered by a touch event
1047 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1049 // finger started touching the screen
1059 ClearPlayerMouseAction();
1061 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1064 else if (button == MB_RELEASED && touched)
1066 // finger stopped touching the screen
1071 SetPlayerMouseAction(old_mx, old_my, last_button);
1073 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1075 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1080 // finger moved while touching the screen
1082 int old_x = getLevelFromScreenX(old_mx);
1083 int old_y = getLevelFromScreenY(old_my);
1084 int new_x = getLevelFromScreenX(mx);
1085 int new_y = getLevelFromScreenY(my);
1087 if (new_x != old_x || new_y != old_y)
1092 // finger moved left or right from (horizontal) starting position
1094 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1096 SetPlayerMouseAction(old_mx, old_my, button_nr);
1098 last_button = button_nr;
1100 Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1104 // finger stays at or returned to (horizontal) starting position
1106 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1108 Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1113 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1115 static int old_mx = 0, old_my = 0;
1116 static int last_button = MB_LEFTBUTTON;
1117 static boolean touched = FALSE;
1118 static boolean tapped = FALSE;
1120 // screen tile was tapped (but finger not touching the screen anymore)
1121 // (this point will also be reached without receiving a touch event)
1122 if (tapped && !touched)
1124 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1129 // stop here if this function was not triggered by a touch event
1133 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1135 // finger started touching the screen
1145 ClearPlayerMouseAction();
1147 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1150 else if (button == MB_RELEASED && touched)
1152 // finger stopped touching the screen
1157 SetPlayerMouseAction(old_mx, old_my, last_button);
1159 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1161 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1166 // finger moved while touching the screen
1168 int old_x = getLevelFromScreenX(old_mx);
1169 int old_y = getLevelFromScreenY(old_my);
1170 int new_x = getLevelFromScreenX(mx);
1171 int new_y = getLevelFromScreenY(my);
1173 if (new_x != old_x || new_y != old_y)
1175 // finger moved away from starting position
1177 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1179 // quickly alternate between clicking and releasing for maximum speed
1180 if (FrameCounter % 2 == 0)
1181 button_nr = MB_RELEASED;
1183 SetPlayerMouseAction(old_mx, old_my, button_nr);
1186 last_button = button_nr;
1190 Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1194 // finger stays at or returned to starting position
1196 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1198 Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1203 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1205 static int old_mx = 0, old_my = 0;
1206 static Key motion_key_x = KSYM_UNDEFINED;
1207 static Key motion_key_y = KSYM_UNDEFINED;
1208 static boolean touched = FALSE;
1209 static boolean started_on_player = FALSE;
1210 static boolean player_is_dropping = FALSE;
1211 static int player_drop_count = 0;
1212 static int last_player_x = -1;
1213 static int last_player_y = -1;
1215 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1224 started_on_player = FALSE;
1225 player_is_dropping = FALSE;
1226 player_drop_count = 0;
1230 motion_key_x = KSYM_UNDEFINED;
1231 motion_key_y = KSYM_UNDEFINED;
1233 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1236 else if (button == MB_RELEASED && touched)
1243 if (motion_key_x != KSYM_UNDEFINED)
1244 HandleKey(motion_key_x, KEY_RELEASED);
1245 if (motion_key_y != KSYM_UNDEFINED)
1246 HandleKey(motion_key_y, KEY_RELEASED);
1248 if (started_on_player)
1250 if (player_is_dropping)
1252 Debug("event:finger", "---------- DROP STOPPED ----------");
1254 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1258 Debug("event:finger", "---------- SNAP STOPPED ----------");
1260 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1264 motion_key_x = KSYM_UNDEFINED;
1265 motion_key_y = KSYM_UNDEFINED;
1267 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1272 int src_x = local_player->jx;
1273 int src_y = local_player->jy;
1274 int dst_x = getLevelFromScreenX(old_mx);
1275 int dst_y = getLevelFromScreenY(old_my);
1276 int dx = dst_x - src_x;
1277 int dy = dst_y - src_y;
1278 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1279 dx > 0 ? setup.input[0].key.right :
1281 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1282 dy > 0 ? setup.input[0].key.down :
1285 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1286 (last_player_x != local_player->jx ||
1287 last_player_y != local_player->jy))
1289 // in case of asymmetric diagonal movement, use "preferred" direction
1291 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1293 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1294 game_em.ply[0]->last_move_dir = last_move_dir;
1296 local_player->last_move_dir = last_move_dir;
1298 // (required to prevent accidentally forcing direction for next movement)
1299 last_player_x = local_player->jx;
1300 last_player_y = local_player->jy;
1303 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1305 started_on_player = TRUE;
1306 player_drop_count = getPlayerInventorySize(0);
1307 player_is_dropping = (player_drop_count > 0);
1309 if (player_is_dropping)
1311 Debug("event:finger", "---------- DROP STARTED ----------");
1313 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1317 Debug("event:finger", "---------- SNAP STARTED ----------");
1319 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1322 else if (dx != 0 || dy != 0)
1324 if (player_is_dropping &&
1325 player_drop_count == getPlayerInventorySize(0))
1327 Debug("event:finger", "---------- DROP -> SNAP ----------");
1329 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1330 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1332 player_is_dropping = FALSE;
1336 if (new_motion_key_x != motion_key_x)
1338 Debug("event:finger", "---------- %s %s ----------",
1339 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1340 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1342 if (motion_key_x != KSYM_UNDEFINED)
1343 HandleKey(motion_key_x, KEY_RELEASED);
1344 if (new_motion_key_x != KSYM_UNDEFINED)
1345 HandleKey(new_motion_key_x, KEY_PRESSED);
1348 if (new_motion_key_y != motion_key_y)
1350 Debug("event:finger", "---------- %s %s ----------",
1351 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1352 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1354 if (motion_key_y != KSYM_UNDEFINED)
1355 HandleKey(motion_key_y, KEY_RELEASED);
1356 if (new_motion_key_y != KSYM_UNDEFINED)
1357 HandleKey(new_motion_key_y, KEY_PRESSED);
1360 motion_key_x = new_motion_key_x;
1361 motion_key_y = new_motion_key_y;
1365 static void HandleButtonOrFinger(int mx, int my, int button)
1367 boolean valid_mouse_event = (mx != -1 && my != -1 && button != -1);
1369 if (game_status != GAME_MODE_PLAYING)
1372 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1374 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1375 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1376 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1377 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1378 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1379 SetPlayerMouseAction(mx, my, button); // special case
1383 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1384 HandleButtonOrFinger_FollowFinger(mx, my, button);
1385 else if (game.use_mouse_actions && valid_mouse_event)
1386 SetPlayerMouseAction(mx, my, button);
1390 static boolean checkTextInputKey(Key key)
1392 // when playing, only handle raw key events and ignore text input
1393 if (game_status == GAME_MODE_PLAYING)
1396 // if Shift or right Alt key is pressed, handle key as text input
1397 if ((GetKeyModState() & KMOD_TextInput) != KMOD_None)
1400 // ignore raw keys as text input when not in text input mode
1401 if (KSYM_RAW(key) && !textinput_status)
1404 // else handle all printable keys as text input
1405 return KSYM_PRINTABLE(key);
1408 void HandleTextEvent(TextEvent *event)
1410 char *text = event->text;
1411 Key key = getKeyFromKeyName(text);
1413 #if DEBUG_EVENTS_TEXT
1414 Debug("event:text", "text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1417 text[0], (int)(text[0]),
1419 getKeyNameFromKey(key),
1423 if (checkTextInputKey(key))
1425 // process printable keys (with uppercase etc.) in text input mode
1426 HandleKey(key, KEY_PRESSED);
1427 HandleKey(key, KEY_RELEASED);
1431 void HandlePauseResumeEvent(PauseResumeEvent *event)
1433 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1437 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1443 void HandleKeyEvent(KeyEvent *event)
1445 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1446 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1447 Key key = GetEventKey(event, with_modifiers);
1448 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1450 #if DEBUG_EVENTS_KEY
1451 Debug("event:key", "key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1452 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1453 event->keysym.scancode,
1458 getKeyNameFromKey(key));
1461 #if defined(PLATFORM_ANDROID)
1462 if (key == KSYM_Back)
1464 // always map the "back" button to the "escape" key on Android devices
1467 else if (key == KSYM_Menu)
1469 // the "menu" button can be used to toggle displaying virtual buttons
1470 if (key_status == KEY_PRESSED)
1471 SetOverlayEnabled(!GetOverlayEnabled());
1475 // for any other "real" key event, disable virtual buttons
1476 SetOverlayEnabled(FALSE);
1478 // for any other "real" key event, disable overlay touch buttons
1479 runtime.uses_touch_device = FALSE;
1483 HandleKeyModState(keymod, key_status);
1485 // process all keys if not in text input mode or if non-printable keys
1486 if (!checkTextInputKey(key))
1487 HandleKey(key, key_status);
1490 static int HandleDropFileEvent(char *filename)
1492 Debug("event:dropfile", "filename == '%s'", filename);
1494 // check and extract dropped zip files into correct user data directory
1495 if (!strSuffixLower(filename, ".zip"))
1497 Warn("file '%s' not supported", filename);
1499 return TREE_TYPE_UNDEFINED;
1502 TreeInfo *tree_node = NULL;
1503 int tree_type = GetZipFileTreeType(filename);
1504 char *directory = TREE_USERDIR(tree_type);
1506 if (directory == NULL)
1508 Warn("zip file '%s' has invalid content!", filename);
1510 return TREE_TYPE_UNDEFINED;
1513 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1514 game_status == GAME_MODE_LEVELS &&
1515 leveldir_current->node_parent != NULL)
1517 // extract new level set next to currently selected level set
1518 tree_node = leveldir_current;
1520 // get parent directory of currently selected level set directory
1521 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1523 // use private level directory instead of top-level package level directory
1524 if (strPrefix(directory, options.level_directory) &&
1525 strEqual(leveldir_current->node_parent->fullpath, "."))
1526 directory = getUserLevelDir(NULL);
1529 // extract level or artwork set from zip file to target directory
1530 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1532 if (top_dir == NULL)
1534 // error message already issued by "ExtractZipFileIntoDirectory()"
1536 return TREE_TYPE_UNDEFINED;
1539 // add extracted level or artwork set to tree info structure
1540 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1542 // update menu screen (and possibly change current level set)
1543 DrawScreenAfterAddingSet(top_dir, tree_type);
1548 static void HandleDropTextEvent(char *text)
1550 Debug("event:droptext", "text == '%s'", text);
1553 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1554 int num_artwork_sets_succeeded,
1555 int num_files_failed)
1557 // only show request dialog if no other request dialog already active
1558 if (game.request_active)
1561 // this case can happen with drag-and-drop with older SDL versions
1562 if (num_level_sets_succeeded == 0 &&
1563 num_artwork_sets_succeeded == 0 &&
1564 num_files_failed == 0)
1569 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1571 char message_part1[50];
1573 sprintf(message_part1, "New %s set%s added",
1574 (num_artwork_sets_succeeded == 0 ? "level" :
1575 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1576 (num_level_sets_succeeded +
1577 num_artwork_sets_succeeded > 1 ? "s" : ""));
1579 if (num_files_failed > 0)
1580 sprintf(message, "%s, but %d dropped file%s failed!",
1581 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1583 sprintf(message, "%s!", message_part1);
1585 else if (num_files_failed > 0)
1587 sprintf(message, "Failed to process dropped file%s!",
1588 num_files_failed > 1 ? "s" : "");
1591 Request(message, REQ_CONFIRM);
1594 void HandleDropEvent(Event *event)
1596 static boolean confirm_on_drop_complete = FALSE;
1597 static int num_level_sets_succeeded = 0;
1598 static int num_artwork_sets_succeeded = 0;
1599 static int num_files_failed = 0;
1601 switch (event->type)
1605 confirm_on_drop_complete = TRUE;
1606 num_level_sets_succeeded = 0;
1607 num_artwork_sets_succeeded = 0;
1608 num_files_failed = 0;
1615 int tree_type = HandleDropFileEvent(event->drop.file);
1617 if (tree_type == TREE_TYPE_LEVEL_DIR)
1618 num_level_sets_succeeded++;
1619 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1620 tree_type == TREE_TYPE_SOUNDS_DIR ||
1621 tree_type == TREE_TYPE_MUSIC_DIR)
1622 num_artwork_sets_succeeded++;
1626 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1627 if (!confirm_on_drop_complete)
1629 // process all remaining events, including further SDL_DROPFILE events
1632 HandleDropCompleteEvent(num_level_sets_succeeded,
1633 num_artwork_sets_succeeded,
1636 num_level_sets_succeeded = 0;
1637 num_artwork_sets_succeeded = 0;
1638 num_files_failed = 0;
1646 HandleDropTextEvent(event->drop.file);
1651 case SDL_DROPCOMPLETE:
1653 HandleDropCompleteEvent(num_level_sets_succeeded,
1654 num_artwork_sets_succeeded,
1661 if (event->drop.file != NULL)
1662 SDL_free(event->drop.file);
1665 void HandleUserEvent(UserEvent *event)
1667 switch (event->code)
1669 case USEREVENT_ANIM_DELAY_ACTION:
1670 case USEREVENT_ANIM_EVENT_ACTION:
1671 // execute action functions until matching action was found
1672 if (DoKeysymAction(event->value1) ||
1673 DoGadgetAction(event->value1) ||
1674 DoScreenAction(event->value1))
1683 void HandleButton(int mx, int my, int button, int button_nr)
1685 static int old_mx = 0, old_my = 0;
1686 boolean button_hold = FALSE;
1687 boolean handle_gadgets = TRUE;
1693 button_nr = -button_nr;
1702 #if defined(PLATFORM_ANDROID)
1703 // when playing, only handle gadgets when using "follow finger" controls
1704 // or when using touch controls in combination with the MM game engine
1705 // or when using gadgets that do not overlap with virtual buttons
1707 (game_status != GAME_MODE_PLAYING ||
1708 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1709 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1710 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1711 !CheckVirtualButtonPressed(mx, my, button)));
1713 // always recognize potentially releasing already pressed gadgets
1714 if (button == MB_RELEASED)
1715 handle_gadgets = TRUE;
1717 // always recognize pressing or releasing overlay touch buttons
1718 if (CheckPosition_OverlayTouchButtons(mx, my, button) && !motion_status)
1719 handle_gadgets = TRUE;
1722 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1724 // do not handle this button event anymore
1725 return; // force mouse event not to be handled at all
1728 if (handle_gadgets && HandleGadgets(mx, my, button))
1730 // do not handle this button event anymore
1731 mx = my = -32; // force mouse event to be outside screen tiles
1734 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1737 // do not use scroll wheel button events for anything other than gadgets
1738 if (IS_WHEEL_BUTTON(button_nr))
1741 switch (game_status)
1743 case GAME_MODE_TITLE:
1744 HandleTitleScreen(mx, my, 0, 0, button);
1747 case GAME_MODE_MAIN:
1748 HandleMainMenu(mx, my, 0, 0, button);
1751 case GAME_MODE_PSEUDO_TYPENAME:
1752 HandleTypeName(KSYM_Return);
1755 case GAME_MODE_NAMES:
1756 HandleChoosePlayerName(mx, my, 0, 0, button);
1759 case GAME_MODE_LEVELS:
1760 HandleChooseLevelSet(mx, my, 0, 0, button);
1763 case GAME_MODE_LEVELNR:
1764 HandleChooseLevelNr(mx, my, 0, 0, button);
1767 case GAME_MODE_SCORES:
1768 HandleHallOfFame(0, 0, 0, 0, button);
1771 case GAME_MODE_EDITOR:
1772 HandleLevelEditorIdle();
1775 case GAME_MODE_INFO:
1776 HandleInfoScreen(mx, my, 0, 0, button);
1779 case GAME_MODE_SETUP:
1780 HandleSetupScreen(mx, my, 0, 0, button);
1783 case GAME_MODE_PLAYING:
1784 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1785 HandleButtonOrFinger(mx, my, button);
1787 SetPlayerMouseAction(mx, my, button);
1790 if (button == MB_PRESSED && !motion_status && !button_hold &&
1791 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1792 DumpTileFromScreen(mx, my);
1802 #define MAX_CHEAT_INPUT_LEN 32
1804 static void HandleKeysSpecial(Key key)
1806 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1807 char letter = getCharFromKey(key);
1808 int cheat_input_len = strlen(cheat_input);
1814 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1816 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1817 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1819 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1822 cheat_input[cheat_input_len++] = letter;
1823 cheat_input[cheat_input_len] = '\0';
1825 #if DEBUG_EVENTS_KEY
1826 Debug("event:key:special", "'%s' [%d]", cheat_input, cheat_input_len);
1829 if (game_status == GAME_MODE_MAIN)
1831 if (strSuffix(cheat_input, ":insert-solution-tape") ||
1832 strSuffix(cheat_input, ":ist"))
1834 InsertSolutionTape();
1836 else if (strSuffix(cheat_input, ":play-solution-tape") ||
1837 strSuffix(cheat_input, ":pst"))
1841 else if (strSuffix(cheat_input, ":reload-graphics") ||
1842 strSuffix(cheat_input, ":rg"))
1844 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1847 else if (strSuffix(cheat_input, ":reload-sounds") ||
1848 strSuffix(cheat_input, ":rs"))
1850 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1853 else if (strSuffix(cheat_input, ":reload-music") ||
1854 strSuffix(cheat_input, ":rm"))
1856 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1859 else if (strSuffix(cheat_input, ":reload-artwork") ||
1860 strSuffix(cheat_input, ":ra"))
1862 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1863 1 << ARTWORK_TYPE_SOUNDS |
1864 1 << ARTWORK_TYPE_MUSIC);
1867 else if (strSuffix(cheat_input, ":dump-level") ||
1868 strSuffix(cheat_input, ":dl"))
1872 else if (strSuffix(cheat_input, ":dump-tape") ||
1873 strSuffix(cheat_input, ":dt"))
1877 else if (strSuffix(cheat_input, ":undo-tape") ||
1878 strSuffix(cheat_input, ":ut"))
1882 else if (strSuffix(cheat_input, ":fix-tape") ||
1883 strSuffix(cheat_input, ":ft"))
1885 FixTape_ForceSinglePlayer();
1887 else if (strSuffix(cheat_input, ":save-native-level") ||
1888 strSuffix(cheat_input, ":snl"))
1890 SaveNativeLevel(&level);
1892 else if (strSuffix(cheat_input, ":frames-per-second") ||
1893 strSuffix(cheat_input, ":fps"))
1895 global.show_frames_per_second = !global.show_frames_per_second;
1897 else if (strSuffix(cheat_input, ":xsn"))
1899 tile_cursor.xsn_debug = TRUE;
1902 else if (game_status == GAME_MODE_PLAYING)
1905 if (strSuffix(cheat_input, ".q"))
1906 DEBUG_SetMaximumDynamite();
1909 else if (game_status == GAME_MODE_EDITOR)
1911 if (strSuffix(cheat_input, ":dump-brush") ||
1912 strSuffix(cheat_input, ":DB"))
1916 else if (strSuffix(cheat_input, ":DDB"))
1921 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1923 if (letter == 'x') // copy brush to clipboard (small size)
1925 CopyBrushToClipboard_Small();
1927 else if (letter == 'c') // copy brush to clipboard (normal size)
1929 CopyBrushToClipboard();
1931 else if (letter == 'v') // paste brush from Clipboard
1933 CopyClipboardToBrush();
1935 else if (letter == 'z') // undo or redo last operation
1937 if (GetKeyModState() & KMOD_Shift)
1938 RedoLevelEditorOperation();
1940 UndoLevelEditorOperation();
1945 // special key shortcuts for all game modes
1946 if (strSuffix(cheat_input, ":dump-event-actions") ||
1947 strSuffix(cheat_input, ":dea") ||
1948 strSuffix(cheat_input, ":DEA"))
1950 DumpGadgetIdentifiers();
1951 DumpScreenIdentifiers();
1955 boolean HandleKeysDebug(Key key, int key_status)
1960 if (key_status != KEY_PRESSED)
1963 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1965 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1967 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1969 if (key == setup.debug.frame_delay_key[i] &&
1970 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1972 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1973 setup.debug.frame_delay[i] : setup.game_frame_delay);
1975 if (!setup.debug.frame_delay_game_only)
1976 MenuFrameDelay = GameFrameDelay;
1978 SetVideoFrameDelay(GameFrameDelay);
1980 if (GameFrameDelay > ONE_SECOND_DELAY)
1981 Debug("event:key:debug", "frame delay == %d ms", GameFrameDelay);
1982 else if (GameFrameDelay != 0)
1983 Debug("event:key:debug", "frame delay == %d ms (max. %d fps / %d %%)",
1984 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1985 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1987 Debug("event:key:debug", "frame delay == 0 ms (maximum speed)");
1994 if (game_status == GAME_MODE_PLAYING)
1998 options.debug = !options.debug;
2000 Debug("event:key:debug", "debug mode %s",
2001 (options.debug ? "enabled" : "disabled"));
2005 else if (key == KSYM_v)
2007 Debug("event:key:debug", "currently using game engine version %d",
2008 game.engine_version);
2018 void HandleKey(Key key, int key_status)
2020 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2021 static boolean ignore_repeated_key = FALSE;
2022 static struct SetupKeyboardInfo ski;
2023 static struct SetupShortcutInfo ssi;
2032 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
2033 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
2034 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
2035 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
2036 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2037 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2042 if (HandleKeysDebug(key, key_status))
2043 return; // do not handle already processed keys again
2045 // map special keys (media keys / remote control buttons) to default keys
2046 if (key == KSYM_PlayPause)
2048 else if (key == KSYM_Select)
2051 HandleSpecialGameControllerKeys(key, key_status);
2053 if (game_status == GAME_MODE_PLAYING)
2055 // only needed for single-step tape recording mode
2056 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2059 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2061 byte key_action = 0;
2062 byte key_snap_action = 0;
2064 if (setup.input[pnr].use_joystick)
2067 ski = setup.input[pnr].key;
2069 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2070 if (key == *key_info[i].key_custom)
2071 key_action |= key_info[i].action;
2073 // use combined snap+direction keys for the first player only
2076 ssi = setup.shortcut;
2078 // also remember normal snap key when handling snap+direction keys
2079 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2081 for (i = 0; i < NUM_DIRECTIONS; i++)
2083 if (key == *key_info[i].key_snap)
2085 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2086 key_snap_action |= key_info[i].action;
2091 if (key_status == KEY_PRESSED)
2093 stored_player[pnr].action |= key_action;
2094 stored_player[pnr].snap_action |= key_snap_action;
2098 stored_player[pnr].action &= ~key_action;
2099 stored_player[pnr].snap_action &= ~key_snap_action;
2102 // restore snap action if one of several pressed snap keys was released
2103 if (stored_player[pnr].snap_action)
2104 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2106 if (tape.recording && tape.pausing && tape.use_key_actions)
2108 if (tape.single_step)
2110 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2112 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2114 // if snap key already pressed, keep pause mode when releasing
2115 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2116 has_snapped[pnr] = TRUE;
2118 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2120 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2122 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2123 getRedDiskReleaseFlag_SP() == 0)
2125 // add a single inactive frame before dropping starts
2126 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2127 stored_player[pnr].force_dropping = TRUE;
2130 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2132 // if snap key was pressed without direction, leave pause mode
2133 if (!has_snapped[pnr])
2134 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2136 has_snapped[pnr] = FALSE;
2141 // prevent key release events from un-pausing a paused game
2142 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2143 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2147 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2148 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2154 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2155 if (key == key_info[i].key_default)
2156 joy |= key_info[i].action;
2161 if (key_status == KEY_PRESSED)
2162 key_joystick_mapping |= joy;
2164 key_joystick_mapping &= ~joy;
2169 if (game_status != GAME_MODE_PLAYING)
2170 key_joystick_mapping = 0;
2172 if (key_status == KEY_RELEASED)
2174 // reset flag to ignore repeated "key pressed" events after key release
2175 ignore_repeated_key = FALSE;
2180 if ((key == KSYM_F11 ||
2181 ((key == KSYM_Return ||
2182 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2183 video.fullscreen_available &&
2184 !ignore_repeated_key)
2186 setup.fullscreen = !setup.fullscreen;
2188 ToggleFullscreenIfNeeded();
2190 if (game_status == GAME_MODE_SETUP)
2191 RedrawSetupScreenAfterFullscreenToggle();
2193 UpdateMousePosition();
2195 // set flag to ignore repeated "key pressed" events
2196 ignore_repeated_key = TRUE;
2201 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2202 key == KSYM_minus || key == KSYM_KP_Subtract ||
2203 key == KSYM_plus || key == KSYM_KP_Add ||
2204 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2205 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2206 video.window_scaling_available &&
2207 !video.fullscreen_enabled)
2209 if (key == KSYM_0 || key == KSYM_KP_0)
2210 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2211 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2212 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2214 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2216 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2217 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2218 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2219 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2221 ChangeWindowScalingIfNeeded();
2223 if (game_status == GAME_MODE_SETUP)
2224 RedrawSetupScreenAfterFullscreenToggle();
2226 UpdateMousePosition();
2231 // some key events are handled like clicks for global animations
2232 boolean click = (key == KSYM_space ||
2233 key == KSYM_Return ||
2234 key == KSYM_Escape);
2236 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2238 // do not handle this key event anymore
2239 if (key != KSYM_Escape) // always allow ESC key to be handled
2243 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2244 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2251 if (game_status == GAME_MODE_MAIN &&
2252 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2254 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2259 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2261 if (key == setup.shortcut.save_game)
2263 else if (key == setup.shortcut.load_game)
2265 else if (key == setup.shortcut.toggle_pause)
2266 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2268 HandleTapeButtonKeys(key);
2269 HandleSoundButtonKeys(key);
2272 if (game_status == GAME_MODE_PLAYING && !network_playing)
2274 int centered_player_nr_next = -999;
2276 if (key == setup.shortcut.focus_player_all)
2277 centered_player_nr_next = -1;
2279 for (i = 0; i < MAX_PLAYERS; i++)
2280 if (key == setup.shortcut.focus_player[i])
2281 centered_player_nr_next = i;
2283 if (centered_player_nr_next != -999)
2285 game.centered_player_nr_next = centered_player_nr_next;
2286 game.set_centered_player = TRUE;
2290 tape.centered_player_nr_next = game.centered_player_nr_next;
2291 tape.set_centered_player = TRUE;
2296 HandleKeysSpecial(key);
2298 if (HandleGadgetsKeyInput(key))
2299 return; // do not handle already processed keys again
2301 switch (game_status)
2303 case GAME_MODE_PSEUDO_TYPENAME:
2304 HandleTypeName(key);
2307 case GAME_MODE_TITLE:
2308 case GAME_MODE_MAIN:
2309 case GAME_MODE_NAMES:
2310 case GAME_MODE_LEVELS:
2311 case GAME_MODE_LEVELNR:
2312 case GAME_MODE_SETUP:
2313 case GAME_MODE_INFO:
2314 case GAME_MODE_SCORES:
2316 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2323 if (game_status == GAME_MODE_TITLE)
2324 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2325 else if (game_status == GAME_MODE_MAIN)
2326 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2327 else if (game_status == GAME_MODE_NAMES)
2328 HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_CHOICE);
2329 else if (game_status == GAME_MODE_LEVELS)
2330 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2331 else if (game_status == GAME_MODE_LEVELNR)
2332 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2333 else if (game_status == GAME_MODE_SETUP)
2334 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2335 else if (game_status == GAME_MODE_INFO)
2336 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2337 else if (game_status == GAME_MODE_SCORES)
2338 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2342 if (game_status != GAME_MODE_MAIN)
2343 FadeSkipNextFadeIn();
2345 if (game_status == GAME_MODE_TITLE)
2346 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2347 else if (game_status == GAME_MODE_NAMES)
2348 HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_LEAVE);
2349 else if (game_status == GAME_MODE_LEVELS)
2350 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2351 else if (game_status == GAME_MODE_LEVELNR)
2352 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2353 else if (game_status == GAME_MODE_SETUP)
2354 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2355 else if (game_status == GAME_MODE_INFO)
2356 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2357 else if (game_status == GAME_MODE_SCORES)
2358 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2362 if (game_status == GAME_MODE_NAMES)
2363 HandleChoosePlayerName(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2364 else if (game_status == GAME_MODE_LEVELS)
2365 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2366 else if (game_status == GAME_MODE_LEVELNR)
2367 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2368 else if (game_status == GAME_MODE_SETUP)
2369 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2370 else if (game_status == GAME_MODE_INFO)
2371 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2372 else if (game_status == GAME_MODE_SCORES)
2373 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2376 case KSYM_Page_Down:
2377 if (game_status == GAME_MODE_NAMES)
2378 HandleChoosePlayerName(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2379 else if (game_status == GAME_MODE_LEVELS)
2380 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2381 else if (game_status == GAME_MODE_LEVELNR)
2382 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2383 else if (game_status == GAME_MODE_SETUP)
2384 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2385 else if (game_status == GAME_MODE_INFO)
2386 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2387 else if (game_status == GAME_MODE_SCORES)
2388 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2396 case GAME_MODE_EDITOR:
2397 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2398 HandleLevelEditorKeyInput(key);
2401 case GAME_MODE_PLAYING:
2406 RequestQuitGame(setup.ask_on_escape);
2416 if (key == KSYM_Escape)
2418 SetGameStatus(GAME_MODE_MAIN);
2427 void HandleNoEvent(void)
2429 HandleMouseCursor();
2431 switch (game_status)
2433 case GAME_MODE_PLAYING:
2434 HandleButtonOrFinger(-1, -1, -1);
2439 void HandleEventActions(void)
2441 // if (button_status && game_status != GAME_MODE_PLAYING)
2442 if (button_status && (game_status != GAME_MODE_PLAYING ||
2444 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2446 HandleButton(0, 0, button_status, -button_status);
2453 if (network.enabled)
2456 switch (game_status)
2458 case GAME_MODE_MAIN:
2459 DrawPreviewLevelAnimation();
2462 case GAME_MODE_EDITOR:
2463 HandleLevelEditorIdle();
2471 static void HandleTileCursor(int dx, int dy, int button)
2474 ClearPlayerMouseAction();
2481 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2482 (dx < 0 ? MB_LEFTBUTTON :
2483 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2485 else if (!tile_cursor.moving)
2487 int old_xpos = tile_cursor.xpos;
2488 int old_ypos = tile_cursor.ypos;
2489 int new_xpos = old_xpos;
2490 int new_ypos = old_ypos;
2492 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2493 new_xpos = old_xpos + dx;
2495 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2496 new_ypos = old_ypos + dy;
2498 SetTileCursorTargetXY(new_xpos, new_ypos);
2502 static int HandleJoystickForAllPlayers(void)
2506 boolean no_joysticks_configured = TRUE;
2507 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2508 static byte joy_action_last[MAX_PLAYERS];
2510 for (i = 0; i < MAX_PLAYERS; i++)
2511 if (setup.input[i].use_joystick)
2512 no_joysticks_configured = FALSE;
2514 // if no joysticks configured, map connected joysticks to players
2515 if (no_joysticks_configured)
2516 use_as_joystick_nr = TRUE;
2518 for (i = 0; i < MAX_PLAYERS; i++)
2520 byte joy_action = 0;
2522 joy_action = JoystickExt(i, use_as_joystick_nr);
2523 result |= joy_action;
2525 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2526 joy_action != joy_action_last[i])
2527 stored_player[i].action = joy_action;
2529 joy_action_last[i] = joy_action;
2535 void HandleJoystick(void)
2537 static unsigned int joytest_delay = 0;
2538 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2539 static int joytest_last = 0;
2540 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2541 int delay_value = GADGET_FRAME_DELAY;
2542 int joystick = HandleJoystickForAllPlayers();
2543 int keyboard = key_joystick_mapping;
2544 int joy = (joystick | keyboard);
2545 int joytest = joystick;
2546 int left = joy & JOY_LEFT;
2547 int right = joy & JOY_RIGHT;
2548 int up = joy & JOY_UP;
2549 int down = joy & JOY_DOWN;
2550 int button = joy & JOY_BUTTON;
2551 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2552 int dx = (left ? -1 : right ? 1 : 0);
2553 int dy = (up ? -1 : down ? 1 : 0);
2554 boolean use_delay_value_first = (joytest != joytest_last);
2556 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2558 // do not handle this button event anymore
2562 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2563 game_status == GAME_MODE_PSEUDO_TYPENAMES ||
2564 anyTextGadgetActive()))
2566 // leave name input in main menu or text input gadget
2567 HandleKey(KSYM_Escape, KEY_PRESSED);
2568 HandleKey(KSYM_Escape, KEY_RELEASED);
2573 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2575 if (game_status == GAME_MODE_PLAYING)
2577 // when playing MM style levels, also use delay for keyboard events
2578 joytest |= keyboard;
2580 // only use first delay value for new events, but not for changed events
2581 use_delay_value_first = (!joytest != !joytest_last);
2583 // only use delay after the initial keyboard event
2587 // for any joystick or keyboard event, enable playfield tile cursor
2588 if (dx || dy || button)
2589 SetTileCursorEnabled(TRUE);
2592 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2594 // delay joystick/keyboard actions if axes/keys continually pressed
2595 newbutton = dx = dy = 0;
2599 // first start with longer delay, then continue with shorter delay
2600 joytest_delay_value =
2601 (use_delay_value_first ? delay_value_first : delay_value);
2604 joytest_last = joytest;
2606 switch (game_status)
2608 case GAME_MODE_TITLE:
2609 case GAME_MODE_MAIN:
2610 case GAME_MODE_NAMES:
2611 case GAME_MODE_LEVELS:
2612 case GAME_MODE_LEVELNR:
2613 case GAME_MODE_SETUP:
2614 case GAME_MODE_INFO:
2615 case GAME_MODE_SCORES:
2617 if (anyTextGadgetActive())
2620 if (game_status == GAME_MODE_TITLE)
2621 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2622 else if (game_status == GAME_MODE_MAIN)
2623 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2624 else if (game_status == GAME_MODE_NAMES)
2625 HandleChoosePlayerName(0,0,dx,dy,newbutton?MB_MENU_CHOICE:MB_MENU_MARK);
2626 else if (game_status == GAME_MODE_LEVELS)
2627 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2628 else if (game_status == GAME_MODE_LEVELNR)
2629 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2630 else if (game_status == GAME_MODE_SETUP)
2631 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2632 else if (game_status == GAME_MODE_INFO)
2633 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2634 else if (game_status == GAME_MODE_SCORES)
2635 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2640 case GAME_MODE_PLAYING:
2642 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2643 if (tape.playing || keyboard)
2644 newbutton = ((joy & JOY_BUTTON) != 0);
2647 if (newbutton && game.all_players_gone)
2654 if (tape.recording && tape.pausing && tape.use_key_actions)
2656 if (tape.single_step)
2658 if (joystick & JOY_ACTION)
2659 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2663 if (joystick & JOY_ACTION)
2664 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2668 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2669 HandleTileCursor(dx, dy, button);
2678 void HandleSpecialGameControllerButtons(Event *event)
2683 switch (event->type)
2685 case SDL_CONTROLLERBUTTONDOWN:
2686 key_status = KEY_PRESSED;
2689 case SDL_CONTROLLERBUTTONUP:
2690 key_status = KEY_RELEASED;
2697 switch (event->cbutton.button)
2699 case SDL_CONTROLLER_BUTTON_START:
2703 case SDL_CONTROLLER_BUTTON_BACK:
2711 HandleKey(key, key_status);
2714 void HandleSpecialGameControllerKeys(Key key, int key_status)
2716 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2717 int button = SDL_CONTROLLER_BUTTON_INVALID;
2719 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2720 if (key == KSYM_Rewind)
2721 button = SDL_CONTROLLER_BUTTON_A;
2722 else if (key == KSYM_FastForward || key == KSYM_Menu)
2723 button = SDL_CONTROLLER_BUTTON_B;
2725 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2729 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2730 SDL_CONTROLLERBUTTONUP);
2732 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2733 event.cbutton.button = button;
2734 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2737 HandleJoystickEvent(&event);
2742 boolean DoKeysymAction(int keysym)
2746 Key key = (Key)(-keysym);
2748 HandleKey(key, KEY_PRESSED);
2749 HandleKey(key, KEY_RELEASED);