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())
678 if (game_status == GAME_MODE_SETUP)
679 RedrawSetupScreenAfterScreenRotation(nr);
681 nr = GRID_ACTIVE_NR();
683 overlay.grid_xsize = setup.touch.grid_xsize[nr];
684 overlay.grid_ysize = setup.touch.grid_ysize[nr];
686 for (x = 0; x < MAX_GRID_XSIZE; x++)
687 for (y = 0; y < MAX_GRID_YSIZE; y++)
688 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
696 #define NUM_TOUCH_FINGERS 3
701 SDL_FingerID finger_id;
705 } touch_info[NUM_TOUCH_FINGERS];
707 static void SetTouchInfo(int pos, SDL_FingerID finger_id, int counter,
708 Key key, byte action)
710 touch_info[pos].touched = (action != JOY_NO_ACTION);
711 touch_info[pos].finger_id = finger_id;
712 touch_info[pos].counter = counter;
713 touch_info[pos].key = key;
714 touch_info[pos].action = action;
717 static void ClearTouchInfo(void)
721 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
722 SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
725 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
727 int x = event->x * overlay.grid_xsize;
728 int y = event->y * overlay.grid_ysize;
729 int grid_button = overlay.grid_button[x][y];
730 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
731 Key key = GetKeyFromGridButton(grid_button);
732 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
734 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
738 // for any touch input event, enable overlay buttons (if activated)
739 SetOverlayEnabled(TRUE);
741 Debug("event:finger", "key '%s' was '%s' [fingerId: %lld]",
742 getKeyNameFromKey(key), key_status_name, event->fingerId);
744 if (key_status == KEY_PRESSED)
745 overlay.grid_button_action |= grid_button_action;
747 overlay.grid_button_action &= ~grid_button_action;
749 // check if we already know this touch event's finger id
750 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
752 if (touch_info[i].touched &&
753 touch_info[i].finger_id == event->fingerId)
755 // Debug("event:finger", "MARK 1: %d", i);
761 if (i >= NUM_TOUCH_FINGERS)
763 if (key_status == KEY_PRESSED)
765 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
767 // unknown finger id -- get new, empty slot, if available
768 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
770 if (touch_info[i].counter < oldest_counter)
773 oldest_counter = touch_info[i].counter;
775 // Debug("event:finger", "MARK 2: %d", i);
778 if (!touch_info[i].touched)
780 // Debug("event:finger", "MARK 3: %d", i);
786 if (i >= NUM_TOUCH_FINGERS)
788 // all slots allocated -- use oldest slot
791 // Debug("event:finger", "MARK 4: %d", i);
796 // release of previously unknown key (should not happen)
798 if (key != KSYM_UNDEFINED)
800 HandleKey(key, KEY_RELEASED);
802 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [1]",
803 getKeyNameFromKey(key), "KEY_RELEASED", i);
808 if (i < NUM_TOUCH_FINGERS)
810 if (key_status == KEY_PRESSED)
812 if (touch_info[i].key != key)
814 if (touch_info[i].key != KSYM_UNDEFINED)
816 HandleKey(touch_info[i].key, KEY_RELEASED);
818 // undraw previous grid button when moving finger away
819 overlay.grid_button_action &= ~touch_info[i].action;
821 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [2]",
822 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
825 if (key != KSYM_UNDEFINED)
827 HandleKey(key, KEY_PRESSED);
829 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [3]",
830 getKeyNameFromKey(key), "KEY_PRESSED", i);
834 SetTouchInfo(i, event->fingerId, Counter(), key, grid_button_action);
838 if (touch_info[i].key != KSYM_UNDEFINED)
840 HandleKey(touch_info[i].key, KEY_RELEASED);
842 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [4]",
843 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
846 SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
851 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
853 static Key motion_key_x = KSYM_UNDEFINED;
854 static Key motion_key_y = KSYM_UNDEFINED;
855 static Key button_key = KSYM_UNDEFINED;
856 static float motion_x1, motion_y1;
857 static float button_x1, button_y1;
858 static SDL_FingerID motion_id = -1;
859 static SDL_FingerID button_id = -1;
860 int move_trigger_distance_percent = setup.touch.move_distance;
861 int drop_trigger_distance_percent = setup.touch.drop_distance;
862 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
863 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
864 float event_x = event->x;
865 float event_y = event->y;
867 if (event->type == EVENT_FINGERPRESS)
869 if (event_x > 1.0 / 3.0)
873 motion_id = event->fingerId;
878 motion_key_x = KSYM_UNDEFINED;
879 motion_key_y = KSYM_UNDEFINED;
881 Debug("event:finger", "---------- MOVE STARTED (WAIT) ----------");
887 button_id = event->fingerId;
892 button_key = setup.input[0].key.snap;
894 HandleKey(button_key, KEY_PRESSED);
896 Debug("event:finger", "---------- SNAP STARTED ----------");
899 else if (event->type == EVENT_FINGERRELEASE)
901 if (event->fingerId == motion_id)
905 if (motion_key_x != KSYM_UNDEFINED)
906 HandleKey(motion_key_x, KEY_RELEASED);
907 if (motion_key_y != KSYM_UNDEFINED)
908 HandleKey(motion_key_y, KEY_RELEASED);
910 motion_key_x = KSYM_UNDEFINED;
911 motion_key_y = KSYM_UNDEFINED;
913 Debug("event:finger", "---------- MOVE STOPPED ----------");
915 else if (event->fingerId == button_id)
919 if (button_key != KSYM_UNDEFINED)
920 HandleKey(button_key, KEY_RELEASED);
922 button_key = KSYM_UNDEFINED;
924 Debug("event:finger", "---------- SNAP STOPPED ----------");
927 else if (event->type == EVENT_FINGERMOTION)
929 if (event->fingerId == motion_id)
931 float distance_x = ABS(event_x - motion_x1);
932 float distance_y = ABS(event_y - motion_y1);
933 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
934 event_x > motion_x1 ? setup.input[0].key.right :
936 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
937 event_y > motion_y1 ? setup.input[0].key.down :
940 if (distance_x < move_trigger_distance / 2 ||
941 distance_x < distance_y)
942 new_motion_key_x = KSYM_UNDEFINED;
944 if (distance_y < move_trigger_distance / 2 ||
945 distance_y < distance_x)
946 new_motion_key_y = KSYM_UNDEFINED;
948 if (distance_x > move_trigger_distance ||
949 distance_y > move_trigger_distance)
951 if (new_motion_key_x != motion_key_x)
953 if (motion_key_x != KSYM_UNDEFINED)
954 HandleKey(motion_key_x, KEY_RELEASED);
955 if (new_motion_key_x != KSYM_UNDEFINED)
956 HandleKey(new_motion_key_x, KEY_PRESSED);
959 if (new_motion_key_y != motion_key_y)
961 if (motion_key_y != KSYM_UNDEFINED)
962 HandleKey(motion_key_y, KEY_RELEASED);
963 if (new_motion_key_y != KSYM_UNDEFINED)
964 HandleKey(new_motion_key_y, KEY_PRESSED);
970 motion_key_x = new_motion_key_x;
971 motion_key_y = new_motion_key_y;
973 Debug("event:finger", "---------- MOVE STARTED (MOVE) ----------");
976 else if (event->fingerId == button_id)
978 float distance_x = ABS(event_x - button_x1);
979 float distance_y = ABS(event_y - button_y1);
981 if (distance_x < drop_trigger_distance / 2 &&
982 distance_y > drop_trigger_distance)
984 if (button_key == setup.input[0].key.snap)
985 HandleKey(button_key, KEY_RELEASED);
990 button_key = setup.input[0].key.drop;
992 HandleKey(button_key, KEY_PRESSED);
994 Debug("event:finger", "---------- DROP STARTED ----------");
1000 void HandleFingerEvent(FingerEvent *event)
1002 #if DEBUG_EVENTS_FINGER
1003 Debug("event:finger", "finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
1004 event->type == EVENT_FINGERPRESS ? "pressed" :
1005 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
1009 event->dx, event->dy,
1013 runtime.uses_touch_device = TRUE;
1015 if (game_status != GAME_MODE_PLAYING)
1018 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1020 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1021 local_player->mouse_action.button_hint =
1022 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1023 event->x < 0.5 ? MB_LEFTBUTTON :
1024 event->x > 0.5 ? MB_RIGHTBUTTON :
1030 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1031 HandleFingerEvent_VirtualButtons(event);
1032 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1033 HandleFingerEvent_WipeGestures(event);
1036 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1038 static int old_mx = 0, old_my = 0;
1039 static int last_button = MB_LEFTBUTTON;
1040 static boolean touched = FALSE;
1041 static boolean tapped = FALSE;
1043 // screen tile was tapped (but finger not touching the screen anymore)
1044 // (this point will also be reached without receiving a touch event)
1045 if (tapped && !touched)
1047 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1052 // stop here if this function was not triggered by a touch event
1056 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1058 // finger started touching the screen
1068 ClearPlayerMouseAction();
1070 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1073 else if (button == MB_RELEASED && touched)
1075 // finger stopped touching the screen
1080 SetPlayerMouseAction(old_mx, old_my, last_button);
1082 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1084 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1089 // finger moved while touching the screen
1091 int old_x = getLevelFromScreenX(old_mx);
1092 int old_y = getLevelFromScreenY(old_my);
1093 int new_x = getLevelFromScreenX(mx);
1094 int new_y = getLevelFromScreenY(my);
1096 if (new_x != old_x || new_y != old_y)
1101 // finger moved left or right from (horizontal) starting position
1103 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1105 SetPlayerMouseAction(old_mx, old_my, button_nr);
1107 last_button = button_nr;
1109 Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1113 // finger stays at or returned to (horizontal) starting position
1115 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1117 Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1122 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1124 static int old_mx = 0, old_my = 0;
1125 static int last_button = MB_LEFTBUTTON;
1126 static boolean touched = FALSE;
1127 static boolean tapped = FALSE;
1129 // screen tile was tapped (but finger not touching the screen anymore)
1130 // (this point will also be reached without receiving a touch event)
1131 if (tapped && !touched)
1133 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1138 // stop here if this function was not triggered by a touch event
1142 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1144 // finger started touching the screen
1154 ClearPlayerMouseAction();
1156 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1159 else if (button == MB_RELEASED && touched)
1161 // finger stopped touching the screen
1166 SetPlayerMouseAction(old_mx, old_my, last_button);
1168 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1170 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1175 // finger moved while touching the screen
1177 int old_x = getLevelFromScreenX(old_mx);
1178 int old_y = getLevelFromScreenY(old_my);
1179 int new_x = getLevelFromScreenX(mx);
1180 int new_y = getLevelFromScreenY(my);
1182 if (new_x != old_x || new_y != old_y)
1184 // finger moved away from starting position
1186 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1188 // quickly alternate between clicking and releasing for maximum speed
1189 if (FrameCounter % 2 == 0)
1190 button_nr = MB_RELEASED;
1192 SetPlayerMouseAction(old_mx, old_my, button_nr);
1195 last_button = button_nr;
1199 Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1203 // finger stays at or returned to starting position
1205 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1207 Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1212 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1214 static int old_mx = 0, old_my = 0;
1215 static Key motion_key_x = KSYM_UNDEFINED;
1216 static Key motion_key_y = KSYM_UNDEFINED;
1217 static boolean touched = FALSE;
1218 static boolean started_on_player = FALSE;
1219 static boolean player_is_dropping = FALSE;
1220 static int player_drop_count = 0;
1221 static int last_player_x = -1;
1222 static int last_player_y = -1;
1224 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1233 started_on_player = FALSE;
1234 player_is_dropping = FALSE;
1235 player_drop_count = 0;
1239 motion_key_x = KSYM_UNDEFINED;
1240 motion_key_y = KSYM_UNDEFINED;
1242 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1245 else if (button == MB_RELEASED && touched)
1252 if (motion_key_x != KSYM_UNDEFINED)
1253 HandleKey(motion_key_x, KEY_RELEASED);
1254 if (motion_key_y != KSYM_UNDEFINED)
1255 HandleKey(motion_key_y, KEY_RELEASED);
1257 if (started_on_player)
1259 if (player_is_dropping)
1261 Debug("event:finger", "---------- DROP STOPPED ----------");
1263 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1267 Debug("event:finger", "---------- SNAP STOPPED ----------");
1269 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1273 motion_key_x = KSYM_UNDEFINED;
1274 motion_key_y = KSYM_UNDEFINED;
1276 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1281 int src_x = local_player->jx;
1282 int src_y = local_player->jy;
1283 int dst_x = getLevelFromScreenX(old_mx);
1284 int dst_y = getLevelFromScreenY(old_my);
1285 int dx = dst_x - src_x;
1286 int dy = dst_y - src_y;
1287 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1288 dx > 0 ? setup.input[0].key.right :
1290 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1291 dy > 0 ? setup.input[0].key.down :
1294 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1295 (last_player_x != local_player->jx ||
1296 last_player_y != local_player->jy))
1298 // in case of asymmetric diagonal movement, use "preferred" direction
1300 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1302 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1303 game_em.ply[0]->last_move_dir = last_move_dir;
1305 local_player->last_move_dir = last_move_dir;
1307 // (required to prevent accidentally forcing direction for next movement)
1308 last_player_x = local_player->jx;
1309 last_player_y = local_player->jy;
1312 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1314 started_on_player = TRUE;
1315 player_drop_count = getPlayerInventorySize(0);
1316 player_is_dropping = (player_drop_count > 0);
1318 if (player_is_dropping)
1320 Debug("event:finger", "---------- DROP STARTED ----------");
1322 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1326 Debug("event:finger", "---------- SNAP STARTED ----------");
1328 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1331 else if (dx != 0 || dy != 0)
1333 if (player_is_dropping &&
1334 player_drop_count == getPlayerInventorySize(0))
1336 Debug("event:finger", "---------- DROP -> SNAP ----------");
1338 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1339 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1341 player_is_dropping = FALSE;
1345 if (new_motion_key_x != motion_key_x)
1347 Debug("event:finger", "---------- %s %s ----------",
1348 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1349 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1351 if (motion_key_x != KSYM_UNDEFINED)
1352 HandleKey(motion_key_x, KEY_RELEASED);
1353 if (new_motion_key_x != KSYM_UNDEFINED)
1354 HandleKey(new_motion_key_x, KEY_PRESSED);
1357 if (new_motion_key_y != motion_key_y)
1359 Debug("event:finger", "---------- %s %s ----------",
1360 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1361 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1363 if (motion_key_y != KSYM_UNDEFINED)
1364 HandleKey(motion_key_y, KEY_RELEASED);
1365 if (new_motion_key_y != KSYM_UNDEFINED)
1366 HandleKey(new_motion_key_y, KEY_PRESSED);
1369 motion_key_x = new_motion_key_x;
1370 motion_key_y = new_motion_key_y;
1374 static void HandleButtonOrFinger(int mx, int my, int button)
1376 boolean valid_mouse_event = (mx != -1 && my != -1 && button != -1);
1378 if (game_status != GAME_MODE_PLAYING)
1381 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1383 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1384 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1385 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1386 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1387 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1388 SetPlayerMouseAction(mx, my, button); // special case
1392 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1393 HandleButtonOrFinger_FollowFinger(mx, my, button);
1394 else if (game.use_mouse_actions && valid_mouse_event)
1395 SetPlayerMouseAction(mx, my, button);
1399 static boolean checkTextInputKey(Key key)
1401 // when playing, only handle raw key events and ignore text input
1402 if (game_status == GAME_MODE_PLAYING)
1405 // if Shift or right Alt key is pressed, handle key as text input
1406 if ((GetKeyModState() & KMOD_TextInput) != KMOD_None)
1409 // ignore raw keys as text input when not in text input mode
1410 if (KSYM_RAW(key) && !textinput_status)
1413 // else handle all printable keys as text input
1414 return KSYM_PRINTABLE(key);
1417 void HandleTextEvent(TextEvent *event)
1419 char *text = event->text;
1420 Key key = getKeyFromKeyName(text);
1422 #if DEBUG_EVENTS_TEXT
1423 Debug("event:text", "text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1426 text[0], (int)(text[0]),
1428 getKeyNameFromKey(key),
1432 if (checkTextInputKey(key))
1434 // process printable keys (with uppercase etc.) in text input mode
1435 HandleKey(key, KEY_PRESSED);
1436 HandleKey(key, KEY_RELEASED);
1440 void HandlePauseResumeEvent(PauseResumeEvent *event)
1442 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1446 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1452 void HandleKeyEvent(KeyEvent *event)
1454 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1455 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1456 Key key = GetEventKey(event, with_modifiers);
1457 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1459 #if DEBUG_EVENTS_KEY
1460 Debug("event:key", "key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1461 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1462 event->keysym.scancode,
1467 getKeyNameFromKey(key));
1470 #if defined(PLATFORM_ANDROID)
1471 if (key == KSYM_Back)
1473 // always map the "back" button to the "escape" key on Android devices
1476 else if (key == KSYM_Menu)
1478 // the "menu" button can be used to toggle displaying virtual buttons
1479 if (key_status == KEY_PRESSED)
1480 SetOverlayEnabled(!GetOverlayEnabled());
1484 // for any other "real" key event, disable virtual buttons
1485 SetOverlayEnabled(FALSE);
1487 // for any other "real" key event, disable overlay touch buttons
1488 runtime.uses_touch_device = FALSE;
1492 HandleKeyModState(keymod, key_status);
1494 // process all keys if not in text input mode or if non-printable keys
1495 if (!checkTextInputKey(key))
1496 HandleKey(key, key_status);
1499 static int HandleDropFileEvent(char *filename)
1501 Debug("event:dropfile", "filename == '%s'", filename);
1503 // check and extract dropped zip files into correct user data directory
1504 if (!strSuffixLower(filename, ".zip"))
1506 Warn("file '%s' not supported", filename);
1508 return TREE_TYPE_UNDEFINED;
1511 TreeInfo *tree_node = NULL;
1512 int tree_type = GetZipFileTreeType(filename);
1513 char *directory = TREE_USERDIR(tree_type);
1515 if (directory == NULL)
1517 Warn("zip file '%s' has invalid content!", filename);
1519 return TREE_TYPE_UNDEFINED;
1522 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1523 game_status == GAME_MODE_LEVELS &&
1524 leveldir_current->node_parent != NULL)
1526 // extract new level set next to currently selected level set
1527 tree_node = leveldir_current;
1529 // get parent directory of currently selected level set directory
1530 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1532 // use private level directory instead of top-level package level directory
1533 if (strPrefix(directory, options.level_directory) &&
1534 strEqual(leveldir_current->node_parent->fullpath, "."))
1535 directory = getUserLevelDir(NULL);
1538 // extract level or artwork set from zip file to target directory
1539 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1541 if (top_dir == NULL)
1543 // error message already issued by "ExtractZipFileIntoDirectory()"
1545 return TREE_TYPE_UNDEFINED;
1548 // add extracted level or artwork set to tree info structure
1549 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1551 // update menu screen (and possibly change current level set)
1552 DrawScreenAfterAddingSet(top_dir, tree_type);
1557 static void HandleDropTextEvent(char *text)
1559 Debug("event:droptext", "text == '%s'", text);
1562 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1563 int num_artwork_sets_succeeded,
1564 int num_files_failed)
1566 // only show request dialog if no other request dialog already active
1567 if (game.request_active)
1570 // this case can happen with drag-and-drop with older SDL versions
1571 if (num_level_sets_succeeded == 0 &&
1572 num_artwork_sets_succeeded == 0 &&
1573 num_files_failed == 0)
1578 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1580 char message_part1[50];
1582 sprintf(message_part1, "New %s set%s added",
1583 (num_artwork_sets_succeeded == 0 ? "level" :
1584 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1585 (num_level_sets_succeeded +
1586 num_artwork_sets_succeeded > 1 ? "s" : ""));
1588 if (num_files_failed > 0)
1589 sprintf(message, "%s, but %d dropped file%s failed!",
1590 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1592 sprintf(message, "%s!", message_part1);
1594 else if (num_files_failed > 0)
1596 sprintf(message, "Failed to process dropped file%s!",
1597 num_files_failed > 1 ? "s" : "");
1600 Request(message, REQ_CONFIRM);
1603 void HandleDropEvent(Event *event)
1605 static boolean confirm_on_drop_complete = FALSE;
1606 static int num_level_sets_succeeded = 0;
1607 static int num_artwork_sets_succeeded = 0;
1608 static int num_files_failed = 0;
1610 switch (event->type)
1614 confirm_on_drop_complete = TRUE;
1615 num_level_sets_succeeded = 0;
1616 num_artwork_sets_succeeded = 0;
1617 num_files_failed = 0;
1624 int tree_type = HandleDropFileEvent(event->drop.file);
1626 if (tree_type == TREE_TYPE_LEVEL_DIR)
1627 num_level_sets_succeeded++;
1628 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1629 tree_type == TREE_TYPE_SOUNDS_DIR ||
1630 tree_type == TREE_TYPE_MUSIC_DIR)
1631 num_artwork_sets_succeeded++;
1635 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1636 if (!confirm_on_drop_complete)
1638 // process all remaining events, including further SDL_DROPFILE events
1641 HandleDropCompleteEvent(num_level_sets_succeeded,
1642 num_artwork_sets_succeeded,
1645 num_level_sets_succeeded = 0;
1646 num_artwork_sets_succeeded = 0;
1647 num_files_failed = 0;
1655 HandleDropTextEvent(event->drop.file);
1660 case SDL_DROPCOMPLETE:
1662 HandleDropCompleteEvent(num_level_sets_succeeded,
1663 num_artwork_sets_succeeded,
1670 if (event->drop.file != NULL)
1671 SDL_free(event->drop.file);
1674 void HandleUserEvent(UserEvent *event)
1676 switch (event->code)
1678 case USEREVENT_ANIM_DELAY_ACTION:
1679 case USEREVENT_ANIM_EVENT_ACTION:
1680 // execute action functions until matching action was found
1681 if (DoKeysymAction(event->value1) ||
1682 DoGadgetAction(event->value1) ||
1683 DoScreenAction(event->value1))
1692 void HandleButton(int mx, int my, int button, int button_nr)
1694 static int old_mx = 0, old_my = 0;
1695 boolean button_hold = FALSE;
1696 boolean handle_gadgets = TRUE;
1702 button_nr = -button_nr;
1711 #if defined(PLATFORM_ANDROID)
1712 // when playing, only handle gadgets when using "follow finger" controls
1713 // or when using touch controls in combination with the MM game engine
1714 // or when using gadgets that do not overlap with virtual buttons
1716 (game_status != GAME_MODE_PLAYING ||
1717 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1718 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1719 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1720 !CheckVirtualButtonPressed(mx, my, button)));
1722 // always recognize potentially releasing already pressed gadgets
1723 if (button == MB_RELEASED)
1724 handle_gadgets = TRUE;
1726 // always recognize pressing or releasing overlay touch buttons
1727 if (CheckPosition_OverlayTouchButtons(mx, my, button) && !motion_status)
1728 handle_gadgets = TRUE;
1731 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1733 // do not handle this button event anymore
1734 return; // force mouse event not to be handled at all
1737 if (handle_gadgets && HandleGadgets(mx, my, button))
1739 // do not handle this button event anymore
1740 mx = my = -32; // force mouse event to be outside screen tiles
1743 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1746 // do not use scroll wheel button events for anything other than gadgets
1747 if (IS_WHEEL_BUTTON(button_nr))
1750 switch (game_status)
1752 case GAME_MODE_TITLE:
1753 HandleTitleScreen(mx, my, 0, 0, button);
1756 case GAME_MODE_MAIN:
1757 HandleMainMenu(mx, my, 0, 0, button);
1760 case GAME_MODE_PSEUDO_TYPENAME:
1761 HandleTypeName(0, KSYM_Return);
1764 case GAME_MODE_LEVELS:
1765 HandleChooseLevelSet(mx, my, 0, 0, button);
1768 case GAME_MODE_LEVELNR:
1769 HandleChooseLevelNr(mx, my, 0, 0, button);
1772 case GAME_MODE_SCORES:
1773 HandleHallOfFame(0, 0, 0, 0, button);
1776 case GAME_MODE_EDITOR:
1777 HandleLevelEditorIdle();
1780 case GAME_MODE_INFO:
1781 HandleInfoScreen(mx, my, 0, 0, button);
1784 case GAME_MODE_SETUP:
1785 HandleSetupScreen(mx, my, 0, 0, button);
1788 case GAME_MODE_PLAYING:
1789 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1790 HandleButtonOrFinger(mx, my, button);
1792 SetPlayerMouseAction(mx, my, button);
1795 if (button == MB_PRESSED && !motion_status && !button_hold &&
1796 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1797 DumpTileFromScreen(mx, my);
1807 #define MAX_CHEAT_INPUT_LEN 32
1809 static void HandleKeysSpecial(Key key)
1811 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1812 char letter = getCharFromKey(key);
1813 int cheat_input_len = strlen(cheat_input);
1819 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1821 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1822 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1824 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1827 cheat_input[cheat_input_len++] = letter;
1828 cheat_input[cheat_input_len] = '\0';
1830 #if DEBUG_EVENTS_KEY
1831 Debug("event:key:special", "'%s' [%d]", cheat_input, cheat_input_len);
1834 if (game_status == GAME_MODE_MAIN)
1836 if (strSuffix(cheat_input, ":insert-solution-tape") ||
1837 strSuffix(cheat_input, ":ist"))
1839 InsertSolutionTape();
1841 else if (strSuffix(cheat_input, ":play-solution-tape") ||
1842 strSuffix(cheat_input, ":pst"))
1846 else if (strSuffix(cheat_input, ":reload-graphics") ||
1847 strSuffix(cheat_input, ":rg"))
1849 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1852 else if (strSuffix(cheat_input, ":reload-sounds") ||
1853 strSuffix(cheat_input, ":rs"))
1855 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1858 else if (strSuffix(cheat_input, ":reload-music") ||
1859 strSuffix(cheat_input, ":rm"))
1861 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1864 else if (strSuffix(cheat_input, ":reload-artwork") ||
1865 strSuffix(cheat_input, ":ra"))
1867 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1868 1 << ARTWORK_TYPE_SOUNDS |
1869 1 << ARTWORK_TYPE_MUSIC);
1872 else if (strSuffix(cheat_input, ":dump-level") ||
1873 strSuffix(cheat_input, ":dl"))
1877 else if (strSuffix(cheat_input, ":dump-tape") ||
1878 strSuffix(cheat_input, ":dt"))
1882 else if (strSuffix(cheat_input, ":undo-tape") ||
1883 strSuffix(cheat_input, ":ut"))
1887 else if (strSuffix(cheat_input, ":fix-tape") ||
1888 strSuffix(cheat_input, ":ft"))
1890 FixTape_ForceSinglePlayer();
1892 else if (strSuffix(cheat_input, ":save-native-level") ||
1893 strSuffix(cheat_input, ":snl"))
1895 SaveNativeLevel(&level);
1897 else if (strSuffix(cheat_input, ":frames-per-second") ||
1898 strSuffix(cheat_input, ":fps"))
1900 global.show_frames_per_second = !global.show_frames_per_second;
1903 else if (game_status == GAME_MODE_PLAYING)
1906 if (strSuffix(cheat_input, ".q"))
1907 DEBUG_SetMaximumDynamite();
1910 else if (game_status == GAME_MODE_EDITOR)
1912 if (strSuffix(cheat_input, ":dump-brush") ||
1913 strSuffix(cheat_input, ":DB"))
1917 else if (strSuffix(cheat_input, ":DDB"))
1922 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1924 if (letter == 'x') // copy brush to clipboard (small size)
1926 CopyBrushToClipboard_Small();
1928 else if (letter == 'c') // copy brush to clipboard (normal size)
1930 CopyBrushToClipboard();
1932 else if (letter == 'v') // paste brush from Clipboard
1934 CopyClipboardToBrush();
1936 else if (letter == 'z') // undo or redo last operation
1938 if (GetKeyModState() & KMOD_Shift)
1939 RedoLevelEditorOperation();
1941 UndoLevelEditorOperation();
1946 // special key shortcuts for all game modes
1947 if (strSuffix(cheat_input, ":dump-event-actions") ||
1948 strSuffix(cheat_input, ":dea") ||
1949 strSuffix(cheat_input, ":DEA"))
1951 DumpGadgetIdentifiers();
1952 DumpScreenIdentifiers();
1956 boolean HandleKeysDebug(Key key, int key_status)
1961 if (key_status != KEY_PRESSED)
1964 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1966 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1968 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1970 if (key == setup.debug.frame_delay_key[i] &&
1971 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1973 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1974 setup.debug.frame_delay[i] : setup.game_frame_delay);
1976 if (!setup.debug.frame_delay_game_only)
1977 MenuFrameDelay = GameFrameDelay;
1979 SetVideoFrameDelay(GameFrameDelay);
1981 if (GameFrameDelay > ONE_SECOND_DELAY)
1982 Debug("event:key:debug", "frame delay == %d ms", GameFrameDelay);
1983 else if (GameFrameDelay != 0)
1984 Debug("event:key:debug", "frame delay == %d ms (max. %d fps / %d %%)",
1985 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1986 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1988 Debug("event:key:debug", "frame delay == 0 ms (maximum speed)");
1995 if (game_status == GAME_MODE_PLAYING)
1999 options.debug = !options.debug;
2001 Debug("event:key:debug", "debug mode %s",
2002 (options.debug ? "enabled" : "disabled"));
2006 else if (key == KSYM_v)
2008 Debug("event:key:debug", "currently using game engine version %d",
2009 game.engine_version);
2019 void HandleKey(Key key, int key_status)
2021 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2022 static boolean ignore_repeated_key = FALSE;
2023 static struct SetupKeyboardInfo ski;
2024 static struct SetupShortcutInfo ssi;
2033 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
2034 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
2035 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
2036 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
2037 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2038 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2043 if (HandleKeysDebug(key, key_status))
2044 return; // do not handle already processed keys again
2046 // map special keys (media keys / remote control buttons) to default keys
2047 if (key == KSYM_PlayPause)
2049 else if (key == KSYM_Select)
2052 HandleSpecialGameControllerKeys(key, key_status);
2054 if (game_status == GAME_MODE_PLAYING)
2056 // only needed for single-step tape recording mode
2057 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2060 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2062 byte key_action = 0;
2063 byte key_snap_action = 0;
2065 if (setup.input[pnr].use_joystick)
2068 ski = setup.input[pnr].key;
2070 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2071 if (key == *key_info[i].key_custom)
2072 key_action |= key_info[i].action;
2074 // use combined snap+direction keys for the first player only
2077 ssi = setup.shortcut;
2079 // also remember normal snap key when handling snap+direction keys
2080 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2082 for (i = 0; i < NUM_DIRECTIONS; i++)
2084 if (key == *key_info[i].key_snap)
2086 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2087 key_snap_action |= key_info[i].action;
2092 if (key_status == KEY_PRESSED)
2094 stored_player[pnr].action |= key_action;
2095 stored_player[pnr].snap_action |= key_snap_action;
2099 stored_player[pnr].action &= ~key_action;
2100 stored_player[pnr].snap_action &= ~key_snap_action;
2103 // restore snap action if one of several pressed snap keys was released
2104 if (stored_player[pnr].snap_action)
2105 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2107 if (tape.recording && tape.pausing && tape.use_key_actions)
2109 if (tape.single_step)
2111 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2113 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2115 // if snap key already pressed, keep pause mode when releasing
2116 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2117 has_snapped[pnr] = TRUE;
2119 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2121 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2123 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2124 getRedDiskReleaseFlag_SP() == 0)
2126 // add a single inactive frame before dropping starts
2127 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2128 stored_player[pnr].force_dropping = TRUE;
2131 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2133 // if snap key was pressed without direction, leave pause mode
2134 if (!has_snapped[pnr])
2135 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2137 has_snapped[pnr] = FALSE;
2142 // prevent key release events from un-pausing a paused game
2143 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2144 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2148 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2149 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2155 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2156 if (key == key_info[i].key_default)
2157 joy |= key_info[i].action;
2162 if (key_status == KEY_PRESSED)
2163 key_joystick_mapping |= joy;
2165 key_joystick_mapping &= ~joy;
2170 if (game_status != GAME_MODE_PLAYING)
2171 key_joystick_mapping = 0;
2173 if (key_status == KEY_RELEASED)
2175 // reset flag to ignore repeated "key pressed" events after key release
2176 ignore_repeated_key = FALSE;
2181 if ((key == KSYM_F11 ||
2182 ((key == KSYM_Return ||
2183 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2184 video.fullscreen_available &&
2185 !ignore_repeated_key)
2187 setup.fullscreen = !setup.fullscreen;
2189 ToggleFullscreenIfNeeded();
2191 if (game_status == GAME_MODE_SETUP)
2192 RedrawSetupScreenAfterFullscreenToggle();
2194 UpdateMousePosition();
2196 // set flag to ignore repeated "key pressed" events
2197 ignore_repeated_key = TRUE;
2202 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2203 key == KSYM_minus || key == KSYM_KP_Subtract ||
2204 key == KSYM_plus || key == KSYM_KP_Add ||
2205 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2206 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2207 video.window_scaling_available &&
2208 !video.fullscreen_enabled)
2210 if (key == KSYM_0 || key == KSYM_KP_0)
2211 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2212 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2213 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2215 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2217 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2218 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2219 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2220 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2222 ChangeWindowScalingIfNeeded();
2224 if (game_status == GAME_MODE_SETUP)
2225 RedrawSetupScreenAfterFullscreenToggle();
2227 UpdateMousePosition();
2232 // some key events are handled like clicks for global animations
2233 boolean click = (key == KSYM_space ||
2234 key == KSYM_Return ||
2235 key == KSYM_Escape);
2237 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2239 // do not handle this key event anymore
2240 if (key != KSYM_Escape) // always allow ESC key to be handled
2244 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2245 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2252 if (game_status == GAME_MODE_MAIN &&
2253 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2255 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2260 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2262 if (key == setup.shortcut.save_game)
2264 else if (key == setup.shortcut.load_game)
2266 else if (key == setup.shortcut.toggle_pause)
2267 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2269 HandleTapeButtonKeys(key);
2270 HandleSoundButtonKeys(key);
2273 if (game_status == GAME_MODE_PLAYING && !network_playing)
2275 int centered_player_nr_next = -999;
2277 if (key == setup.shortcut.focus_player_all)
2278 centered_player_nr_next = -1;
2280 for (i = 0; i < MAX_PLAYERS; i++)
2281 if (key == setup.shortcut.focus_player[i])
2282 centered_player_nr_next = i;
2284 if (centered_player_nr_next != -999)
2286 game.centered_player_nr_next = centered_player_nr_next;
2287 game.set_centered_player = TRUE;
2291 tape.centered_player_nr_next = game.centered_player_nr_next;
2292 tape.set_centered_player = TRUE;
2297 HandleKeysSpecial(key);
2299 if (HandleGadgetsKeyInput(key))
2300 return; // do not handle already processed keys again
2302 switch (game_status)
2304 case GAME_MODE_PSEUDO_TYPENAME:
2305 HandleTypeName(0, key);
2308 case GAME_MODE_TITLE:
2309 case GAME_MODE_MAIN:
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_LEVELS)
2328 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2329 else if (game_status == GAME_MODE_LEVELNR)
2330 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2331 else if (game_status == GAME_MODE_SETUP)
2332 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2333 else if (game_status == GAME_MODE_INFO)
2334 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2335 else if (game_status == GAME_MODE_SCORES)
2336 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2340 if (game_status != GAME_MODE_MAIN)
2341 FadeSkipNextFadeIn();
2343 if (game_status == GAME_MODE_TITLE)
2344 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2345 else if (game_status == GAME_MODE_LEVELS)
2346 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2347 else if (game_status == GAME_MODE_LEVELNR)
2348 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2349 else if (game_status == GAME_MODE_SETUP)
2350 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2351 else if (game_status == GAME_MODE_INFO)
2352 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2353 else if (game_status == GAME_MODE_SCORES)
2354 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2358 if (game_status == GAME_MODE_LEVELS)
2359 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2360 else if (game_status == GAME_MODE_LEVELNR)
2361 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2362 else if (game_status == GAME_MODE_SETUP)
2363 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2364 else if (game_status == GAME_MODE_INFO)
2365 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2366 else if (game_status == GAME_MODE_SCORES)
2367 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2370 case KSYM_Page_Down:
2371 if (game_status == GAME_MODE_LEVELS)
2372 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2373 else if (game_status == GAME_MODE_LEVELNR)
2374 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2375 else if (game_status == GAME_MODE_SETUP)
2376 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2377 else if (game_status == GAME_MODE_INFO)
2378 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2379 else if (game_status == GAME_MODE_SCORES)
2380 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2388 case GAME_MODE_EDITOR:
2389 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2390 HandleLevelEditorKeyInput(key);
2393 case GAME_MODE_PLAYING:
2398 RequestQuitGame(setup.ask_on_escape);
2408 if (key == KSYM_Escape)
2410 SetGameStatus(GAME_MODE_MAIN);
2419 void HandleNoEvent(void)
2421 HandleMouseCursor();
2423 switch (game_status)
2425 case GAME_MODE_PLAYING:
2426 HandleButtonOrFinger(-1, -1, -1);
2431 void HandleEventActions(void)
2433 // if (button_status && game_status != GAME_MODE_PLAYING)
2434 if (button_status && (game_status != GAME_MODE_PLAYING ||
2436 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2438 HandleButton(0, 0, button_status, -button_status);
2445 if (network.enabled)
2448 switch (game_status)
2450 case GAME_MODE_MAIN:
2451 DrawPreviewLevelAnimation();
2454 case GAME_MODE_EDITOR:
2455 HandleLevelEditorIdle();
2463 static void HandleTileCursor(int dx, int dy, int button)
2466 ClearPlayerMouseAction();
2473 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2474 (dx < 0 ? MB_LEFTBUTTON :
2475 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2477 else if (!tile_cursor.moving)
2479 int old_xpos = tile_cursor.xpos;
2480 int old_ypos = tile_cursor.ypos;
2481 int new_xpos = old_xpos;
2482 int new_ypos = old_ypos;
2484 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2485 new_xpos = old_xpos + dx;
2487 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2488 new_ypos = old_ypos + dy;
2490 SetTileCursorTargetXY(new_xpos, new_ypos);
2494 static int HandleJoystickForAllPlayers(void)
2498 boolean no_joysticks_configured = TRUE;
2499 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2500 static byte joy_action_last[MAX_PLAYERS];
2502 for (i = 0; i < MAX_PLAYERS; i++)
2503 if (setup.input[i].use_joystick)
2504 no_joysticks_configured = FALSE;
2506 // if no joysticks configured, map connected joysticks to players
2507 if (no_joysticks_configured)
2508 use_as_joystick_nr = TRUE;
2510 for (i = 0; i < MAX_PLAYERS; i++)
2512 byte joy_action = 0;
2514 joy_action = JoystickExt(i, use_as_joystick_nr);
2515 result |= joy_action;
2517 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2518 joy_action != joy_action_last[i])
2519 stored_player[i].action = joy_action;
2521 joy_action_last[i] = joy_action;
2527 void HandleJoystick(void)
2529 static unsigned int joytest_delay = 0;
2530 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2531 static int joytest_last = 0;
2532 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2533 int delay_value = GADGET_FRAME_DELAY;
2534 int joystick = HandleJoystickForAllPlayers();
2535 int keyboard = key_joystick_mapping;
2536 int joy = (joystick | keyboard);
2537 int joytest = joystick;
2538 int left = joy & JOY_LEFT;
2539 int right = joy & JOY_RIGHT;
2540 int up = joy & JOY_UP;
2541 int down = joy & JOY_DOWN;
2542 int button = joy & JOY_BUTTON;
2543 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2544 int dx = (left ? -1 : right ? 1 : 0);
2545 int dy = (up ? -1 : down ? 1 : 0);
2546 boolean use_delay_value_first = (joytest != joytest_last);
2548 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2550 // do not handle this button event anymore
2554 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2555 anyTextGadgetActive()))
2557 // leave name input in main menu or text input gadget
2558 HandleKey(KSYM_Escape, KEY_PRESSED);
2559 HandleKey(KSYM_Escape, KEY_RELEASED);
2564 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2566 if (game_status == GAME_MODE_PLAYING)
2568 // when playing MM style levels, also use delay for keyboard events
2569 joytest |= keyboard;
2571 // only use first delay value for new events, but not for changed events
2572 use_delay_value_first = (!joytest != !joytest_last);
2574 // only use delay after the initial keyboard event
2578 // for any joystick or keyboard event, enable playfield tile cursor
2579 if (dx || dy || button)
2580 SetTileCursorEnabled(TRUE);
2583 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2585 // delay joystick/keyboard actions if axes/keys continually pressed
2586 newbutton = dx = dy = 0;
2590 // first start with longer delay, then continue with shorter delay
2591 joytest_delay_value =
2592 (use_delay_value_first ? delay_value_first : delay_value);
2595 joytest_last = joytest;
2597 switch (game_status)
2599 case GAME_MODE_TITLE:
2600 case GAME_MODE_MAIN:
2601 case GAME_MODE_LEVELS:
2602 case GAME_MODE_LEVELNR:
2603 case GAME_MODE_SETUP:
2604 case GAME_MODE_INFO:
2605 case GAME_MODE_SCORES:
2607 if (anyTextGadgetActive())
2610 if (game_status == GAME_MODE_TITLE)
2611 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2612 else if (game_status == GAME_MODE_MAIN)
2613 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2614 else if (game_status == GAME_MODE_LEVELS)
2615 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2616 else if (game_status == GAME_MODE_LEVELNR)
2617 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2618 else if (game_status == GAME_MODE_SETUP)
2619 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2620 else if (game_status == GAME_MODE_INFO)
2621 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2622 else if (game_status == GAME_MODE_SCORES)
2623 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2628 case GAME_MODE_PLAYING:
2630 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2631 if (tape.playing || keyboard)
2632 newbutton = ((joy & JOY_BUTTON) != 0);
2635 if (newbutton && game.all_players_gone)
2642 if (tape.recording && tape.pausing && tape.use_key_actions)
2644 if (tape.single_step)
2646 if (joystick & JOY_ACTION)
2647 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2651 if (joystick & JOY_ACTION)
2652 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2656 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2657 HandleTileCursor(dx, dy, button);
2666 void HandleSpecialGameControllerButtons(Event *event)
2671 switch (event->type)
2673 case SDL_CONTROLLERBUTTONDOWN:
2674 key_status = KEY_PRESSED;
2677 case SDL_CONTROLLERBUTTONUP:
2678 key_status = KEY_RELEASED;
2685 switch (event->cbutton.button)
2687 case SDL_CONTROLLER_BUTTON_START:
2691 case SDL_CONTROLLER_BUTTON_BACK:
2699 HandleKey(key, key_status);
2702 void HandleSpecialGameControllerKeys(Key key, int key_status)
2704 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2705 int button = SDL_CONTROLLER_BUTTON_INVALID;
2707 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2708 if (key == KSYM_Rewind)
2709 button = SDL_CONTROLLER_BUTTON_A;
2710 else if (key == KSYM_FastForward || key == KSYM_Menu)
2711 button = SDL_CONTROLLER_BUTTON_B;
2713 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2717 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2718 SDL_CONTROLLERBUTTONUP);
2720 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2721 event.cbutton.button = button;
2722 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2725 HandleJoystickEvent(&event);
2730 boolean DoKeysymAction(int keysym)
2734 Key key = (Key)(-keysym);
2736 HandleKey(key, KEY_PRESSED);
2737 HandleKey(key, KEY_RELEASED);