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 // do no reset mouse cursor before all pending events have been processed
107 if (gfx.cursor_mode == cursor_mode_last &&
108 ((game_status == GAME_MODE_TITLE &&
109 gfx.cursor_mode == CURSOR_NONE) ||
110 (game_status == GAME_MODE_PLAYING &&
111 gfx.cursor_mode == CURSOR_PLAYFIELD)))
113 SetMouseCursor(CURSOR_DEFAULT);
115 DelayReached(&special_cursor_delay, 0);
117 cursor_mode_last = CURSOR_DEFAULT;
120 // skip mouse motion events without pressed button outside level editor
121 if (button_status == MB_RELEASED &&
122 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
128 // to prevent delay problems, skip mouse motion events if the very next
129 // event is also a mouse motion event (and therefore effectively only
130 // handling the last of a row of mouse motion events in the event queue)
132 static boolean SkipPressedMouseMotionEvent(const Event *event)
134 // nothing to do if the current event is not a mouse motion event
135 if (event->type != EVENT_MOTIONNOTIFY)
138 // only skip motion events with pressed button outside the game
139 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
146 PeekEvent(&next_event);
148 // if next event is also a mouse motion event, skip the current one
149 if (next_event.type == EVENT_MOTIONNOTIFY)
156 static boolean WaitValidEvent(Event *event)
160 if (!FilterEvents(event))
163 if (SkipPressedMouseMotionEvent(event))
169 /* this is especially needed for event modifications for the Android target:
170 if mouse coordinates should be modified in the event filter function,
171 using a properly installed SDL event filter does not work, because in
172 the event filter, mouse coordinates in the event structure are still
173 physical pixel positions, not logical (scaled) screen positions, so this
174 has to be handled at a later stage in the event processing functions
175 (when device pixel positions are already converted to screen positions) */
177 boolean NextValidEvent(Event *event)
179 while (PendingEvent())
180 if (WaitValidEvent(event))
186 void StopProcessingEvents(void)
188 stop_processing_events = TRUE;
191 static void HandleEvents(void)
194 unsigned int event_frame_delay = 0;
195 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
197 ResetDelayCounter(&event_frame_delay);
199 stop_processing_events = FALSE;
201 while (NextValidEvent(&event))
205 case EVENT_BUTTONPRESS:
206 case EVENT_BUTTONRELEASE:
207 HandleButtonEvent((ButtonEvent *) &event);
210 case EVENT_MOTIONNOTIFY:
211 HandleMotionEvent((MotionEvent *) &event);
214 case EVENT_WHEELMOTION:
215 HandleWheelEvent((WheelEvent *) &event);
218 case SDL_WINDOWEVENT:
219 HandleWindowEvent((WindowEvent *) &event);
222 case EVENT_FINGERPRESS:
223 case EVENT_FINGERRELEASE:
224 case EVENT_FINGERMOTION:
225 HandleFingerEvent((FingerEvent *) &event);
228 case EVENT_TEXTINPUT:
229 HandleTextEvent((TextEvent *) &event);
232 case SDL_APP_WILLENTERBACKGROUND:
233 case SDL_APP_DIDENTERBACKGROUND:
234 case SDL_APP_WILLENTERFOREGROUND:
235 case SDL_APP_DIDENTERFOREGROUND:
236 HandlePauseResumeEvent((PauseResumeEvent *) &event);
240 case EVENT_KEYRELEASE:
241 HandleKeyEvent((KeyEvent *) &event);
245 HandleUserEvent((UserEvent *) &event);
249 HandleOtherEvents(&event);
253 // do not handle events for longer than standard frame delay period
254 if (DelayReached(&event_frame_delay, event_frame_delay_value))
257 // do not handle any further events if triggered by a special flag
258 if (stop_processing_events)
263 void HandleOtherEvents(Event *event)
267 case SDL_CONTROLLERBUTTONDOWN:
268 case SDL_CONTROLLERBUTTONUP:
269 // for any game controller button event, disable overlay buttons
270 SetOverlayEnabled(FALSE);
272 HandleSpecialGameControllerButtons(event);
275 case SDL_CONTROLLERDEVICEADDED:
276 case SDL_CONTROLLERDEVICEREMOVED:
277 case SDL_CONTROLLERAXISMOTION:
278 case SDL_JOYAXISMOTION:
279 case SDL_JOYBUTTONDOWN:
280 case SDL_JOYBUTTONUP:
281 HandleJoystickEvent(event);
285 case SDL_DROPCOMPLETE:
288 HandleDropEvent(event);
300 static void HandleMouseCursor(void)
302 if (game_status == GAME_MODE_TITLE)
304 // when showing title screens, hide mouse pointer (if not moved)
306 if (gfx.cursor_mode != CURSOR_NONE &&
307 DelayReached(&special_cursor_delay, special_cursor_delay_value))
309 SetMouseCursor(CURSOR_NONE);
312 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
315 // when playing, display a special mouse pointer inside the playfield
317 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
318 cursor_inside_playfield &&
319 DelayReached(&special_cursor_delay, special_cursor_delay_value))
321 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
323 SetMouseCursor(CURSOR_PLAYFIELD);
326 else if (gfx.cursor_mode != CURSOR_DEFAULT)
328 SetMouseCursor(CURSOR_DEFAULT);
331 // this is set after all pending events have been processed
332 cursor_mode_last = gfx.cursor_mode;
344 // execute event related actions after pending events have been processed
345 HandleEventActions();
347 // don't use all CPU time when idle; the main loop while playing
348 // has its own synchronization and is CPU friendly, too
350 if (game_status == GAME_MODE_PLAYING)
353 // always copy backbuffer to visible screen for every video frame
356 // reset video frame delay to default (may change again while playing)
357 SetVideoFrameDelay(MenuFrameDelay);
359 if (game_status == GAME_MODE_QUIT)
364 void ClearAutoRepeatKeyEvents(void)
366 while (PendingEvent())
370 PeekEvent(&next_event);
372 // if event is repeated key press event, remove it from event queue
373 if (next_event.type == EVENT_KEYPRESS &&
374 next_event.key.repeat)
375 WaitEvent(&next_event);
381 void ClearEventQueue(void)
385 while (NextValidEvent(&event))
389 case EVENT_BUTTONRELEASE:
390 button_status = MB_RELEASED;
393 case EVENT_KEYRELEASE:
397 case SDL_CONTROLLERBUTTONUP:
398 HandleJoystickEvent(&event);
403 HandleOtherEvents(&event);
409 static void ClearPlayerMouseAction(void)
411 local_player->mouse_action.lx = 0;
412 local_player->mouse_action.ly = 0;
413 local_player->mouse_action.button = 0;
416 void ClearPlayerAction(void)
420 // simulate key release events for still pressed keys
421 key_joystick_mapping = 0;
422 for (i = 0; i < MAX_PLAYERS; i++)
424 stored_player[i].action = 0;
425 stored_player[i].snap_action = 0;
428 ClearJoystickState();
429 ClearPlayerMouseAction();
432 static void SetPlayerMouseAction(int mx, int my, int button)
434 int lx = getLevelFromScreenX(mx);
435 int ly = getLevelFromScreenY(my);
436 int new_button = (!local_player->mouse_action.button && button);
438 if (local_player->mouse_action.button_hint)
439 button = local_player->mouse_action.button_hint;
441 ClearPlayerMouseAction();
443 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
446 local_player->mouse_action.lx = lx;
447 local_player->mouse_action.ly = ly;
448 local_player->mouse_action.button = button;
450 if (tape.recording && tape.pausing && tape.use_mouse)
452 // un-pause a paused game only if mouse button was newly pressed down
454 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
457 SetTileCursorXY(lx, ly);
460 void HandleButtonEvent(ButtonEvent *event)
462 #if DEBUG_EVENTS_BUTTON
463 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
465 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
469 // for any mouse button event, disable playfield tile cursor
470 SetTileCursorEnabled(FALSE);
472 #if defined(HAS_SCREEN_KEYBOARD)
473 if (video.shifted_up)
474 event->y += video.shifted_up_pos;
477 motion_status = FALSE;
479 if (event->type == EVENT_BUTTONPRESS)
480 button_status = event->button;
482 button_status = MB_RELEASED;
484 HandleButton(event->x, event->y, button_status, event->button);
487 void HandleMotionEvent(MotionEvent *event)
489 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
492 motion_status = TRUE;
494 #if DEBUG_EVENTS_MOTION
495 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
496 button_status, event->x, event->y);
499 HandleButton(event->x, event->y, button_status, button_status);
502 void HandleWheelEvent(WheelEvent *event)
506 #if DEBUG_EVENTS_WHEEL
508 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
509 event->which, event->x, event->y);
511 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
512 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
513 event->which, event->x, event->y,
514 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
515 "SDL_MOUSEWHEEL_FLIPPED"));
519 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
520 event->x > 0 ? MB_WHEEL_RIGHT :
521 event->y < 0 ? MB_WHEEL_DOWN :
522 event->y > 0 ? MB_WHEEL_UP : 0);
524 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
525 // accelerated mouse wheel available on Mac and Windows
526 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
528 // no accelerated mouse wheel available on Unix/Linux
529 wheel_steps = DEFAULT_WHEEL_STEPS;
532 motion_status = FALSE;
534 button_status = button_nr;
535 HandleButton(0, 0, button_status, -button_nr);
537 button_status = MB_RELEASED;
538 HandleButton(0, 0, button_status, -button_nr);
541 void HandleWindowEvent(WindowEvent *event)
543 #if DEBUG_EVENTS_WINDOW
544 int subtype = event->event;
547 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
548 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
549 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
550 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
551 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
552 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
553 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
554 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
555 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
556 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
557 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
558 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
559 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
560 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
563 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
564 event_name, event->data1, event->data2);
568 // (not needed, as the screen gets redrawn every 20 ms anyway)
569 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
570 event->event == SDL_WINDOWEVENT_RESIZED ||
571 event->event == SDL_WINDOWEVENT_EXPOSED)
575 if (event->event == SDL_WINDOWEVENT_RESIZED)
577 if (!video.fullscreen_enabled)
579 int new_window_width = event->data1;
580 int new_window_height = event->data2;
582 // if window size has changed after resizing, calculate new scaling factor
583 if (new_window_width != video.window_width ||
584 new_window_height != video.window_height)
586 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
587 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
589 // (extreme window scaling allowed, but cannot be saved permanently)
590 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
591 setup.window_scaling_percent =
592 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
593 MAX_WINDOW_SCALING_PERCENT);
595 video.window_width = new_window_width;
596 video.window_height = new_window_height;
598 if (game_status == GAME_MODE_SETUP)
599 RedrawSetupScreenAfterFullscreenToggle();
601 UpdateMousePosition();
606 #if defined(PLATFORM_ANDROID)
609 int new_display_width = event->data1;
610 int new_display_height = event->data2;
612 // if fullscreen display size has changed, device has been rotated
613 if (new_display_width != video.display_width ||
614 new_display_height != video.display_height)
616 int nr = GRID_ACTIVE_NR(); // previous screen orientation
618 video.display_width = new_display_width;
619 video.display_height = new_display_height;
621 SDLSetScreenProperties();
623 // check if screen orientation has changed (should always be true here)
624 if (nr != GRID_ACTIVE_NR())
628 if (game_status == GAME_MODE_SETUP)
629 RedrawSetupScreenAfterScreenRotation(nr);
631 nr = GRID_ACTIVE_NR();
633 overlay.grid_xsize = setup.touch.grid_xsize[nr];
634 overlay.grid_ysize = setup.touch.grid_ysize[nr];
636 for (x = 0; x < MAX_GRID_XSIZE; x++)
637 for (y = 0; y < MAX_GRID_YSIZE; y++)
638 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
646 #define NUM_TOUCH_FINGERS 3
651 SDL_FingerID finger_id;
655 } touch_info[NUM_TOUCH_FINGERS];
657 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
660 int x = event->x * overlay.grid_xsize;
661 int y = event->y * overlay.grid_ysize;
662 int grid_button = overlay.grid_button[x][y];
663 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
664 Key key = (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
665 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
666 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
667 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
668 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
669 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
672 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
673 float event_x = (event->x);
674 float event_y = (event->y - ypos) / (1 - ypos);
675 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
676 event_y > 2.0 / 3.0 && event_y < 1 ?
677 setup.input[0].key.snap :
678 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
679 event_y > 2.0 / 3.0 && event_y < 1 ?
680 setup.input[0].key.drop :
681 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
682 event_y > 0 && event_y < 1.0 / 3.0 ?
683 setup.input[0].key.up :
684 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
685 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
686 setup.input[0].key.left :
687 event_x > 8.0 / 9.0 && event_x < 1 &&
688 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
689 setup.input[0].key.right :
690 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
691 event_y > 2.0 / 3.0 && event_y < 1 ?
692 setup.input[0].key.down :
695 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
697 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
701 virtual_button_pressed = (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
703 // for any touch input event, enable overlay buttons (if activated)
704 SetOverlayEnabled(TRUE);
706 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
707 getKeyNameFromKey(key), key_status_name, event->fingerId);
709 if (key_status == KEY_PRESSED)
710 overlay.grid_button_action |= grid_button_action;
712 overlay.grid_button_action &= ~grid_button_action;
714 // check if we already know this touch event's finger id
715 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
717 if (touch_info[i].touched &&
718 touch_info[i].finger_id == event->fingerId)
720 // Error(ERR_DEBUG, "MARK 1: %d", i);
726 if (i >= NUM_TOUCH_FINGERS)
728 if (key_status == KEY_PRESSED)
730 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
732 // unknown finger id -- get new, empty slot, if available
733 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
735 if (touch_info[i].counter < oldest_counter)
738 oldest_counter = touch_info[i].counter;
740 // Error(ERR_DEBUG, "MARK 2: %d", i);
743 if (!touch_info[i].touched)
745 // Error(ERR_DEBUG, "MARK 3: %d", i);
751 if (i >= NUM_TOUCH_FINGERS)
753 // all slots allocated -- use oldest slot
756 // Error(ERR_DEBUG, "MARK 4: %d", i);
761 // release of previously unknown key (should not happen)
763 if (key != KSYM_UNDEFINED)
765 HandleKey(key, KEY_RELEASED);
767 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
768 getKeyNameFromKey(key), "KEY_RELEASED", i);
773 if (i < NUM_TOUCH_FINGERS)
775 if (key_status == KEY_PRESSED)
777 if (touch_info[i].key != key)
779 if (touch_info[i].key != KSYM_UNDEFINED)
781 HandleKey(touch_info[i].key, KEY_RELEASED);
783 // undraw previous grid button when moving finger away
784 overlay.grid_button_action &= ~touch_info[i].action;
786 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
787 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
790 if (key != KSYM_UNDEFINED)
792 HandleKey(key, KEY_PRESSED);
794 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
795 getKeyNameFromKey(key), "KEY_PRESSED", i);
799 touch_info[i].touched = TRUE;
800 touch_info[i].finger_id = event->fingerId;
801 touch_info[i].counter = Counter();
802 touch_info[i].key = key;
803 touch_info[i].action = grid_button_action;
807 if (touch_info[i].key != KSYM_UNDEFINED)
809 HandleKey(touch_info[i].key, KEY_RELEASED);
811 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
812 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
815 touch_info[i].touched = FALSE;
816 touch_info[i].finger_id = 0;
817 touch_info[i].counter = 0;
818 touch_info[i].key = 0;
819 touch_info[i].action = JOY_NO_ACTION;
824 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
826 static Key motion_key_x = KSYM_UNDEFINED;
827 static Key motion_key_y = KSYM_UNDEFINED;
828 static Key button_key = KSYM_UNDEFINED;
829 static float motion_x1, motion_y1;
830 static float button_x1, button_y1;
831 static SDL_FingerID motion_id = -1;
832 static SDL_FingerID button_id = -1;
833 int move_trigger_distance_percent = setup.touch.move_distance;
834 int drop_trigger_distance_percent = setup.touch.drop_distance;
835 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
836 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
837 float event_x = event->x;
838 float event_y = event->y;
840 if (event->type == EVENT_FINGERPRESS)
842 if (event_x > 1.0 / 3.0)
846 motion_id = event->fingerId;
851 motion_key_x = KSYM_UNDEFINED;
852 motion_key_y = KSYM_UNDEFINED;
854 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
860 button_id = event->fingerId;
865 button_key = setup.input[0].key.snap;
867 HandleKey(button_key, KEY_PRESSED);
869 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
872 else if (event->type == EVENT_FINGERRELEASE)
874 if (event->fingerId == motion_id)
878 if (motion_key_x != KSYM_UNDEFINED)
879 HandleKey(motion_key_x, KEY_RELEASED);
880 if (motion_key_y != KSYM_UNDEFINED)
881 HandleKey(motion_key_y, KEY_RELEASED);
883 motion_key_x = KSYM_UNDEFINED;
884 motion_key_y = KSYM_UNDEFINED;
886 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
888 else if (event->fingerId == button_id)
892 if (button_key != KSYM_UNDEFINED)
893 HandleKey(button_key, KEY_RELEASED);
895 button_key = KSYM_UNDEFINED;
897 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
900 else if (event->type == EVENT_FINGERMOTION)
902 if (event->fingerId == motion_id)
904 float distance_x = ABS(event_x - motion_x1);
905 float distance_y = ABS(event_y - motion_y1);
906 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
907 event_x > motion_x1 ? setup.input[0].key.right :
909 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
910 event_y > motion_y1 ? setup.input[0].key.down :
913 if (distance_x < move_trigger_distance / 2 ||
914 distance_x < distance_y)
915 new_motion_key_x = KSYM_UNDEFINED;
917 if (distance_y < move_trigger_distance / 2 ||
918 distance_y < distance_x)
919 new_motion_key_y = KSYM_UNDEFINED;
921 if (distance_x > move_trigger_distance ||
922 distance_y > move_trigger_distance)
924 if (new_motion_key_x != motion_key_x)
926 if (motion_key_x != KSYM_UNDEFINED)
927 HandleKey(motion_key_x, KEY_RELEASED);
928 if (new_motion_key_x != KSYM_UNDEFINED)
929 HandleKey(new_motion_key_x, KEY_PRESSED);
932 if (new_motion_key_y != motion_key_y)
934 if (motion_key_y != KSYM_UNDEFINED)
935 HandleKey(motion_key_y, KEY_RELEASED);
936 if (new_motion_key_y != KSYM_UNDEFINED)
937 HandleKey(new_motion_key_y, KEY_PRESSED);
943 motion_key_x = new_motion_key_x;
944 motion_key_y = new_motion_key_y;
946 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
949 else if (event->fingerId == button_id)
951 float distance_x = ABS(event_x - button_x1);
952 float distance_y = ABS(event_y - button_y1);
954 if (distance_x < drop_trigger_distance / 2 &&
955 distance_y > drop_trigger_distance)
957 if (button_key == setup.input[0].key.snap)
958 HandleKey(button_key, KEY_RELEASED);
963 button_key = setup.input[0].key.drop;
965 HandleKey(button_key, KEY_PRESSED);
967 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
973 void HandleFingerEvent(FingerEvent *event)
975 #if DEBUG_EVENTS_FINGER
976 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
977 event->type == EVENT_FINGERPRESS ? "pressed" :
978 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
982 event->dx, event->dy,
986 runtime.uses_touch_device = TRUE;
988 if (game_status != GAME_MODE_PLAYING)
991 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
993 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
994 local_player->mouse_action.button_hint =
995 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
996 event->x < 0.5 ? MB_LEFTBUTTON :
997 event->x > 0.5 ? MB_RIGHTBUTTON :
1003 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1004 HandleFingerEvent_VirtualButtons(event);
1005 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1006 HandleFingerEvent_WipeGestures(event);
1009 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1011 static int old_mx = 0, old_my = 0;
1012 static int last_button = MB_LEFTBUTTON;
1013 static boolean touched = FALSE;
1014 static boolean tapped = FALSE;
1016 // screen tile was tapped (but finger not touching the screen anymore)
1017 // (this point will also be reached without receiving a touch event)
1018 if (tapped && !touched)
1020 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1025 // stop here if this function was not triggered by a touch event
1029 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1031 // finger started touching the screen
1041 ClearPlayerMouseAction();
1043 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1046 else if (button == MB_RELEASED && touched)
1048 // finger stopped touching the screen
1053 SetPlayerMouseAction(old_mx, old_my, last_button);
1055 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1057 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1062 // finger moved while touching the screen
1064 int old_x = getLevelFromScreenX(old_mx);
1065 int old_y = getLevelFromScreenY(old_my);
1066 int new_x = getLevelFromScreenX(mx);
1067 int new_y = getLevelFromScreenY(my);
1069 if (new_x != old_x || new_y != old_y)
1074 // finger moved left or right from (horizontal) starting position
1076 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1078 SetPlayerMouseAction(old_mx, old_my, button_nr);
1080 last_button = button_nr;
1082 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1086 // finger stays at or returned to (horizontal) starting position
1088 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1090 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1095 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1097 static int old_mx = 0, old_my = 0;
1098 static int last_button = MB_LEFTBUTTON;
1099 static boolean touched = FALSE;
1100 static boolean tapped = FALSE;
1102 // screen tile was tapped (but finger not touching the screen anymore)
1103 // (this point will also be reached without receiving a touch event)
1104 if (tapped && !touched)
1106 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1111 // stop here if this function was not triggered by a touch event
1115 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1117 // finger started touching the screen
1127 ClearPlayerMouseAction();
1129 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1132 else if (button == MB_RELEASED && touched)
1134 // finger stopped touching the screen
1139 SetPlayerMouseAction(old_mx, old_my, last_button);
1141 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1143 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1148 // finger moved while touching the screen
1150 int old_x = getLevelFromScreenX(old_mx);
1151 int old_y = getLevelFromScreenY(old_my);
1152 int new_x = getLevelFromScreenX(mx);
1153 int new_y = getLevelFromScreenY(my);
1155 if (new_x != old_x || new_y != old_y)
1157 // finger moved away from starting position
1159 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1161 // quickly alternate between clicking and releasing for maximum speed
1162 if (FrameCounter % 2 == 0)
1163 button_nr = MB_RELEASED;
1165 SetPlayerMouseAction(old_mx, old_my, button_nr);
1168 last_button = button_nr;
1172 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1176 // finger stays at or returned to starting position
1178 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1180 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1185 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1187 static int old_mx = 0, old_my = 0;
1188 static Key motion_key_x = KSYM_UNDEFINED;
1189 static Key motion_key_y = KSYM_UNDEFINED;
1190 static boolean touched = FALSE;
1191 static boolean started_on_player = FALSE;
1192 static boolean player_is_dropping = FALSE;
1193 static int player_drop_count = 0;
1194 static int last_player_x = -1;
1195 static int last_player_y = -1;
1197 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1206 started_on_player = FALSE;
1207 player_is_dropping = FALSE;
1208 player_drop_count = 0;
1212 motion_key_x = KSYM_UNDEFINED;
1213 motion_key_y = KSYM_UNDEFINED;
1215 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1218 else if (button == MB_RELEASED && touched)
1225 if (motion_key_x != KSYM_UNDEFINED)
1226 HandleKey(motion_key_x, KEY_RELEASED);
1227 if (motion_key_y != KSYM_UNDEFINED)
1228 HandleKey(motion_key_y, KEY_RELEASED);
1230 if (started_on_player)
1232 if (player_is_dropping)
1234 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1236 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1240 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1242 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1246 motion_key_x = KSYM_UNDEFINED;
1247 motion_key_y = KSYM_UNDEFINED;
1249 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1254 int src_x = local_player->jx;
1255 int src_y = local_player->jy;
1256 int dst_x = getLevelFromScreenX(old_mx);
1257 int dst_y = getLevelFromScreenY(old_my);
1258 int dx = dst_x - src_x;
1259 int dy = dst_y - src_y;
1260 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1261 dx > 0 ? setup.input[0].key.right :
1263 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1264 dy > 0 ? setup.input[0].key.down :
1267 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1268 (last_player_x != local_player->jx ||
1269 last_player_y != local_player->jy))
1271 // in case of asymmetric diagonal movement, use "preferred" direction
1273 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1275 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1276 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1278 local_player->last_move_dir = last_move_dir;
1280 // (required to prevent accidentally forcing direction for next movement)
1281 last_player_x = local_player->jx;
1282 last_player_y = local_player->jy;
1285 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1287 started_on_player = TRUE;
1288 player_drop_count = getPlayerInventorySize(0);
1289 player_is_dropping = (player_drop_count > 0);
1291 if (player_is_dropping)
1293 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1295 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1299 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1301 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1304 else if (dx != 0 || dy != 0)
1306 if (player_is_dropping &&
1307 player_drop_count == getPlayerInventorySize(0))
1309 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1311 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1312 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1314 player_is_dropping = FALSE;
1318 if (new_motion_key_x != motion_key_x)
1320 Error(ERR_DEBUG, "---------- %s %s ----------",
1321 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1322 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1324 if (motion_key_x != KSYM_UNDEFINED)
1325 HandleKey(motion_key_x, KEY_RELEASED);
1326 if (new_motion_key_x != KSYM_UNDEFINED)
1327 HandleKey(new_motion_key_x, KEY_PRESSED);
1330 if (new_motion_key_y != motion_key_y)
1332 Error(ERR_DEBUG, "---------- %s %s ----------",
1333 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1334 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1336 if (motion_key_y != KSYM_UNDEFINED)
1337 HandleKey(motion_key_y, KEY_RELEASED);
1338 if (new_motion_key_y != KSYM_UNDEFINED)
1339 HandleKey(new_motion_key_y, KEY_PRESSED);
1342 motion_key_x = new_motion_key_x;
1343 motion_key_y = new_motion_key_y;
1347 static void HandleButtonOrFinger(int mx, int my, int button)
1349 if (game_status != GAME_MODE_PLAYING)
1352 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1354 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1355 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1356 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1357 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1358 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1359 SetPlayerMouseAction(mx, my, button); // special case
1363 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1364 HandleButtonOrFinger_FollowFinger(mx, my, button);
1368 static boolean checkTextInputKeyModState(void)
1370 // when playing, only handle raw key events and ignore text input
1371 if (game_status == GAME_MODE_PLAYING)
1374 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1377 void HandleTextEvent(TextEvent *event)
1379 char *text = event->text;
1380 Key key = getKeyFromKeyName(text);
1382 #if DEBUG_EVENTS_TEXT
1383 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1386 text[0], (int)(text[0]),
1388 getKeyNameFromKey(key),
1392 #if !defined(HAS_SCREEN_KEYBOARD)
1393 // non-mobile devices: only handle key input with modifier keys pressed here
1394 // (every other key input is handled directly as physical key input event)
1395 if (!checkTextInputKeyModState())
1399 // process text input as "classic" (with uppercase etc.) key input event
1400 HandleKey(key, KEY_PRESSED);
1401 HandleKey(key, KEY_RELEASED);
1404 void HandlePauseResumeEvent(PauseResumeEvent *event)
1406 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1410 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1416 void HandleKeyEvent(KeyEvent *event)
1418 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1419 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1420 Key key = GetEventKey(event, with_modifiers);
1421 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1423 #if DEBUG_EVENTS_KEY
1424 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1425 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1426 event->keysym.scancode,
1431 getKeyNameFromKey(key));
1434 #if defined(PLATFORM_ANDROID)
1435 if (key == KSYM_Back)
1437 // always map the "back" button to the "escape" key on Android devices
1440 else if (key == KSYM_Menu)
1442 // the "menu" button can be used to toggle displaying virtual buttons
1443 if (key_status == KEY_PRESSED)
1444 SetOverlayEnabled(!GetOverlayEnabled());
1448 // for any other "real" key event, disable virtual buttons
1449 SetOverlayEnabled(FALSE);
1453 HandleKeyModState(keymod, key_status);
1455 // only handle raw key input without text modifier keys pressed
1456 if (!checkTextInputKeyModState())
1457 HandleKey(key, key_status);
1460 static int HandleDropFileEvent(char *filename)
1462 Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
1464 // check and extract dropped zip files into correct user data directory
1465 if (!strSuffixLower(filename, ".zip"))
1467 Error(ERR_WARN, "file '%s' not supported", filename);
1469 return TREE_TYPE_UNDEFINED;
1472 TreeInfo *tree_node = NULL;
1473 int tree_type = GetZipFileTreeType(filename);
1474 char *directory = TREE_USERDIR(tree_type);
1476 if (directory == NULL)
1478 Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1480 return TREE_TYPE_UNDEFINED;
1483 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1484 game_status == GAME_MODE_LEVELS &&
1485 leveldir_current->node_parent != NULL)
1487 // extract new level set next to currently selected level set
1488 tree_node = leveldir_current;
1490 // get parent directory of currently selected level set directory
1491 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1493 // use private level directory instead of top-level package level directory
1494 if (strPrefix(directory, options.level_directory) &&
1495 strEqual(leveldir_current->node_parent->fullpath, "."))
1496 directory = getUserLevelDir(NULL);
1499 // extract level or artwork set from zip file to target directory
1500 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1502 if (top_dir == NULL)
1504 // error message already issued by "ExtractZipFileIntoDirectory()"
1506 return TREE_TYPE_UNDEFINED;
1509 // add extracted level or artwork set to tree info structure
1510 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1512 // update menu screen (and possibly change current level set)
1513 DrawScreenAfterAddingSet(top_dir, tree_type);
1518 static void HandleDropTextEvent(char *text)
1520 Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1523 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1524 int num_artwork_sets_succeeded,
1525 int num_files_failed)
1527 // only show request dialog if no other request dialog already active
1528 if (game.request_active)
1531 // this case can happen with drag-and-drop with older SDL versions
1532 if (num_level_sets_succeeded == 0 &&
1533 num_artwork_sets_succeeded == 0 &&
1534 num_files_failed == 0)
1539 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1541 char message_part1[50];
1543 sprintf(message_part1, "New %s set%s added",
1544 (num_artwork_sets_succeeded == 0 ? "level" :
1545 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1546 (num_level_sets_succeeded +
1547 num_artwork_sets_succeeded > 1 ? "s" : ""));
1549 if (num_files_failed > 0)
1550 sprintf(message, "%s, but %d dropped file%s failed!",
1551 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1553 sprintf(message, "%s!", message_part1);
1555 else if (num_files_failed > 0)
1557 sprintf(message, "Failed to process dropped file%s!",
1558 num_files_failed > 1 ? "s" : "");
1561 Request(message, REQ_CONFIRM);
1564 void HandleDropEvent(Event *event)
1566 static boolean confirm_on_drop_complete = FALSE;
1567 static int num_level_sets_succeeded = 0;
1568 static int num_artwork_sets_succeeded = 0;
1569 static int num_files_failed = 0;
1571 switch (event->type)
1575 confirm_on_drop_complete = TRUE;
1576 num_level_sets_succeeded = 0;
1577 num_artwork_sets_succeeded = 0;
1578 num_files_failed = 0;
1585 int tree_type = HandleDropFileEvent(event->drop.file);
1587 if (tree_type == TREE_TYPE_LEVEL_DIR)
1588 num_level_sets_succeeded++;
1589 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1590 tree_type == TREE_TYPE_SOUNDS_DIR ||
1591 tree_type == TREE_TYPE_MUSIC_DIR)
1592 num_artwork_sets_succeeded++;
1596 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1597 if (!confirm_on_drop_complete)
1599 // process all remaining events, including further SDL_DROPFILE events
1602 HandleDropCompleteEvent(num_level_sets_succeeded,
1603 num_artwork_sets_succeeded,
1606 num_level_sets_succeeded = 0;
1607 num_artwork_sets_succeeded = 0;
1608 num_files_failed = 0;
1616 HandleDropTextEvent(event->drop.file);
1621 case SDL_DROPCOMPLETE:
1623 HandleDropCompleteEvent(num_level_sets_succeeded,
1624 num_artwork_sets_succeeded,
1631 if (event->drop.file != NULL)
1632 SDL_free(event->drop.file);
1635 void HandleUserEvent(UserEvent *event)
1637 switch (event->code)
1639 case USEREVENT_ANIM_DELAY_ACTION:
1640 case USEREVENT_ANIM_EVENT_ACTION:
1641 // execute action functions until matching action was found
1642 if (DoKeysymAction(event->value1) ||
1643 DoGadgetAction(event->value1) ||
1644 DoScreenAction(event->value1))
1653 void HandleButton(int mx, int my, int button, int button_nr)
1655 static int old_mx = 0, old_my = 0;
1656 boolean button_hold = FALSE;
1657 boolean handle_gadgets = TRUE;
1663 button_nr = -button_nr;
1672 #if defined(PLATFORM_ANDROID)
1673 // when playing, only handle gadgets when using "follow finger" controls
1674 // or when using touch controls in combination with the MM game engine
1675 // or when using gadgets that do not overlap with virtual buttons
1677 (game_status != GAME_MODE_PLAYING ||
1678 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1679 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1680 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1681 !virtual_button_pressed));
1684 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1686 // do not handle this button event anymore
1687 return; // force mouse event not to be handled at all
1690 if (handle_gadgets && HandleGadgets(mx, my, button))
1692 // do not handle this button event anymore
1693 mx = my = -32; // force mouse event to be outside screen tiles
1696 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1699 // do not use scroll wheel button events for anything other than gadgets
1700 if (IS_WHEEL_BUTTON(button_nr))
1703 switch (game_status)
1705 case GAME_MODE_TITLE:
1706 HandleTitleScreen(mx, my, 0, 0, button);
1709 case GAME_MODE_MAIN:
1710 HandleMainMenu(mx, my, 0, 0, button);
1713 case GAME_MODE_PSEUDO_TYPENAME:
1714 HandleTypeName(0, KSYM_Return);
1717 case GAME_MODE_LEVELS:
1718 HandleChooseLevelSet(mx, my, 0, 0, button);
1721 case GAME_MODE_LEVELNR:
1722 HandleChooseLevelNr(mx, my, 0, 0, button);
1725 case GAME_MODE_SCORES:
1726 HandleHallOfFame(0, 0, 0, 0, button);
1729 case GAME_MODE_EDITOR:
1730 HandleLevelEditorIdle();
1733 case GAME_MODE_INFO:
1734 HandleInfoScreen(mx, my, 0, 0, button);
1737 case GAME_MODE_SETUP:
1738 HandleSetupScreen(mx, my, 0, 0, button);
1741 case GAME_MODE_PLAYING:
1742 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1743 HandleButtonOrFinger(mx, my, button);
1745 SetPlayerMouseAction(mx, my, button);
1748 if (button == MB_PRESSED && !motion_status && !button_hold &&
1749 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1750 DumpTileFromScreen(mx, my);
1760 static boolean is_string_suffix(char *string, char *suffix)
1762 int string_len = strlen(string);
1763 int suffix_len = strlen(suffix);
1765 if (suffix_len > string_len)
1768 return (strEqual(&string[string_len - suffix_len], suffix));
1771 #define MAX_CHEAT_INPUT_LEN 32
1773 static void HandleKeysSpecial(Key key)
1775 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1776 char letter = getCharFromKey(key);
1777 int cheat_input_len = strlen(cheat_input);
1783 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1785 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1786 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1788 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1791 cheat_input[cheat_input_len++] = letter;
1792 cheat_input[cheat_input_len] = '\0';
1794 #if DEBUG_EVENTS_KEY
1795 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1798 if (game_status == GAME_MODE_MAIN)
1800 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1801 is_string_suffix(cheat_input, ":ist"))
1803 InsertSolutionTape();
1805 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1806 is_string_suffix(cheat_input, ":pst"))
1810 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1811 is_string_suffix(cheat_input, ":rg"))
1813 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1816 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1817 is_string_suffix(cheat_input, ":rs"))
1819 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1822 else if (is_string_suffix(cheat_input, ":reload-music") ||
1823 is_string_suffix(cheat_input, ":rm"))
1825 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1828 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1829 is_string_suffix(cheat_input, ":ra"))
1831 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1832 1 << ARTWORK_TYPE_SOUNDS |
1833 1 << ARTWORK_TYPE_MUSIC);
1836 else if (is_string_suffix(cheat_input, ":dump-level") ||
1837 is_string_suffix(cheat_input, ":dl"))
1841 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1842 is_string_suffix(cheat_input, ":dt"))
1846 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1847 is_string_suffix(cheat_input, ":ft"))
1849 /* fix single-player tapes that contain player input for more than one
1850 player (due to a bug in 3.3.1.2 and earlier versions), which results
1851 in playing levels with more than one player in multi-player mode,
1852 even though the tape was originally recorded in single-player mode */
1854 // remove player input actions for all players but the first one
1855 for (i = 1; i < MAX_PLAYERS; i++)
1856 tape.player_participates[i] = FALSE;
1858 tape.changed = TRUE;
1860 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1861 is_string_suffix(cheat_input, ":snl"))
1863 SaveNativeLevel(&level);
1865 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1866 is_string_suffix(cheat_input, ":fps"))
1868 global.show_frames_per_second = !global.show_frames_per_second;
1871 else if (game_status == GAME_MODE_PLAYING)
1874 if (is_string_suffix(cheat_input, ".q"))
1875 DEBUG_SetMaximumDynamite();
1878 else if (game_status == GAME_MODE_EDITOR)
1880 if (is_string_suffix(cheat_input, ":dump-brush") ||
1881 is_string_suffix(cheat_input, ":DB"))
1885 else if (is_string_suffix(cheat_input, ":DDB"))
1890 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1892 if (letter == 'x') // copy brush to clipboard (small size)
1894 CopyBrushToClipboard_Small();
1896 else if (letter == 'c') // copy brush to clipboard (normal size)
1898 CopyBrushToClipboard();
1900 else if (letter == 'v') // paste brush from Clipboard
1902 CopyClipboardToBrush();
1907 // special key shortcuts for all game modes
1908 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1909 is_string_suffix(cheat_input, ":dea") ||
1910 is_string_suffix(cheat_input, ":DEA"))
1912 DumpGadgetIdentifiers();
1913 DumpScreenIdentifiers();
1917 boolean HandleKeysDebug(Key key, int key_status)
1922 if (key_status != KEY_PRESSED)
1925 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1927 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1929 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1931 if (key == setup.debug.frame_delay_key[i] &&
1932 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1934 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1935 setup.debug.frame_delay[i] : setup.game_frame_delay);
1937 if (!setup.debug.frame_delay_game_only)
1938 MenuFrameDelay = GameFrameDelay;
1940 SetVideoFrameDelay(GameFrameDelay);
1942 if (GameFrameDelay > ONE_SECOND_DELAY)
1943 Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
1944 else if (GameFrameDelay != 0)
1945 Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
1946 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1947 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1949 Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
1956 if (game_status == GAME_MODE_PLAYING)
1960 options.debug = !options.debug;
1962 Error(ERR_INFO, "debug mode %s",
1963 (options.debug ? "enabled" : "disabled"));
1967 else if (key == KSYM_v)
1969 Error(ERR_INFO, "currently using game engine version %d",
1970 game.engine_version);
1980 void HandleKey(Key key, int key_status)
1982 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1983 static boolean ignore_repeated_key = FALSE;
1984 static struct SetupKeyboardInfo ski;
1985 static struct SetupShortcutInfo ssi;
1994 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1995 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1996 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1997 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1998 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1999 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2004 if (HandleKeysDebug(key, key_status))
2005 return; // do not handle already processed keys again
2007 // map special keys (media keys / remote control buttons) to default keys
2008 if (key == KSYM_PlayPause)
2010 else if (key == KSYM_Select)
2013 HandleSpecialGameControllerKeys(key, key_status);
2015 if (game_status == GAME_MODE_PLAYING)
2017 // only needed for single-step tape recording mode
2018 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2021 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2023 byte key_action = 0;
2024 byte key_snap_action = 0;
2026 if (setup.input[pnr].use_joystick)
2029 ski = setup.input[pnr].key;
2031 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2032 if (key == *key_info[i].key_custom)
2033 key_action |= key_info[i].action;
2035 // use combined snap+direction keys for the first player only
2038 ssi = setup.shortcut;
2040 // also remember normal snap key when handling snap+direction keys
2041 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2043 for (i = 0; i < NUM_DIRECTIONS; i++)
2045 if (key == *key_info[i].key_snap)
2047 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2048 key_snap_action |= key_info[i].action;
2053 if (key_status == KEY_PRESSED)
2055 stored_player[pnr].action |= key_action;
2056 stored_player[pnr].snap_action |= key_snap_action;
2060 stored_player[pnr].action &= ~key_action;
2061 stored_player[pnr].snap_action &= ~key_snap_action;
2064 // restore snap action if one of several pressed snap keys was released
2065 if (stored_player[pnr].snap_action)
2066 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2068 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2070 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2072 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2074 // if snap key already pressed, keep pause mode when releasing
2075 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2076 has_snapped[pnr] = TRUE;
2078 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2080 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2082 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2083 getRedDiskReleaseFlag_SP() == 0)
2085 // add a single inactive frame before dropping starts
2086 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2087 stored_player[pnr].force_dropping = TRUE;
2090 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2092 // if snap key was pressed without direction, leave pause mode
2093 if (!has_snapped[pnr])
2094 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2096 has_snapped[pnr] = FALSE;
2099 else if (tape.recording && tape.pausing && !tape.use_mouse)
2101 // prevent key release events from un-pausing a paused game
2102 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2103 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2106 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2107 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2113 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2114 if (key == key_info[i].key_default)
2115 joy |= key_info[i].action;
2120 if (key_status == KEY_PRESSED)
2121 key_joystick_mapping |= joy;
2123 key_joystick_mapping &= ~joy;
2128 if (game_status != GAME_MODE_PLAYING)
2129 key_joystick_mapping = 0;
2131 if (key_status == KEY_RELEASED)
2133 // reset flag to ignore repeated "key pressed" events after key release
2134 ignore_repeated_key = FALSE;
2139 if ((key == KSYM_F11 ||
2140 ((key == KSYM_Return ||
2141 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2142 video.fullscreen_available &&
2143 !ignore_repeated_key)
2145 setup.fullscreen = !setup.fullscreen;
2147 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2149 if (game_status == GAME_MODE_SETUP)
2150 RedrawSetupScreenAfterFullscreenToggle();
2152 UpdateMousePosition();
2154 // set flag to ignore repeated "key pressed" events
2155 ignore_repeated_key = TRUE;
2160 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2161 key == KSYM_minus || key == KSYM_KP_Subtract ||
2162 key == KSYM_plus || key == KSYM_KP_Add ||
2163 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2164 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2165 video.window_scaling_available &&
2166 !video.fullscreen_enabled)
2168 if (key == KSYM_0 || key == KSYM_KP_0)
2169 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2170 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2171 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2173 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2175 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2176 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2177 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2178 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2180 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2182 if (game_status == GAME_MODE_SETUP)
2183 RedrawSetupScreenAfterFullscreenToggle();
2185 UpdateMousePosition();
2190 // some key events are handled like clicks for global animations
2191 boolean click = (key == KSYM_space ||
2192 key == KSYM_Return ||
2193 key == KSYM_Escape);
2195 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2197 // do not handle this key event anymore
2198 if (key != KSYM_Escape) // always allow ESC key to be handled
2202 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2203 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2210 if (game_status == GAME_MODE_MAIN &&
2211 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2213 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2218 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2220 if (key == setup.shortcut.save_game)
2222 else if (key == setup.shortcut.load_game)
2224 else if (key == setup.shortcut.toggle_pause)
2225 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2227 HandleTapeButtonKeys(key);
2228 HandleSoundButtonKeys(key);
2231 if (game_status == GAME_MODE_PLAYING && !network_playing)
2233 int centered_player_nr_next = -999;
2235 if (key == setup.shortcut.focus_player_all)
2236 centered_player_nr_next = -1;
2238 for (i = 0; i < MAX_PLAYERS; i++)
2239 if (key == setup.shortcut.focus_player[i])
2240 centered_player_nr_next = i;
2242 if (centered_player_nr_next != -999)
2244 game.centered_player_nr_next = centered_player_nr_next;
2245 game.set_centered_player = TRUE;
2249 tape.centered_player_nr_next = game.centered_player_nr_next;
2250 tape.set_centered_player = TRUE;
2255 HandleKeysSpecial(key);
2257 if (HandleGadgetsKeyInput(key))
2258 return; // do not handle already processed keys again
2260 switch (game_status)
2262 case GAME_MODE_PSEUDO_TYPENAME:
2263 HandleTypeName(0, key);
2266 case GAME_MODE_TITLE:
2267 case GAME_MODE_MAIN:
2268 case GAME_MODE_LEVELS:
2269 case GAME_MODE_LEVELNR:
2270 case GAME_MODE_SETUP:
2271 case GAME_MODE_INFO:
2272 case GAME_MODE_SCORES:
2274 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2281 if (game_status == GAME_MODE_TITLE)
2282 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2283 else if (game_status == GAME_MODE_MAIN)
2284 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2285 else if (game_status == GAME_MODE_LEVELS)
2286 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2287 else if (game_status == GAME_MODE_LEVELNR)
2288 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2289 else if (game_status == GAME_MODE_SETUP)
2290 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2291 else if (game_status == GAME_MODE_INFO)
2292 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2293 else if (game_status == GAME_MODE_SCORES)
2294 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2298 if (game_status != GAME_MODE_MAIN)
2299 FadeSkipNextFadeIn();
2301 if (game_status == GAME_MODE_TITLE)
2302 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2303 else if (game_status == GAME_MODE_LEVELS)
2304 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2305 else if (game_status == GAME_MODE_LEVELNR)
2306 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2307 else if (game_status == GAME_MODE_SETUP)
2308 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2309 else if (game_status == GAME_MODE_INFO)
2310 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2311 else if (game_status == GAME_MODE_SCORES)
2312 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2316 if (game_status == GAME_MODE_LEVELS)
2317 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2318 else if (game_status == GAME_MODE_LEVELNR)
2319 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2320 else if (game_status == GAME_MODE_SETUP)
2321 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2322 else if (game_status == GAME_MODE_INFO)
2323 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2324 else if (game_status == GAME_MODE_SCORES)
2325 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2328 case KSYM_Page_Down:
2329 if (game_status == GAME_MODE_LEVELS)
2330 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2331 else if (game_status == GAME_MODE_LEVELNR)
2332 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2333 else if (game_status == GAME_MODE_SETUP)
2334 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2335 else if (game_status == GAME_MODE_INFO)
2336 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2337 else if (game_status == GAME_MODE_SCORES)
2338 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2346 case GAME_MODE_EDITOR:
2347 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2348 HandleLevelEditorKeyInput(key);
2351 case GAME_MODE_PLAYING:
2356 RequestQuitGame(setup.ask_on_escape);
2366 if (key == KSYM_Escape)
2368 SetGameStatus(GAME_MODE_MAIN);
2377 void HandleNoEvent(void)
2379 HandleMouseCursor();
2381 switch (game_status)
2383 case GAME_MODE_PLAYING:
2384 HandleButtonOrFinger(-1, -1, -1);
2389 void HandleEventActions(void)
2391 // if (button_status && game_status != GAME_MODE_PLAYING)
2392 if (button_status && (game_status != GAME_MODE_PLAYING ||
2394 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2396 HandleButton(0, 0, button_status, -button_status);
2403 if (network.enabled)
2406 switch (game_status)
2408 case GAME_MODE_MAIN:
2409 DrawPreviewLevelAnimation();
2412 case GAME_MODE_EDITOR:
2413 HandleLevelEditorIdle();
2421 static void HandleTileCursor(int dx, int dy, int button)
2424 ClearPlayerMouseAction();
2431 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2432 (dx < 0 ? MB_LEFTBUTTON :
2433 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2435 else if (!tile_cursor.moving)
2437 int old_xpos = tile_cursor.xpos;
2438 int old_ypos = tile_cursor.ypos;
2439 int new_xpos = old_xpos;
2440 int new_ypos = old_ypos;
2442 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2443 new_xpos = old_xpos + dx;
2445 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2446 new_ypos = old_ypos + dy;
2448 SetTileCursorTargetXY(new_xpos, new_ypos);
2452 static int HandleJoystickForAllPlayers(void)
2456 boolean no_joysticks_configured = TRUE;
2457 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2458 static byte joy_action_last[MAX_PLAYERS];
2460 for (i = 0; i < MAX_PLAYERS; i++)
2461 if (setup.input[i].use_joystick)
2462 no_joysticks_configured = FALSE;
2464 // if no joysticks configured, map connected joysticks to players
2465 if (no_joysticks_configured)
2466 use_as_joystick_nr = TRUE;
2468 for (i = 0; i < MAX_PLAYERS; i++)
2470 byte joy_action = 0;
2472 joy_action = JoystickExt(i, use_as_joystick_nr);
2473 result |= joy_action;
2475 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2476 joy_action != joy_action_last[i])
2477 stored_player[i].action = joy_action;
2479 joy_action_last[i] = joy_action;
2485 void HandleJoystick(void)
2487 static unsigned int joytest_delay = 0;
2488 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2489 static int joytest_last = 0;
2490 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2491 int delay_value = GADGET_FRAME_DELAY;
2492 int joystick = HandleJoystickForAllPlayers();
2493 int keyboard = key_joystick_mapping;
2494 int joy = (joystick | keyboard);
2495 int joytest = joystick;
2496 int left = joy & JOY_LEFT;
2497 int right = joy & JOY_RIGHT;
2498 int up = joy & JOY_UP;
2499 int down = joy & JOY_DOWN;
2500 int button = joy & JOY_BUTTON;
2501 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2502 int dx = (left ? -1 : right ? 1 : 0);
2503 int dy = (up ? -1 : down ? 1 : 0);
2504 boolean use_delay_value_first = (joytest != joytest_last);
2506 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2508 // do not handle this button event anymore
2512 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2513 anyTextGadgetActive()))
2515 // leave name input in main menu or text input gadget
2516 HandleKey(KSYM_Escape, KEY_PRESSED);
2517 HandleKey(KSYM_Escape, KEY_RELEASED);
2522 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2524 if (game_status == GAME_MODE_PLAYING)
2526 // when playing MM style levels, also use delay for keyboard events
2527 joytest |= keyboard;
2529 // only use first delay value for new events, but not for changed events
2530 use_delay_value_first = (!joytest != !joytest_last);
2532 // only use delay after the initial keyboard event
2536 // for any joystick or keyboard event, enable playfield tile cursor
2537 if (dx || dy || button)
2538 SetTileCursorEnabled(TRUE);
2541 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2543 // delay joystick/keyboard actions if axes/keys continually pressed
2544 newbutton = dx = dy = 0;
2548 // first start with longer delay, then continue with shorter delay
2549 joytest_delay_value =
2550 (use_delay_value_first ? delay_value_first : delay_value);
2553 joytest_last = joytest;
2555 switch (game_status)
2557 case GAME_MODE_TITLE:
2558 case GAME_MODE_MAIN:
2559 case GAME_MODE_LEVELS:
2560 case GAME_MODE_LEVELNR:
2561 case GAME_MODE_SETUP:
2562 case GAME_MODE_INFO:
2563 case GAME_MODE_SCORES:
2565 if (anyTextGadgetActive())
2568 if (game_status == GAME_MODE_TITLE)
2569 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2570 else if (game_status == GAME_MODE_MAIN)
2571 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2572 else if (game_status == GAME_MODE_LEVELS)
2573 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2574 else if (game_status == GAME_MODE_LEVELNR)
2575 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2576 else if (game_status == GAME_MODE_SETUP)
2577 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2578 else if (game_status == GAME_MODE_INFO)
2579 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2580 else if (game_status == GAME_MODE_SCORES)
2581 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2586 case GAME_MODE_PLAYING:
2588 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2589 if (tape.playing || keyboard)
2590 newbutton = ((joy & JOY_BUTTON) != 0);
2593 if (newbutton && game.all_players_gone)
2600 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2602 if (joystick & JOY_ACTION)
2603 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2605 else if (tape.recording && tape.pausing && !tape.use_mouse)
2607 if (joystick & JOY_ACTION)
2608 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2611 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2612 HandleTileCursor(dx, dy, button);
2621 void HandleSpecialGameControllerButtons(Event *event)
2626 switch (event->type)
2628 case SDL_CONTROLLERBUTTONDOWN:
2629 key_status = KEY_PRESSED;
2632 case SDL_CONTROLLERBUTTONUP:
2633 key_status = KEY_RELEASED;
2640 switch (event->cbutton.button)
2642 case SDL_CONTROLLER_BUTTON_START:
2646 case SDL_CONTROLLER_BUTTON_BACK:
2654 HandleKey(key, key_status);
2657 void HandleSpecialGameControllerKeys(Key key, int key_status)
2659 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2660 int button = SDL_CONTROLLER_BUTTON_INVALID;
2662 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2663 if (key == KSYM_Rewind)
2664 button = SDL_CONTROLLER_BUTTON_A;
2665 else if (key == KSYM_FastForward || key == KSYM_Menu)
2666 button = SDL_CONTROLLER_BUTTON_B;
2668 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2672 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2673 SDL_CONTROLLERBUTTONUP);
2675 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2676 event.cbutton.button = button;
2677 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2680 HandleJoystickEvent(&event);
2685 boolean DoKeysymAction(int keysym)
2689 Key key = (Key)(-keysym);
2691 HandleKey(key, KEY_PRESSED);
2692 HandleKey(key, KEY_RELEASED);