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)));
1685 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1687 // do not handle this button event anymore
1688 return; // force mouse event not to be handled at all
1691 if (handle_gadgets && HandleGadgets(mx, my, button))
1693 // do not handle this button event anymore
1694 mx = my = -32; // force mouse event to be outside screen tiles
1697 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1700 // do not use scroll wheel button events for anything other than gadgets
1701 if (IS_WHEEL_BUTTON(button_nr))
1704 switch (game_status)
1706 case GAME_MODE_TITLE:
1707 HandleTitleScreen(mx, my, 0, 0, button);
1710 case GAME_MODE_MAIN:
1711 HandleMainMenu(mx, my, 0, 0, button);
1714 case GAME_MODE_PSEUDO_TYPENAME:
1715 HandleTypeName(0, KSYM_Return);
1718 case GAME_MODE_LEVELS:
1719 HandleChooseLevelSet(mx, my, 0, 0, button);
1722 case GAME_MODE_LEVELNR:
1723 HandleChooseLevelNr(mx, my, 0, 0, button);
1726 case GAME_MODE_SCORES:
1727 HandleHallOfFame(0, 0, 0, 0, button);
1730 case GAME_MODE_EDITOR:
1731 HandleLevelEditorIdle();
1734 case GAME_MODE_INFO:
1735 HandleInfoScreen(mx, my, 0, 0, button);
1738 case GAME_MODE_SETUP:
1739 HandleSetupScreen(mx, my, 0, 0, button);
1742 case GAME_MODE_PLAYING:
1743 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1744 HandleButtonOrFinger(mx, my, button);
1746 SetPlayerMouseAction(mx, my, button);
1749 if (button == MB_PRESSED && !motion_status && !button_hold &&
1750 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1751 DumpTileFromScreen(mx, my);
1761 static boolean is_string_suffix(char *string, char *suffix)
1763 int string_len = strlen(string);
1764 int suffix_len = strlen(suffix);
1766 if (suffix_len > string_len)
1769 return (strEqual(&string[string_len - suffix_len], suffix));
1772 #define MAX_CHEAT_INPUT_LEN 32
1774 static void HandleKeysSpecial(Key key)
1776 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1777 char letter = getCharFromKey(key);
1778 int cheat_input_len = strlen(cheat_input);
1784 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1786 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1787 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1789 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1792 cheat_input[cheat_input_len++] = letter;
1793 cheat_input[cheat_input_len] = '\0';
1795 #if DEBUG_EVENTS_KEY
1796 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1799 if (game_status == GAME_MODE_MAIN)
1801 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1802 is_string_suffix(cheat_input, ":ist"))
1804 InsertSolutionTape();
1806 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1807 is_string_suffix(cheat_input, ":pst"))
1811 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1812 is_string_suffix(cheat_input, ":rg"))
1814 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1817 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1818 is_string_suffix(cheat_input, ":rs"))
1820 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1823 else if (is_string_suffix(cheat_input, ":reload-music") ||
1824 is_string_suffix(cheat_input, ":rm"))
1826 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1829 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1830 is_string_suffix(cheat_input, ":ra"))
1832 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1833 1 << ARTWORK_TYPE_SOUNDS |
1834 1 << ARTWORK_TYPE_MUSIC);
1837 else if (is_string_suffix(cheat_input, ":dump-level") ||
1838 is_string_suffix(cheat_input, ":dl"))
1842 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1843 is_string_suffix(cheat_input, ":dt"))
1847 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1848 is_string_suffix(cheat_input, ":ft"))
1850 /* fix single-player tapes that contain player input for more than one
1851 player (due to a bug in 3.3.1.2 and earlier versions), which results
1852 in playing levels with more than one player in multi-player mode,
1853 even though the tape was originally recorded in single-player mode */
1855 // remove player input actions for all players but the first one
1856 for (i = 1; i < MAX_PLAYERS; i++)
1857 tape.player_participates[i] = FALSE;
1859 tape.changed = TRUE;
1861 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1862 is_string_suffix(cheat_input, ":snl"))
1864 SaveNativeLevel(&level);
1866 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1867 is_string_suffix(cheat_input, ":fps"))
1869 global.show_frames_per_second = !global.show_frames_per_second;
1872 else if (game_status == GAME_MODE_PLAYING)
1875 if (is_string_suffix(cheat_input, ".q"))
1876 DEBUG_SetMaximumDynamite();
1879 else if (game_status == GAME_MODE_EDITOR)
1881 if (is_string_suffix(cheat_input, ":dump-brush") ||
1882 is_string_suffix(cheat_input, ":DB"))
1886 else if (is_string_suffix(cheat_input, ":DDB"))
1891 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1893 if (letter == 'x') // copy brush to clipboard (small size)
1895 CopyBrushToClipboard_Small();
1897 else if (letter == 'c') // copy brush to clipboard (normal size)
1899 CopyBrushToClipboard();
1901 else if (letter == 'v') // paste brush from Clipboard
1903 CopyClipboardToBrush();
1905 else if (letter == 'z') // undo or redo last operation
1907 if (GetKeyModState() & KMOD_Shift)
1908 RedoLevelEditorOperation();
1910 UndoLevelEditorOperation();
1915 // special key shortcuts for all game modes
1916 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1917 is_string_suffix(cheat_input, ":dea") ||
1918 is_string_suffix(cheat_input, ":DEA"))
1920 DumpGadgetIdentifiers();
1921 DumpScreenIdentifiers();
1925 boolean HandleKeysDebug(Key key, int key_status)
1930 if (key_status != KEY_PRESSED)
1933 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1935 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1937 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1939 if (key == setup.debug.frame_delay_key[i] &&
1940 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1942 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1943 setup.debug.frame_delay[i] : setup.game_frame_delay);
1945 if (!setup.debug.frame_delay_game_only)
1946 MenuFrameDelay = GameFrameDelay;
1948 SetVideoFrameDelay(GameFrameDelay);
1950 if (GameFrameDelay > ONE_SECOND_DELAY)
1951 Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
1952 else if (GameFrameDelay != 0)
1953 Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
1954 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1955 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1957 Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
1964 if (game_status == GAME_MODE_PLAYING)
1968 options.debug = !options.debug;
1970 Error(ERR_INFO, "debug mode %s",
1971 (options.debug ? "enabled" : "disabled"));
1975 else if (key == KSYM_v)
1977 Error(ERR_INFO, "currently using game engine version %d",
1978 game.engine_version);
1988 void HandleKey(Key key, int key_status)
1990 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1991 static boolean ignore_repeated_key = FALSE;
1992 static struct SetupKeyboardInfo ski;
1993 static struct SetupShortcutInfo ssi;
2002 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
2003 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
2004 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
2005 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
2006 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2007 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2012 if (HandleKeysDebug(key, key_status))
2013 return; // do not handle already processed keys again
2015 // map special keys (media keys / remote control buttons) to default keys
2016 if (key == KSYM_PlayPause)
2018 else if (key == KSYM_Select)
2021 HandleSpecialGameControllerKeys(key, key_status);
2023 if (game_status == GAME_MODE_PLAYING)
2025 // only needed for single-step tape recording mode
2026 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2029 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2031 byte key_action = 0;
2032 byte key_snap_action = 0;
2034 if (setup.input[pnr].use_joystick)
2037 ski = setup.input[pnr].key;
2039 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2040 if (key == *key_info[i].key_custom)
2041 key_action |= key_info[i].action;
2043 // use combined snap+direction keys for the first player only
2046 ssi = setup.shortcut;
2048 // also remember normal snap key when handling snap+direction keys
2049 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2051 for (i = 0; i < NUM_DIRECTIONS; i++)
2053 if (key == *key_info[i].key_snap)
2055 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2056 key_snap_action |= key_info[i].action;
2061 if (key_status == KEY_PRESSED)
2063 stored_player[pnr].action |= key_action;
2064 stored_player[pnr].snap_action |= key_snap_action;
2068 stored_player[pnr].action &= ~key_action;
2069 stored_player[pnr].snap_action &= ~key_snap_action;
2072 // restore snap action if one of several pressed snap keys was released
2073 if (stored_player[pnr].snap_action)
2074 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2076 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2078 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2080 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2082 // if snap key already pressed, keep pause mode when releasing
2083 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2084 has_snapped[pnr] = TRUE;
2086 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2088 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2090 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2091 getRedDiskReleaseFlag_SP() == 0)
2093 // add a single inactive frame before dropping starts
2094 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2095 stored_player[pnr].force_dropping = TRUE;
2098 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2100 // if snap key was pressed without direction, leave pause mode
2101 if (!has_snapped[pnr])
2102 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2104 has_snapped[pnr] = FALSE;
2107 else if (tape.recording && tape.pausing && !tape.use_mouse)
2109 // prevent key release events from un-pausing a paused game
2110 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2111 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2114 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2115 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2121 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2122 if (key == key_info[i].key_default)
2123 joy |= key_info[i].action;
2128 if (key_status == KEY_PRESSED)
2129 key_joystick_mapping |= joy;
2131 key_joystick_mapping &= ~joy;
2136 if (game_status != GAME_MODE_PLAYING)
2137 key_joystick_mapping = 0;
2139 if (key_status == KEY_RELEASED)
2141 // reset flag to ignore repeated "key pressed" events after key release
2142 ignore_repeated_key = FALSE;
2147 if ((key == KSYM_F11 ||
2148 ((key == KSYM_Return ||
2149 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2150 video.fullscreen_available &&
2151 !ignore_repeated_key)
2153 setup.fullscreen = !setup.fullscreen;
2155 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2157 if (game_status == GAME_MODE_SETUP)
2158 RedrawSetupScreenAfterFullscreenToggle();
2160 UpdateMousePosition();
2162 // set flag to ignore repeated "key pressed" events
2163 ignore_repeated_key = TRUE;
2168 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2169 key == KSYM_minus || key == KSYM_KP_Subtract ||
2170 key == KSYM_plus || key == KSYM_KP_Add ||
2171 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2172 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2173 video.window_scaling_available &&
2174 !video.fullscreen_enabled)
2176 if (key == KSYM_0 || key == KSYM_KP_0)
2177 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2178 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2179 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2181 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2183 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2184 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2185 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2186 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2188 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2190 if (game_status == GAME_MODE_SETUP)
2191 RedrawSetupScreenAfterFullscreenToggle();
2193 UpdateMousePosition();
2198 // some key events are handled like clicks for global animations
2199 boolean click = (key == KSYM_space ||
2200 key == KSYM_Return ||
2201 key == KSYM_Escape);
2203 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2205 // do not handle this key event anymore
2206 if (key != KSYM_Escape) // always allow ESC key to be handled
2210 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2211 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2218 if (game_status == GAME_MODE_MAIN &&
2219 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2221 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2226 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2228 if (key == setup.shortcut.save_game)
2230 else if (key == setup.shortcut.load_game)
2232 else if (key == setup.shortcut.toggle_pause)
2233 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2235 HandleTapeButtonKeys(key);
2236 HandleSoundButtonKeys(key);
2239 if (game_status == GAME_MODE_PLAYING && !network_playing)
2241 int centered_player_nr_next = -999;
2243 if (key == setup.shortcut.focus_player_all)
2244 centered_player_nr_next = -1;
2246 for (i = 0; i < MAX_PLAYERS; i++)
2247 if (key == setup.shortcut.focus_player[i])
2248 centered_player_nr_next = i;
2250 if (centered_player_nr_next != -999)
2252 game.centered_player_nr_next = centered_player_nr_next;
2253 game.set_centered_player = TRUE;
2257 tape.centered_player_nr_next = game.centered_player_nr_next;
2258 tape.set_centered_player = TRUE;
2263 HandleKeysSpecial(key);
2265 if (HandleGadgetsKeyInput(key))
2266 return; // do not handle already processed keys again
2268 switch (game_status)
2270 case GAME_MODE_PSEUDO_TYPENAME:
2271 HandleTypeName(0, key);
2274 case GAME_MODE_TITLE:
2275 case GAME_MODE_MAIN:
2276 case GAME_MODE_LEVELS:
2277 case GAME_MODE_LEVELNR:
2278 case GAME_MODE_SETUP:
2279 case GAME_MODE_INFO:
2280 case GAME_MODE_SCORES:
2282 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2289 if (game_status == GAME_MODE_TITLE)
2290 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2291 else if (game_status == GAME_MODE_MAIN)
2292 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2293 else if (game_status == GAME_MODE_LEVELS)
2294 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2295 else if (game_status == GAME_MODE_LEVELNR)
2296 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2297 else if (game_status == GAME_MODE_SETUP)
2298 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2299 else if (game_status == GAME_MODE_INFO)
2300 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2301 else if (game_status == GAME_MODE_SCORES)
2302 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2306 if (game_status != GAME_MODE_MAIN)
2307 FadeSkipNextFadeIn();
2309 if (game_status == GAME_MODE_TITLE)
2310 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2311 else if (game_status == GAME_MODE_LEVELS)
2312 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2313 else if (game_status == GAME_MODE_LEVELNR)
2314 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2315 else if (game_status == GAME_MODE_SETUP)
2316 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2317 else if (game_status == GAME_MODE_INFO)
2318 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2319 else if (game_status == GAME_MODE_SCORES)
2320 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2324 if (game_status == GAME_MODE_LEVELS)
2325 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2326 else if (game_status == GAME_MODE_LEVELNR)
2327 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2328 else if (game_status == GAME_MODE_SETUP)
2329 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2330 else if (game_status == GAME_MODE_INFO)
2331 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2332 else if (game_status == GAME_MODE_SCORES)
2333 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2336 case KSYM_Page_Down:
2337 if (game_status == GAME_MODE_LEVELS)
2338 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2339 else if (game_status == GAME_MODE_LEVELNR)
2340 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2341 else if (game_status == GAME_MODE_SETUP)
2342 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2343 else if (game_status == GAME_MODE_INFO)
2344 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2345 else if (game_status == GAME_MODE_SCORES)
2346 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2354 case GAME_MODE_EDITOR:
2355 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2356 HandleLevelEditorKeyInput(key);
2359 case GAME_MODE_PLAYING:
2364 RequestQuitGame(setup.ask_on_escape);
2374 if (key == KSYM_Escape)
2376 SetGameStatus(GAME_MODE_MAIN);
2385 void HandleNoEvent(void)
2387 HandleMouseCursor();
2389 switch (game_status)
2391 case GAME_MODE_PLAYING:
2392 HandleButtonOrFinger(-1, -1, -1);
2397 void HandleEventActions(void)
2399 // if (button_status && game_status != GAME_MODE_PLAYING)
2400 if (button_status && (game_status != GAME_MODE_PLAYING ||
2402 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2404 HandleButton(0, 0, button_status, -button_status);
2411 if (network.enabled)
2414 switch (game_status)
2416 case GAME_MODE_MAIN:
2417 DrawPreviewLevelAnimation();
2420 case GAME_MODE_EDITOR:
2421 HandleLevelEditorIdle();
2429 static void HandleTileCursor(int dx, int dy, int button)
2432 ClearPlayerMouseAction();
2439 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2440 (dx < 0 ? MB_LEFTBUTTON :
2441 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2443 else if (!tile_cursor.moving)
2445 int old_xpos = tile_cursor.xpos;
2446 int old_ypos = tile_cursor.ypos;
2447 int new_xpos = old_xpos;
2448 int new_ypos = old_ypos;
2450 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2451 new_xpos = old_xpos + dx;
2453 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2454 new_ypos = old_ypos + dy;
2456 SetTileCursorTargetXY(new_xpos, new_ypos);
2460 static int HandleJoystickForAllPlayers(void)
2464 boolean no_joysticks_configured = TRUE;
2465 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2466 static byte joy_action_last[MAX_PLAYERS];
2468 for (i = 0; i < MAX_PLAYERS; i++)
2469 if (setup.input[i].use_joystick)
2470 no_joysticks_configured = FALSE;
2472 // if no joysticks configured, map connected joysticks to players
2473 if (no_joysticks_configured)
2474 use_as_joystick_nr = TRUE;
2476 for (i = 0; i < MAX_PLAYERS; i++)
2478 byte joy_action = 0;
2480 joy_action = JoystickExt(i, use_as_joystick_nr);
2481 result |= joy_action;
2483 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2484 joy_action != joy_action_last[i])
2485 stored_player[i].action = joy_action;
2487 joy_action_last[i] = joy_action;
2493 void HandleJoystick(void)
2495 static unsigned int joytest_delay = 0;
2496 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2497 static int joytest_last = 0;
2498 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2499 int delay_value = GADGET_FRAME_DELAY;
2500 int joystick = HandleJoystickForAllPlayers();
2501 int keyboard = key_joystick_mapping;
2502 int joy = (joystick | keyboard);
2503 int joytest = joystick;
2504 int left = joy & JOY_LEFT;
2505 int right = joy & JOY_RIGHT;
2506 int up = joy & JOY_UP;
2507 int down = joy & JOY_DOWN;
2508 int button = joy & JOY_BUTTON;
2509 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2510 int dx = (left ? -1 : right ? 1 : 0);
2511 int dy = (up ? -1 : down ? 1 : 0);
2512 boolean use_delay_value_first = (joytest != joytest_last);
2514 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2516 // do not handle this button event anymore
2520 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2521 anyTextGadgetActive()))
2523 // leave name input in main menu or text input gadget
2524 HandleKey(KSYM_Escape, KEY_PRESSED);
2525 HandleKey(KSYM_Escape, KEY_RELEASED);
2530 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2532 if (game_status == GAME_MODE_PLAYING)
2534 // when playing MM style levels, also use delay for keyboard events
2535 joytest |= keyboard;
2537 // only use first delay value for new events, but not for changed events
2538 use_delay_value_first = (!joytest != !joytest_last);
2540 // only use delay after the initial keyboard event
2544 // for any joystick or keyboard event, enable playfield tile cursor
2545 if (dx || dy || button)
2546 SetTileCursorEnabled(TRUE);
2549 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2551 // delay joystick/keyboard actions if axes/keys continually pressed
2552 newbutton = dx = dy = 0;
2556 // first start with longer delay, then continue with shorter delay
2557 joytest_delay_value =
2558 (use_delay_value_first ? delay_value_first : delay_value);
2561 joytest_last = joytest;
2563 switch (game_status)
2565 case GAME_MODE_TITLE:
2566 case GAME_MODE_MAIN:
2567 case GAME_MODE_LEVELS:
2568 case GAME_MODE_LEVELNR:
2569 case GAME_MODE_SETUP:
2570 case GAME_MODE_INFO:
2571 case GAME_MODE_SCORES:
2573 if (anyTextGadgetActive())
2576 if (game_status == GAME_MODE_TITLE)
2577 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2578 else if (game_status == GAME_MODE_MAIN)
2579 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2580 else if (game_status == GAME_MODE_LEVELS)
2581 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2582 else if (game_status == GAME_MODE_LEVELNR)
2583 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2584 else if (game_status == GAME_MODE_SETUP)
2585 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2586 else if (game_status == GAME_MODE_INFO)
2587 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2588 else if (game_status == GAME_MODE_SCORES)
2589 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2594 case GAME_MODE_PLAYING:
2596 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2597 if (tape.playing || keyboard)
2598 newbutton = ((joy & JOY_BUTTON) != 0);
2601 if (newbutton && game.all_players_gone)
2608 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2610 if (joystick & JOY_ACTION)
2611 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2613 else if (tape.recording && tape.pausing && !tape.use_mouse)
2615 if (joystick & JOY_ACTION)
2616 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2619 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2620 HandleTileCursor(dx, dy, button);
2629 void HandleSpecialGameControllerButtons(Event *event)
2634 switch (event->type)
2636 case SDL_CONTROLLERBUTTONDOWN:
2637 key_status = KEY_PRESSED;
2640 case SDL_CONTROLLERBUTTONUP:
2641 key_status = KEY_RELEASED;
2648 switch (event->cbutton.button)
2650 case SDL_CONTROLLER_BUTTON_START:
2654 case SDL_CONTROLLER_BUTTON_BACK:
2662 HandleKey(key, key_status);
2665 void HandleSpecialGameControllerKeys(Key key, int key_status)
2667 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2668 int button = SDL_CONTROLLER_BUTTON_INVALID;
2670 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2671 if (key == KSYM_Rewind)
2672 button = SDL_CONTROLLER_BUTTON_A;
2673 else if (key == KSYM_FastForward || key == KSYM_Menu)
2674 button = SDL_CONTROLLER_BUTTON_B;
2676 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2680 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2681 SDL_CONTROLLERBUTTONUP);
2683 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2684 event.cbutton.button = button;
2685 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2688 HandleJoystickEvent(&event);
2693 boolean DoKeysymAction(int keysym)
2697 Key key = (Key)(-keysym);
2699 HandleKey(key, KEY_PRESSED);
2700 HandleKey(key, KEY_RELEASED);