1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_EVENTS 0
28 #define DEBUG_EVENTS_BUTTON (DEBUG_EVENTS * 0)
29 #define DEBUG_EVENTS_MOTION (DEBUG_EVENTS * 0)
30 #define DEBUG_EVENTS_WHEEL (DEBUG_EVENTS * 1)
31 #define DEBUG_EVENTS_WINDOW (DEBUG_EVENTS * 0)
32 #define DEBUG_EVENTS_FINGER (DEBUG_EVENTS * 0)
33 #define DEBUG_EVENTS_TEXT (DEBUG_EVENTS * 1)
34 #define DEBUG_EVENTS_KEY (DEBUG_EVENTS * 1)
37 static boolean cursor_inside_playfield = FALSE;
38 static int cursor_mode_last = CURSOR_DEFAULT;
39 static unsigned int special_cursor_delay = 0;
40 static unsigned int special_cursor_delay_value = 1000;
42 static boolean stop_processing_events = FALSE;
45 // forward declarations for internal use
46 static void HandleNoEvent(void);
47 static void HandleEventActions(void);
50 // event filter to set mouse x/y position (for pointer class global animations)
51 // (this is especially required to ensure smooth global animation mouse pointer
52 // movement when the screen is updated without handling events; this can happen
53 // when drawing door/envelope request animations, for example)
55 int FilterMouseMotionEvents(void *userdata, Event *event)
57 if (event->type == EVENT_MOTIONNOTIFY)
59 int mouse_x = ((MotionEvent *)event)->x;
60 int mouse_y = ((MotionEvent *)event)->y;
62 UpdateRawMousePosition(mouse_x, mouse_y);
68 // event filter especially needed for SDL event filtering due to
69 // delay problems with lots of mouse motion events when mouse button
70 // not pressed (X11 can handle this with 'PointerMotionHintMask')
72 // event filter addition for SDL2: as SDL2 does not have a function to enable
73 // or disable keyboard auto-repeat, filter repeated keyboard events instead
75 static int FilterEvents(const Event *event)
79 // skip repeated key press events if keyboard auto-repeat is disabled
80 if (event->type == EVENT_KEYPRESS &&
85 if (event->type == EVENT_BUTTONPRESS ||
86 event->type == EVENT_BUTTONRELEASE)
88 ((ButtonEvent *)event)->x -= video.screen_xoffset;
89 ((ButtonEvent *)event)->y -= video.screen_yoffset;
91 else if (event->type == EVENT_MOTIONNOTIFY)
93 ((MotionEvent *)event)->x -= video.screen_xoffset;
94 ((MotionEvent *)event)->y -= video.screen_yoffset;
97 // non-motion events are directly passed to event handler functions
98 if (event->type != EVENT_MOTIONNOTIFY)
101 motion = (MotionEvent *)event;
102 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
103 motion->y >= SY && motion->y < SY + SYSIZE);
105 // set correct mouse x/y position (for pointer class global animations)
106 // (this is required in rare cases where the mouse x/y position calculated
107 // from raw values (to apply logical screen size scaling corrections) does
108 // not match the final mouse event x/y position -- this may happen because
109 // the SDL renderer's viewport position is internally represented as float,
110 // but only accessible as integer, which may lead to rounding errors)
111 gfx.mouse_x = motion->x;
112 gfx.mouse_y = motion->y;
114 // do no reset mouse cursor before all pending events have been processed
115 if (gfx.cursor_mode == cursor_mode_last &&
116 ((game_status == GAME_MODE_TITLE &&
117 gfx.cursor_mode == CURSOR_NONE) ||
118 (game_status == GAME_MODE_PLAYING &&
119 gfx.cursor_mode == CURSOR_PLAYFIELD)))
121 SetMouseCursor(CURSOR_DEFAULT);
123 DelayReached(&special_cursor_delay, 0);
125 cursor_mode_last = CURSOR_DEFAULT;
128 // skip mouse motion events without pressed button outside level editor
129 if (button_status == MB_RELEASED &&
130 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
136 // to prevent delay problems, skip mouse motion events if the very next
137 // event is also a mouse motion event (and therefore effectively only
138 // handling the last of a row of mouse motion events in the event queue)
140 static boolean SkipPressedMouseMotionEvent(const Event *event)
142 // nothing to do if the current event is not a mouse motion event
143 if (event->type != EVENT_MOTIONNOTIFY)
146 // only skip motion events with pressed button outside the game
147 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
154 PeekEvent(&next_event);
156 // if next event is also a mouse motion event, skip the current one
157 if (next_event.type == EVENT_MOTIONNOTIFY)
164 static boolean WaitValidEvent(Event *event)
168 if (!FilterEvents(event))
171 if (SkipPressedMouseMotionEvent(event))
177 /* this is especially needed for event modifications for the Android target:
178 if mouse coordinates should be modified in the event filter function,
179 using a properly installed SDL event filter does not work, because in
180 the event filter, mouse coordinates in the event structure are still
181 physical pixel positions, not logical (scaled) screen positions, so this
182 has to be handled at a later stage in the event processing functions
183 (when device pixel positions are already converted to screen positions) */
185 boolean NextValidEvent(Event *event)
187 while (PendingEvent())
188 if (WaitValidEvent(event))
194 void StopProcessingEvents(void)
196 stop_processing_events = TRUE;
199 static void HandleEvents(void)
202 unsigned int event_frame_delay = 0;
203 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
205 ResetDelayCounter(&event_frame_delay);
207 stop_processing_events = FALSE;
209 while (NextValidEvent(&event))
213 case EVENT_BUTTONPRESS:
214 case EVENT_BUTTONRELEASE:
215 HandleButtonEvent((ButtonEvent *) &event);
218 case EVENT_MOTIONNOTIFY:
219 HandleMotionEvent((MotionEvent *) &event);
222 case EVENT_WHEELMOTION:
223 HandleWheelEvent((WheelEvent *) &event);
226 case SDL_WINDOWEVENT:
227 HandleWindowEvent((WindowEvent *) &event);
230 case EVENT_FINGERPRESS:
231 case EVENT_FINGERRELEASE:
232 case EVENT_FINGERMOTION:
233 HandleFingerEvent((FingerEvent *) &event);
236 case EVENT_TEXTINPUT:
237 HandleTextEvent((TextEvent *) &event);
240 case SDL_APP_WILLENTERBACKGROUND:
241 case SDL_APP_DIDENTERBACKGROUND:
242 case SDL_APP_WILLENTERFOREGROUND:
243 case SDL_APP_DIDENTERFOREGROUND:
244 HandlePauseResumeEvent((PauseResumeEvent *) &event);
248 case EVENT_KEYRELEASE:
249 HandleKeyEvent((KeyEvent *) &event);
253 HandleUserEvent((UserEvent *) &event);
257 HandleOtherEvents(&event);
261 // do not handle events for longer than standard frame delay period
262 if (DelayReached(&event_frame_delay, event_frame_delay_value))
265 // do not handle any further events if triggered by a special flag
266 if (stop_processing_events)
271 void HandleOtherEvents(Event *event)
275 case SDL_CONTROLLERBUTTONDOWN:
276 case SDL_CONTROLLERBUTTONUP:
277 // for any game controller button event, disable overlay buttons
278 SetOverlayEnabled(FALSE);
280 HandleSpecialGameControllerButtons(event);
283 case SDL_CONTROLLERDEVICEADDED:
284 case SDL_CONTROLLERDEVICEREMOVED:
285 case SDL_CONTROLLERAXISMOTION:
286 case SDL_JOYAXISMOTION:
287 case SDL_JOYBUTTONDOWN:
288 case SDL_JOYBUTTONUP:
289 HandleJoystickEvent(event);
293 case SDL_DROPCOMPLETE:
296 HandleDropEvent(event);
308 static void HandleMouseCursor(void)
310 if (game_status == GAME_MODE_TITLE)
312 // when showing title screens, hide mouse pointer (if not moved)
314 if (gfx.cursor_mode != CURSOR_NONE &&
315 DelayReached(&special_cursor_delay, special_cursor_delay_value))
317 SetMouseCursor(CURSOR_NONE);
320 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
323 // when playing, display a special mouse pointer inside the playfield
325 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
326 cursor_inside_playfield &&
327 DelayReached(&special_cursor_delay, special_cursor_delay_value))
329 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
331 SetMouseCursor(CURSOR_PLAYFIELD);
334 else if (gfx.cursor_mode != CURSOR_DEFAULT)
336 SetMouseCursor(CURSOR_DEFAULT);
339 // this is set after all pending events have been processed
340 cursor_mode_last = gfx.cursor_mode;
352 // execute event related actions after pending events have been processed
353 HandleEventActions();
355 // don't use all CPU time when idle; the main loop while playing
356 // has its own synchronization and is CPU friendly, too
358 if (game_status == GAME_MODE_PLAYING)
361 // always copy backbuffer to visible screen for every video frame
364 // reset video frame delay to default (may change again while playing)
365 SetVideoFrameDelay(MenuFrameDelay);
367 if (game_status == GAME_MODE_QUIT)
372 void ClearAutoRepeatKeyEvents(void)
374 while (PendingEvent())
378 PeekEvent(&next_event);
380 // if event is repeated key press event, remove it from event queue
381 if (next_event.type == EVENT_KEYPRESS &&
382 next_event.key.repeat)
383 WaitEvent(&next_event);
389 void ClearEventQueue(void)
393 while (NextValidEvent(&event))
397 case EVENT_BUTTONRELEASE:
398 button_status = MB_RELEASED;
401 case EVENT_KEYRELEASE:
405 case SDL_CONTROLLERBUTTONUP:
406 HandleJoystickEvent(&event);
411 HandleOtherEvents(&event);
417 static void ClearPlayerMouseAction(void)
419 local_player->mouse_action.lx = 0;
420 local_player->mouse_action.ly = 0;
421 local_player->mouse_action.button = 0;
424 void ClearPlayerAction(void)
428 // simulate key release events for still pressed keys
429 key_joystick_mapping = 0;
430 for (i = 0; i < MAX_PLAYERS; i++)
432 stored_player[i].action = 0;
433 stored_player[i].snap_action = 0;
436 ClearJoystickState();
437 ClearPlayerMouseAction();
440 static void SetPlayerMouseAction(int mx, int my, int button)
442 int lx = getLevelFromScreenX(mx);
443 int ly = getLevelFromScreenY(my);
444 int new_button = (!local_player->mouse_action.button && button);
446 if (local_player->mouse_action.button_hint)
447 button = local_player->mouse_action.button_hint;
449 ClearPlayerMouseAction();
451 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
454 local_player->mouse_action.lx = lx;
455 local_player->mouse_action.ly = ly;
456 local_player->mouse_action.button = button;
458 if (tape.recording && tape.pausing && tape.use_mouse)
460 // un-pause a paused game only if mouse button was newly pressed down
462 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
465 SetTileCursorXY(lx, ly);
468 static Key GetKeyFromGridButton(int grid_button)
470 return (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
471 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
472 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
473 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
474 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
475 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
479 #if defined(PLATFORM_ANDROID)
480 static boolean CheckVirtualButtonPressed(int mx, int my, int button)
482 float touch_x = (float)(mx + video.screen_xoffset) / video.screen_width;
483 float touch_y = (float)(my + video.screen_yoffset) / video.screen_height;
484 int x = touch_x * overlay.grid_xsize;
485 int y = touch_y * overlay.grid_ysize;
486 int grid_button = overlay.grid_button[x][y];
487 Key key = GetKeyFromGridButton(grid_button);
488 int key_status = (button == MB_RELEASED ? KEY_RELEASED : KEY_PRESSED);
490 return (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
494 void HandleButtonEvent(ButtonEvent *event)
496 #if DEBUG_EVENTS_BUTTON
497 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
499 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
503 // for any mouse button event, disable playfield tile cursor
504 SetTileCursorEnabled(FALSE);
506 #if defined(HAS_SCREEN_KEYBOARD)
507 if (video.shifted_up)
508 event->y += video.shifted_up_pos;
511 motion_status = FALSE;
513 if (event->type == EVENT_BUTTONPRESS)
514 button_status = event->button;
516 button_status = MB_RELEASED;
518 HandleButton(event->x, event->y, button_status, event->button);
521 void HandleMotionEvent(MotionEvent *event)
523 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
526 motion_status = TRUE;
528 #if DEBUG_EVENTS_MOTION
529 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
530 button_status, event->x, event->y);
533 HandleButton(event->x, event->y, button_status, button_status);
536 void HandleWheelEvent(WheelEvent *event)
540 #if DEBUG_EVENTS_WHEEL
542 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
543 event->which, event->x, event->y);
545 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
546 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
547 event->which, event->x, event->y,
548 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
549 "SDL_MOUSEWHEEL_FLIPPED"));
553 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
554 event->x > 0 ? MB_WHEEL_RIGHT :
555 event->y < 0 ? MB_WHEEL_DOWN :
556 event->y > 0 ? MB_WHEEL_UP : 0);
558 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
559 // accelerated mouse wheel available on Mac and Windows
560 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
562 // no accelerated mouse wheel available on Unix/Linux
563 wheel_steps = DEFAULT_WHEEL_STEPS;
566 motion_status = FALSE;
568 button_status = button_nr;
569 HandleButton(0, 0, button_status, -button_nr);
571 button_status = MB_RELEASED;
572 HandleButton(0, 0, button_status, -button_nr);
575 void HandleWindowEvent(WindowEvent *event)
577 #if DEBUG_EVENTS_WINDOW
578 int subtype = event->event;
581 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
582 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
583 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
584 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
585 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
586 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
587 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
588 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
589 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
590 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
591 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
592 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
593 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
594 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
597 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
598 event_name, event->data1, event->data2);
602 // (not needed, as the screen gets redrawn every 20 ms anyway)
603 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
604 event->event == SDL_WINDOWEVENT_RESIZED ||
605 event->event == SDL_WINDOWEVENT_EXPOSED)
609 if (event->event == SDL_WINDOWEVENT_RESIZED)
611 if (!video.fullscreen_enabled)
613 int new_window_width = event->data1;
614 int new_window_height = event->data2;
616 // if window size has changed after resizing, calculate new scaling factor
617 if (new_window_width != video.window_width ||
618 new_window_height != video.window_height)
620 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
621 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
623 // (extreme window scaling allowed, but cannot be saved permanently)
624 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
625 setup.window_scaling_percent =
626 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
627 MAX_WINDOW_SCALING_PERCENT);
629 video.window_width = new_window_width;
630 video.window_height = new_window_height;
632 if (game_status == GAME_MODE_SETUP)
633 RedrawSetupScreenAfterFullscreenToggle();
635 UpdateMousePosition();
640 #if defined(PLATFORM_ANDROID)
643 int new_display_width = event->data1;
644 int new_display_height = event->data2;
646 // if fullscreen display size has changed, device has been rotated
647 if (new_display_width != video.display_width ||
648 new_display_height != video.display_height)
650 int nr = GRID_ACTIVE_NR(); // previous screen orientation
652 video.display_width = new_display_width;
653 video.display_height = new_display_height;
655 SDLSetScreenProperties();
657 // check if screen orientation has changed (should always be true here)
658 if (nr != GRID_ACTIVE_NR())
662 if (game_status == GAME_MODE_SETUP)
663 RedrawSetupScreenAfterScreenRotation(nr);
665 nr = GRID_ACTIVE_NR();
667 overlay.grid_xsize = setup.touch.grid_xsize[nr];
668 overlay.grid_ysize = setup.touch.grid_ysize[nr];
670 for (x = 0; x < MAX_GRID_XSIZE; x++)
671 for (y = 0; y < MAX_GRID_YSIZE; y++)
672 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
680 #define NUM_TOUCH_FINGERS 3
685 SDL_FingerID finger_id;
689 } touch_info[NUM_TOUCH_FINGERS];
691 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
693 int x = event->x * overlay.grid_xsize;
694 int y = event->y * overlay.grid_ysize;
695 int grid_button = overlay.grid_button[x][y];
696 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
697 Key key = GetKeyFromGridButton(grid_button);
698 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
700 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
704 // for any touch input event, enable overlay buttons (if activated)
705 SetOverlayEnabled(TRUE);
707 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
708 getKeyNameFromKey(key), key_status_name, event->fingerId);
710 if (key_status == KEY_PRESSED)
711 overlay.grid_button_action |= grid_button_action;
713 overlay.grid_button_action &= ~grid_button_action;
715 // check if we already know this touch event's finger id
716 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
718 if (touch_info[i].touched &&
719 touch_info[i].finger_id == event->fingerId)
721 // Error(ERR_DEBUG, "MARK 1: %d", i);
727 if (i >= NUM_TOUCH_FINGERS)
729 if (key_status == KEY_PRESSED)
731 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
733 // unknown finger id -- get new, empty slot, if available
734 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
736 if (touch_info[i].counter < oldest_counter)
739 oldest_counter = touch_info[i].counter;
741 // Error(ERR_DEBUG, "MARK 2: %d", i);
744 if (!touch_info[i].touched)
746 // Error(ERR_DEBUG, "MARK 3: %d", i);
752 if (i >= NUM_TOUCH_FINGERS)
754 // all slots allocated -- use oldest slot
757 // Error(ERR_DEBUG, "MARK 4: %d", i);
762 // release of previously unknown key (should not happen)
764 if (key != KSYM_UNDEFINED)
766 HandleKey(key, KEY_RELEASED);
768 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
769 getKeyNameFromKey(key), "KEY_RELEASED", i);
774 if (i < NUM_TOUCH_FINGERS)
776 if (key_status == KEY_PRESSED)
778 if (touch_info[i].key != key)
780 if (touch_info[i].key != KSYM_UNDEFINED)
782 HandleKey(touch_info[i].key, KEY_RELEASED);
784 // undraw previous grid button when moving finger away
785 overlay.grid_button_action &= ~touch_info[i].action;
787 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
788 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
791 if (key != KSYM_UNDEFINED)
793 HandleKey(key, KEY_PRESSED);
795 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
796 getKeyNameFromKey(key), "KEY_PRESSED", i);
800 touch_info[i].touched = TRUE;
801 touch_info[i].finger_id = event->fingerId;
802 touch_info[i].counter = Counter();
803 touch_info[i].key = key;
804 touch_info[i].action = grid_button_action;
808 if (touch_info[i].key != KSYM_UNDEFINED)
810 HandleKey(touch_info[i].key, KEY_RELEASED);
812 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
813 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
816 touch_info[i].touched = FALSE;
817 touch_info[i].finger_id = 0;
818 touch_info[i].counter = 0;
819 touch_info[i].key = 0;
820 touch_info[i].action = JOY_NO_ACTION;
825 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
827 static Key motion_key_x = KSYM_UNDEFINED;
828 static Key motion_key_y = KSYM_UNDEFINED;
829 static Key button_key = KSYM_UNDEFINED;
830 static float motion_x1, motion_y1;
831 static float button_x1, button_y1;
832 static SDL_FingerID motion_id = -1;
833 static SDL_FingerID button_id = -1;
834 int move_trigger_distance_percent = setup.touch.move_distance;
835 int drop_trigger_distance_percent = setup.touch.drop_distance;
836 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
837 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
838 float event_x = event->x;
839 float event_y = event->y;
841 if (event->type == EVENT_FINGERPRESS)
843 if (event_x > 1.0 / 3.0)
847 motion_id = event->fingerId;
852 motion_key_x = KSYM_UNDEFINED;
853 motion_key_y = KSYM_UNDEFINED;
855 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
861 button_id = event->fingerId;
866 button_key = setup.input[0].key.snap;
868 HandleKey(button_key, KEY_PRESSED);
870 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
873 else if (event->type == EVENT_FINGERRELEASE)
875 if (event->fingerId == motion_id)
879 if (motion_key_x != KSYM_UNDEFINED)
880 HandleKey(motion_key_x, KEY_RELEASED);
881 if (motion_key_y != KSYM_UNDEFINED)
882 HandleKey(motion_key_y, KEY_RELEASED);
884 motion_key_x = KSYM_UNDEFINED;
885 motion_key_y = KSYM_UNDEFINED;
887 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
889 else if (event->fingerId == button_id)
893 if (button_key != KSYM_UNDEFINED)
894 HandleKey(button_key, KEY_RELEASED);
896 button_key = KSYM_UNDEFINED;
898 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
901 else if (event->type == EVENT_FINGERMOTION)
903 if (event->fingerId == motion_id)
905 float distance_x = ABS(event_x - motion_x1);
906 float distance_y = ABS(event_y - motion_y1);
907 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
908 event_x > motion_x1 ? setup.input[0].key.right :
910 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
911 event_y > motion_y1 ? setup.input[0].key.down :
914 if (distance_x < move_trigger_distance / 2 ||
915 distance_x < distance_y)
916 new_motion_key_x = KSYM_UNDEFINED;
918 if (distance_y < move_trigger_distance / 2 ||
919 distance_y < distance_x)
920 new_motion_key_y = KSYM_UNDEFINED;
922 if (distance_x > move_trigger_distance ||
923 distance_y > move_trigger_distance)
925 if (new_motion_key_x != motion_key_x)
927 if (motion_key_x != KSYM_UNDEFINED)
928 HandleKey(motion_key_x, KEY_RELEASED);
929 if (new_motion_key_x != KSYM_UNDEFINED)
930 HandleKey(new_motion_key_x, KEY_PRESSED);
933 if (new_motion_key_y != motion_key_y)
935 if (motion_key_y != KSYM_UNDEFINED)
936 HandleKey(motion_key_y, KEY_RELEASED);
937 if (new_motion_key_y != KSYM_UNDEFINED)
938 HandleKey(new_motion_key_y, KEY_PRESSED);
944 motion_key_x = new_motion_key_x;
945 motion_key_y = new_motion_key_y;
947 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
950 else if (event->fingerId == button_id)
952 float distance_x = ABS(event_x - button_x1);
953 float distance_y = ABS(event_y - button_y1);
955 if (distance_x < drop_trigger_distance / 2 &&
956 distance_y > drop_trigger_distance)
958 if (button_key == setup.input[0].key.snap)
959 HandleKey(button_key, KEY_RELEASED);
964 button_key = setup.input[0].key.drop;
966 HandleKey(button_key, KEY_PRESSED);
968 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
974 void HandleFingerEvent(FingerEvent *event)
976 #if DEBUG_EVENTS_FINGER
977 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
978 event->type == EVENT_FINGERPRESS ? "pressed" :
979 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
983 event->dx, event->dy,
987 runtime.uses_touch_device = TRUE;
989 if (game_status != GAME_MODE_PLAYING)
992 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
994 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
995 local_player->mouse_action.button_hint =
996 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
997 event->x < 0.5 ? MB_LEFTBUTTON :
998 event->x > 0.5 ? MB_RIGHTBUTTON :
1004 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1005 HandleFingerEvent_VirtualButtons(event);
1006 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1007 HandleFingerEvent_WipeGestures(event);
1010 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1012 static int old_mx = 0, old_my = 0;
1013 static int last_button = MB_LEFTBUTTON;
1014 static boolean touched = FALSE;
1015 static boolean tapped = FALSE;
1017 // screen tile was tapped (but finger not touching the screen anymore)
1018 // (this point will also be reached without receiving a touch event)
1019 if (tapped && !touched)
1021 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1026 // stop here if this function was not triggered by a touch event
1030 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1032 // finger started touching the screen
1042 ClearPlayerMouseAction();
1044 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1047 else if (button == MB_RELEASED && touched)
1049 // finger stopped touching the screen
1054 SetPlayerMouseAction(old_mx, old_my, last_button);
1056 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1058 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1063 // finger moved while touching the screen
1065 int old_x = getLevelFromScreenX(old_mx);
1066 int old_y = getLevelFromScreenY(old_my);
1067 int new_x = getLevelFromScreenX(mx);
1068 int new_y = getLevelFromScreenY(my);
1070 if (new_x != old_x || new_y != old_y)
1075 // finger moved left or right from (horizontal) starting position
1077 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1079 SetPlayerMouseAction(old_mx, old_my, button_nr);
1081 last_button = button_nr;
1083 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1087 // finger stays at or returned to (horizontal) starting position
1089 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1091 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1096 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1098 static int old_mx = 0, old_my = 0;
1099 static int last_button = MB_LEFTBUTTON;
1100 static boolean touched = FALSE;
1101 static boolean tapped = FALSE;
1103 // screen tile was tapped (but finger not touching the screen anymore)
1104 // (this point will also be reached without receiving a touch event)
1105 if (tapped && !touched)
1107 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1112 // stop here if this function was not triggered by a touch event
1116 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1118 // finger started touching the screen
1128 ClearPlayerMouseAction();
1130 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1133 else if (button == MB_RELEASED && touched)
1135 // finger stopped touching the screen
1140 SetPlayerMouseAction(old_mx, old_my, last_button);
1142 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1144 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1149 // finger moved while touching the screen
1151 int old_x = getLevelFromScreenX(old_mx);
1152 int old_y = getLevelFromScreenY(old_my);
1153 int new_x = getLevelFromScreenX(mx);
1154 int new_y = getLevelFromScreenY(my);
1156 if (new_x != old_x || new_y != old_y)
1158 // finger moved away from starting position
1160 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1162 // quickly alternate between clicking and releasing for maximum speed
1163 if (FrameCounter % 2 == 0)
1164 button_nr = MB_RELEASED;
1166 SetPlayerMouseAction(old_mx, old_my, button_nr);
1169 last_button = button_nr;
1173 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1177 // finger stays at or returned to starting position
1179 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1181 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1186 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1188 static int old_mx = 0, old_my = 0;
1189 static Key motion_key_x = KSYM_UNDEFINED;
1190 static Key motion_key_y = KSYM_UNDEFINED;
1191 static boolean touched = FALSE;
1192 static boolean started_on_player = FALSE;
1193 static boolean player_is_dropping = FALSE;
1194 static int player_drop_count = 0;
1195 static int last_player_x = -1;
1196 static int last_player_y = -1;
1198 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1207 started_on_player = FALSE;
1208 player_is_dropping = FALSE;
1209 player_drop_count = 0;
1213 motion_key_x = KSYM_UNDEFINED;
1214 motion_key_y = KSYM_UNDEFINED;
1216 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1219 else if (button == MB_RELEASED && touched)
1226 if (motion_key_x != KSYM_UNDEFINED)
1227 HandleKey(motion_key_x, KEY_RELEASED);
1228 if (motion_key_y != KSYM_UNDEFINED)
1229 HandleKey(motion_key_y, KEY_RELEASED);
1231 if (started_on_player)
1233 if (player_is_dropping)
1235 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1237 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1241 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1243 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1247 motion_key_x = KSYM_UNDEFINED;
1248 motion_key_y = KSYM_UNDEFINED;
1250 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1255 int src_x = local_player->jx;
1256 int src_y = local_player->jy;
1257 int dst_x = getLevelFromScreenX(old_mx);
1258 int dst_y = getLevelFromScreenY(old_my);
1259 int dx = dst_x - src_x;
1260 int dy = dst_y - src_y;
1261 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1262 dx > 0 ? setup.input[0].key.right :
1264 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1265 dy > 0 ? setup.input[0].key.down :
1268 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1269 (last_player_x != local_player->jx ||
1270 last_player_y != local_player->jy))
1272 // in case of asymmetric diagonal movement, use "preferred" direction
1274 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1276 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1277 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1279 local_player->last_move_dir = last_move_dir;
1281 // (required to prevent accidentally forcing direction for next movement)
1282 last_player_x = local_player->jx;
1283 last_player_y = local_player->jy;
1286 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1288 started_on_player = TRUE;
1289 player_drop_count = getPlayerInventorySize(0);
1290 player_is_dropping = (player_drop_count > 0);
1292 if (player_is_dropping)
1294 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1296 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1300 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1302 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1305 else if (dx != 0 || dy != 0)
1307 if (player_is_dropping &&
1308 player_drop_count == getPlayerInventorySize(0))
1310 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1312 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1313 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1315 player_is_dropping = FALSE;
1319 if (new_motion_key_x != motion_key_x)
1321 Error(ERR_DEBUG, "---------- %s %s ----------",
1322 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1323 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1325 if (motion_key_x != KSYM_UNDEFINED)
1326 HandleKey(motion_key_x, KEY_RELEASED);
1327 if (new_motion_key_x != KSYM_UNDEFINED)
1328 HandleKey(new_motion_key_x, KEY_PRESSED);
1331 if (new_motion_key_y != motion_key_y)
1333 Error(ERR_DEBUG, "---------- %s %s ----------",
1334 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1335 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1337 if (motion_key_y != KSYM_UNDEFINED)
1338 HandleKey(motion_key_y, KEY_RELEASED);
1339 if (new_motion_key_y != KSYM_UNDEFINED)
1340 HandleKey(new_motion_key_y, KEY_PRESSED);
1343 motion_key_x = new_motion_key_x;
1344 motion_key_y = new_motion_key_y;
1348 static void HandleButtonOrFinger(int mx, int my, int button)
1350 if (game_status != GAME_MODE_PLAYING)
1353 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1355 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1356 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1357 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1358 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1359 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1360 SetPlayerMouseAction(mx, my, button); // special case
1364 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1365 HandleButtonOrFinger_FollowFinger(mx, my, button);
1369 static boolean checkTextInputKeyModState(void)
1371 // when playing, only handle raw key events and ignore text input
1372 if (game_status == GAME_MODE_PLAYING)
1375 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1378 void HandleTextEvent(TextEvent *event)
1380 char *text = event->text;
1381 Key key = getKeyFromKeyName(text);
1383 #if DEBUG_EVENTS_TEXT
1384 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1387 text[0], (int)(text[0]),
1389 getKeyNameFromKey(key),
1393 #if !defined(HAS_SCREEN_KEYBOARD)
1394 // non-mobile devices: only handle key input with modifier keys pressed here
1395 // (every other key input is handled directly as physical key input event)
1396 if (!checkTextInputKeyModState())
1400 // process text input as "classic" (with uppercase etc.) key input event
1401 HandleKey(key, KEY_PRESSED);
1402 HandleKey(key, KEY_RELEASED);
1405 void HandlePauseResumeEvent(PauseResumeEvent *event)
1407 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1411 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1417 void HandleKeyEvent(KeyEvent *event)
1419 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1420 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1421 Key key = GetEventKey(event, with_modifiers);
1422 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1424 #if DEBUG_EVENTS_KEY
1425 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1426 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1427 event->keysym.scancode,
1432 getKeyNameFromKey(key));
1435 #if defined(PLATFORM_ANDROID)
1436 if (key == KSYM_Back)
1438 // always map the "back" button to the "escape" key on Android devices
1441 else if (key == KSYM_Menu)
1443 // the "menu" button can be used to toggle displaying virtual buttons
1444 if (key_status == KEY_PRESSED)
1445 SetOverlayEnabled(!GetOverlayEnabled());
1449 // for any other "real" key event, disable virtual buttons
1450 SetOverlayEnabled(FALSE);
1454 HandleKeyModState(keymod, key_status);
1456 // only handle raw key input without text modifier keys pressed
1457 if (!checkTextInputKeyModState())
1458 HandleKey(key, key_status);
1461 static int HandleDropFileEvent(char *filename)
1463 Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
1465 // check and extract dropped zip files into correct user data directory
1466 if (!strSuffixLower(filename, ".zip"))
1468 Error(ERR_WARN, "file '%s' not supported", filename);
1470 return TREE_TYPE_UNDEFINED;
1473 TreeInfo *tree_node = NULL;
1474 int tree_type = GetZipFileTreeType(filename);
1475 char *directory = TREE_USERDIR(tree_type);
1477 if (directory == NULL)
1479 Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1481 return TREE_TYPE_UNDEFINED;
1484 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1485 game_status == GAME_MODE_LEVELS &&
1486 leveldir_current->node_parent != NULL)
1488 // extract new level set next to currently selected level set
1489 tree_node = leveldir_current;
1491 // get parent directory of currently selected level set directory
1492 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1494 // use private level directory instead of top-level package level directory
1495 if (strPrefix(directory, options.level_directory) &&
1496 strEqual(leveldir_current->node_parent->fullpath, "."))
1497 directory = getUserLevelDir(NULL);
1500 // extract level or artwork set from zip file to target directory
1501 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1503 if (top_dir == NULL)
1505 // error message already issued by "ExtractZipFileIntoDirectory()"
1507 return TREE_TYPE_UNDEFINED;
1510 // add extracted level or artwork set to tree info structure
1511 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1513 // update menu screen (and possibly change current level set)
1514 DrawScreenAfterAddingSet(top_dir, tree_type);
1519 static void HandleDropTextEvent(char *text)
1521 Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1524 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1525 int num_artwork_sets_succeeded,
1526 int num_files_failed)
1528 // only show request dialog if no other request dialog already active
1529 if (game.request_active)
1532 // this case can happen with drag-and-drop with older SDL versions
1533 if (num_level_sets_succeeded == 0 &&
1534 num_artwork_sets_succeeded == 0 &&
1535 num_files_failed == 0)
1540 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1542 char message_part1[50];
1544 sprintf(message_part1, "New %s set%s added",
1545 (num_artwork_sets_succeeded == 0 ? "level" :
1546 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1547 (num_level_sets_succeeded +
1548 num_artwork_sets_succeeded > 1 ? "s" : ""));
1550 if (num_files_failed > 0)
1551 sprintf(message, "%s, but %d dropped file%s failed!",
1552 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1554 sprintf(message, "%s!", message_part1);
1556 else if (num_files_failed > 0)
1558 sprintf(message, "Failed to process dropped file%s!",
1559 num_files_failed > 1 ? "s" : "");
1562 Request(message, REQ_CONFIRM);
1565 void HandleDropEvent(Event *event)
1567 static boolean confirm_on_drop_complete = FALSE;
1568 static int num_level_sets_succeeded = 0;
1569 static int num_artwork_sets_succeeded = 0;
1570 static int num_files_failed = 0;
1572 switch (event->type)
1576 confirm_on_drop_complete = TRUE;
1577 num_level_sets_succeeded = 0;
1578 num_artwork_sets_succeeded = 0;
1579 num_files_failed = 0;
1586 int tree_type = HandleDropFileEvent(event->drop.file);
1588 if (tree_type == TREE_TYPE_LEVEL_DIR)
1589 num_level_sets_succeeded++;
1590 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1591 tree_type == TREE_TYPE_SOUNDS_DIR ||
1592 tree_type == TREE_TYPE_MUSIC_DIR)
1593 num_artwork_sets_succeeded++;
1597 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1598 if (!confirm_on_drop_complete)
1600 // process all remaining events, including further SDL_DROPFILE events
1603 HandleDropCompleteEvent(num_level_sets_succeeded,
1604 num_artwork_sets_succeeded,
1607 num_level_sets_succeeded = 0;
1608 num_artwork_sets_succeeded = 0;
1609 num_files_failed = 0;
1617 HandleDropTextEvent(event->drop.file);
1622 case SDL_DROPCOMPLETE:
1624 HandleDropCompleteEvent(num_level_sets_succeeded,
1625 num_artwork_sets_succeeded,
1632 if (event->drop.file != NULL)
1633 SDL_free(event->drop.file);
1636 void HandleUserEvent(UserEvent *event)
1638 switch (event->code)
1640 case USEREVENT_ANIM_DELAY_ACTION:
1641 case USEREVENT_ANIM_EVENT_ACTION:
1642 // execute action functions until matching action was found
1643 if (DoKeysymAction(event->value1) ||
1644 DoGadgetAction(event->value1) ||
1645 DoScreenAction(event->value1))
1654 void HandleButton(int mx, int my, int button, int button_nr)
1656 static int old_mx = 0, old_my = 0;
1657 boolean button_hold = FALSE;
1658 boolean handle_gadgets = TRUE;
1664 button_nr = -button_nr;
1673 #if defined(PLATFORM_ANDROID)
1674 // when playing, only handle gadgets when using "follow finger" controls
1675 // or when using touch controls in combination with the MM game engine
1676 // or when using gadgets that do not overlap with virtual buttons
1678 (game_status != GAME_MODE_PLAYING ||
1679 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1680 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1681 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1682 !CheckVirtualButtonPressed(mx, my, button)));
1684 // always recognize potentially releasing already pressed gadgets
1685 if (button == MB_RELEASED)
1686 handle_gadgets = TRUE;
1689 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1691 // do not handle this button event anymore
1692 return; // force mouse event not to be handled at all
1695 if (handle_gadgets && HandleGadgets(mx, my, button))
1697 // do not handle this button event anymore
1698 mx = my = -32; // force mouse event to be outside screen tiles
1701 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1704 // do not use scroll wheel button events for anything other than gadgets
1705 if (IS_WHEEL_BUTTON(button_nr))
1708 switch (game_status)
1710 case GAME_MODE_TITLE:
1711 HandleTitleScreen(mx, my, 0, 0, button);
1714 case GAME_MODE_MAIN:
1715 HandleMainMenu(mx, my, 0, 0, button);
1718 case GAME_MODE_PSEUDO_TYPENAME:
1719 HandleTypeName(0, KSYM_Return);
1722 case GAME_MODE_LEVELS:
1723 HandleChooseLevelSet(mx, my, 0, 0, button);
1726 case GAME_MODE_LEVELNR:
1727 HandleChooseLevelNr(mx, my, 0, 0, button);
1730 case GAME_MODE_SCORES:
1731 HandleHallOfFame(0, 0, 0, 0, button);
1734 case GAME_MODE_EDITOR:
1735 HandleLevelEditorIdle();
1738 case GAME_MODE_INFO:
1739 HandleInfoScreen(mx, my, 0, 0, button);
1742 case GAME_MODE_SETUP:
1743 HandleSetupScreen(mx, my, 0, 0, button);
1746 case GAME_MODE_PLAYING:
1747 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1748 HandleButtonOrFinger(mx, my, button);
1750 SetPlayerMouseAction(mx, my, button);
1753 if (button == MB_PRESSED && !motion_status && !button_hold &&
1754 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1755 DumpTileFromScreen(mx, my);
1765 static boolean is_string_suffix(char *string, char *suffix)
1767 int string_len = strlen(string);
1768 int suffix_len = strlen(suffix);
1770 if (suffix_len > string_len)
1773 return (strEqual(&string[string_len - suffix_len], suffix));
1776 #define MAX_CHEAT_INPUT_LEN 32
1778 static void HandleKeysSpecial(Key key)
1780 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1781 char letter = getCharFromKey(key);
1782 int cheat_input_len = strlen(cheat_input);
1788 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1790 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1791 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1793 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1796 cheat_input[cheat_input_len++] = letter;
1797 cheat_input[cheat_input_len] = '\0';
1799 #if DEBUG_EVENTS_KEY
1800 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1803 if (game_status == GAME_MODE_MAIN)
1805 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1806 is_string_suffix(cheat_input, ":ist"))
1808 InsertSolutionTape();
1810 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1811 is_string_suffix(cheat_input, ":pst"))
1815 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1816 is_string_suffix(cheat_input, ":rg"))
1818 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1821 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1822 is_string_suffix(cheat_input, ":rs"))
1824 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1827 else if (is_string_suffix(cheat_input, ":reload-music") ||
1828 is_string_suffix(cheat_input, ":rm"))
1830 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1833 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1834 is_string_suffix(cheat_input, ":ra"))
1836 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1837 1 << ARTWORK_TYPE_SOUNDS |
1838 1 << ARTWORK_TYPE_MUSIC);
1841 else if (is_string_suffix(cheat_input, ":dump-level") ||
1842 is_string_suffix(cheat_input, ":dl"))
1846 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1847 is_string_suffix(cheat_input, ":dt"))
1851 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1852 is_string_suffix(cheat_input, ":ft"))
1854 /* fix single-player tapes that contain player input for more than one
1855 player (due to a bug in 3.3.1.2 and earlier versions), which results
1856 in playing levels with more than one player in multi-player mode,
1857 even though the tape was originally recorded in single-player mode */
1859 // remove player input actions for all players but the first one
1860 for (i = 1; i < MAX_PLAYERS; i++)
1861 tape.player_participates[i] = FALSE;
1863 tape.changed = TRUE;
1865 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1866 is_string_suffix(cheat_input, ":snl"))
1868 SaveNativeLevel(&level);
1870 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1871 is_string_suffix(cheat_input, ":fps"))
1873 global.show_frames_per_second = !global.show_frames_per_second;
1876 else if (game_status == GAME_MODE_PLAYING)
1879 if (is_string_suffix(cheat_input, ".q"))
1880 DEBUG_SetMaximumDynamite();
1883 else if (game_status == GAME_MODE_EDITOR)
1885 if (is_string_suffix(cheat_input, ":dump-brush") ||
1886 is_string_suffix(cheat_input, ":DB"))
1890 else if (is_string_suffix(cheat_input, ":DDB"))
1895 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1897 if (letter == 'x') // copy brush to clipboard (small size)
1899 CopyBrushToClipboard_Small();
1901 else if (letter == 'c') // copy brush to clipboard (normal size)
1903 CopyBrushToClipboard();
1905 else if (letter == 'v') // paste brush from Clipboard
1907 CopyClipboardToBrush();
1909 else if (letter == 'z') // undo or redo last operation
1911 if (GetKeyModState() & KMOD_Shift)
1912 RedoLevelEditorOperation();
1914 UndoLevelEditorOperation();
1919 // special key shortcuts for all game modes
1920 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1921 is_string_suffix(cheat_input, ":dea") ||
1922 is_string_suffix(cheat_input, ":DEA"))
1924 DumpGadgetIdentifiers();
1925 DumpScreenIdentifiers();
1929 boolean HandleKeysDebug(Key key, int key_status)
1934 if (key_status != KEY_PRESSED)
1937 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1939 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1941 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1943 if (key == setup.debug.frame_delay_key[i] &&
1944 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1946 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1947 setup.debug.frame_delay[i] : setup.game_frame_delay);
1949 if (!setup.debug.frame_delay_game_only)
1950 MenuFrameDelay = GameFrameDelay;
1952 SetVideoFrameDelay(GameFrameDelay);
1954 if (GameFrameDelay > ONE_SECOND_DELAY)
1955 Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
1956 else if (GameFrameDelay != 0)
1957 Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
1958 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1959 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1961 Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
1968 if (game_status == GAME_MODE_PLAYING)
1972 options.debug = !options.debug;
1974 Error(ERR_INFO, "debug mode %s",
1975 (options.debug ? "enabled" : "disabled"));
1979 else if (key == KSYM_v)
1981 Error(ERR_INFO, "currently using game engine version %d",
1982 game.engine_version);
1992 void HandleKey(Key key, int key_status)
1994 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1995 static boolean ignore_repeated_key = FALSE;
1996 static struct SetupKeyboardInfo ski;
1997 static struct SetupShortcutInfo ssi;
2006 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
2007 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
2008 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
2009 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
2010 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2011 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2016 if (HandleKeysDebug(key, key_status))
2017 return; // do not handle already processed keys again
2019 // map special keys (media keys / remote control buttons) to default keys
2020 if (key == KSYM_PlayPause)
2022 else if (key == KSYM_Select)
2025 HandleSpecialGameControllerKeys(key, key_status);
2027 if (game_status == GAME_MODE_PLAYING)
2029 // only needed for single-step tape recording mode
2030 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2033 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2035 byte key_action = 0;
2036 byte key_snap_action = 0;
2038 if (setup.input[pnr].use_joystick)
2041 ski = setup.input[pnr].key;
2043 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2044 if (key == *key_info[i].key_custom)
2045 key_action |= key_info[i].action;
2047 // use combined snap+direction keys for the first player only
2050 ssi = setup.shortcut;
2052 // also remember normal snap key when handling snap+direction keys
2053 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2055 for (i = 0; i < NUM_DIRECTIONS; i++)
2057 if (key == *key_info[i].key_snap)
2059 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2060 key_snap_action |= key_info[i].action;
2065 if (key_status == KEY_PRESSED)
2067 stored_player[pnr].action |= key_action;
2068 stored_player[pnr].snap_action |= key_snap_action;
2072 stored_player[pnr].action &= ~key_action;
2073 stored_player[pnr].snap_action &= ~key_snap_action;
2076 // restore snap action if one of several pressed snap keys was released
2077 if (stored_player[pnr].snap_action)
2078 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2080 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2082 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2084 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2086 // if snap key already pressed, keep pause mode when releasing
2087 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2088 has_snapped[pnr] = TRUE;
2090 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2092 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2094 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2095 getRedDiskReleaseFlag_SP() == 0)
2097 // add a single inactive frame before dropping starts
2098 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2099 stored_player[pnr].force_dropping = TRUE;
2102 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2104 // if snap key was pressed without direction, leave pause mode
2105 if (!has_snapped[pnr])
2106 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2108 has_snapped[pnr] = FALSE;
2111 else if (tape.recording && tape.pausing && !tape.use_mouse)
2113 // prevent key release events from un-pausing a paused game
2114 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2115 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2118 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2119 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2125 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2126 if (key == key_info[i].key_default)
2127 joy |= key_info[i].action;
2132 if (key_status == KEY_PRESSED)
2133 key_joystick_mapping |= joy;
2135 key_joystick_mapping &= ~joy;
2140 if (game_status != GAME_MODE_PLAYING)
2141 key_joystick_mapping = 0;
2143 if (key_status == KEY_RELEASED)
2145 // reset flag to ignore repeated "key pressed" events after key release
2146 ignore_repeated_key = FALSE;
2151 if ((key == KSYM_F11 ||
2152 ((key == KSYM_Return ||
2153 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2154 video.fullscreen_available &&
2155 !ignore_repeated_key)
2157 setup.fullscreen = !setup.fullscreen;
2159 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2161 if (game_status == GAME_MODE_SETUP)
2162 RedrawSetupScreenAfterFullscreenToggle();
2164 UpdateMousePosition();
2166 // set flag to ignore repeated "key pressed" events
2167 ignore_repeated_key = TRUE;
2172 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2173 key == KSYM_minus || key == KSYM_KP_Subtract ||
2174 key == KSYM_plus || key == KSYM_KP_Add ||
2175 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2176 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2177 video.window_scaling_available &&
2178 !video.fullscreen_enabled)
2180 if (key == KSYM_0 || key == KSYM_KP_0)
2181 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2182 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2183 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2185 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2187 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2188 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2189 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2190 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2192 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2194 if (game_status == GAME_MODE_SETUP)
2195 RedrawSetupScreenAfterFullscreenToggle();
2197 UpdateMousePosition();
2202 // some key events are handled like clicks for global animations
2203 boolean click = (key == KSYM_space ||
2204 key == KSYM_Return ||
2205 key == KSYM_Escape);
2207 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2209 // do not handle this key event anymore
2210 if (key != KSYM_Escape) // always allow ESC key to be handled
2214 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2215 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2222 if (game_status == GAME_MODE_MAIN &&
2223 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2225 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2230 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2232 if (key == setup.shortcut.save_game)
2234 else if (key == setup.shortcut.load_game)
2236 else if (key == setup.shortcut.toggle_pause)
2237 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2239 HandleTapeButtonKeys(key);
2240 HandleSoundButtonKeys(key);
2243 if (game_status == GAME_MODE_PLAYING && !network_playing)
2245 int centered_player_nr_next = -999;
2247 if (key == setup.shortcut.focus_player_all)
2248 centered_player_nr_next = -1;
2250 for (i = 0; i < MAX_PLAYERS; i++)
2251 if (key == setup.shortcut.focus_player[i])
2252 centered_player_nr_next = i;
2254 if (centered_player_nr_next != -999)
2256 game.centered_player_nr_next = centered_player_nr_next;
2257 game.set_centered_player = TRUE;
2261 tape.centered_player_nr_next = game.centered_player_nr_next;
2262 tape.set_centered_player = TRUE;
2267 HandleKeysSpecial(key);
2269 if (HandleGadgetsKeyInput(key))
2270 return; // do not handle already processed keys again
2272 switch (game_status)
2274 case GAME_MODE_PSEUDO_TYPENAME:
2275 HandleTypeName(0, key);
2278 case GAME_MODE_TITLE:
2279 case GAME_MODE_MAIN:
2280 case GAME_MODE_LEVELS:
2281 case GAME_MODE_LEVELNR:
2282 case GAME_MODE_SETUP:
2283 case GAME_MODE_INFO:
2284 case GAME_MODE_SCORES:
2286 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2293 if (game_status == GAME_MODE_TITLE)
2294 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2295 else if (game_status == GAME_MODE_MAIN)
2296 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2297 else if (game_status == GAME_MODE_LEVELS)
2298 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2299 else if (game_status == GAME_MODE_LEVELNR)
2300 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2301 else if (game_status == GAME_MODE_SETUP)
2302 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2303 else if (game_status == GAME_MODE_INFO)
2304 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2305 else if (game_status == GAME_MODE_SCORES)
2306 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2310 if (game_status != GAME_MODE_MAIN)
2311 FadeSkipNextFadeIn();
2313 if (game_status == GAME_MODE_TITLE)
2314 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2315 else if (game_status == GAME_MODE_LEVELS)
2316 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2317 else if (game_status == GAME_MODE_LEVELNR)
2318 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2319 else if (game_status == GAME_MODE_SETUP)
2320 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2321 else if (game_status == GAME_MODE_INFO)
2322 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2323 else if (game_status == GAME_MODE_SCORES)
2324 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2328 if (game_status == GAME_MODE_LEVELS)
2329 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2330 else if (game_status == GAME_MODE_LEVELNR)
2331 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2332 else if (game_status == GAME_MODE_SETUP)
2333 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2334 else if (game_status == GAME_MODE_INFO)
2335 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2336 else if (game_status == GAME_MODE_SCORES)
2337 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2340 case KSYM_Page_Down:
2341 if (game_status == GAME_MODE_LEVELS)
2342 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2343 else if (game_status == GAME_MODE_LEVELNR)
2344 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2345 else if (game_status == GAME_MODE_SETUP)
2346 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2347 else if (game_status == GAME_MODE_INFO)
2348 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2349 else if (game_status == GAME_MODE_SCORES)
2350 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2358 case GAME_MODE_EDITOR:
2359 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2360 HandleLevelEditorKeyInput(key);
2363 case GAME_MODE_PLAYING:
2368 RequestQuitGame(setup.ask_on_escape);
2378 if (key == KSYM_Escape)
2380 SetGameStatus(GAME_MODE_MAIN);
2389 void HandleNoEvent(void)
2391 HandleMouseCursor();
2393 switch (game_status)
2395 case GAME_MODE_PLAYING:
2396 HandleButtonOrFinger(-1, -1, -1);
2401 void HandleEventActions(void)
2403 // if (button_status && game_status != GAME_MODE_PLAYING)
2404 if (button_status && (game_status != GAME_MODE_PLAYING ||
2406 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2408 HandleButton(0, 0, button_status, -button_status);
2415 if (network.enabled)
2418 switch (game_status)
2420 case GAME_MODE_MAIN:
2421 DrawPreviewLevelAnimation();
2424 case GAME_MODE_EDITOR:
2425 HandleLevelEditorIdle();
2433 static void HandleTileCursor(int dx, int dy, int button)
2436 ClearPlayerMouseAction();
2443 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2444 (dx < 0 ? MB_LEFTBUTTON :
2445 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2447 else if (!tile_cursor.moving)
2449 int old_xpos = tile_cursor.xpos;
2450 int old_ypos = tile_cursor.ypos;
2451 int new_xpos = old_xpos;
2452 int new_ypos = old_ypos;
2454 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2455 new_xpos = old_xpos + dx;
2457 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2458 new_ypos = old_ypos + dy;
2460 SetTileCursorTargetXY(new_xpos, new_ypos);
2464 static int HandleJoystickForAllPlayers(void)
2468 boolean no_joysticks_configured = TRUE;
2469 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2470 static byte joy_action_last[MAX_PLAYERS];
2472 for (i = 0; i < MAX_PLAYERS; i++)
2473 if (setup.input[i].use_joystick)
2474 no_joysticks_configured = FALSE;
2476 // if no joysticks configured, map connected joysticks to players
2477 if (no_joysticks_configured)
2478 use_as_joystick_nr = TRUE;
2480 for (i = 0; i < MAX_PLAYERS; i++)
2482 byte joy_action = 0;
2484 joy_action = JoystickExt(i, use_as_joystick_nr);
2485 result |= joy_action;
2487 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2488 joy_action != joy_action_last[i])
2489 stored_player[i].action = joy_action;
2491 joy_action_last[i] = joy_action;
2497 void HandleJoystick(void)
2499 static unsigned int joytest_delay = 0;
2500 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2501 static int joytest_last = 0;
2502 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2503 int delay_value = GADGET_FRAME_DELAY;
2504 int joystick = HandleJoystickForAllPlayers();
2505 int keyboard = key_joystick_mapping;
2506 int joy = (joystick | keyboard);
2507 int joytest = joystick;
2508 int left = joy & JOY_LEFT;
2509 int right = joy & JOY_RIGHT;
2510 int up = joy & JOY_UP;
2511 int down = joy & JOY_DOWN;
2512 int button = joy & JOY_BUTTON;
2513 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2514 int dx = (left ? -1 : right ? 1 : 0);
2515 int dy = (up ? -1 : down ? 1 : 0);
2516 boolean use_delay_value_first = (joytest != joytest_last);
2518 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2520 // do not handle this button event anymore
2524 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2525 anyTextGadgetActive()))
2527 // leave name input in main menu or text input gadget
2528 HandleKey(KSYM_Escape, KEY_PRESSED);
2529 HandleKey(KSYM_Escape, KEY_RELEASED);
2534 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2536 if (game_status == GAME_MODE_PLAYING)
2538 // when playing MM style levels, also use delay for keyboard events
2539 joytest |= keyboard;
2541 // only use first delay value for new events, but not for changed events
2542 use_delay_value_first = (!joytest != !joytest_last);
2544 // only use delay after the initial keyboard event
2548 // for any joystick or keyboard event, enable playfield tile cursor
2549 if (dx || dy || button)
2550 SetTileCursorEnabled(TRUE);
2553 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2555 // delay joystick/keyboard actions if axes/keys continually pressed
2556 newbutton = dx = dy = 0;
2560 // first start with longer delay, then continue with shorter delay
2561 joytest_delay_value =
2562 (use_delay_value_first ? delay_value_first : delay_value);
2565 joytest_last = joytest;
2567 switch (game_status)
2569 case GAME_MODE_TITLE:
2570 case GAME_MODE_MAIN:
2571 case GAME_MODE_LEVELS:
2572 case GAME_MODE_LEVELNR:
2573 case GAME_MODE_SETUP:
2574 case GAME_MODE_INFO:
2575 case GAME_MODE_SCORES:
2577 if (anyTextGadgetActive())
2580 if (game_status == GAME_MODE_TITLE)
2581 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2582 else if (game_status == GAME_MODE_MAIN)
2583 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2584 else if (game_status == GAME_MODE_LEVELS)
2585 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2586 else if (game_status == GAME_MODE_LEVELNR)
2587 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2588 else if (game_status == GAME_MODE_SETUP)
2589 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2590 else if (game_status == GAME_MODE_INFO)
2591 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2592 else if (game_status == GAME_MODE_SCORES)
2593 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2598 case GAME_MODE_PLAYING:
2600 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2601 if (tape.playing || keyboard)
2602 newbutton = ((joy & JOY_BUTTON) != 0);
2605 if (newbutton && game.all_players_gone)
2612 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2614 if (joystick & JOY_ACTION)
2615 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2617 else if (tape.recording && tape.pausing && !tape.use_mouse)
2619 if (joystick & JOY_ACTION)
2620 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2623 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2624 HandleTileCursor(dx, dy, button);
2633 void HandleSpecialGameControllerButtons(Event *event)
2638 switch (event->type)
2640 case SDL_CONTROLLERBUTTONDOWN:
2641 key_status = KEY_PRESSED;
2644 case SDL_CONTROLLERBUTTONUP:
2645 key_status = KEY_RELEASED;
2652 switch (event->cbutton.button)
2654 case SDL_CONTROLLER_BUTTON_START:
2658 case SDL_CONTROLLER_BUTTON_BACK:
2666 HandleKey(key, key_status);
2669 void HandleSpecialGameControllerKeys(Key key, int key_status)
2671 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2672 int button = SDL_CONTROLLER_BUTTON_INVALID;
2674 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2675 if (key == KSYM_Rewind)
2676 button = SDL_CONTROLLER_BUTTON_A;
2677 else if (key == KSYM_FastForward || key == KSYM_Menu)
2678 button = SDL_CONTROLLER_BUTTON_B;
2680 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2684 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2685 SDL_CONTROLLERBUTTONUP);
2687 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2688 event.cbutton.button = button;
2689 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2692 HandleJoystickEvent(&event);
2697 boolean DoKeysymAction(int keysym)
2701 Key key = (Key)(-keysym);
2703 HandleKey(key, KEY_PRESSED);
2704 HandleKey(key, KEY_RELEASED);