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 virtual_button_pressed = FALSE;
43 static boolean stop_processing_events = FALSE;
46 // forward declarations for internal use
47 static void HandleNoEvent(void);
48 static void HandleEventActions(void);
51 // event filter to set mouse x/y position (for pointer class global animations)
52 // (this is especially required to ensure smooth global animation mouse pointer
53 // movement when the screen is updated without handling events; this can happen
54 // when drawing door/envelope request animations, for example)
56 int FilterMouseMotionEvents(void *userdata, Event *event)
58 if (event->type == EVENT_MOTIONNOTIFY)
60 int mouse_x = ((MotionEvent *)event)->x;
61 int mouse_y = ((MotionEvent *)event)->y;
63 UpdateRawMousePosition(mouse_x, mouse_y);
69 // event filter especially needed for SDL event filtering due to
70 // delay problems with lots of mouse motion events when mouse button
71 // not pressed (X11 can handle this with 'PointerMotionHintMask')
73 // event filter addition for SDL2: as SDL2 does not have a function to enable
74 // or disable keyboard auto-repeat, filter repeated keyboard events instead
76 static int FilterEvents(const Event *event)
80 // skip repeated key press events if keyboard auto-repeat is disabled
81 if (event->type == EVENT_KEYPRESS &&
86 if (event->type == EVENT_BUTTONPRESS ||
87 event->type == EVENT_BUTTONRELEASE)
89 ((ButtonEvent *)event)->x -= video.screen_xoffset;
90 ((ButtonEvent *)event)->y -= video.screen_yoffset;
92 else if (event->type == EVENT_MOTIONNOTIFY)
94 ((MotionEvent *)event)->x -= video.screen_xoffset;
95 ((MotionEvent *)event)->y -= video.screen_yoffset;
98 // non-motion events are directly passed to event handler functions
99 if (event->type != EVENT_MOTIONNOTIFY)
102 motion = (MotionEvent *)event;
103 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
104 motion->y >= SY && motion->y < SY + SYSIZE);
106 // set correct mouse x/y position (for pointer class global animations)
107 // (this is required in rare cases where the mouse x/y position calculated
108 // from raw values (to apply logical screen size scaling corrections) does
109 // not match the final mouse event x/y position -- this may happen because
110 // the SDL renderer's viewport position is internally represented as float,
111 // but only accessible as integer, which may lead to rounding errors)
112 gfx.mouse_x = motion->x;
113 gfx.mouse_y = motion->y;
115 // do no reset mouse cursor before all pending events have been processed
116 if (gfx.cursor_mode == cursor_mode_last &&
117 ((game_status == GAME_MODE_TITLE &&
118 gfx.cursor_mode == CURSOR_NONE) ||
119 (game_status == GAME_MODE_PLAYING &&
120 gfx.cursor_mode == CURSOR_PLAYFIELD)))
122 SetMouseCursor(CURSOR_DEFAULT);
124 DelayReached(&special_cursor_delay, 0);
126 cursor_mode_last = CURSOR_DEFAULT;
129 // skip mouse motion events without pressed button outside level editor
130 if (button_status == MB_RELEASED &&
131 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
137 // to prevent delay problems, skip mouse motion events if the very next
138 // event is also a mouse motion event (and therefore effectively only
139 // handling the last of a row of mouse motion events in the event queue)
141 static boolean SkipPressedMouseMotionEvent(const Event *event)
143 // nothing to do if the current event is not a mouse motion event
144 if (event->type != EVENT_MOTIONNOTIFY)
147 // only skip motion events with pressed button outside the game
148 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
155 PeekEvent(&next_event);
157 // if next event is also a mouse motion event, skip the current one
158 if (next_event.type == EVENT_MOTIONNOTIFY)
165 static boolean WaitValidEvent(Event *event)
169 if (!FilterEvents(event))
172 if (SkipPressedMouseMotionEvent(event))
178 /* this is especially needed for event modifications for the Android target:
179 if mouse coordinates should be modified in the event filter function,
180 using a properly installed SDL event filter does not work, because in
181 the event filter, mouse coordinates in the event structure are still
182 physical pixel positions, not logical (scaled) screen positions, so this
183 has to be handled at a later stage in the event processing functions
184 (when device pixel positions are already converted to screen positions) */
186 boolean NextValidEvent(Event *event)
188 while (PendingEvent())
189 if (WaitValidEvent(event))
195 void StopProcessingEvents(void)
197 stop_processing_events = TRUE;
200 static void HandleEvents(void)
203 unsigned int event_frame_delay = 0;
204 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
206 ResetDelayCounter(&event_frame_delay);
208 stop_processing_events = FALSE;
210 while (NextValidEvent(&event))
214 case EVENT_BUTTONPRESS:
215 case EVENT_BUTTONRELEASE:
216 HandleButtonEvent((ButtonEvent *) &event);
219 case EVENT_MOTIONNOTIFY:
220 HandleMotionEvent((MotionEvent *) &event);
223 case EVENT_WHEELMOTION:
224 HandleWheelEvent((WheelEvent *) &event);
227 case SDL_WINDOWEVENT:
228 HandleWindowEvent((WindowEvent *) &event);
231 case EVENT_FINGERPRESS:
232 case EVENT_FINGERRELEASE:
233 case EVENT_FINGERMOTION:
234 HandleFingerEvent((FingerEvent *) &event);
237 case EVENT_TEXTINPUT:
238 HandleTextEvent((TextEvent *) &event);
241 case SDL_APP_WILLENTERBACKGROUND:
242 case SDL_APP_DIDENTERBACKGROUND:
243 case SDL_APP_WILLENTERFOREGROUND:
244 case SDL_APP_DIDENTERFOREGROUND:
245 HandlePauseResumeEvent((PauseResumeEvent *) &event);
249 case EVENT_KEYRELEASE:
250 HandleKeyEvent((KeyEvent *) &event);
254 HandleUserEvent((UserEvent *) &event);
258 HandleOtherEvents(&event);
262 // do not handle events for longer than standard frame delay period
263 if (DelayReached(&event_frame_delay, event_frame_delay_value))
266 // do not handle any further events if triggered by a special flag
267 if (stop_processing_events)
272 void HandleOtherEvents(Event *event)
276 case SDL_CONTROLLERBUTTONDOWN:
277 case SDL_CONTROLLERBUTTONUP:
278 // for any game controller button event, disable overlay buttons
279 SetOverlayEnabled(FALSE);
281 HandleSpecialGameControllerButtons(event);
284 case SDL_CONTROLLERDEVICEADDED:
285 case SDL_CONTROLLERDEVICEREMOVED:
286 case SDL_CONTROLLERAXISMOTION:
287 case SDL_JOYAXISMOTION:
288 case SDL_JOYBUTTONDOWN:
289 case SDL_JOYBUTTONUP:
290 HandleJoystickEvent(event);
294 case SDL_DROPCOMPLETE:
297 HandleDropEvent(event);
309 static void HandleMouseCursor(void)
311 if (game_status == GAME_MODE_TITLE)
313 // when showing title screens, hide mouse pointer (if not moved)
315 if (gfx.cursor_mode != CURSOR_NONE &&
316 DelayReached(&special_cursor_delay, special_cursor_delay_value))
318 SetMouseCursor(CURSOR_NONE);
321 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
324 // when playing, display a special mouse pointer inside the playfield
326 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
327 cursor_inside_playfield &&
328 DelayReached(&special_cursor_delay, special_cursor_delay_value))
330 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
332 SetMouseCursor(CURSOR_PLAYFIELD);
335 else if (gfx.cursor_mode != CURSOR_DEFAULT)
337 SetMouseCursor(CURSOR_DEFAULT);
340 // this is set after all pending events have been processed
341 cursor_mode_last = gfx.cursor_mode;
353 // execute event related actions after pending events have been processed
354 HandleEventActions();
356 // don't use all CPU time when idle; the main loop while playing
357 // has its own synchronization and is CPU friendly, too
359 if (game_status == GAME_MODE_PLAYING)
362 // always copy backbuffer to visible screen for every video frame
365 // reset video frame delay to default (may change again while playing)
366 SetVideoFrameDelay(MenuFrameDelay);
368 if (game_status == GAME_MODE_QUIT)
373 void ClearAutoRepeatKeyEvents(void)
375 while (PendingEvent())
379 PeekEvent(&next_event);
381 // if event is repeated key press event, remove it from event queue
382 if (next_event.type == EVENT_KEYPRESS &&
383 next_event.key.repeat)
384 WaitEvent(&next_event);
390 void ClearEventQueue(void)
394 while (NextValidEvent(&event))
398 case EVENT_BUTTONRELEASE:
399 button_status = MB_RELEASED;
402 case EVENT_KEYRELEASE:
406 case SDL_CONTROLLERBUTTONUP:
407 HandleJoystickEvent(&event);
412 HandleOtherEvents(&event);
418 static void ClearPlayerMouseAction(void)
420 local_player->mouse_action.lx = 0;
421 local_player->mouse_action.ly = 0;
422 local_player->mouse_action.button = 0;
425 void ClearPlayerAction(void)
429 // simulate key release events for still pressed keys
430 key_joystick_mapping = 0;
431 for (i = 0; i < MAX_PLAYERS; i++)
433 stored_player[i].action = 0;
434 stored_player[i].snap_action = 0;
437 ClearJoystickState();
438 ClearPlayerMouseAction();
441 static void SetPlayerMouseAction(int mx, int my, int button)
443 int lx = getLevelFromScreenX(mx);
444 int ly = getLevelFromScreenY(my);
445 int new_button = (!local_player->mouse_action.button && button);
447 if (local_player->mouse_action.button_hint)
448 button = local_player->mouse_action.button_hint;
450 ClearPlayerMouseAction();
452 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
455 local_player->mouse_action.lx = lx;
456 local_player->mouse_action.ly = ly;
457 local_player->mouse_action.button = button;
459 if (tape.recording && tape.pausing && tape.use_mouse)
461 // un-pause a paused game only if mouse button was newly pressed down
463 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
466 SetTileCursorXY(lx, ly);
469 static Key GetKeyFromGridButton(int grid_button)
471 return (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
472 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
473 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
474 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
475 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
476 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
480 void HandleButtonEvent(ButtonEvent *event)
482 #if DEBUG_EVENTS_BUTTON
483 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
485 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
489 // for any mouse button event, disable playfield tile cursor
490 SetTileCursorEnabled(FALSE);
492 #if defined(HAS_SCREEN_KEYBOARD)
493 if (video.shifted_up)
494 event->y += video.shifted_up_pos;
497 motion_status = FALSE;
499 if (event->type == EVENT_BUTTONPRESS)
500 button_status = event->button;
502 button_status = MB_RELEASED;
504 HandleButton(event->x, event->y, button_status, event->button);
507 void HandleMotionEvent(MotionEvent *event)
509 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
512 motion_status = TRUE;
514 #if DEBUG_EVENTS_MOTION
515 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
516 button_status, event->x, event->y);
519 HandleButton(event->x, event->y, button_status, button_status);
522 void HandleWheelEvent(WheelEvent *event)
526 #if DEBUG_EVENTS_WHEEL
528 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
529 event->which, event->x, event->y);
531 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
532 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
533 event->which, event->x, event->y,
534 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
535 "SDL_MOUSEWHEEL_FLIPPED"));
539 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
540 event->x > 0 ? MB_WHEEL_RIGHT :
541 event->y < 0 ? MB_WHEEL_DOWN :
542 event->y > 0 ? MB_WHEEL_UP : 0);
544 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
545 // accelerated mouse wheel available on Mac and Windows
546 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
548 // no accelerated mouse wheel available on Unix/Linux
549 wheel_steps = DEFAULT_WHEEL_STEPS;
552 motion_status = FALSE;
554 button_status = button_nr;
555 HandleButton(0, 0, button_status, -button_nr);
557 button_status = MB_RELEASED;
558 HandleButton(0, 0, button_status, -button_nr);
561 void HandleWindowEvent(WindowEvent *event)
563 #if DEBUG_EVENTS_WINDOW
564 int subtype = event->event;
567 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
568 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
569 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
570 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
571 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
572 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
573 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
574 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
575 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
576 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
577 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
578 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
579 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
580 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
583 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
584 event_name, event->data1, event->data2);
588 // (not needed, as the screen gets redrawn every 20 ms anyway)
589 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
590 event->event == SDL_WINDOWEVENT_RESIZED ||
591 event->event == SDL_WINDOWEVENT_EXPOSED)
595 if (event->event == SDL_WINDOWEVENT_RESIZED)
597 if (!video.fullscreen_enabled)
599 int new_window_width = event->data1;
600 int new_window_height = event->data2;
602 // if window size has changed after resizing, calculate new scaling factor
603 if (new_window_width != video.window_width ||
604 new_window_height != video.window_height)
606 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
607 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
609 // (extreme window scaling allowed, but cannot be saved permanently)
610 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
611 setup.window_scaling_percent =
612 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
613 MAX_WINDOW_SCALING_PERCENT);
615 video.window_width = new_window_width;
616 video.window_height = new_window_height;
618 if (game_status == GAME_MODE_SETUP)
619 RedrawSetupScreenAfterFullscreenToggle();
621 UpdateMousePosition();
626 #if defined(PLATFORM_ANDROID)
629 int new_display_width = event->data1;
630 int new_display_height = event->data2;
632 // if fullscreen display size has changed, device has been rotated
633 if (new_display_width != video.display_width ||
634 new_display_height != video.display_height)
636 int nr = GRID_ACTIVE_NR(); // previous screen orientation
638 video.display_width = new_display_width;
639 video.display_height = new_display_height;
641 SDLSetScreenProperties();
643 // check if screen orientation has changed (should always be true here)
644 if (nr != GRID_ACTIVE_NR())
648 if (game_status == GAME_MODE_SETUP)
649 RedrawSetupScreenAfterScreenRotation(nr);
651 nr = GRID_ACTIVE_NR();
653 overlay.grid_xsize = setup.touch.grid_xsize[nr];
654 overlay.grid_ysize = setup.touch.grid_ysize[nr];
656 for (x = 0; x < MAX_GRID_XSIZE; x++)
657 for (y = 0; y < MAX_GRID_YSIZE; y++)
658 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
666 #define NUM_TOUCH_FINGERS 3
671 SDL_FingerID finger_id;
675 } touch_info[NUM_TOUCH_FINGERS];
677 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
679 int x = event->x * overlay.grid_xsize;
680 int y = event->y * overlay.grid_ysize;
681 int grid_button = overlay.grid_button[x][y];
682 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
683 Key key = GetKeyFromGridButton(grid_button);
684 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
686 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
690 virtual_button_pressed = (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
692 // for any touch input event, enable overlay buttons (if activated)
693 SetOverlayEnabled(TRUE);
695 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
696 getKeyNameFromKey(key), key_status_name, event->fingerId);
698 if (key_status == KEY_PRESSED)
699 overlay.grid_button_action |= grid_button_action;
701 overlay.grid_button_action &= ~grid_button_action;
703 // check if we already know this touch event's finger id
704 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
706 if (touch_info[i].touched &&
707 touch_info[i].finger_id == event->fingerId)
709 // Error(ERR_DEBUG, "MARK 1: %d", i);
715 if (i >= NUM_TOUCH_FINGERS)
717 if (key_status == KEY_PRESSED)
719 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
721 // unknown finger id -- get new, empty slot, if available
722 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
724 if (touch_info[i].counter < oldest_counter)
727 oldest_counter = touch_info[i].counter;
729 // Error(ERR_DEBUG, "MARK 2: %d", i);
732 if (!touch_info[i].touched)
734 // Error(ERR_DEBUG, "MARK 3: %d", i);
740 if (i >= NUM_TOUCH_FINGERS)
742 // all slots allocated -- use oldest slot
745 // Error(ERR_DEBUG, "MARK 4: %d", i);
750 // release of previously unknown key (should not happen)
752 if (key != KSYM_UNDEFINED)
754 HandleKey(key, KEY_RELEASED);
756 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
757 getKeyNameFromKey(key), "KEY_RELEASED", i);
762 if (i < NUM_TOUCH_FINGERS)
764 if (key_status == KEY_PRESSED)
766 if (touch_info[i].key != key)
768 if (touch_info[i].key != KSYM_UNDEFINED)
770 HandleKey(touch_info[i].key, KEY_RELEASED);
772 // undraw previous grid button when moving finger away
773 overlay.grid_button_action &= ~touch_info[i].action;
775 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
776 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
779 if (key != KSYM_UNDEFINED)
781 HandleKey(key, KEY_PRESSED);
783 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
784 getKeyNameFromKey(key), "KEY_PRESSED", i);
788 touch_info[i].touched = TRUE;
789 touch_info[i].finger_id = event->fingerId;
790 touch_info[i].counter = Counter();
791 touch_info[i].key = key;
792 touch_info[i].action = grid_button_action;
796 if (touch_info[i].key != KSYM_UNDEFINED)
798 HandleKey(touch_info[i].key, KEY_RELEASED);
800 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
801 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
804 touch_info[i].touched = FALSE;
805 touch_info[i].finger_id = 0;
806 touch_info[i].counter = 0;
807 touch_info[i].key = 0;
808 touch_info[i].action = JOY_NO_ACTION;
813 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
815 static Key motion_key_x = KSYM_UNDEFINED;
816 static Key motion_key_y = KSYM_UNDEFINED;
817 static Key button_key = KSYM_UNDEFINED;
818 static float motion_x1, motion_y1;
819 static float button_x1, button_y1;
820 static SDL_FingerID motion_id = -1;
821 static SDL_FingerID button_id = -1;
822 int move_trigger_distance_percent = setup.touch.move_distance;
823 int drop_trigger_distance_percent = setup.touch.drop_distance;
824 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
825 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
826 float event_x = event->x;
827 float event_y = event->y;
829 if (event->type == EVENT_FINGERPRESS)
831 if (event_x > 1.0 / 3.0)
835 motion_id = event->fingerId;
840 motion_key_x = KSYM_UNDEFINED;
841 motion_key_y = KSYM_UNDEFINED;
843 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
849 button_id = event->fingerId;
854 button_key = setup.input[0].key.snap;
856 HandleKey(button_key, KEY_PRESSED);
858 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
861 else if (event->type == EVENT_FINGERRELEASE)
863 if (event->fingerId == motion_id)
867 if (motion_key_x != KSYM_UNDEFINED)
868 HandleKey(motion_key_x, KEY_RELEASED);
869 if (motion_key_y != KSYM_UNDEFINED)
870 HandleKey(motion_key_y, KEY_RELEASED);
872 motion_key_x = KSYM_UNDEFINED;
873 motion_key_y = KSYM_UNDEFINED;
875 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
877 else if (event->fingerId == button_id)
881 if (button_key != KSYM_UNDEFINED)
882 HandleKey(button_key, KEY_RELEASED);
884 button_key = KSYM_UNDEFINED;
886 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
889 else if (event->type == EVENT_FINGERMOTION)
891 if (event->fingerId == motion_id)
893 float distance_x = ABS(event_x - motion_x1);
894 float distance_y = ABS(event_y - motion_y1);
895 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
896 event_x > motion_x1 ? setup.input[0].key.right :
898 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
899 event_y > motion_y1 ? setup.input[0].key.down :
902 if (distance_x < move_trigger_distance / 2 ||
903 distance_x < distance_y)
904 new_motion_key_x = KSYM_UNDEFINED;
906 if (distance_y < move_trigger_distance / 2 ||
907 distance_y < distance_x)
908 new_motion_key_y = KSYM_UNDEFINED;
910 if (distance_x > move_trigger_distance ||
911 distance_y > move_trigger_distance)
913 if (new_motion_key_x != motion_key_x)
915 if (motion_key_x != KSYM_UNDEFINED)
916 HandleKey(motion_key_x, KEY_RELEASED);
917 if (new_motion_key_x != KSYM_UNDEFINED)
918 HandleKey(new_motion_key_x, KEY_PRESSED);
921 if (new_motion_key_y != motion_key_y)
923 if (motion_key_y != KSYM_UNDEFINED)
924 HandleKey(motion_key_y, KEY_RELEASED);
925 if (new_motion_key_y != KSYM_UNDEFINED)
926 HandleKey(new_motion_key_y, KEY_PRESSED);
932 motion_key_x = new_motion_key_x;
933 motion_key_y = new_motion_key_y;
935 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
938 else if (event->fingerId == button_id)
940 float distance_x = ABS(event_x - button_x1);
941 float distance_y = ABS(event_y - button_y1);
943 if (distance_x < drop_trigger_distance / 2 &&
944 distance_y > drop_trigger_distance)
946 if (button_key == setup.input[0].key.snap)
947 HandleKey(button_key, KEY_RELEASED);
952 button_key = setup.input[0].key.drop;
954 HandleKey(button_key, KEY_PRESSED);
956 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
962 void HandleFingerEvent(FingerEvent *event)
964 #if DEBUG_EVENTS_FINGER
965 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
966 event->type == EVENT_FINGERPRESS ? "pressed" :
967 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
971 event->dx, event->dy,
975 runtime.uses_touch_device = TRUE;
977 if (game_status != GAME_MODE_PLAYING)
980 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
982 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
983 local_player->mouse_action.button_hint =
984 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
985 event->x < 0.5 ? MB_LEFTBUTTON :
986 event->x > 0.5 ? MB_RIGHTBUTTON :
992 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
993 HandleFingerEvent_VirtualButtons(event);
994 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
995 HandleFingerEvent_WipeGestures(event);
998 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1000 static int old_mx = 0, old_my = 0;
1001 static int last_button = MB_LEFTBUTTON;
1002 static boolean touched = FALSE;
1003 static boolean tapped = FALSE;
1005 // screen tile was tapped (but finger not touching the screen anymore)
1006 // (this point will also be reached without receiving a touch event)
1007 if (tapped && !touched)
1009 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1014 // stop here if this function was not triggered by a touch event
1018 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1020 // finger started touching the screen
1030 ClearPlayerMouseAction();
1032 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1035 else if (button == MB_RELEASED && touched)
1037 // finger stopped touching the screen
1042 SetPlayerMouseAction(old_mx, old_my, last_button);
1044 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1046 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1051 // finger moved while touching the screen
1053 int old_x = getLevelFromScreenX(old_mx);
1054 int old_y = getLevelFromScreenY(old_my);
1055 int new_x = getLevelFromScreenX(mx);
1056 int new_y = getLevelFromScreenY(my);
1058 if (new_x != old_x || new_y != old_y)
1063 // finger moved left or right from (horizontal) starting position
1065 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1067 SetPlayerMouseAction(old_mx, old_my, button_nr);
1069 last_button = button_nr;
1071 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1075 // finger stays at or returned to (horizontal) starting position
1077 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1079 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1084 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1086 static int old_mx = 0, old_my = 0;
1087 static int last_button = MB_LEFTBUTTON;
1088 static boolean touched = FALSE;
1089 static boolean tapped = FALSE;
1091 // screen tile was tapped (but finger not touching the screen anymore)
1092 // (this point will also be reached without receiving a touch event)
1093 if (tapped && !touched)
1095 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1100 // stop here if this function was not triggered by a touch event
1104 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1106 // finger started touching the screen
1116 ClearPlayerMouseAction();
1118 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1121 else if (button == MB_RELEASED && touched)
1123 // finger stopped touching the screen
1128 SetPlayerMouseAction(old_mx, old_my, last_button);
1130 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1132 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1137 // finger moved while touching the screen
1139 int old_x = getLevelFromScreenX(old_mx);
1140 int old_y = getLevelFromScreenY(old_my);
1141 int new_x = getLevelFromScreenX(mx);
1142 int new_y = getLevelFromScreenY(my);
1144 if (new_x != old_x || new_y != old_y)
1146 // finger moved away from starting position
1148 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1150 // quickly alternate between clicking and releasing for maximum speed
1151 if (FrameCounter % 2 == 0)
1152 button_nr = MB_RELEASED;
1154 SetPlayerMouseAction(old_mx, old_my, button_nr);
1157 last_button = button_nr;
1161 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1165 // finger stays at or returned to starting position
1167 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1169 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1174 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1176 static int old_mx = 0, old_my = 0;
1177 static Key motion_key_x = KSYM_UNDEFINED;
1178 static Key motion_key_y = KSYM_UNDEFINED;
1179 static boolean touched = FALSE;
1180 static boolean started_on_player = FALSE;
1181 static boolean player_is_dropping = FALSE;
1182 static int player_drop_count = 0;
1183 static int last_player_x = -1;
1184 static int last_player_y = -1;
1186 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1195 started_on_player = FALSE;
1196 player_is_dropping = FALSE;
1197 player_drop_count = 0;
1201 motion_key_x = KSYM_UNDEFINED;
1202 motion_key_y = KSYM_UNDEFINED;
1204 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1207 else if (button == MB_RELEASED && touched)
1214 if (motion_key_x != KSYM_UNDEFINED)
1215 HandleKey(motion_key_x, KEY_RELEASED);
1216 if (motion_key_y != KSYM_UNDEFINED)
1217 HandleKey(motion_key_y, KEY_RELEASED);
1219 if (started_on_player)
1221 if (player_is_dropping)
1223 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1225 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1229 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1231 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1235 motion_key_x = KSYM_UNDEFINED;
1236 motion_key_y = KSYM_UNDEFINED;
1238 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1243 int src_x = local_player->jx;
1244 int src_y = local_player->jy;
1245 int dst_x = getLevelFromScreenX(old_mx);
1246 int dst_y = getLevelFromScreenY(old_my);
1247 int dx = dst_x - src_x;
1248 int dy = dst_y - src_y;
1249 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1250 dx > 0 ? setup.input[0].key.right :
1252 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1253 dy > 0 ? setup.input[0].key.down :
1256 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1257 (last_player_x != local_player->jx ||
1258 last_player_y != local_player->jy))
1260 // in case of asymmetric diagonal movement, use "preferred" direction
1262 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1264 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1265 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1267 local_player->last_move_dir = last_move_dir;
1269 // (required to prevent accidentally forcing direction for next movement)
1270 last_player_x = local_player->jx;
1271 last_player_y = local_player->jy;
1274 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1276 started_on_player = TRUE;
1277 player_drop_count = getPlayerInventorySize(0);
1278 player_is_dropping = (player_drop_count > 0);
1280 if (player_is_dropping)
1282 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1284 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1288 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1290 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1293 else if (dx != 0 || dy != 0)
1295 if (player_is_dropping &&
1296 player_drop_count == getPlayerInventorySize(0))
1298 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1300 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1301 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1303 player_is_dropping = FALSE;
1307 if (new_motion_key_x != motion_key_x)
1309 Error(ERR_DEBUG, "---------- %s %s ----------",
1310 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1311 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1313 if (motion_key_x != KSYM_UNDEFINED)
1314 HandleKey(motion_key_x, KEY_RELEASED);
1315 if (new_motion_key_x != KSYM_UNDEFINED)
1316 HandleKey(new_motion_key_x, KEY_PRESSED);
1319 if (new_motion_key_y != motion_key_y)
1321 Error(ERR_DEBUG, "---------- %s %s ----------",
1322 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1323 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1325 if (motion_key_y != KSYM_UNDEFINED)
1326 HandleKey(motion_key_y, KEY_RELEASED);
1327 if (new_motion_key_y != KSYM_UNDEFINED)
1328 HandleKey(new_motion_key_y, KEY_PRESSED);
1331 motion_key_x = new_motion_key_x;
1332 motion_key_y = new_motion_key_y;
1336 static void HandleButtonOrFinger(int mx, int my, int button)
1338 if (game_status != GAME_MODE_PLAYING)
1341 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1343 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1344 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1345 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1346 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1347 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1348 SetPlayerMouseAction(mx, my, button); // special case
1352 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1353 HandleButtonOrFinger_FollowFinger(mx, my, button);
1357 static boolean checkTextInputKeyModState(void)
1359 // when playing, only handle raw key events and ignore text input
1360 if (game_status == GAME_MODE_PLAYING)
1363 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1366 void HandleTextEvent(TextEvent *event)
1368 char *text = event->text;
1369 Key key = getKeyFromKeyName(text);
1371 #if DEBUG_EVENTS_TEXT
1372 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1375 text[0], (int)(text[0]),
1377 getKeyNameFromKey(key),
1381 #if !defined(HAS_SCREEN_KEYBOARD)
1382 // non-mobile devices: only handle key input with modifier keys pressed here
1383 // (every other key input is handled directly as physical key input event)
1384 if (!checkTextInputKeyModState())
1388 // process text input as "classic" (with uppercase etc.) key input event
1389 HandleKey(key, KEY_PRESSED);
1390 HandleKey(key, KEY_RELEASED);
1393 void HandlePauseResumeEvent(PauseResumeEvent *event)
1395 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1399 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1405 void HandleKeyEvent(KeyEvent *event)
1407 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1408 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1409 Key key = GetEventKey(event, with_modifiers);
1410 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1412 #if DEBUG_EVENTS_KEY
1413 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1414 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1415 event->keysym.scancode,
1420 getKeyNameFromKey(key));
1423 #if defined(PLATFORM_ANDROID)
1424 if (key == KSYM_Back)
1426 // always map the "back" button to the "escape" key on Android devices
1429 else if (key == KSYM_Menu)
1431 // the "menu" button can be used to toggle displaying virtual buttons
1432 if (key_status == KEY_PRESSED)
1433 SetOverlayEnabled(!GetOverlayEnabled());
1437 // for any other "real" key event, disable virtual buttons
1438 SetOverlayEnabled(FALSE);
1442 HandleKeyModState(keymod, key_status);
1444 // only handle raw key input without text modifier keys pressed
1445 if (!checkTextInputKeyModState())
1446 HandleKey(key, key_status);
1449 static int HandleDropFileEvent(char *filename)
1451 Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
1453 // check and extract dropped zip files into correct user data directory
1454 if (!strSuffixLower(filename, ".zip"))
1456 Error(ERR_WARN, "file '%s' not supported", filename);
1458 return TREE_TYPE_UNDEFINED;
1461 TreeInfo *tree_node = NULL;
1462 int tree_type = GetZipFileTreeType(filename);
1463 char *directory = TREE_USERDIR(tree_type);
1465 if (directory == NULL)
1467 Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1469 return TREE_TYPE_UNDEFINED;
1472 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1473 game_status == GAME_MODE_LEVELS &&
1474 leveldir_current->node_parent != NULL)
1476 // extract new level set next to currently selected level set
1477 tree_node = leveldir_current;
1479 // get parent directory of currently selected level set directory
1480 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1482 // use private level directory instead of top-level package level directory
1483 if (strPrefix(directory, options.level_directory) &&
1484 strEqual(leveldir_current->node_parent->fullpath, "."))
1485 directory = getUserLevelDir(NULL);
1488 // extract level or artwork set from zip file to target directory
1489 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1491 if (top_dir == NULL)
1493 // error message already issued by "ExtractZipFileIntoDirectory()"
1495 return TREE_TYPE_UNDEFINED;
1498 // add extracted level or artwork set to tree info structure
1499 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1501 // update menu screen (and possibly change current level set)
1502 DrawScreenAfterAddingSet(top_dir, tree_type);
1507 static void HandleDropTextEvent(char *text)
1509 Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1512 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1513 int num_artwork_sets_succeeded,
1514 int num_files_failed)
1516 // only show request dialog if no other request dialog already active
1517 if (game.request_active)
1520 // this case can happen with drag-and-drop with older SDL versions
1521 if (num_level_sets_succeeded == 0 &&
1522 num_artwork_sets_succeeded == 0 &&
1523 num_files_failed == 0)
1528 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1530 char message_part1[50];
1532 sprintf(message_part1, "New %s set%s added",
1533 (num_artwork_sets_succeeded == 0 ? "level" :
1534 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1535 (num_level_sets_succeeded +
1536 num_artwork_sets_succeeded > 1 ? "s" : ""));
1538 if (num_files_failed > 0)
1539 sprintf(message, "%s, but %d dropped file%s failed!",
1540 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1542 sprintf(message, "%s!", message_part1);
1544 else if (num_files_failed > 0)
1546 sprintf(message, "Failed to process dropped file%s!",
1547 num_files_failed > 1 ? "s" : "");
1550 Request(message, REQ_CONFIRM);
1553 void HandleDropEvent(Event *event)
1555 static boolean confirm_on_drop_complete = FALSE;
1556 static int num_level_sets_succeeded = 0;
1557 static int num_artwork_sets_succeeded = 0;
1558 static int num_files_failed = 0;
1560 switch (event->type)
1564 confirm_on_drop_complete = TRUE;
1565 num_level_sets_succeeded = 0;
1566 num_artwork_sets_succeeded = 0;
1567 num_files_failed = 0;
1574 int tree_type = HandleDropFileEvent(event->drop.file);
1576 if (tree_type == TREE_TYPE_LEVEL_DIR)
1577 num_level_sets_succeeded++;
1578 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1579 tree_type == TREE_TYPE_SOUNDS_DIR ||
1580 tree_type == TREE_TYPE_MUSIC_DIR)
1581 num_artwork_sets_succeeded++;
1585 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1586 if (!confirm_on_drop_complete)
1588 // process all remaining events, including further SDL_DROPFILE events
1591 HandleDropCompleteEvent(num_level_sets_succeeded,
1592 num_artwork_sets_succeeded,
1595 num_level_sets_succeeded = 0;
1596 num_artwork_sets_succeeded = 0;
1597 num_files_failed = 0;
1605 HandleDropTextEvent(event->drop.file);
1610 case SDL_DROPCOMPLETE:
1612 HandleDropCompleteEvent(num_level_sets_succeeded,
1613 num_artwork_sets_succeeded,
1620 if (event->drop.file != NULL)
1621 SDL_free(event->drop.file);
1624 void HandleUserEvent(UserEvent *event)
1626 switch (event->code)
1628 case USEREVENT_ANIM_DELAY_ACTION:
1629 case USEREVENT_ANIM_EVENT_ACTION:
1630 // execute action functions until matching action was found
1631 if (DoKeysymAction(event->value1) ||
1632 DoGadgetAction(event->value1) ||
1633 DoScreenAction(event->value1))
1642 void HandleButton(int mx, int my, int button, int button_nr)
1644 static int old_mx = 0, old_my = 0;
1645 boolean button_hold = FALSE;
1646 boolean handle_gadgets = TRUE;
1652 button_nr = -button_nr;
1661 #if defined(PLATFORM_ANDROID)
1662 // when playing, only handle gadgets when using "follow finger" controls
1663 // or when using touch controls in combination with the MM game engine
1664 // or when using gadgets that do not overlap with virtual buttons
1666 (game_status != GAME_MODE_PLAYING ||
1667 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1668 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1669 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1670 !virtual_button_pressed));
1673 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1675 // do not handle this button event anymore
1676 return; // force mouse event not to be handled at all
1679 if (handle_gadgets && HandleGadgets(mx, my, button))
1681 // do not handle this button event anymore
1682 mx = my = -32; // force mouse event to be outside screen tiles
1685 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1688 // do not use scroll wheel button events for anything other than gadgets
1689 if (IS_WHEEL_BUTTON(button_nr))
1692 switch (game_status)
1694 case GAME_MODE_TITLE:
1695 HandleTitleScreen(mx, my, 0, 0, button);
1698 case GAME_MODE_MAIN:
1699 HandleMainMenu(mx, my, 0, 0, button);
1702 case GAME_MODE_PSEUDO_TYPENAME:
1703 HandleTypeName(0, KSYM_Return);
1706 case GAME_MODE_LEVELS:
1707 HandleChooseLevelSet(mx, my, 0, 0, button);
1710 case GAME_MODE_LEVELNR:
1711 HandleChooseLevelNr(mx, my, 0, 0, button);
1714 case GAME_MODE_SCORES:
1715 HandleHallOfFame(0, 0, 0, 0, button);
1718 case GAME_MODE_EDITOR:
1719 HandleLevelEditorIdle();
1722 case GAME_MODE_INFO:
1723 HandleInfoScreen(mx, my, 0, 0, button);
1726 case GAME_MODE_SETUP:
1727 HandleSetupScreen(mx, my, 0, 0, button);
1730 case GAME_MODE_PLAYING:
1731 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1732 HandleButtonOrFinger(mx, my, button);
1734 SetPlayerMouseAction(mx, my, button);
1737 if (button == MB_PRESSED && !motion_status && !button_hold &&
1738 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1739 DumpTileFromScreen(mx, my);
1749 static boolean is_string_suffix(char *string, char *suffix)
1751 int string_len = strlen(string);
1752 int suffix_len = strlen(suffix);
1754 if (suffix_len > string_len)
1757 return (strEqual(&string[string_len - suffix_len], suffix));
1760 #define MAX_CHEAT_INPUT_LEN 32
1762 static void HandleKeysSpecial(Key key)
1764 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1765 char letter = getCharFromKey(key);
1766 int cheat_input_len = strlen(cheat_input);
1772 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1774 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1775 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1777 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1780 cheat_input[cheat_input_len++] = letter;
1781 cheat_input[cheat_input_len] = '\0';
1783 #if DEBUG_EVENTS_KEY
1784 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1787 if (game_status == GAME_MODE_MAIN)
1789 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1790 is_string_suffix(cheat_input, ":ist"))
1792 InsertSolutionTape();
1794 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1795 is_string_suffix(cheat_input, ":pst"))
1799 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1800 is_string_suffix(cheat_input, ":rg"))
1802 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1805 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1806 is_string_suffix(cheat_input, ":rs"))
1808 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1811 else if (is_string_suffix(cheat_input, ":reload-music") ||
1812 is_string_suffix(cheat_input, ":rm"))
1814 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1817 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1818 is_string_suffix(cheat_input, ":ra"))
1820 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1821 1 << ARTWORK_TYPE_SOUNDS |
1822 1 << ARTWORK_TYPE_MUSIC);
1825 else if (is_string_suffix(cheat_input, ":dump-level") ||
1826 is_string_suffix(cheat_input, ":dl"))
1830 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1831 is_string_suffix(cheat_input, ":dt"))
1835 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1836 is_string_suffix(cheat_input, ":ft"))
1838 /* fix single-player tapes that contain player input for more than one
1839 player (due to a bug in 3.3.1.2 and earlier versions), which results
1840 in playing levels with more than one player in multi-player mode,
1841 even though the tape was originally recorded in single-player mode */
1843 // remove player input actions for all players but the first one
1844 for (i = 1; i < MAX_PLAYERS; i++)
1845 tape.player_participates[i] = FALSE;
1847 tape.changed = TRUE;
1849 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1850 is_string_suffix(cheat_input, ":snl"))
1852 SaveNativeLevel(&level);
1854 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1855 is_string_suffix(cheat_input, ":fps"))
1857 global.show_frames_per_second = !global.show_frames_per_second;
1860 else if (game_status == GAME_MODE_PLAYING)
1863 if (is_string_suffix(cheat_input, ".q"))
1864 DEBUG_SetMaximumDynamite();
1867 else if (game_status == GAME_MODE_EDITOR)
1869 if (is_string_suffix(cheat_input, ":dump-brush") ||
1870 is_string_suffix(cheat_input, ":DB"))
1874 else if (is_string_suffix(cheat_input, ":DDB"))
1879 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1881 if (letter == 'x') // copy brush to clipboard (small size)
1883 CopyBrushToClipboard_Small();
1885 else if (letter == 'c') // copy brush to clipboard (normal size)
1887 CopyBrushToClipboard();
1889 else if (letter == 'v') // paste brush from Clipboard
1891 CopyClipboardToBrush();
1893 else if (letter == 'z') // undo or redo last operation
1895 if (GetKeyModState() & KMOD_Shift)
1896 RedoLevelEditorOperation();
1898 UndoLevelEditorOperation();
1903 // special key shortcuts for all game modes
1904 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1905 is_string_suffix(cheat_input, ":dea") ||
1906 is_string_suffix(cheat_input, ":DEA"))
1908 DumpGadgetIdentifiers();
1909 DumpScreenIdentifiers();
1913 boolean HandleKeysDebug(Key key, int key_status)
1918 if (key_status != KEY_PRESSED)
1921 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1923 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1925 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1927 if (key == setup.debug.frame_delay_key[i] &&
1928 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1930 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1931 setup.debug.frame_delay[i] : setup.game_frame_delay);
1933 if (!setup.debug.frame_delay_game_only)
1934 MenuFrameDelay = GameFrameDelay;
1936 SetVideoFrameDelay(GameFrameDelay);
1938 if (GameFrameDelay > ONE_SECOND_DELAY)
1939 Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
1940 else if (GameFrameDelay != 0)
1941 Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
1942 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1943 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1945 Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
1952 if (game_status == GAME_MODE_PLAYING)
1956 options.debug = !options.debug;
1958 Error(ERR_INFO, "debug mode %s",
1959 (options.debug ? "enabled" : "disabled"));
1963 else if (key == KSYM_v)
1965 Error(ERR_INFO, "currently using game engine version %d",
1966 game.engine_version);
1976 void HandleKey(Key key, int key_status)
1978 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1979 static boolean ignore_repeated_key = FALSE;
1980 static struct SetupKeyboardInfo ski;
1981 static struct SetupShortcutInfo ssi;
1990 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1991 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1992 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1993 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1994 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1995 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2000 if (HandleKeysDebug(key, key_status))
2001 return; // do not handle already processed keys again
2003 // map special keys (media keys / remote control buttons) to default keys
2004 if (key == KSYM_PlayPause)
2006 else if (key == KSYM_Select)
2009 HandleSpecialGameControllerKeys(key, key_status);
2011 if (game_status == GAME_MODE_PLAYING)
2013 // only needed for single-step tape recording mode
2014 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2017 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2019 byte key_action = 0;
2020 byte key_snap_action = 0;
2022 if (setup.input[pnr].use_joystick)
2025 ski = setup.input[pnr].key;
2027 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2028 if (key == *key_info[i].key_custom)
2029 key_action |= key_info[i].action;
2031 // use combined snap+direction keys for the first player only
2034 ssi = setup.shortcut;
2036 // also remember normal snap key when handling snap+direction keys
2037 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2039 for (i = 0; i < NUM_DIRECTIONS; i++)
2041 if (key == *key_info[i].key_snap)
2043 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2044 key_snap_action |= key_info[i].action;
2049 if (key_status == KEY_PRESSED)
2051 stored_player[pnr].action |= key_action;
2052 stored_player[pnr].snap_action |= key_snap_action;
2056 stored_player[pnr].action &= ~key_action;
2057 stored_player[pnr].snap_action &= ~key_snap_action;
2060 // restore snap action if one of several pressed snap keys was released
2061 if (stored_player[pnr].snap_action)
2062 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2064 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2066 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2068 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2070 // if snap key already pressed, keep pause mode when releasing
2071 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2072 has_snapped[pnr] = TRUE;
2074 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2076 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2078 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2079 getRedDiskReleaseFlag_SP() == 0)
2081 // add a single inactive frame before dropping starts
2082 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2083 stored_player[pnr].force_dropping = TRUE;
2086 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2088 // if snap key was pressed without direction, leave pause mode
2089 if (!has_snapped[pnr])
2090 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2092 has_snapped[pnr] = FALSE;
2095 else if (tape.recording && tape.pausing && !tape.use_mouse)
2097 // prevent key release events from un-pausing a paused game
2098 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2099 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2102 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2103 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2109 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2110 if (key == key_info[i].key_default)
2111 joy |= key_info[i].action;
2116 if (key_status == KEY_PRESSED)
2117 key_joystick_mapping |= joy;
2119 key_joystick_mapping &= ~joy;
2124 if (game_status != GAME_MODE_PLAYING)
2125 key_joystick_mapping = 0;
2127 if (key_status == KEY_RELEASED)
2129 // reset flag to ignore repeated "key pressed" events after key release
2130 ignore_repeated_key = FALSE;
2135 if ((key == KSYM_F11 ||
2136 ((key == KSYM_Return ||
2137 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2138 video.fullscreen_available &&
2139 !ignore_repeated_key)
2141 setup.fullscreen = !setup.fullscreen;
2143 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2145 if (game_status == GAME_MODE_SETUP)
2146 RedrawSetupScreenAfterFullscreenToggle();
2148 UpdateMousePosition();
2150 // set flag to ignore repeated "key pressed" events
2151 ignore_repeated_key = TRUE;
2156 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2157 key == KSYM_minus || key == KSYM_KP_Subtract ||
2158 key == KSYM_plus || key == KSYM_KP_Add ||
2159 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2160 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2161 video.window_scaling_available &&
2162 !video.fullscreen_enabled)
2164 if (key == KSYM_0 || key == KSYM_KP_0)
2165 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2166 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2167 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2169 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2171 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2172 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2173 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2174 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2176 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2178 if (game_status == GAME_MODE_SETUP)
2179 RedrawSetupScreenAfterFullscreenToggle();
2181 UpdateMousePosition();
2186 // some key events are handled like clicks for global animations
2187 boolean click = (key == KSYM_space ||
2188 key == KSYM_Return ||
2189 key == KSYM_Escape);
2191 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2193 // do not handle this key event anymore
2194 if (key != KSYM_Escape) // always allow ESC key to be handled
2198 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2199 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2206 if (game_status == GAME_MODE_MAIN &&
2207 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2209 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2214 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2216 if (key == setup.shortcut.save_game)
2218 else if (key == setup.shortcut.load_game)
2220 else if (key == setup.shortcut.toggle_pause)
2221 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2223 HandleTapeButtonKeys(key);
2224 HandleSoundButtonKeys(key);
2227 if (game_status == GAME_MODE_PLAYING && !network_playing)
2229 int centered_player_nr_next = -999;
2231 if (key == setup.shortcut.focus_player_all)
2232 centered_player_nr_next = -1;
2234 for (i = 0; i < MAX_PLAYERS; i++)
2235 if (key == setup.shortcut.focus_player[i])
2236 centered_player_nr_next = i;
2238 if (centered_player_nr_next != -999)
2240 game.centered_player_nr_next = centered_player_nr_next;
2241 game.set_centered_player = TRUE;
2245 tape.centered_player_nr_next = game.centered_player_nr_next;
2246 tape.set_centered_player = TRUE;
2251 HandleKeysSpecial(key);
2253 if (HandleGadgetsKeyInput(key))
2254 return; // do not handle already processed keys again
2256 switch (game_status)
2258 case GAME_MODE_PSEUDO_TYPENAME:
2259 HandleTypeName(0, key);
2262 case GAME_MODE_TITLE:
2263 case GAME_MODE_MAIN:
2264 case GAME_MODE_LEVELS:
2265 case GAME_MODE_LEVELNR:
2266 case GAME_MODE_SETUP:
2267 case GAME_MODE_INFO:
2268 case GAME_MODE_SCORES:
2270 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2277 if (game_status == GAME_MODE_TITLE)
2278 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2279 else if (game_status == GAME_MODE_MAIN)
2280 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2281 else if (game_status == GAME_MODE_LEVELS)
2282 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2283 else if (game_status == GAME_MODE_LEVELNR)
2284 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2285 else if (game_status == GAME_MODE_SETUP)
2286 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2287 else if (game_status == GAME_MODE_INFO)
2288 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2289 else if (game_status == GAME_MODE_SCORES)
2290 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2294 if (game_status != GAME_MODE_MAIN)
2295 FadeSkipNextFadeIn();
2297 if (game_status == GAME_MODE_TITLE)
2298 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2299 else if (game_status == GAME_MODE_LEVELS)
2300 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2301 else if (game_status == GAME_MODE_LEVELNR)
2302 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2303 else if (game_status == GAME_MODE_SETUP)
2304 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2305 else if (game_status == GAME_MODE_INFO)
2306 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2307 else if (game_status == GAME_MODE_SCORES)
2308 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2312 if (game_status == GAME_MODE_LEVELS)
2313 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2314 else if (game_status == GAME_MODE_LEVELNR)
2315 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2316 else if (game_status == GAME_MODE_SETUP)
2317 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2318 else if (game_status == GAME_MODE_INFO)
2319 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2320 else if (game_status == GAME_MODE_SCORES)
2321 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2324 case KSYM_Page_Down:
2325 if (game_status == GAME_MODE_LEVELS)
2326 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2327 else if (game_status == GAME_MODE_LEVELNR)
2328 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2329 else if (game_status == GAME_MODE_SETUP)
2330 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2331 else if (game_status == GAME_MODE_INFO)
2332 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2333 else if (game_status == GAME_MODE_SCORES)
2334 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2342 case GAME_MODE_EDITOR:
2343 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2344 HandleLevelEditorKeyInput(key);
2347 case GAME_MODE_PLAYING:
2352 RequestQuitGame(setup.ask_on_escape);
2362 if (key == KSYM_Escape)
2364 SetGameStatus(GAME_MODE_MAIN);
2373 void HandleNoEvent(void)
2375 HandleMouseCursor();
2377 switch (game_status)
2379 case GAME_MODE_PLAYING:
2380 HandleButtonOrFinger(-1, -1, -1);
2385 void HandleEventActions(void)
2387 // if (button_status && game_status != GAME_MODE_PLAYING)
2388 if (button_status && (game_status != GAME_MODE_PLAYING ||
2390 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2392 HandleButton(0, 0, button_status, -button_status);
2399 if (network.enabled)
2402 switch (game_status)
2404 case GAME_MODE_MAIN:
2405 DrawPreviewLevelAnimation();
2408 case GAME_MODE_EDITOR:
2409 HandleLevelEditorIdle();
2417 static void HandleTileCursor(int dx, int dy, int button)
2420 ClearPlayerMouseAction();
2427 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2428 (dx < 0 ? MB_LEFTBUTTON :
2429 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2431 else if (!tile_cursor.moving)
2433 int old_xpos = tile_cursor.xpos;
2434 int old_ypos = tile_cursor.ypos;
2435 int new_xpos = old_xpos;
2436 int new_ypos = old_ypos;
2438 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2439 new_xpos = old_xpos + dx;
2441 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2442 new_ypos = old_ypos + dy;
2444 SetTileCursorTargetXY(new_xpos, new_ypos);
2448 static int HandleJoystickForAllPlayers(void)
2452 boolean no_joysticks_configured = TRUE;
2453 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2454 static byte joy_action_last[MAX_PLAYERS];
2456 for (i = 0; i < MAX_PLAYERS; i++)
2457 if (setup.input[i].use_joystick)
2458 no_joysticks_configured = FALSE;
2460 // if no joysticks configured, map connected joysticks to players
2461 if (no_joysticks_configured)
2462 use_as_joystick_nr = TRUE;
2464 for (i = 0; i < MAX_PLAYERS; i++)
2466 byte joy_action = 0;
2468 joy_action = JoystickExt(i, use_as_joystick_nr);
2469 result |= joy_action;
2471 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2472 joy_action != joy_action_last[i])
2473 stored_player[i].action = joy_action;
2475 joy_action_last[i] = joy_action;
2481 void HandleJoystick(void)
2483 static unsigned int joytest_delay = 0;
2484 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2485 static int joytest_last = 0;
2486 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2487 int delay_value = GADGET_FRAME_DELAY;
2488 int joystick = HandleJoystickForAllPlayers();
2489 int keyboard = key_joystick_mapping;
2490 int joy = (joystick | keyboard);
2491 int joytest = joystick;
2492 int left = joy & JOY_LEFT;
2493 int right = joy & JOY_RIGHT;
2494 int up = joy & JOY_UP;
2495 int down = joy & JOY_DOWN;
2496 int button = joy & JOY_BUTTON;
2497 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2498 int dx = (left ? -1 : right ? 1 : 0);
2499 int dy = (up ? -1 : down ? 1 : 0);
2500 boolean use_delay_value_first = (joytest != joytest_last);
2502 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2504 // do not handle this button event anymore
2508 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2509 anyTextGadgetActive()))
2511 // leave name input in main menu or text input gadget
2512 HandleKey(KSYM_Escape, KEY_PRESSED);
2513 HandleKey(KSYM_Escape, KEY_RELEASED);
2518 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2520 if (game_status == GAME_MODE_PLAYING)
2522 // when playing MM style levels, also use delay for keyboard events
2523 joytest |= keyboard;
2525 // only use first delay value for new events, but not for changed events
2526 use_delay_value_first = (!joytest != !joytest_last);
2528 // only use delay after the initial keyboard event
2532 // for any joystick or keyboard event, enable playfield tile cursor
2533 if (dx || dy || button)
2534 SetTileCursorEnabled(TRUE);
2537 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2539 // delay joystick/keyboard actions if axes/keys continually pressed
2540 newbutton = dx = dy = 0;
2544 // first start with longer delay, then continue with shorter delay
2545 joytest_delay_value =
2546 (use_delay_value_first ? delay_value_first : delay_value);
2549 joytest_last = joytest;
2551 switch (game_status)
2553 case GAME_MODE_TITLE:
2554 case GAME_MODE_MAIN:
2555 case GAME_MODE_LEVELS:
2556 case GAME_MODE_LEVELNR:
2557 case GAME_MODE_SETUP:
2558 case GAME_MODE_INFO:
2559 case GAME_MODE_SCORES:
2561 if (anyTextGadgetActive())
2564 if (game_status == GAME_MODE_TITLE)
2565 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2566 else if (game_status == GAME_MODE_MAIN)
2567 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2568 else if (game_status == GAME_MODE_LEVELS)
2569 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2570 else if (game_status == GAME_MODE_LEVELNR)
2571 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2572 else if (game_status == GAME_MODE_SETUP)
2573 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2574 else if (game_status == GAME_MODE_INFO)
2575 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2576 else if (game_status == GAME_MODE_SCORES)
2577 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2582 case GAME_MODE_PLAYING:
2584 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2585 if (tape.playing || keyboard)
2586 newbutton = ((joy & JOY_BUTTON) != 0);
2589 if (newbutton && game.all_players_gone)
2596 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2598 if (joystick & JOY_ACTION)
2599 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2601 else if (tape.recording && tape.pausing && !tape.use_mouse)
2603 if (joystick & JOY_ACTION)
2604 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2607 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2608 HandleTileCursor(dx, dy, button);
2617 void HandleSpecialGameControllerButtons(Event *event)
2622 switch (event->type)
2624 case SDL_CONTROLLERBUTTONDOWN:
2625 key_status = KEY_PRESSED;
2628 case SDL_CONTROLLERBUTTONUP:
2629 key_status = KEY_RELEASED;
2636 switch (event->cbutton.button)
2638 case SDL_CONTROLLER_BUTTON_START:
2642 case SDL_CONTROLLER_BUTTON_BACK:
2650 HandleKey(key, key_status);
2653 void HandleSpecialGameControllerKeys(Key key, int key_status)
2655 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2656 int button = SDL_CONTROLLER_BUTTON_INVALID;
2658 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2659 if (key == KSYM_Rewind)
2660 button = SDL_CONTROLLER_BUTTON_A;
2661 else if (key == KSYM_FastForward || key == KSYM_Menu)
2662 button = SDL_CONTROLLER_BUTTON_B;
2664 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2668 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2669 SDL_CONTROLLERBUTTONUP);
2671 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2672 event.cbutton.button = button;
2673 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2676 HandleJoystickEvent(&event);
2681 boolean DoKeysymAction(int keysym)
2685 Key key = (Key)(-keysym);
2687 HandleKey(key, KEY_PRESSED);
2688 HandleKey(key, KEY_RELEASED);