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(0, KSYM_Return);
1755 case GAME_MODE_LEVELS:
1756 HandleChooseLevelSet(mx, my, 0, 0, button);
1759 case GAME_MODE_LEVELNR:
1760 HandleChooseLevelNr(mx, my, 0, 0, button);
1763 case GAME_MODE_SCORES:
1764 HandleHallOfFame(0, 0, 0, 0, button);
1767 case GAME_MODE_EDITOR:
1768 HandleLevelEditorIdle();
1771 case GAME_MODE_INFO:
1772 HandleInfoScreen(mx, my, 0, 0, button);
1775 case GAME_MODE_SETUP:
1776 HandleSetupScreen(mx, my, 0, 0, button);
1779 case GAME_MODE_PLAYING:
1780 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1781 HandleButtonOrFinger(mx, my, button);
1783 SetPlayerMouseAction(mx, my, button);
1786 if (button == MB_PRESSED && !motion_status && !button_hold &&
1787 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1788 DumpTileFromScreen(mx, my);
1798 #define MAX_CHEAT_INPUT_LEN 32
1800 static void HandleKeysSpecial(Key key)
1802 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1803 char letter = getCharFromKey(key);
1804 int cheat_input_len = strlen(cheat_input);
1810 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1812 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1813 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1815 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1818 cheat_input[cheat_input_len++] = letter;
1819 cheat_input[cheat_input_len] = '\0';
1821 #if DEBUG_EVENTS_KEY
1822 Debug("event:key:special", "'%s' [%d]", cheat_input, cheat_input_len);
1825 if (game_status == GAME_MODE_MAIN)
1827 if (strSuffix(cheat_input, ":insert-solution-tape") ||
1828 strSuffix(cheat_input, ":ist"))
1830 InsertSolutionTape();
1832 else if (strSuffix(cheat_input, ":play-solution-tape") ||
1833 strSuffix(cheat_input, ":pst"))
1837 else if (strSuffix(cheat_input, ":reload-graphics") ||
1838 strSuffix(cheat_input, ":rg"))
1840 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1843 else if (strSuffix(cheat_input, ":reload-sounds") ||
1844 strSuffix(cheat_input, ":rs"))
1846 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1849 else if (strSuffix(cheat_input, ":reload-music") ||
1850 strSuffix(cheat_input, ":rm"))
1852 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1855 else if (strSuffix(cheat_input, ":reload-artwork") ||
1856 strSuffix(cheat_input, ":ra"))
1858 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1859 1 << ARTWORK_TYPE_SOUNDS |
1860 1 << ARTWORK_TYPE_MUSIC);
1863 else if (strSuffix(cheat_input, ":dump-level") ||
1864 strSuffix(cheat_input, ":dl"))
1868 else if (strSuffix(cheat_input, ":dump-tape") ||
1869 strSuffix(cheat_input, ":dt"))
1873 else if (strSuffix(cheat_input, ":undo-tape") ||
1874 strSuffix(cheat_input, ":ut"))
1878 else if (strSuffix(cheat_input, ":fix-tape") ||
1879 strSuffix(cheat_input, ":ft"))
1881 FixTape_ForceSinglePlayer();
1883 else if (strSuffix(cheat_input, ":save-native-level") ||
1884 strSuffix(cheat_input, ":snl"))
1886 SaveNativeLevel(&level);
1888 else if (strSuffix(cheat_input, ":frames-per-second") ||
1889 strSuffix(cheat_input, ":fps"))
1891 global.show_frames_per_second = !global.show_frames_per_second;
1893 else if (strSuffix(cheat_input, ":xsn"))
1895 tile_cursor.xsn_debug = TRUE;
1898 else if (game_status == GAME_MODE_PLAYING)
1901 if (strSuffix(cheat_input, ".q"))
1902 DEBUG_SetMaximumDynamite();
1905 else if (game_status == GAME_MODE_EDITOR)
1907 if (strSuffix(cheat_input, ":dump-brush") ||
1908 strSuffix(cheat_input, ":DB"))
1912 else if (strSuffix(cheat_input, ":DDB"))
1917 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1919 if (letter == 'x') // copy brush to clipboard (small size)
1921 CopyBrushToClipboard_Small();
1923 else if (letter == 'c') // copy brush to clipboard (normal size)
1925 CopyBrushToClipboard();
1927 else if (letter == 'v') // paste brush from Clipboard
1929 CopyClipboardToBrush();
1931 else if (letter == 'z') // undo or redo last operation
1933 if (GetKeyModState() & KMOD_Shift)
1934 RedoLevelEditorOperation();
1936 UndoLevelEditorOperation();
1941 // special key shortcuts for all game modes
1942 if (strSuffix(cheat_input, ":dump-event-actions") ||
1943 strSuffix(cheat_input, ":dea") ||
1944 strSuffix(cheat_input, ":DEA"))
1946 DumpGadgetIdentifiers();
1947 DumpScreenIdentifiers();
1951 boolean HandleKeysDebug(Key key, int key_status)
1956 if (key_status != KEY_PRESSED)
1959 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1961 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1963 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1965 if (key == setup.debug.frame_delay_key[i] &&
1966 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1968 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1969 setup.debug.frame_delay[i] : setup.game_frame_delay);
1971 if (!setup.debug.frame_delay_game_only)
1972 MenuFrameDelay = GameFrameDelay;
1974 SetVideoFrameDelay(GameFrameDelay);
1976 if (GameFrameDelay > ONE_SECOND_DELAY)
1977 Debug("event:key:debug", "frame delay == %d ms", GameFrameDelay);
1978 else if (GameFrameDelay != 0)
1979 Debug("event:key:debug", "frame delay == %d ms (max. %d fps / %d %%)",
1980 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1981 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1983 Debug("event:key:debug", "frame delay == 0 ms (maximum speed)");
1990 if (game_status == GAME_MODE_PLAYING)
1994 options.debug = !options.debug;
1996 Debug("event:key:debug", "debug mode %s",
1997 (options.debug ? "enabled" : "disabled"));
2001 else if (key == KSYM_v)
2003 Debug("event:key:debug", "currently using game engine version %d",
2004 game.engine_version);
2014 void HandleKey(Key key, int key_status)
2016 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2017 static boolean ignore_repeated_key = FALSE;
2018 static struct SetupKeyboardInfo ski;
2019 static struct SetupShortcutInfo ssi;
2028 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
2029 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
2030 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
2031 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
2032 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2033 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2038 if (HandleKeysDebug(key, key_status))
2039 return; // do not handle already processed keys again
2041 // map special keys (media keys / remote control buttons) to default keys
2042 if (key == KSYM_PlayPause)
2044 else if (key == KSYM_Select)
2047 HandleSpecialGameControllerKeys(key, key_status);
2049 if (game_status == GAME_MODE_PLAYING)
2051 // only needed for single-step tape recording mode
2052 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2055 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2057 byte key_action = 0;
2058 byte key_snap_action = 0;
2060 if (setup.input[pnr].use_joystick)
2063 ski = setup.input[pnr].key;
2065 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2066 if (key == *key_info[i].key_custom)
2067 key_action |= key_info[i].action;
2069 // use combined snap+direction keys for the first player only
2072 ssi = setup.shortcut;
2074 // also remember normal snap key when handling snap+direction keys
2075 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2077 for (i = 0; i < NUM_DIRECTIONS; i++)
2079 if (key == *key_info[i].key_snap)
2081 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2082 key_snap_action |= key_info[i].action;
2087 if (key_status == KEY_PRESSED)
2089 stored_player[pnr].action |= key_action;
2090 stored_player[pnr].snap_action |= key_snap_action;
2094 stored_player[pnr].action &= ~key_action;
2095 stored_player[pnr].snap_action &= ~key_snap_action;
2098 // restore snap action if one of several pressed snap keys was released
2099 if (stored_player[pnr].snap_action)
2100 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2102 if (tape.recording && tape.pausing && tape.use_key_actions)
2104 if (tape.single_step)
2106 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2108 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2110 // if snap key already pressed, keep pause mode when releasing
2111 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2112 has_snapped[pnr] = TRUE;
2114 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2116 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2118 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2119 getRedDiskReleaseFlag_SP() == 0)
2121 // add a single inactive frame before dropping starts
2122 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2123 stored_player[pnr].force_dropping = TRUE;
2126 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2128 // if snap key was pressed without direction, leave pause mode
2129 if (!has_snapped[pnr])
2130 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2132 has_snapped[pnr] = FALSE;
2137 // prevent key release events from un-pausing a paused game
2138 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2139 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2143 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2144 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2150 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2151 if (key == key_info[i].key_default)
2152 joy |= key_info[i].action;
2157 if (key_status == KEY_PRESSED)
2158 key_joystick_mapping |= joy;
2160 key_joystick_mapping &= ~joy;
2165 if (game_status != GAME_MODE_PLAYING)
2166 key_joystick_mapping = 0;
2168 if (key_status == KEY_RELEASED)
2170 // reset flag to ignore repeated "key pressed" events after key release
2171 ignore_repeated_key = FALSE;
2176 if ((key == KSYM_F11 ||
2177 ((key == KSYM_Return ||
2178 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2179 video.fullscreen_available &&
2180 !ignore_repeated_key)
2182 setup.fullscreen = !setup.fullscreen;
2184 ToggleFullscreenIfNeeded();
2186 if (game_status == GAME_MODE_SETUP)
2187 RedrawSetupScreenAfterFullscreenToggle();
2189 UpdateMousePosition();
2191 // set flag to ignore repeated "key pressed" events
2192 ignore_repeated_key = TRUE;
2197 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2198 key == KSYM_minus || key == KSYM_KP_Subtract ||
2199 key == KSYM_plus || key == KSYM_KP_Add ||
2200 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2201 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2202 video.window_scaling_available &&
2203 !video.fullscreen_enabled)
2205 if (key == KSYM_0 || key == KSYM_KP_0)
2206 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2207 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2208 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2210 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2212 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2213 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2214 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2215 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2217 ChangeWindowScalingIfNeeded();
2219 if (game_status == GAME_MODE_SETUP)
2220 RedrawSetupScreenAfterFullscreenToggle();
2222 UpdateMousePosition();
2227 // some key events are handled like clicks for global animations
2228 boolean click = (key == KSYM_space ||
2229 key == KSYM_Return ||
2230 key == KSYM_Escape);
2232 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2234 // do not handle this key event anymore
2235 if (key != KSYM_Escape) // always allow ESC key to be handled
2239 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2240 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2247 if (game_status == GAME_MODE_MAIN &&
2248 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2250 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2255 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2257 if (key == setup.shortcut.save_game)
2259 else if (key == setup.shortcut.load_game)
2261 else if (key == setup.shortcut.toggle_pause)
2262 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2264 HandleTapeButtonKeys(key);
2265 HandleSoundButtonKeys(key);
2268 if (game_status == GAME_MODE_PLAYING && !network_playing)
2270 int centered_player_nr_next = -999;
2272 if (key == setup.shortcut.focus_player_all)
2273 centered_player_nr_next = -1;
2275 for (i = 0; i < MAX_PLAYERS; i++)
2276 if (key == setup.shortcut.focus_player[i])
2277 centered_player_nr_next = i;
2279 if (centered_player_nr_next != -999)
2281 game.centered_player_nr_next = centered_player_nr_next;
2282 game.set_centered_player = TRUE;
2286 tape.centered_player_nr_next = game.centered_player_nr_next;
2287 tape.set_centered_player = TRUE;
2292 HandleKeysSpecial(key);
2294 if (HandleGadgetsKeyInput(key))
2295 return; // do not handle already processed keys again
2297 switch (game_status)
2299 case GAME_MODE_PSEUDO_TYPENAME:
2300 HandleTypeName(0, key);
2303 case GAME_MODE_TITLE:
2304 case GAME_MODE_MAIN:
2305 case GAME_MODE_LEVELS:
2306 case GAME_MODE_LEVELNR:
2307 case GAME_MODE_SETUP:
2308 case GAME_MODE_INFO:
2309 case GAME_MODE_SCORES:
2311 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2318 if (game_status == GAME_MODE_TITLE)
2319 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2320 else if (game_status == GAME_MODE_MAIN)
2321 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2322 else if (game_status == GAME_MODE_LEVELS)
2323 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2324 else if (game_status == GAME_MODE_LEVELNR)
2325 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2326 else if (game_status == GAME_MODE_SETUP)
2327 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2328 else if (game_status == GAME_MODE_INFO)
2329 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2330 else if (game_status == GAME_MODE_SCORES)
2331 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2335 if (game_status != GAME_MODE_MAIN)
2336 FadeSkipNextFadeIn();
2338 if (game_status == GAME_MODE_TITLE)
2339 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2340 else if (game_status == GAME_MODE_LEVELS)
2341 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2342 else if (game_status == GAME_MODE_LEVELNR)
2343 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2344 else if (game_status == GAME_MODE_SETUP)
2345 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2346 else if (game_status == GAME_MODE_INFO)
2347 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2348 else if (game_status == GAME_MODE_SCORES)
2349 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2353 if (game_status == GAME_MODE_LEVELS)
2354 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2355 else if (game_status == GAME_MODE_LEVELNR)
2356 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2357 else if (game_status == GAME_MODE_SETUP)
2358 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2359 else if (game_status == GAME_MODE_INFO)
2360 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2361 else if (game_status == GAME_MODE_SCORES)
2362 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2365 case KSYM_Page_Down:
2366 if (game_status == GAME_MODE_LEVELS)
2367 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2368 else if (game_status == GAME_MODE_LEVELNR)
2369 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2370 else if (game_status == GAME_MODE_SETUP)
2371 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2372 else if (game_status == GAME_MODE_INFO)
2373 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2374 else if (game_status == GAME_MODE_SCORES)
2375 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2383 case GAME_MODE_EDITOR:
2384 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2385 HandleLevelEditorKeyInput(key);
2388 case GAME_MODE_PLAYING:
2393 RequestQuitGame(setup.ask_on_escape);
2403 if (key == KSYM_Escape)
2405 SetGameStatus(GAME_MODE_MAIN);
2414 void HandleNoEvent(void)
2416 HandleMouseCursor();
2418 switch (game_status)
2420 case GAME_MODE_PLAYING:
2421 HandleButtonOrFinger(-1, -1, -1);
2426 void HandleEventActions(void)
2428 // if (button_status && game_status != GAME_MODE_PLAYING)
2429 if (button_status && (game_status != GAME_MODE_PLAYING ||
2431 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2433 HandleButton(0, 0, button_status, -button_status);
2440 if (network.enabled)
2443 switch (game_status)
2445 case GAME_MODE_MAIN:
2446 DrawPreviewLevelAnimation();
2449 case GAME_MODE_EDITOR:
2450 HandleLevelEditorIdle();
2458 static void HandleTileCursor(int dx, int dy, int button)
2461 ClearPlayerMouseAction();
2468 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2469 (dx < 0 ? MB_LEFTBUTTON :
2470 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2472 else if (!tile_cursor.moving)
2474 int old_xpos = tile_cursor.xpos;
2475 int old_ypos = tile_cursor.ypos;
2476 int new_xpos = old_xpos;
2477 int new_ypos = old_ypos;
2479 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2480 new_xpos = old_xpos + dx;
2482 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2483 new_ypos = old_ypos + dy;
2485 SetTileCursorTargetXY(new_xpos, new_ypos);
2489 static int HandleJoystickForAllPlayers(void)
2493 boolean no_joysticks_configured = TRUE;
2494 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2495 static byte joy_action_last[MAX_PLAYERS];
2497 for (i = 0; i < MAX_PLAYERS; i++)
2498 if (setup.input[i].use_joystick)
2499 no_joysticks_configured = FALSE;
2501 // if no joysticks configured, map connected joysticks to players
2502 if (no_joysticks_configured)
2503 use_as_joystick_nr = TRUE;
2505 for (i = 0; i < MAX_PLAYERS; i++)
2507 byte joy_action = 0;
2509 joy_action = JoystickExt(i, use_as_joystick_nr);
2510 result |= joy_action;
2512 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2513 joy_action != joy_action_last[i])
2514 stored_player[i].action = joy_action;
2516 joy_action_last[i] = joy_action;
2522 void HandleJoystick(void)
2524 static unsigned int joytest_delay = 0;
2525 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2526 static int joytest_last = 0;
2527 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2528 int delay_value = GADGET_FRAME_DELAY;
2529 int joystick = HandleJoystickForAllPlayers();
2530 int keyboard = key_joystick_mapping;
2531 int joy = (joystick | keyboard);
2532 int joytest = joystick;
2533 int left = joy & JOY_LEFT;
2534 int right = joy & JOY_RIGHT;
2535 int up = joy & JOY_UP;
2536 int down = joy & JOY_DOWN;
2537 int button = joy & JOY_BUTTON;
2538 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2539 int dx = (left ? -1 : right ? 1 : 0);
2540 int dy = (up ? -1 : down ? 1 : 0);
2541 boolean use_delay_value_first = (joytest != joytest_last);
2543 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2545 // do not handle this button event anymore
2549 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2550 anyTextGadgetActive()))
2552 // leave name input in main menu or text input gadget
2553 HandleKey(KSYM_Escape, KEY_PRESSED);
2554 HandleKey(KSYM_Escape, KEY_RELEASED);
2559 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2561 if (game_status == GAME_MODE_PLAYING)
2563 // when playing MM style levels, also use delay for keyboard events
2564 joytest |= keyboard;
2566 // only use first delay value for new events, but not for changed events
2567 use_delay_value_first = (!joytest != !joytest_last);
2569 // only use delay after the initial keyboard event
2573 // for any joystick or keyboard event, enable playfield tile cursor
2574 if (dx || dy || button)
2575 SetTileCursorEnabled(TRUE);
2578 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2580 // delay joystick/keyboard actions if axes/keys continually pressed
2581 newbutton = dx = dy = 0;
2585 // first start with longer delay, then continue with shorter delay
2586 joytest_delay_value =
2587 (use_delay_value_first ? delay_value_first : delay_value);
2590 joytest_last = joytest;
2592 switch (game_status)
2594 case GAME_MODE_TITLE:
2595 case GAME_MODE_MAIN:
2596 case GAME_MODE_LEVELS:
2597 case GAME_MODE_LEVELNR:
2598 case GAME_MODE_SETUP:
2599 case GAME_MODE_INFO:
2600 case GAME_MODE_SCORES:
2602 if (anyTextGadgetActive())
2605 if (game_status == GAME_MODE_TITLE)
2606 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2607 else if (game_status == GAME_MODE_MAIN)
2608 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2609 else if (game_status == GAME_MODE_LEVELS)
2610 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2611 else if (game_status == GAME_MODE_LEVELNR)
2612 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2613 else if (game_status == GAME_MODE_SETUP)
2614 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2615 else if (game_status == GAME_MODE_INFO)
2616 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2617 else if (game_status == GAME_MODE_SCORES)
2618 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2623 case GAME_MODE_PLAYING:
2625 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2626 if (tape.playing || keyboard)
2627 newbutton = ((joy & JOY_BUTTON) != 0);
2630 if (newbutton && game.all_players_gone)
2637 if (tape.recording && tape.pausing && tape.use_key_actions)
2639 if (tape.single_step)
2641 if (joystick & JOY_ACTION)
2642 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2646 if (joystick & JOY_ACTION)
2647 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2651 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2652 HandleTileCursor(dx, dy, button);
2661 void HandleSpecialGameControllerButtons(Event *event)
2666 switch (event->type)
2668 case SDL_CONTROLLERBUTTONDOWN:
2669 key_status = KEY_PRESSED;
2672 case SDL_CONTROLLERBUTTONUP:
2673 key_status = KEY_RELEASED;
2680 switch (event->cbutton.button)
2682 case SDL_CONTROLLER_BUTTON_START:
2686 case SDL_CONTROLLER_BUTTON_BACK:
2694 HandleKey(key, key_status);
2697 void HandleSpecialGameControllerKeys(Key key, int key_status)
2699 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2700 int button = SDL_CONTROLLER_BUTTON_INVALID;
2702 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2703 if (key == KSYM_Rewind)
2704 button = SDL_CONTROLLER_BUTTON_A;
2705 else if (key == KSYM_FastForward || key == KSYM_Menu)
2706 button = SDL_CONTROLLER_BUTTON_B;
2708 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2712 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2713 SDL_CONTROLLERBUTTONUP);
2715 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2716 event.cbutton.button = button;
2717 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2720 HandleJoystickEvent(&event);
2725 boolean DoKeysymAction(int keysym)
2729 Key key = (Key)(-keysym);
2731 HandleKey(key, KEY_PRESSED);
2732 HandleKey(key, KEY_RELEASED);