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_KEYRELEASE:
415 case SDL_CONTROLLERBUTTONUP:
416 HandleJoystickEvent(&event);
421 HandleOtherEvents(&event);
427 static void ClearPlayerMouseAction(void)
429 local_player->mouse_action.lx = 0;
430 local_player->mouse_action.ly = 0;
431 local_player->mouse_action.button = 0;
434 void ClearPlayerAction(void)
438 // simulate key release events for still pressed keys
439 key_joystick_mapping = 0;
440 for (i = 0; i < MAX_PLAYERS; i++)
442 stored_player[i].action = 0;
443 stored_player[i].snap_action = 0;
446 // simulate finger release events for still pressed virtual buttons
447 overlay.grid_button_action = JOY_NO_ACTION;
450 ClearJoystickState();
451 ClearPlayerMouseAction();
454 static void SetPlayerMouseAction(int mx, int my, int button)
456 int lx = getLevelFromScreenX(mx);
457 int ly = getLevelFromScreenY(my);
458 int new_button = (!local_player->mouse_action.button && button);
460 if (local_player->mouse_action.button_hint)
461 button = local_player->mouse_action.button_hint;
463 ClearPlayerMouseAction();
465 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
468 local_player->mouse_action.lx = lx;
469 local_player->mouse_action.ly = ly;
470 local_player->mouse_action.button = button;
472 if (tape.recording && tape.pausing && tape.use_mouse_actions)
474 // un-pause a paused game only if mouse button was newly pressed down
476 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
479 SetTileCursorXY(lx, ly);
482 static Key GetKeyFromGridButton(int grid_button)
484 return (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
485 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
486 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
487 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
488 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
489 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
493 #if defined(PLATFORM_ANDROID)
494 static boolean CheckVirtualButtonPressed(int mx, int my, int button)
496 float touch_x = (float)(mx + video.screen_xoffset) / video.screen_width;
497 float touch_y = (float)(my + video.screen_yoffset) / video.screen_height;
498 int x = touch_x * overlay.grid_xsize;
499 int y = touch_y * overlay.grid_ysize;
500 int grid_button = overlay.grid_button[x][y];
501 Key key = GetKeyFromGridButton(grid_button);
502 int key_status = (button == MB_RELEASED ? KEY_RELEASED : KEY_PRESSED);
504 return (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
508 void HandleButtonEvent(ButtonEvent *event)
510 #if DEBUG_EVENTS_BUTTON
511 Debug("event:button", "button %d %s, x/y %d/%d\n",
513 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
517 // for any mouse button event, disable playfield tile cursor
518 SetTileCursorEnabled(FALSE);
520 #if defined(HAS_SCREEN_KEYBOARD)
521 if (video.shifted_up)
522 event->y += video.shifted_up_pos;
525 motion_status = FALSE;
527 if (event->type == EVENT_BUTTONPRESS)
528 button_status = event->button;
530 button_status = MB_RELEASED;
532 HandleButton(event->x, event->y, button_status, event->button);
535 void HandleMotionEvent(MotionEvent *event)
537 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
540 motion_status = TRUE;
542 #if DEBUG_EVENTS_MOTION
543 Debug("event:motion", "button %d moved, x/y %d/%d\n",
544 button_status, event->x, event->y);
547 HandleButton(event->x, event->y, button_status, button_status);
550 void HandleWheelEvent(WheelEvent *event)
554 #if DEBUG_EVENTS_WHEEL
556 Debug("event:wheel", "mouse == %d, x/y == %d/%d\n",
557 event->which, event->x, event->y);
559 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
560 Debug("event:wheel", "mouse == %d, x/y == %d/%d, direction == %s\n",
561 event->which, event->x, event->y,
562 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
563 "SDL_MOUSEWHEEL_FLIPPED"));
567 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
568 event->x > 0 ? MB_WHEEL_RIGHT :
569 event->y < 0 ? MB_WHEEL_DOWN :
570 event->y > 0 ? MB_WHEEL_UP : 0);
572 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
573 // accelerated mouse wheel available on Mac and Windows
574 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
576 // no accelerated mouse wheel available on Unix/Linux
577 wheel_steps = DEFAULT_WHEEL_STEPS;
580 motion_status = FALSE;
582 button_status = button_nr;
583 HandleButton(0, 0, button_status, -button_nr);
585 button_status = MB_RELEASED;
586 HandleButton(0, 0, button_status, -button_nr);
589 void HandleWindowEvent(WindowEvent *event)
591 #if DEBUG_EVENTS_WINDOW
592 int subtype = event->event;
595 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
596 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
597 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
598 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
599 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
600 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
601 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
602 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
603 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
604 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
605 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
606 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
607 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
608 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
611 Debug("event:window", "name: '%s', data1: %ld, data2: %ld",
612 event_name, event->data1, event->data2);
616 // (not needed, as the screen gets redrawn every 20 ms anyway)
617 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
618 event->event == SDL_WINDOWEVENT_RESIZED ||
619 event->event == SDL_WINDOWEVENT_EXPOSED)
623 if (event->event == SDL_WINDOWEVENT_RESIZED)
625 if (!video.fullscreen_enabled)
627 int new_window_width = event->data1;
628 int new_window_height = event->data2;
630 // if window size has changed after resizing, calculate new scaling factor
631 if (new_window_width != video.window_width ||
632 new_window_height != video.window_height)
634 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
635 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
637 // (extreme window scaling allowed, but cannot be saved permanently)
638 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
639 setup.window_scaling_percent =
640 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
641 MAX_WINDOW_SCALING_PERCENT);
643 video.window_width = new_window_width;
644 video.window_height = new_window_height;
646 if (game_status == GAME_MODE_SETUP)
647 RedrawSetupScreenAfterFullscreenToggle();
649 UpdateMousePosition();
654 #if defined(PLATFORM_ANDROID)
657 int new_display_width = event->data1;
658 int new_display_height = event->data2;
660 // if fullscreen display size has changed, device has been rotated
661 if (new_display_width != video.display_width ||
662 new_display_height != video.display_height)
664 int nr = GRID_ACTIVE_NR(); // previous screen orientation
666 video.display_width = new_display_width;
667 video.display_height = new_display_height;
669 SDLSetScreenProperties();
670 SetGadgetsPosition_OverlayTouchButtons();
672 // check if screen orientation has changed (should always be true here)
673 if (nr != GRID_ACTIVE_NR())
677 if (game_status == GAME_MODE_SETUP)
678 RedrawSetupScreenAfterScreenRotation(nr);
680 nr = GRID_ACTIVE_NR();
682 overlay.grid_xsize = setup.touch.grid_xsize[nr];
683 overlay.grid_ysize = setup.touch.grid_ysize[nr];
685 for (x = 0; x < MAX_GRID_XSIZE; x++)
686 for (y = 0; y < MAX_GRID_YSIZE; y++)
687 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
695 #define NUM_TOUCH_FINGERS 3
700 SDL_FingerID finger_id;
704 } touch_info[NUM_TOUCH_FINGERS];
706 static void SetTouchInfo(int pos, SDL_FingerID finger_id, int counter,
707 Key key, byte action)
709 touch_info[pos].touched = (action != JOY_NO_ACTION);
710 touch_info[pos].finger_id = finger_id;
711 touch_info[pos].counter = counter;
712 touch_info[pos].key = key;
713 touch_info[pos].action = action;
716 static void ClearTouchInfo(void)
720 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
721 SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
724 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
726 int x = event->x * overlay.grid_xsize;
727 int y = event->y * overlay.grid_ysize;
728 int grid_button = overlay.grid_button[x][y];
729 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
730 Key key = GetKeyFromGridButton(grid_button);
731 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
733 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
737 // for any touch input event, enable overlay buttons (if activated)
738 SetOverlayEnabled(TRUE);
740 Debug("event:finger", "key '%s' was '%s' [fingerId: %lld]",
741 getKeyNameFromKey(key), key_status_name, event->fingerId);
743 if (key_status == KEY_PRESSED)
744 overlay.grid_button_action |= grid_button_action;
746 overlay.grid_button_action &= ~grid_button_action;
748 // check if we already know this touch event's finger id
749 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
751 if (touch_info[i].touched &&
752 touch_info[i].finger_id == event->fingerId)
754 // Debug("event:finger", "MARK 1: %d", i);
760 if (i >= NUM_TOUCH_FINGERS)
762 if (key_status == KEY_PRESSED)
764 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
766 // unknown finger id -- get new, empty slot, if available
767 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
769 if (touch_info[i].counter < oldest_counter)
772 oldest_counter = touch_info[i].counter;
774 // Debug("event:finger", "MARK 2: %d", i);
777 if (!touch_info[i].touched)
779 // Debug("event:finger", "MARK 3: %d", i);
785 if (i >= NUM_TOUCH_FINGERS)
787 // all slots allocated -- use oldest slot
790 // Debug("event:finger", "MARK 4: %d", i);
795 // release of previously unknown key (should not happen)
797 if (key != KSYM_UNDEFINED)
799 HandleKey(key, KEY_RELEASED);
801 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [1]",
802 getKeyNameFromKey(key), "KEY_RELEASED", i);
807 if (i < NUM_TOUCH_FINGERS)
809 if (key_status == KEY_PRESSED)
811 if (touch_info[i].key != key)
813 if (touch_info[i].key != KSYM_UNDEFINED)
815 HandleKey(touch_info[i].key, KEY_RELEASED);
817 // undraw previous grid button when moving finger away
818 overlay.grid_button_action &= ~touch_info[i].action;
820 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [2]",
821 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
824 if (key != KSYM_UNDEFINED)
826 HandleKey(key, KEY_PRESSED);
828 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [3]",
829 getKeyNameFromKey(key), "KEY_PRESSED", i);
833 SetTouchInfo(i, event->fingerId, Counter(), key, grid_button_action);
837 if (touch_info[i].key != KSYM_UNDEFINED)
839 HandleKey(touch_info[i].key, KEY_RELEASED);
841 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [4]",
842 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
845 SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
850 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
852 static Key motion_key_x = KSYM_UNDEFINED;
853 static Key motion_key_y = KSYM_UNDEFINED;
854 static Key button_key = KSYM_UNDEFINED;
855 static float motion_x1, motion_y1;
856 static float button_x1, button_y1;
857 static SDL_FingerID motion_id = -1;
858 static SDL_FingerID button_id = -1;
859 int move_trigger_distance_percent = setup.touch.move_distance;
860 int drop_trigger_distance_percent = setup.touch.drop_distance;
861 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
862 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
863 float event_x = event->x;
864 float event_y = event->y;
866 if (event->type == EVENT_FINGERPRESS)
868 if (event_x > 1.0 / 3.0)
872 motion_id = event->fingerId;
877 motion_key_x = KSYM_UNDEFINED;
878 motion_key_y = KSYM_UNDEFINED;
880 Debug("event:finger", "---------- MOVE STARTED (WAIT) ----------");
886 button_id = event->fingerId;
891 button_key = setup.input[0].key.snap;
893 HandleKey(button_key, KEY_PRESSED);
895 Debug("event:finger", "---------- SNAP STARTED ----------");
898 else if (event->type == EVENT_FINGERRELEASE)
900 if (event->fingerId == motion_id)
904 if (motion_key_x != KSYM_UNDEFINED)
905 HandleKey(motion_key_x, KEY_RELEASED);
906 if (motion_key_y != KSYM_UNDEFINED)
907 HandleKey(motion_key_y, KEY_RELEASED);
909 motion_key_x = KSYM_UNDEFINED;
910 motion_key_y = KSYM_UNDEFINED;
912 Debug("event:finger", "---------- MOVE STOPPED ----------");
914 else if (event->fingerId == button_id)
918 if (button_key != KSYM_UNDEFINED)
919 HandleKey(button_key, KEY_RELEASED);
921 button_key = KSYM_UNDEFINED;
923 Debug("event:finger", "---------- SNAP STOPPED ----------");
926 else if (event->type == EVENT_FINGERMOTION)
928 if (event->fingerId == motion_id)
930 float distance_x = ABS(event_x - motion_x1);
931 float distance_y = ABS(event_y - motion_y1);
932 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
933 event_x > motion_x1 ? setup.input[0].key.right :
935 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
936 event_y > motion_y1 ? setup.input[0].key.down :
939 if (distance_x < move_trigger_distance / 2 ||
940 distance_x < distance_y)
941 new_motion_key_x = KSYM_UNDEFINED;
943 if (distance_y < move_trigger_distance / 2 ||
944 distance_y < distance_x)
945 new_motion_key_y = KSYM_UNDEFINED;
947 if (distance_x > move_trigger_distance ||
948 distance_y > move_trigger_distance)
950 if (new_motion_key_x != motion_key_x)
952 if (motion_key_x != KSYM_UNDEFINED)
953 HandleKey(motion_key_x, KEY_RELEASED);
954 if (new_motion_key_x != KSYM_UNDEFINED)
955 HandleKey(new_motion_key_x, KEY_PRESSED);
958 if (new_motion_key_y != motion_key_y)
960 if (motion_key_y != KSYM_UNDEFINED)
961 HandleKey(motion_key_y, KEY_RELEASED);
962 if (new_motion_key_y != KSYM_UNDEFINED)
963 HandleKey(new_motion_key_y, KEY_PRESSED);
969 motion_key_x = new_motion_key_x;
970 motion_key_y = new_motion_key_y;
972 Debug("event:finger", "---------- MOVE STARTED (MOVE) ----------");
975 else if (event->fingerId == button_id)
977 float distance_x = ABS(event_x - button_x1);
978 float distance_y = ABS(event_y - button_y1);
980 if (distance_x < drop_trigger_distance / 2 &&
981 distance_y > drop_trigger_distance)
983 if (button_key == setup.input[0].key.snap)
984 HandleKey(button_key, KEY_RELEASED);
989 button_key = setup.input[0].key.drop;
991 HandleKey(button_key, KEY_PRESSED);
993 Debug("event:finger", "---------- DROP STARTED ----------");
999 void HandleFingerEvent(FingerEvent *event)
1001 #if DEBUG_EVENTS_FINGER
1002 Debug("event:finger", "finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
1003 event->type == EVENT_FINGERPRESS ? "pressed" :
1004 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
1008 event->dx, event->dy,
1012 runtime.uses_touch_device = TRUE;
1014 if (game_status != GAME_MODE_PLAYING)
1017 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1019 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1020 local_player->mouse_action.button_hint =
1021 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1022 event->x < 0.5 ? MB_LEFTBUTTON :
1023 event->x > 0.5 ? MB_RIGHTBUTTON :
1029 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1030 HandleFingerEvent_VirtualButtons(event);
1031 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1032 HandleFingerEvent_WipeGestures(event);
1035 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1037 static int old_mx = 0, old_my = 0;
1038 static int last_button = MB_LEFTBUTTON;
1039 static boolean touched = FALSE;
1040 static boolean tapped = FALSE;
1042 // screen tile was tapped (but finger not touching the screen anymore)
1043 // (this point will also be reached without receiving a touch event)
1044 if (tapped && !touched)
1046 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1051 // stop here if this function was not triggered by a touch event
1055 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1057 // finger started touching the screen
1067 ClearPlayerMouseAction();
1069 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1072 else if (button == MB_RELEASED && touched)
1074 // finger stopped touching the screen
1079 SetPlayerMouseAction(old_mx, old_my, last_button);
1081 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1083 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1088 // finger moved while touching the screen
1090 int old_x = getLevelFromScreenX(old_mx);
1091 int old_y = getLevelFromScreenY(old_my);
1092 int new_x = getLevelFromScreenX(mx);
1093 int new_y = getLevelFromScreenY(my);
1095 if (new_x != old_x || new_y != old_y)
1100 // finger moved left or right from (horizontal) starting position
1102 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1104 SetPlayerMouseAction(old_mx, old_my, button_nr);
1106 last_button = button_nr;
1108 Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1112 // finger stays at or returned to (horizontal) starting position
1114 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1116 Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1121 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1123 static int old_mx = 0, old_my = 0;
1124 static int last_button = MB_LEFTBUTTON;
1125 static boolean touched = FALSE;
1126 static boolean tapped = FALSE;
1128 // screen tile was tapped (but finger not touching the screen anymore)
1129 // (this point will also be reached without receiving a touch event)
1130 if (tapped && !touched)
1132 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1137 // stop here if this function was not triggered by a touch event
1141 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1143 // finger started touching the screen
1153 ClearPlayerMouseAction();
1155 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1158 else if (button == MB_RELEASED && touched)
1160 // finger stopped touching the screen
1165 SetPlayerMouseAction(old_mx, old_my, last_button);
1167 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1169 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1174 // finger moved while touching the screen
1176 int old_x = getLevelFromScreenX(old_mx);
1177 int old_y = getLevelFromScreenY(old_my);
1178 int new_x = getLevelFromScreenX(mx);
1179 int new_y = getLevelFromScreenY(my);
1181 if (new_x != old_x || new_y != old_y)
1183 // finger moved away from starting position
1185 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1187 // quickly alternate between clicking and releasing for maximum speed
1188 if (FrameCounter % 2 == 0)
1189 button_nr = MB_RELEASED;
1191 SetPlayerMouseAction(old_mx, old_my, button_nr);
1194 last_button = button_nr;
1198 Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1202 // finger stays at or returned to starting position
1204 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1206 Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1211 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1213 static int old_mx = 0, old_my = 0;
1214 static Key motion_key_x = KSYM_UNDEFINED;
1215 static Key motion_key_y = KSYM_UNDEFINED;
1216 static boolean touched = FALSE;
1217 static boolean started_on_player = FALSE;
1218 static boolean player_is_dropping = FALSE;
1219 static int player_drop_count = 0;
1220 static int last_player_x = -1;
1221 static int last_player_y = -1;
1223 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1232 started_on_player = FALSE;
1233 player_is_dropping = FALSE;
1234 player_drop_count = 0;
1238 motion_key_x = KSYM_UNDEFINED;
1239 motion_key_y = KSYM_UNDEFINED;
1241 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1244 else if (button == MB_RELEASED && touched)
1251 if (motion_key_x != KSYM_UNDEFINED)
1252 HandleKey(motion_key_x, KEY_RELEASED);
1253 if (motion_key_y != KSYM_UNDEFINED)
1254 HandleKey(motion_key_y, KEY_RELEASED);
1256 if (started_on_player)
1258 if (player_is_dropping)
1260 Debug("event:finger", "---------- DROP STOPPED ----------");
1262 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1266 Debug("event:finger", "---------- SNAP STOPPED ----------");
1268 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1272 motion_key_x = KSYM_UNDEFINED;
1273 motion_key_y = KSYM_UNDEFINED;
1275 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1280 int src_x = local_player->jx;
1281 int src_y = local_player->jy;
1282 int dst_x = getLevelFromScreenX(old_mx);
1283 int dst_y = getLevelFromScreenY(old_my);
1284 int dx = dst_x - src_x;
1285 int dy = dst_y - src_y;
1286 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1287 dx > 0 ? setup.input[0].key.right :
1289 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1290 dy > 0 ? setup.input[0].key.down :
1293 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1294 (last_player_x != local_player->jx ||
1295 last_player_y != local_player->jy))
1297 // in case of asymmetric diagonal movement, use "preferred" direction
1299 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1301 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1302 game_em.ply[0]->last_move_dir = last_move_dir;
1304 local_player->last_move_dir = last_move_dir;
1306 // (required to prevent accidentally forcing direction for next movement)
1307 last_player_x = local_player->jx;
1308 last_player_y = local_player->jy;
1311 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1313 started_on_player = TRUE;
1314 player_drop_count = getPlayerInventorySize(0);
1315 player_is_dropping = (player_drop_count > 0);
1317 if (player_is_dropping)
1319 Debug("event:finger", "---------- DROP STARTED ----------");
1321 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1325 Debug("event:finger", "---------- SNAP STARTED ----------");
1327 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1330 else if (dx != 0 || dy != 0)
1332 if (player_is_dropping &&
1333 player_drop_count == getPlayerInventorySize(0))
1335 Debug("event:finger", "---------- DROP -> SNAP ----------");
1337 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1338 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1340 player_is_dropping = FALSE;
1344 if (new_motion_key_x != motion_key_x)
1346 Debug("event:finger", "---------- %s %s ----------",
1347 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1348 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1350 if (motion_key_x != KSYM_UNDEFINED)
1351 HandleKey(motion_key_x, KEY_RELEASED);
1352 if (new_motion_key_x != KSYM_UNDEFINED)
1353 HandleKey(new_motion_key_x, KEY_PRESSED);
1356 if (new_motion_key_y != motion_key_y)
1358 Debug("event:finger", "---------- %s %s ----------",
1359 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1360 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1362 if (motion_key_y != KSYM_UNDEFINED)
1363 HandleKey(motion_key_y, KEY_RELEASED);
1364 if (new_motion_key_y != KSYM_UNDEFINED)
1365 HandleKey(new_motion_key_y, KEY_PRESSED);
1368 motion_key_x = new_motion_key_x;
1369 motion_key_y = new_motion_key_y;
1373 static void HandleButtonOrFinger(int mx, int my, int button)
1375 boolean valid_mouse_event = (mx != -1 && my != -1 && button != -1);
1377 if (game_status != GAME_MODE_PLAYING)
1380 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1382 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1383 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1384 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1385 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1386 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1387 SetPlayerMouseAction(mx, my, button); // special case
1391 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1392 HandleButtonOrFinger_FollowFinger(mx, my, button);
1393 else if (game.use_mouse_actions && valid_mouse_event)
1394 SetPlayerMouseAction(mx, my, button);
1398 static boolean checkTextInputKey(Key key)
1400 // when playing, only handle raw key events and ignore text input
1401 if (game_status == GAME_MODE_PLAYING)
1404 // if Shift or right Alt key is pressed, handle key as text input
1405 if ((GetKeyModState() & KMOD_TextInput) != KMOD_None)
1408 // ignore raw keys as text input when not in text input mode
1409 if (KSYM_RAW(key) && !textinput_status)
1412 // else handle all printable keys as text input
1413 return KSYM_PRINTABLE(key);
1416 void HandleTextEvent(TextEvent *event)
1418 char *text = event->text;
1419 Key key = getKeyFromKeyName(text);
1421 #if DEBUG_EVENTS_TEXT
1422 Debug("event:text", "text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1425 text[0], (int)(text[0]),
1427 getKeyNameFromKey(key),
1431 if (checkTextInputKey(key))
1433 // process printable keys (with uppercase etc.) in text input mode
1434 HandleKey(key, KEY_PRESSED);
1435 HandleKey(key, KEY_RELEASED);
1439 void HandlePauseResumeEvent(PauseResumeEvent *event)
1441 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1445 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1451 void HandleKeyEvent(KeyEvent *event)
1453 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1454 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1455 Key key = GetEventKey(event, with_modifiers);
1456 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1458 #if DEBUG_EVENTS_KEY
1459 Debug("event:key", "key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1460 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1461 event->keysym.scancode,
1466 getKeyNameFromKey(key));
1469 #if defined(PLATFORM_ANDROID)
1470 if (key == KSYM_Back)
1472 // always map the "back" button to the "escape" key on Android devices
1475 else if (key == KSYM_Menu)
1477 // the "menu" button can be used to toggle displaying virtual buttons
1478 if (key_status == KEY_PRESSED)
1479 SetOverlayEnabled(!GetOverlayEnabled());
1483 // for any other "real" key event, disable virtual buttons
1484 SetOverlayEnabled(FALSE);
1486 // for any other "real" key event, disable overlay touch buttons
1487 runtime.uses_touch_device = FALSE;
1491 HandleKeyModState(keymod, key_status);
1493 // process all keys if not in text input mode or if non-printable keys
1494 if (!checkTextInputKey(key))
1495 HandleKey(key, key_status);
1498 static int HandleDropFileEvent(char *filename)
1500 Debug("event:dropfile", "filename == '%s'", filename);
1502 // check and extract dropped zip files into correct user data directory
1503 if (!strSuffixLower(filename, ".zip"))
1505 Warn("file '%s' not supported", filename);
1507 return TREE_TYPE_UNDEFINED;
1510 TreeInfo *tree_node = NULL;
1511 int tree_type = GetZipFileTreeType(filename);
1512 char *directory = TREE_USERDIR(tree_type);
1514 if (directory == NULL)
1516 Warn("zip file '%s' has invalid content!", filename);
1518 return TREE_TYPE_UNDEFINED;
1521 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1522 game_status == GAME_MODE_LEVELS &&
1523 leveldir_current->node_parent != NULL)
1525 // extract new level set next to currently selected level set
1526 tree_node = leveldir_current;
1528 // get parent directory of currently selected level set directory
1529 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1531 // use private level directory instead of top-level package level directory
1532 if (strPrefix(directory, options.level_directory) &&
1533 strEqual(leveldir_current->node_parent->fullpath, "."))
1534 directory = getUserLevelDir(NULL);
1537 // extract level or artwork set from zip file to target directory
1538 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1540 if (top_dir == NULL)
1542 // error message already issued by "ExtractZipFileIntoDirectory()"
1544 return TREE_TYPE_UNDEFINED;
1547 // add extracted level or artwork set to tree info structure
1548 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1550 // update menu screen (and possibly change current level set)
1551 DrawScreenAfterAddingSet(top_dir, tree_type);
1556 static void HandleDropTextEvent(char *text)
1558 Debug("event:droptext", "text == '%s'", text);
1561 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1562 int num_artwork_sets_succeeded,
1563 int num_files_failed)
1565 // only show request dialog if no other request dialog already active
1566 if (game.request_active)
1569 // this case can happen with drag-and-drop with older SDL versions
1570 if (num_level_sets_succeeded == 0 &&
1571 num_artwork_sets_succeeded == 0 &&
1572 num_files_failed == 0)
1577 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1579 char message_part1[50];
1581 sprintf(message_part1, "New %s set%s added",
1582 (num_artwork_sets_succeeded == 0 ? "level" :
1583 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1584 (num_level_sets_succeeded +
1585 num_artwork_sets_succeeded > 1 ? "s" : ""));
1587 if (num_files_failed > 0)
1588 sprintf(message, "%s, but %d dropped file%s failed!",
1589 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1591 sprintf(message, "%s!", message_part1);
1593 else if (num_files_failed > 0)
1595 sprintf(message, "Failed to process dropped file%s!",
1596 num_files_failed > 1 ? "s" : "");
1599 Request(message, REQ_CONFIRM);
1602 void HandleDropEvent(Event *event)
1604 static boolean confirm_on_drop_complete = FALSE;
1605 static int num_level_sets_succeeded = 0;
1606 static int num_artwork_sets_succeeded = 0;
1607 static int num_files_failed = 0;
1609 switch (event->type)
1613 confirm_on_drop_complete = TRUE;
1614 num_level_sets_succeeded = 0;
1615 num_artwork_sets_succeeded = 0;
1616 num_files_failed = 0;
1623 int tree_type = HandleDropFileEvent(event->drop.file);
1625 if (tree_type == TREE_TYPE_LEVEL_DIR)
1626 num_level_sets_succeeded++;
1627 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1628 tree_type == TREE_TYPE_SOUNDS_DIR ||
1629 tree_type == TREE_TYPE_MUSIC_DIR)
1630 num_artwork_sets_succeeded++;
1634 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1635 if (!confirm_on_drop_complete)
1637 // process all remaining events, including further SDL_DROPFILE events
1640 HandleDropCompleteEvent(num_level_sets_succeeded,
1641 num_artwork_sets_succeeded,
1644 num_level_sets_succeeded = 0;
1645 num_artwork_sets_succeeded = 0;
1646 num_files_failed = 0;
1654 HandleDropTextEvent(event->drop.file);
1659 case SDL_DROPCOMPLETE:
1661 HandleDropCompleteEvent(num_level_sets_succeeded,
1662 num_artwork_sets_succeeded,
1669 if (event->drop.file != NULL)
1670 SDL_free(event->drop.file);
1673 void HandleUserEvent(UserEvent *event)
1675 switch (event->code)
1677 case USEREVENT_ANIM_DELAY_ACTION:
1678 case USEREVENT_ANIM_EVENT_ACTION:
1679 // execute action functions until matching action was found
1680 if (DoKeysymAction(event->value1) ||
1681 DoGadgetAction(event->value1) ||
1682 DoScreenAction(event->value1))
1691 void HandleButton(int mx, int my, int button, int button_nr)
1693 static int old_mx = 0, old_my = 0;
1694 boolean button_hold = FALSE;
1695 boolean handle_gadgets = TRUE;
1701 button_nr = -button_nr;
1710 #if defined(PLATFORM_ANDROID)
1711 // when playing, only handle gadgets when using "follow finger" controls
1712 // or when using touch controls in combination with the MM game engine
1713 // or when using gadgets that do not overlap with virtual buttons
1715 (game_status != GAME_MODE_PLAYING ||
1716 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1717 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1718 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1719 !CheckVirtualButtonPressed(mx, my, button)));
1721 // always recognize potentially releasing already pressed gadgets
1722 if (button == MB_RELEASED)
1723 handle_gadgets = TRUE;
1725 // always recognize pressing or releasing overlay touch buttons
1726 if (CheckPosition_OverlayTouchButtons(mx, my, button) && !motion_status)
1727 handle_gadgets = TRUE;
1730 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1732 // do not handle this button event anymore
1733 return; // force mouse event not to be handled at all
1736 if (handle_gadgets && HandleGadgets(mx, my, button))
1738 // do not handle this button event anymore
1739 mx = my = -32; // force mouse event to be outside screen tiles
1742 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1745 // do not use scroll wheel button events for anything other than gadgets
1746 if (IS_WHEEL_BUTTON(button_nr))
1749 switch (game_status)
1751 case GAME_MODE_TITLE:
1752 HandleTitleScreen(mx, my, 0, 0, button);
1755 case GAME_MODE_MAIN:
1756 HandleMainMenu(mx, my, 0, 0, button);
1759 case GAME_MODE_PSEUDO_TYPENAME:
1760 HandleTypeName(0, KSYM_Return);
1763 case GAME_MODE_LEVELS:
1764 HandleChooseLevelSet(mx, my, 0, 0, button);
1767 case GAME_MODE_LEVELNR:
1768 HandleChooseLevelNr(mx, my, 0, 0, button);
1771 case GAME_MODE_SCORES:
1772 HandleHallOfFame(0, 0, 0, 0, button);
1775 case GAME_MODE_EDITOR:
1776 HandleLevelEditorIdle();
1779 case GAME_MODE_INFO:
1780 HandleInfoScreen(mx, my, 0, 0, button);
1783 case GAME_MODE_SETUP:
1784 HandleSetupScreen(mx, my, 0, 0, button);
1787 case GAME_MODE_PLAYING:
1788 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1789 HandleButtonOrFinger(mx, my, button);
1791 SetPlayerMouseAction(mx, my, button);
1794 if (button == MB_PRESSED && !motion_status && !button_hold &&
1795 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1796 DumpTileFromScreen(mx, my);
1806 #define MAX_CHEAT_INPUT_LEN 32
1808 static void HandleKeysSpecial(Key key)
1810 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1811 char letter = getCharFromKey(key);
1812 int cheat_input_len = strlen(cheat_input);
1818 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1820 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1821 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1823 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1826 cheat_input[cheat_input_len++] = letter;
1827 cheat_input[cheat_input_len] = '\0';
1829 #if DEBUG_EVENTS_KEY
1830 Debug("event:key:special", "'%s' [%d]", cheat_input, cheat_input_len);
1833 if (game_status == GAME_MODE_MAIN)
1835 if (strSuffix(cheat_input, ":insert-solution-tape") ||
1836 strSuffix(cheat_input, ":ist"))
1838 InsertSolutionTape();
1840 else if (strSuffix(cheat_input, ":play-solution-tape") ||
1841 strSuffix(cheat_input, ":pst"))
1845 else if (strSuffix(cheat_input, ":reload-graphics") ||
1846 strSuffix(cheat_input, ":rg"))
1848 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1851 else if (strSuffix(cheat_input, ":reload-sounds") ||
1852 strSuffix(cheat_input, ":rs"))
1854 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1857 else if (strSuffix(cheat_input, ":reload-music") ||
1858 strSuffix(cheat_input, ":rm"))
1860 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1863 else if (strSuffix(cheat_input, ":reload-artwork") ||
1864 strSuffix(cheat_input, ":ra"))
1866 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1867 1 << ARTWORK_TYPE_SOUNDS |
1868 1 << ARTWORK_TYPE_MUSIC);
1871 else if (strSuffix(cheat_input, ":dump-level") ||
1872 strSuffix(cheat_input, ":dl"))
1876 else if (strSuffix(cheat_input, ":dump-tape") ||
1877 strSuffix(cheat_input, ":dt"))
1881 else if (strSuffix(cheat_input, ":undo-tape") ||
1882 strSuffix(cheat_input, ":ut"))
1886 else if (strSuffix(cheat_input, ":fix-tape") ||
1887 strSuffix(cheat_input, ":ft"))
1889 FixTape_ForceSinglePlayer();
1891 else if (strSuffix(cheat_input, ":save-native-level") ||
1892 strSuffix(cheat_input, ":snl"))
1894 SaveNativeLevel(&level);
1896 else if (strSuffix(cheat_input, ":frames-per-second") ||
1897 strSuffix(cheat_input, ":fps"))
1899 global.show_frames_per_second = !global.show_frames_per_second;
1902 else if (game_status == GAME_MODE_PLAYING)
1905 if (strSuffix(cheat_input, ".q"))
1906 DEBUG_SetMaximumDynamite();
1909 else if (game_status == GAME_MODE_EDITOR)
1911 if (strSuffix(cheat_input, ":dump-brush") ||
1912 strSuffix(cheat_input, ":DB"))
1916 else if (strSuffix(cheat_input, ":DDB"))
1921 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1923 if (letter == 'x') // copy brush to clipboard (small size)
1925 CopyBrushToClipboard_Small();
1927 else if (letter == 'c') // copy brush to clipboard (normal size)
1929 CopyBrushToClipboard();
1931 else if (letter == 'v') // paste brush from Clipboard
1933 CopyClipboardToBrush();
1935 else if (letter == 'z') // undo or redo last operation
1937 if (GetKeyModState() & KMOD_Shift)
1938 RedoLevelEditorOperation();
1940 UndoLevelEditorOperation();
1945 // special key shortcuts for all game modes
1946 if (strSuffix(cheat_input, ":dump-event-actions") ||
1947 strSuffix(cheat_input, ":dea") ||
1948 strSuffix(cheat_input, ":DEA"))
1950 DumpGadgetIdentifiers();
1951 DumpScreenIdentifiers();
1955 boolean HandleKeysDebug(Key key, int key_status)
1960 if (key_status != KEY_PRESSED)
1963 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1965 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1967 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1969 if (key == setup.debug.frame_delay_key[i] &&
1970 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1972 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1973 setup.debug.frame_delay[i] : setup.game_frame_delay);
1975 if (!setup.debug.frame_delay_game_only)
1976 MenuFrameDelay = GameFrameDelay;
1978 SetVideoFrameDelay(GameFrameDelay);
1980 if (GameFrameDelay > ONE_SECOND_DELAY)
1981 Debug("event:key:debug", "frame delay == %d ms", GameFrameDelay);
1982 else if (GameFrameDelay != 0)
1983 Debug("event:key:debug", "frame delay == %d ms (max. %d fps / %d %%)",
1984 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1985 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1987 Debug("event:key:debug", "frame delay == 0 ms (maximum speed)");
1994 if (game_status == GAME_MODE_PLAYING)
1998 options.debug = !options.debug;
2000 Debug("event:key:debug", "debug mode %s",
2001 (options.debug ? "enabled" : "disabled"));
2005 else if (key == KSYM_v)
2007 Debug("event:key:debug", "currently using game engine version %d",
2008 game.engine_version);
2018 void HandleKey(Key key, int key_status)
2020 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2021 static boolean ignore_repeated_key = FALSE;
2022 static struct SetupKeyboardInfo ski;
2023 static struct SetupShortcutInfo ssi;
2032 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
2033 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
2034 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
2035 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
2036 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2037 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2042 if (HandleKeysDebug(key, key_status))
2043 return; // do not handle already processed keys again
2045 // map special keys (media keys / remote control buttons) to default keys
2046 if (key == KSYM_PlayPause)
2048 else if (key == KSYM_Select)
2051 HandleSpecialGameControllerKeys(key, key_status);
2053 if (game_status == GAME_MODE_PLAYING)
2055 // only needed for single-step tape recording mode
2056 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2059 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2061 byte key_action = 0;
2062 byte key_snap_action = 0;
2064 if (setup.input[pnr].use_joystick)
2067 ski = setup.input[pnr].key;
2069 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2070 if (key == *key_info[i].key_custom)
2071 key_action |= key_info[i].action;
2073 // use combined snap+direction keys for the first player only
2076 ssi = setup.shortcut;
2078 // also remember normal snap key when handling snap+direction keys
2079 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2081 for (i = 0; i < NUM_DIRECTIONS; i++)
2083 if (key == *key_info[i].key_snap)
2085 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2086 key_snap_action |= key_info[i].action;
2091 if (key_status == KEY_PRESSED)
2093 stored_player[pnr].action |= key_action;
2094 stored_player[pnr].snap_action |= key_snap_action;
2098 stored_player[pnr].action &= ~key_action;
2099 stored_player[pnr].snap_action &= ~key_snap_action;
2102 // restore snap action if one of several pressed snap keys was released
2103 if (stored_player[pnr].snap_action)
2104 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2106 if (tape.recording && tape.pausing && tape.use_key_actions)
2108 if (tape.single_step)
2110 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2112 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2114 // if snap key already pressed, keep pause mode when releasing
2115 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2116 has_snapped[pnr] = TRUE;
2118 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2120 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2122 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2123 getRedDiskReleaseFlag_SP() == 0)
2125 // add a single inactive frame before dropping starts
2126 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2127 stored_player[pnr].force_dropping = TRUE;
2130 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2132 // if snap key was pressed without direction, leave pause mode
2133 if (!has_snapped[pnr])
2134 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2136 has_snapped[pnr] = FALSE;
2141 // prevent key release events from un-pausing a paused game
2142 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2143 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2147 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2148 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2154 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2155 if (key == key_info[i].key_default)
2156 joy |= key_info[i].action;
2161 if (key_status == KEY_PRESSED)
2162 key_joystick_mapping |= joy;
2164 key_joystick_mapping &= ~joy;
2169 if (game_status != GAME_MODE_PLAYING)
2170 key_joystick_mapping = 0;
2172 if (key_status == KEY_RELEASED)
2174 // reset flag to ignore repeated "key pressed" events after key release
2175 ignore_repeated_key = FALSE;
2180 if ((key == KSYM_F11 ||
2181 ((key == KSYM_Return ||
2182 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2183 video.fullscreen_available &&
2184 !ignore_repeated_key)
2186 setup.fullscreen = !setup.fullscreen;
2188 ToggleFullscreenIfNeeded();
2190 if (game_status == GAME_MODE_SETUP)
2191 RedrawSetupScreenAfterFullscreenToggle();
2193 UpdateMousePosition();
2195 // set flag to ignore repeated "key pressed" events
2196 ignore_repeated_key = TRUE;
2201 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2202 key == KSYM_minus || key == KSYM_KP_Subtract ||
2203 key == KSYM_plus || key == KSYM_KP_Add ||
2204 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2205 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2206 video.window_scaling_available &&
2207 !video.fullscreen_enabled)
2209 if (key == KSYM_0 || key == KSYM_KP_0)
2210 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2211 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2212 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2214 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2216 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2217 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2218 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2219 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2221 ChangeWindowScalingIfNeeded();
2223 if (game_status == GAME_MODE_SETUP)
2224 RedrawSetupScreenAfterFullscreenToggle();
2226 UpdateMousePosition();
2231 // some key events are handled like clicks for global animations
2232 boolean click = (key == KSYM_space ||
2233 key == KSYM_Return ||
2234 key == KSYM_Escape);
2236 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2238 // do not handle this key event anymore
2239 if (key != KSYM_Escape) // always allow ESC key to be handled
2243 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2244 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2251 if (game_status == GAME_MODE_MAIN &&
2252 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2254 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2259 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2261 if (key == setup.shortcut.save_game)
2263 else if (key == setup.shortcut.load_game)
2265 else if (key == setup.shortcut.toggle_pause)
2266 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2268 HandleTapeButtonKeys(key);
2269 HandleSoundButtonKeys(key);
2272 if (game_status == GAME_MODE_PLAYING && !network_playing)
2274 int centered_player_nr_next = -999;
2276 if (key == setup.shortcut.focus_player_all)
2277 centered_player_nr_next = -1;
2279 for (i = 0; i < MAX_PLAYERS; i++)
2280 if (key == setup.shortcut.focus_player[i])
2281 centered_player_nr_next = i;
2283 if (centered_player_nr_next != -999)
2285 game.centered_player_nr_next = centered_player_nr_next;
2286 game.set_centered_player = TRUE;
2290 tape.centered_player_nr_next = game.centered_player_nr_next;
2291 tape.set_centered_player = TRUE;
2296 HandleKeysSpecial(key);
2298 if (HandleGadgetsKeyInput(key))
2299 return; // do not handle already processed keys again
2301 switch (game_status)
2303 case GAME_MODE_PSEUDO_TYPENAME:
2304 HandleTypeName(0, key);
2307 case GAME_MODE_TITLE:
2308 case GAME_MODE_MAIN:
2309 case GAME_MODE_LEVELS:
2310 case GAME_MODE_LEVELNR:
2311 case GAME_MODE_SETUP:
2312 case GAME_MODE_INFO:
2313 case GAME_MODE_SCORES:
2315 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2322 if (game_status == GAME_MODE_TITLE)
2323 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2324 else if (game_status == GAME_MODE_MAIN)
2325 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2326 else if (game_status == GAME_MODE_LEVELS)
2327 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2328 else if (game_status == GAME_MODE_LEVELNR)
2329 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2330 else if (game_status == GAME_MODE_SETUP)
2331 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2332 else if (game_status == GAME_MODE_INFO)
2333 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2334 else if (game_status == GAME_MODE_SCORES)
2335 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2339 if (game_status != GAME_MODE_MAIN)
2340 FadeSkipNextFadeIn();
2342 if (game_status == GAME_MODE_TITLE)
2343 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2344 else if (game_status == GAME_MODE_LEVELS)
2345 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2346 else if (game_status == GAME_MODE_LEVELNR)
2347 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2348 else if (game_status == GAME_MODE_SETUP)
2349 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2350 else if (game_status == GAME_MODE_INFO)
2351 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2352 else if (game_status == GAME_MODE_SCORES)
2353 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2357 if (game_status == GAME_MODE_LEVELS)
2358 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2359 else if (game_status == GAME_MODE_LEVELNR)
2360 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2361 else if (game_status == GAME_MODE_SETUP)
2362 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2363 else if (game_status == GAME_MODE_INFO)
2364 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2365 else if (game_status == GAME_MODE_SCORES)
2366 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2369 case KSYM_Page_Down:
2370 if (game_status == GAME_MODE_LEVELS)
2371 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2372 else if (game_status == GAME_MODE_LEVELNR)
2373 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2374 else if (game_status == GAME_MODE_SETUP)
2375 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2376 else if (game_status == GAME_MODE_INFO)
2377 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2378 else if (game_status == GAME_MODE_SCORES)
2379 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2387 case GAME_MODE_EDITOR:
2388 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2389 HandleLevelEditorKeyInput(key);
2392 case GAME_MODE_PLAYING:
2397 RequestQuitGame(setup.ask_on_escape);
2407 if (key == KSYM_Escape)
2409 SetGameStatus(GAME_MODE_MAIN);
2418 void HandleNoEvent(void)
2420 HandleMouseCursor();
2422 switch (game_status)
2424 case GAME_MODE_PLAYING:
2425 HandleButtonOrFinger(-1, -1, -1);
2430 void HandleEventActions(void)
2432 // if (button_status && game_status != GAME_MODE_PLAYING)
2433 if (button_status && (game_status != GAME_MODE_PLAYING ||
2435 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2437 HandleButton(0, 0, button_status, -button_status);
2444 if (network.enabled)
2447 switch (game_status)
2449 case GAME_MODE_MAIN:
2450 DrawPreviewLevelAnimation();
2453 case GAME_MODE_EDITOR:
2454 HandleLevelEditorIdle();
2462 static void HandleTileCursor(int dx, int dy, int button)
2465 ClearPlayerMouseAction();
2472 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2473 (dx < 0 ? MB_LEFTBUTTON :
2474 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2476 else if (!tile_cursor.moving)
2478 int old_xpos = tile_cursor.xpos;
2479 int old_ypos = tile_cursor.ypos;
2480 int new_xpos = old_xpos;
2481 int new_ypos = old_ypos;
2483 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2484 new_xpos = old_xpos + dx;
2486 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2487 new_ypos = old_ypos + dy;
2489 SetTileCursorTargetXY(new_xpos, new_ypos);
2493 static int HandleJoystickForAllPlayers(void)
2497 boolean no_joysticks_configured = TRUE;
2498 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2499 static byte joy_action_last[MAX_PLAYERS];
2501 for (i = 0; i < MAX_PLAYERS; i++)
2502 if (setup.input[i].use_joystick)
2503 no_joysticks_configured = FALSE;
2505 // if no joysticks configured, map connected joysticks to players
2506 if (no_joysticks_configured)
2507 use_as_joystick_nr = TRUE;
2509 for (i = 0; i < MAX_PLAYERS; i++)
2511 byte joy_action = 0;
2513 joy_action = JoystickExt(i, use_as_joystick_nr);
2514 result |= joy_action;
2516 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2517 joy_action != joy_action_last[i])
2518 stored_player[i].action = joy_action;
2520 joy_action_last[i] = joy_action;
2526 void HandleJoystick(void)
2528 static unsigned int joytest_delay = 0;
2529 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2530 static int joytest_last = 0;
2531 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2532 int delay_value = GADGET_FRAME_DELAY;
2533 int joystick = HandleJoystickForAllPlayers();
2534 int keyboard = key_joystick_mapping;
2535 int joy = (joystick | keyboard);
2536 int joytest = joystick;
2537 int left = joy & JOY_LEFT;
2538 int right = joy & JOY_RIGHT;
2539 int up = joy & JOY_UP;
2540 int down = joy & JOY_DOWN;
2541 int button = joy & JOY_BUTTON;
2542 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2543 int dx = (left ? -1 : right ? 1 : 0);
2544 int dy = (up ? -1 : down ? 1 : 0);
2545 boolean use_delay_value_first = (joytest != joytest_last);
2547 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2549 // do not handle this button event anymore
2553 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2554 anyTextGadgetActive()))
2556 // leave name input in main menu or text input gadget
2557 HandleKey(KSYM_Escape, KEY_PRESSED);
2558 HandleKey(KSYM_Escape, KEY_RELEASED);
2563 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2565 if (game_status == GAME_MODE_PLAYING)
2567 // when playing MM style levels, also use delay for keyboard events
2568 joytest |= keyboard;
2570 // only use first delay value for new events, but not for changed events
2571 use_delay_value_first = (!joytest != !joytest_last);
2573 // only use delay after the initial keyboard event
2577 // for any joystick or keyboard event, enable playfield tile cursor
2578 if (dx || dy || button)
2579 SetTileCursorEnabled(TRUE);
2582 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2584 // delay joystick/keyboard actions if axes/keys continually pressed
2585 newbutton = dx = dy = 0;
2589 // first start with longer delay, then continue with shorter delay
2590 joytest_delay_value =
2591 (use_delay_value_first ? delay_value_first : delay_value);
2594 joytest_last = joytest;
2596 switch (game_status)
2598 case GAME_MODE_TITLE:
2599 case GAME_MODE_MAIN:
2600 case GAME_MODE_LEVELS:
2601 case GAME_MODE_LEVELNR:
2602 case GAME_MODE_SETUP:
2603 case GAME_MODE_INFO:
2604 case GAME_MODE_SCORES:
2606 if (anyTextGadgetActive())
2609 if (game_status == GAME_MODE_TITLE)
2610 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2611 else if (game_status == GAME_MODE_MAIN)
2612 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2613 else if (game_status == GAME_MODE_LEVELS)
2614 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2615 else if (game_status == GAME_MODE_LEVELNR)
2616 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2617 else if (game_status == GAME_MODE_SETUP)
2618 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2619 else if (game_status == GAME_MODE_INFO)
2620 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2621 else if (game_status == GAME_MODE_SCORES)
2622 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2627 case GAME_MODE_PLAYING:
2629 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2630 if (tape.playing || keyboard)
2631 newbutton = ((joy & JOY_BUTTON) != 0);
2634 if (newbutton && game.all_players_gone)
2641 if (tape.recording && tape.pausing && tape.use_key_actions)
2643 if (tape.single_step)
2645 if (joystick & JOY_ACTION)
2646 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2650 if (joystick & JOY_ACTION)
2651 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2655 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2656 HandleTileCursor(dx, dy, button);
2665 void HandleSpecialGameControllerButtons(Event *event)
2670 switch (event->type)
2672 case SDL_CONTROLLERBUTTONDOWN:
2673 key_status = KEY_PRESSED;
2676 case SDL_CONTROLLERBUTTONUP:
2677 key_status = KEY_RELEASED;
2684 switch (event->cbutton.button)
2686 case SDL_CONTROLLER_BUTTON_START:
2690 case SDL_CONTROLLER_BUTTON_BACK:
2698 HandleKey(key, key_status);
2701 void HandleSpecialGameControllerKeys(Key key, int key_status)
2703 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2704 int button = SDL_CONTROLLER_BUTTON_INVALID;
2706 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2707 if (key == KSYM_Rewind)
2708 button = SDL_CONTROLLER_BUTTON_A;
2709 else if (key == KSYM_FastForward || key == KSYM_Menu)
2710 button = SDL_CONTROLLER_BUTTON_B;
2712 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2716 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2717 SDL_CONTROLLERBUTTONUP);
2719 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2720 event.cbutton.button = button;
2721 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2724 HandleJoystickEvent(&event);
2729 boolean DoKeysymAction(int keysym)
2733 Key key = (Key)(-keysym);
2735 HandleKey(key, KEY_PRESSED);
2736 HandleKey(key, KEY_RELEASED);