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;
45 // forward declarations for internal use
46 static void HandleNoEvent(void);
47 static void HandleEventActions(void);
50 // event filter to set mouse x/y position (for pointer class global animations)
51 // (this is especially required to ensure smooth global animation mouse pointer
52 // movement when the screen is updated without handling events; this can happen
53 // when drawing door/envelope request animations, for example)
55 int FilterMouseMotionEvents(void *userdata, Event *event)
57 if (event->type != EVENT_MOTIONNOTIFY)
60 int mouse_x = ((MotionEvent *)event)->x;
61 int mouse_y = ((MotionEvent *)event)->y;
63 // mouse events do not contain logical screen size corrections at this stage
64 SDLCorrectMouseEventXY(&mouse_x, &mouse_y);
66 mouse_x -= video.screen_xoffset;
67 mouse_y -= video.screen_yoffset;
69 gfx.mouse_x = mouse_x;
70 gfx.mouse_y = mouse_y;
75 // event filter especially needed for SDL event filtering due to
76 // delay problems with lots of mouse motion events when mouse button
77 // not pressed (X11 can handle this with 'PointerMotionHintMask')
79 // event filter addition for SDL2: as SDL2 does not have a function to enable
80 // or disable keyboard auto-repeat, filter repeated keyboard events instead
82 static int FilterEvents(const Event *event)
86 // skip repeated key press events if keyboard auto-repeat is disabled
87 if (event->type == EVENT_KEYPRESS &&
92 if (event->type == EVENT_BUTTONPRESS ||
93 event->type == EVENT_BUTTONRELEASE)
95 ((ButtonEvent *)event)->x -= video.screen_xoffset;
96 ((ButtonEvent *)event)->y -= video.screen_yoffset;
98 else if (event->type == EVENT_MOTIONNOTIFY)
100 ((MotionEvent *)event)->x -= video.screen_xoffset;
101 ((MotionEvent *)event)->y -= video.screen_yoffset;
104 // non-motion events are directly passed to event handler functions
105 if (event->type != EVENT_MOTIONNOTIFY)
108 motion = (MotionEvent *)event;
109 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
110 motion->y >= SY && motion->y < SY + SYSIZE);
112 // do no reset mouse cursor before all pending events have been processed
113 if (gfx.cursor_mode == cursor_mode_last &&
114 ((game_status == GAME_MODE_TITLE &&
115 gfx.cursor_mode == CURSOR_NONE) ||
116 (game_status == GAME_MODE_PLAYING &&
117 gfx.cursor_mode == CURSOR_PLAYFIELD)))
119 SetMouseCursor(CURSOR_DEFAULT);
121 DelayReached(&special_cursor_delay, 0);
123 cursor_mode_last = CURSOR_DEFAULT;
126 // skip mouse motion events without pressed button outside level editor
127 if (button_status == MB_RELEASED &&
128 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
134 // to prevent delay problems, skip mouse motion events if the very next
135 // event is also a mouse motion event (and therefore effectively only
136 // handling the last of a row of mouse motion events in the event queue)
138 static boolean SkipPressedMouseMotionEvent(const Event *event)
140 // nothing to do if the current event is not a mouse motion event
141 if (event->type != EVENT_MOTIONNOTIFY)
144 // only skip motion events with pressed button outside the game
145 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
152 PeekEvent(&next_event);
154 // if next event is also a mouse motion event, skip the current one
155 if (next_event.type == EVENT_MOTIONNOTIFY)
162 static boolean WaitValidEvent(Event *event)
166 if (!FilterEvents(event))
169 if (SkipPressedMouseMotionEvent(event))
175 /* this is especially needed for event modifications for the Android target:
176 if mouse coordinates should be modified in the event filter function,
177 using a properly installed SDL event filter does not work, because in
178 the event filter, mouse coordinates in the event structure are still
179 physical pixel positions, not logical (scaled) screen positions, so this
180 has to be handled at a later stage in the event processing functions
181 (when device pixel positions are already converted to screen positions) */
183 boolean NextValidEvent(Event *event)
185 while (PendingEvent())
186 if (WaitValidEvent(event))
192 static void HandleEvents(void)
195 unsigned int event_frame_delay = 0;
196 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
198 ResetDelayCounter(&event_frame_delay);
200 while (NextValidEvent(&event))
204 case EVENT_BUTTONPRESS:
205 case EVENT_BUTTONRELEASE:
206 HandleButtonEvent((ButtonEvent *) &event);
209 case EVENT_MOTIONNOTIFY:
210 HandleMotionEvent((MotionEvent *) &event);
213 case EVENT_WHEELMOTION:
214 HandleWheelEvent((WheelEvent *) &event);
217 case SDL_WINDOWEVENT:
218 HandleWindowEvent((WindowEvent *) &event);
221 case EVENT_FINGERPRESS:
222 case EVENT_FINGERRELEASE:
223 case EVENT_FINGERMOTION:
224 HandleFingerEvent((FingerEvent *) &event);
227 case EVENT_TEXTINPUT:
228 HandleTextEvent((TextEvent *) &event);
231 case SDL_APP_WILLENTERBACKGROUND:
232 case SDL_APP_DIDENTERBACKGROUND:
233 case SDL_APP_WILLENTERFOREGROUND:
234 case SDL_APP_DIDENTERFOREGROUND:
235 HandlePauseResumeEvent((PauseResumeEvent *) &event);
239 case EVENT_KEYRELEASE:
240 HandleKeyEvent((KeyEvent *) &event);
244 HandleUserEvent((UserEvent *) &event);
248 HandleOtherEvents(&event);
252 // do not handle events for longer than standard frame delay period
253 if (DelayReached(&event_frame_delay, event_frame_delay_value))
258 void HandleOtherEvents(Event *event)
262 case SDL_CONTROLLERBUTTONDOWN:
263 case SDL_CONTROLLERBUTTONUP:
264 // for any game controller button event, disable overlay buttons
265 SetOverlayEnabled(FALSE);
267 HandleSpecialGameControllerButtons(event);
270 case SDL_CONTROLLERDEVICEADDED:
271 case SDL_CONTROLLERDEVICEREMOVED:
272 case SDL_CONTROLLERAXISMOTION:
273 case SDL_JOYAXISMOTION:
274 case SDL_JOYBUTTONDOWN:
275 case SDL_JOYBUTTONUP:
276 HandleJoystickEvent(event);
280 case SDL_DROPCOMPLETE:
283 HandleDropEvent(event);
295 static void HandleMouseCursor(void)
297 if (game_status == GAME_MODE_TITLE)
299 // when showing title screens, hide mouse pointer (if not moved)
301 if (gfx.cursor_mode != CURSOR_NONE &&
302 DelayReached(&special_cursor_delay, special_cursor_delay_value))
304 SetMouseCursor(CURSOR_NONE);
307 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
310 // when playing, display a special mouse pointer inside the playfield
312 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
313 cursor_inside_playfield &&
314 DelayReached(&special_cursor_delay, special_cursor_delay_value))
316 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
318 SetMouseCursor(CURSOR_PLAYFIELD);
321 else if (gfx.cursor_mode != CURSOR_DEFAULT)
323 SetMouseCursor(CURSOR_DEFAULT);
326 // this is set after all pending events have been processed
327 cursor_mode_last = gfx.cursor_mode;
339 // execute event related actions after pending events have been processed
340 HandleEventActions();
342 // don't use all CPU time when idle; the main loop while playing
343 // has its own synchronization and is CPU friendly, too
345 if (game_status == GAME_MODE_PLAYING)
348 // always copy backbuffer to visible screen for every video frame
351 // reset video frame delay to default (may change again while playing)
352 SetVideoFrameDelay(MenuFrameDelay);
354 if (game_status == GAME_MODE_QUIT)
359 void ClearAutoRepeatKeyEvents(void)
361 while (PendingEvent())
365 PeekEvent(&next_event);
367 // if event is repeated key press event, remove it from event queue
368 if (next_event.type == EVENT_KEYPRESS &&
369 next_event.key.repeat)
370 WaitEvent(&next_event);
376 void ClearEventQueue(void)
380 while (NextValidEvent(&event))
384 case EVENT_BUTTONRELEASE:
385 button_status = MB_RELEASED;
388 case EVENT_KEYRELEASE:
392 case SDL_CONTROLLERBUTTONUP:
393 HandleJoystickEvent(&event);
398 HandleOtherEvents(&event);
404 static void ClearPlayerMouseAction(void)
406 local_player->mouse_action.lx = 0;
407 local_player->mouse_action.ly = 0;
408 local_player->mouse_action.button = 0;
411 void ClearPlayerAction(void)
415 // simulate key release events for still pressed keys
416 key_joystick_mapping = 0;
417 for (i = 0; i < MAX_PLAYERS; i++)
419 stored_player[i].action = 0;
420 stored_player[i].snap_action = 0;
423 ClearJoystickState();
424 ClearPlayerMouseAction();
427 static void SetPlayerMouseAction(int mx, int my, int button)
429 int lx = getLevelFromScreenX(mx);
430 int ly = getLevelFromScreenY(my);
431 int new_button = (!local_player->mouse_action.button && button);
433 if (local_player->mouse_action.button_hint)
434 button = local_player->mouse_action.button_hint;
436 ClearPlayerMouseAction();
438 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
441 local_player->mouse_action.lx = lx;
442 local_player->mouse_action.ly = ly;
443 local_player->mouse_action.button = button;
445 if (tape.recording && tape.pausing && tape.use_mouse)
447 // un-pause a paused game only if mouse button was newly pressed down
449 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
452 SetTileCursorXY(lx, ly);
455 void HandleButtonEvent(ButtonEvent *event)
457 #if DEBUG_EVENTS_BUTTON
458 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
460 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
464 // for any mouse button event, disable playfield tile cursor
465 SetTileCursorEnabled(FALSE);
467 #if defined(HAS_SCREEN_KEYBOARD)
468 if (video.shifted_up)
469 event->y += video.shifted_up_pos;
472 motion_status = FALSE;
474 if (event->type == EVENT_BUTTONPRESS)
475 button_status = event->button;
477 button_status = MB_RELEASED;
479 HandleButton(event->x, event->y, button_status, event->button);
482 void HandleMotionEvent(MotionEvent *event)
484 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
487 motion_status = TRUE;
489 #if DEBUG_EVENTS_MOTION
490 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
491 button_status, event->x, event->y);
494 HandleButton(event->x, event->y, button_status, button_status);
497 void HandleWheelEvent(WheelEvent *event)
501 #if DEBUG_EVENTS_WHEEL
503 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
504 event->which, event->x, event->y);
506 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
507 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
508 event->which, event->x, event->y,
509 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
510 "SDL_MOUSEWHEEL_FLIPPED"));
514 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
515 event->x > 0 ? MB_WHEEL_RIGHT :
516 event->y < 0 ? MB_WHEEL_DOWN :
517 event->y > 0 ? MB_WHEEL_UP : 0);
519 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
520 // accelerated mouse wheel available on Mac and Windows
521 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
523 // no accelerated mouse wheel available on Unix/Linux
524 wheel_steps = DEFAULT_WHEEL_STEPS;
527 motion_status = FALSE;
529 button_status = button_nr;
530 HandleButton(0, 0, button_status, -button_nr);
532 button_status = MB_RELEASED;
533 HandleButton(0, 0, button_status, -button_nr);
536 void HandleWindowEvent(WindowEvent *event)
538 #if DEBUG_EVENTS_WINDOW
539 int subtype = event->event;
542 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
543 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
544 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
545 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
546 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
547 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
548 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
549 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
550 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
551 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
552 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
553 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
554 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
555 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
558 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
559 event_name, event->data1, event->data2);
563 // (not needed, as the screen gets redrawn every 20 ms anyway)
564 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
565 event->event == SDL_WINDOWEVENT_RESIZED ||
566 event->event == SDL_WINDOWEVENT_EXPOSED)
570 if (event->event == SDL_WINDOWEVENT_RESIZED)
572 if (!video.fullscreen_enabled)
574 int new_window_width = event->data1;
575 int new_window_height = event->data2;
577 // if window size has changed after resizing, calculate new scaling factor
578 if (new_window_width != video.window_width ||
579 new_window_height != video.window_height)
581 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
582 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
584 // (extreme window scaling allowed, but cannot be saved permanently)
585 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
586 setup.window_scaling_percent =
587 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
588 MAX_WINDOW_SCALING_PERCENT);
590 video.window_width = new_window_width;
591 video.window_height = new_window_height;
593 if (game_status == GAME_MODE_SETUP)
594 RedrawSetupScreenAfterFullscreenToggle();
599 #if defined(PLATFORM_ANDROID)
602 int new_display_width = event->data1;
603 int new_display_height = event->data2;
605 // if fullscreen display size has changed, device has been rotated
606 if (new_display_width != video.display_width ||
607 new_display_height != video.display_height)
609 int nr = GRID_ACTIVE_NR(); // previous screen orientation
611 video.display_width = new_display_width;
612 video.display_height = new_display_height;
614 SDLSetScreenProperties();
616 // check if screen orientation has changed (should always be true here)
617 if (nr != GRID_ACTIVE_NR())
621 if (game_status == GAME_MODE_SETUP)
622 RedrawSetupScreenAfterScreenRotation(nr);
624 nr = GRID_ACTIVE_NR();
626 overlay.grid_xsize = setup.touch.grid_xsize[nr];
627 overlay.grid_ysize = setup.touch.grid_ysize[nr];
629 for (x = 0; x < MAX_GRID_XSIZE; x++)
630 for (y = 0; y < MAX_GRID_YSIZE; y++)
631 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
639 #define NUM_TOUCH_FINGERS 3
644 SDL_FingerID finger_id;
648 } touch_info[NUM_TOUCH_FINGERS];
650 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
653 int x = event->x * overlay.grid_xsize;
654 int y = event->y * overlay.grid_ysize;
655 int grid_button = overlay.grid_button[x][y];
656 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
657 Key key = (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
658 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
659 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
660 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
661 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
662 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
665 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
666 float event_x = (event->x);
667 float event_y = (event->y - ypos) / (1 - ypos);
668 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
669 event_y > 2.0 / 3.0 && event_y < 1 ?
670 setup.input[0].key.snap :
671 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
672 event_y > 2.0 / 3.0 && event_y < 1 ?
673 setup.input[0].key.drop :
674 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
675 event_y > 0 && event_y < 1.0 / 3.0 ?
676 setup.input[0].key.up :
677 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
678 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
679 setup.input[0].key.left :
680 event_x > 8.0 / 9.0 && event_x < 1 &&
681 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
682 setup.input[0].key.right :
683 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
684 event_y > 2.0 / 3.0 && event_y < 1 ?
685 setup.input[0].key.down :
688 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
690 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
694 virtual_button_pressed = (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
696 // for any touch input event, enable overlay buttons (if activated)
697 SetOverlayEnabled(TRUE);
699 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
700 getKeyNameFromKey(key), key_status_name, event->fingerId);
702 if (key_status == KEY_PRESSED)
703 overlay.grid_button_action |= grid_button_action;
705 overlay.grid_button_action &= ~grid_button_action;
707 // check if we already know this touch event's finger id
708 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
710 if (touch_info[i].touched &&
711 touch_info[i].finger_id == event->fingerId)
713 // Error(ERR_DEBUG, "MARK 1: %d", i);
719 if (i >= NUM_TOUCH_FINGERS)
721 if (key_status == KEY_PRESSED)
723 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
725 // unknown finger id -- get new, empty slot, if available
726 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
728 if (touch_info[i].counter < oldest_counter)
731 oldest_counter = touch_info[i].counter;
733 // Error(ERR_DEBUG, "MARK 2: %d", i);
736 if (!touch_info[i].touched)
738 // Error(ERR_DEBUG, "MARK 3: %d", i);
744 if (i >= NUM_TOUCH_FINGERS)
746 // all slots allocated -- use oldest slot
749 // Error(ERR_DEBUG, "MARK 4: %d", i);
754 // release of previously unknown key (should not happen)
756 if (key != KSYM_UNDEFINED)
758 HandleKey(key, KEY_RELEASED);
760 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
761 getKeyNameFromKey(key), "KEY_RELEASED", i);
766 if (i < NUM_TOUCH_FINGERS)
768 if (key_status == KEY_PRESSED)
770 if (touch_info[i].key != key)
772 if (touch_info[i].key != KSYM_UNDEFINED)
774 HandleKey(touch_info[i].key, KEY_RELEASED);
776 // undraw previous grid button when moving finger away
777 overlay.grid_button_action &= ~touch_info[i].action;
779 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
780 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
783 if (key != KSYM_UNDEFINED)
785 HandleKey(key, KEY_PRESSED);
787 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
788 getKeyNameFromKey(key), "KEY_PRESSED", i);
792 touch_info[i].touched = TRUE;
793 touch_info[i].finger_id = event->fingerId;
794 touch_info[i].counter = Counter();
795 touch_info[i].key = key;
796 touch_info[i].action = grid_button_action;
800 if (touch_info[i].key != KSYM_UNDEFINED)
802 HandleKey(touch_info[i].key, KEY_RELEASED);
804 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
805 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
808 touch_info[i].touched = FALSE;
809 touch_info[i].finger_id = 0;
810 touch_info[i].counter = 0;
811 touch_info[i].key = 0;
812 touch_info[i].action = JOY_NO_ACTION;
817 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
819 static Key motion_key_x = KSYM_UNDEFINED;
820 static Key motion_key_y = KSYM_UNDEFINED;
821 static Key button_key = KSYM_UNDEFINED;
822 static float motion_x1, motion_y1;
823 static float button_x1, button_y1;
824 static SDL_FingerID motion_id = -1;
825 static SDL_FingerID button_id = -1;
826 int move_trigger_distance_percent = setup.touch.move_distance;
827 int drop_trigger_distance_percent = setup.touch.drop_distance;
828 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
829 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
830 float event_x = event->x;
831 float event_y = event->y;
833 if (event->type == EVENT_FINGERPRESS)
835 if (event_x > 1.0 / 3.0)
839 motion_id = event->fingerId;
844 motion_key_x = KSYM_UNDEFINED;
845 motion_key_y = KSYM_UNDEFINED;
847 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
853 button_id = event->fingerId;
858 button_key = setup.input[0].key.snap;
860 HandleKey(button_key, KEY_PRESSED);
862 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
865 else if (event->type == EVENT_FINGERRELEASE)
867 if (event->fingerId == motion_id)
871 if (motion_key_x != KSYM_UNDEFINED)
872 HandleKey(motion_key_x, KEY_RELEASED);
873 if (motion_key_y != KSYM_UNDEFINED)
874 HandleKey(motion_key_y, KEY_RELEASED);
876 motion_key_x = KSYM_UNDEFINED;
877 motion_key_y = KSYM_UNDEFINED;
879 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
881 else if (event->fingerId == button_id)
885 if (button_key != KSYM_UNDEFINED)
886 HandleKey(button_key, KEY_RELEASED);
888 button_key = KSYM_UNDEFINED;
890 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
893 else if (event->type == EVENT_FINGERMOTION)
895 if (event->fingerId == motion_id)
897 float distance_x = ABS(event_x - motion_x1);
898 float distance_y = ABS(event_y - motion_y1);
899 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
900 event_x > motion_x1 ? setup.input[0].key.right :
902 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
903 event_y > motion_y1 ? setup.input[0].key.down :
906 if (distance_x < move_trigger_distance / 2 ||
907 distance_x < distance_y)
908 new_motion_key_x = KSYM_UNDEFINED;
910 if (distance_y < move_trigger_distance / 2 ||
911 distance_y < distance_x)
912 new_motion_key_y = KSYM_UNDEFINED;
914 if (distance_x > move_trigger_distance ||
915 distance_y > move_trigger_distance)
917 if (new_motion_key_x != motion_key_x)
919 if (motion_key_x != KSYM_UNDEFINED)
920 HandleKey(motion_key_x, KEY_RELEASED);
921 if (new_motion_key_x != KSYM_UNDEFINED)
922 HandleKey(new_motion_key_x, KEY_PRESSED);
925 if (new_motion_key_y != motion_key_y)
927 if (motion_key_y != KSYM_UNDEFINED)
928 HandleKey(motion_key_y, KEY_RELEASED);
929 if (new_motion_key_y != KSYM_UNDEFINED)
930 HandleKey(new_motion_key_y, KEY_PRESSED);
936 motion_key_x = new_motion_key_x;
937 motion_key_y = new_motion_key_y;
939 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
942 else if (event->fingerId == button_id)
944 float distance_x = ABS(event_x - button_x1);
945 float distance_y = ABS(event_y - button_y1);
947 if (distance_x < drop_trigger_distance / 2 &&
948 distance_y > drop_trigger_distance)
950 if (button_key == setup.input[0].key.snap)
951 HandleKey(button_key, KEY_RELEASED);
956 button_key = setup.input[0].key.drop;
958 HandleKey(button_key, KEY_PRESSED);
960 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
966 void HandleFingerEvent(FingerEvent *event)
968 #if DEBUG_EVENTS_FINGER
969 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
970 event->type == EVENT_FINGERPRESS ? "pressed" :
971 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
975 event->dx, event->dy,
979 runtime.uses_touch_device = TRUE;
981 if (game_status != GAME_MODE_PLAYING)
984 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
986 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
987 local_player->mouse_action.button_hint =
988 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
989 event->x < 0.5 ? MB_LEFTBUTTON :
990 event->x > 0.5 ? MB_RIGHTBUTTON :
996 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
997 HandleFingerEvent_VirtualButtons(event);
998 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
999 HandleFingerEvent_WipeGestures(event);
1002 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1004 static int old_mx = 0, old_my = 0;
1005 static int last_button = MB_LEFTBUTTON;
1006 static boolean touched = FALSE;
1007 static boolean tapped = FALSE;
1009 // screen tile was tapped (but finger not touching the screen anymore)
1010 // (this point will also be reached without receiving a touch event)
1011 if (tapped && !touched)
1013 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1018 // stop here if this function was not triggered by a touch event
1022 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1024 // finger started touching the screen
1034 ClearPlayerMouseAction();
1036 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1039 else if (button == MB_RELEASED && touched)
1041 // finger stopped touching the screen
1046 SetPlayerMouseAction(old_mx, old_my, last_button);
1048 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1050 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1055 // finger moved while touching the screen
1057 int old_x = getLevelFromScreenX(old_mx);
1058 int old_y = getLevelFromScreenY(old_my);
1059 int new_x = getLevelFromScreenX(mx);
1060 int new_y = getLevelFromScreenY(my);
1062 if (new_x != old_x || new_y != old_y)
1067 // finger moved left or right from (horizontal) starting position
1069 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1071 SetPlayerMouseAction(old_mx, old_my, button_nr);
1073 last_button = button_nr;
1075 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1079 // finger stays at or returned to (horizontal) starting position
1081 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1083 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1088 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1090 static int old_mx = 0, old_my = 0;
1091 static int last_button = MB_LEFTBUTTON;
1092 static boolean touched = FALSE;
1093 static boolean tapped = FALSE;
1095 // screen tile was tapped (but finger not touching the screen anymore)
1096 // (this point will also be reached without receiving a touch event)
1097 if (tapped && !touched)
1099 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1104 // stop here if this function was not triggered by a touch event
1108 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1110 // finger started touching the screen
1120 ClearPlayerMouseAction();
1122 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1125 else if (button == MB_RELEASED && touched)
1127 // finger stopped touching the screen
1132 SetPlayerMouseAction(old_mx, old_my, last_button);
1134 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1136 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1141 // finger moved while touching the screen
1143 int old_x = getLevelFromScreenX(old_mx);
1144 int old_y = getLevelFromScreenY(old_my);
1145 int new_x = getLevelFromScreenX(mx);
1146 int new_y = getLevelFromScreenY(my);
1148 if (new_x != old_x || new_y != old_y)
1150 // finger moved away from starting position
1152 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1154 // quickly alternate between clicking and releasing for maximum speed
1155 if (FrameCounter % 2 == 0)
1156 button_nr = MB_RELEASED;
1158 SetPlayerMouseAction(old_mx, old_my, button_nr);
1161 last_button = button_nr;
1165 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1169 // finger stays at or returned to starting position
1171 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1173 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1178 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1180 static int old_mx = 0, old_my = 0;
1181 static Key motion_key_x = KSYM_UNDEFINED;
1182 static Key motion_key_y = KSYM_UNDEFINED;
1183 static boolean touched = FALSE;
1184 static boolean started_on_player = FALSE;
1185 static boolean player_is_dropping = FALSE;
1186 static int player_drop_count = 0;
1187 static int last_player_x = -1;
1188 static int last_player_y = -1;
1190 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1199 started_on_player = FALSE;
1200 player_is_dropping = FALSE;
1201 player_drop_count = 0;
1205 motion_key_x = KSYM_UNDEFINED;
1206 motion_key_y = KSYM_UNDEFINED;
1208 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1211 else if (button == MB_RELEASED && touched)
1218 if (motion_key_x != KSYM_UNDEFINED)
1219 HandleKey(motion_key_x, KEY_RELEASED);
1220 if (motion_key_y != KSYM_UNDEFINED)
1221 HandleKey(motion_key_y, KEY_RELEASED);
1223 if (started_on_player)
1225 if (player_is_dropping)
1227 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1229 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1233 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1235 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1239 motion_key_x = KSYM_UNDEFINED;
1240 motion_key_y = KSYM_UNDEFINED;
1242 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1247 int src_x = local_player->jx;
1248 int src_y = local_player->jy;
1249 int dst_x = getLevelFromScreenX(old_mx);
1250 int dst_y = getLevelFromScreenY(old_my);
1251 int dx = dst_x - src_x;
1252 int dy = dst_y - src_y;
1253 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1254 dx > 0 ? setup.input[0].key.right :
1256 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1257 dy > 0 ? setup.input[0].key.down :
1260 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1261 (last_player_x != local_player->jx ||
1262 last_player_y != local_player->jy))
1264 // in case of asymmetric diagonal movement, use "preferred" direction
1266 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1268 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1269 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1271 local_player->last_move_dir = last_move_dir;
1273 // (required to prevent accidentally forcing direction for next movement)
1274 last_player_x = local_player->jx;
1275 last_player_y = local_player->jy;
1278 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1280 started_on_player = TRUE;
1281 player_drop_count = getPlayerInventorySize(0);
1282 player_is_dropping = (player_drop_count > 0);
1284 if (player_is_dropping)
1286 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1288 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1292 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1294 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1297 else if (dx != 0 || dy != 0)
1299 if (player_is_dropping &&
1300 player_drop_count == getPlayerInventorySize(0))
1302 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1304 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1305 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1307 player_is_dropping = FALSE;
1311 if (new_motion_key_x != motion_key_x)
1313 Error(ERR_DEBUG, "---------- %s %s ----------",
1314 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1315 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1317 if (motion_key_x != KSYM_UNDEFINED)
1318 HandleKey(motion_key_x, KEY_RELEASED);
1319 if (new_motion_key_x != KSYM_UNDEFINED)
1320 HandleKey(new_motion_key_x, KEY_PRESSED);
1323 if (new_motion_key_y != motion_key_y)
1325 Error(ERR_DEBUG, "---------- %s %s ----------",
1326 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1327 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1329 if (motion_key_y != KSYM_UNDEFINED)
1330 HandleKey(motion_key_y, KEY_RELEASED);
1331 if (new_motion_key_y != KSYM_UNDEFINED)
1332 HandleKey(new_motion_key_y, KEY_PRESSED);
1335 motion_key_x = new_motion_key_x;
1336 motion_key_y = new_motion_key_y;
1340 static void HandleButtonOrFinger(int mx, int my, int button)
1342 if (game_status != GAME_MODE_PLAYING)
1345 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1347 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1348 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1349 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1350 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1351 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1352 SetPlayerMouseAction(mx, my, button); // special case
1356 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1357 HandleButtonOrFinger_FollowFinger(mx, my, button);
1361 static boolean checkTextInputKeyModState(void)
1363 // when playing, only handle raw key events and ignore text input
1364 if (game_status == GAME_MODE_PLAYING)
1367 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1370 void HandleTextEvent(TextEvent *event)
1372 char *text = event->text;
1373 Key key = getKeyFromKeyName(text);
1375 #if DEBUG_EVENTS_TEXT
1376 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1379 text[0], (int)(text[0]),
1381 getKeyNameFromKey(key),
1385 #if !defined(HAS_SCREEN_KEYBOARD)
1386 // non-mobile devices: only handle key input with modifier keys pressed here
1387 // (every other key input is handled directly as physical key input event)
1388 if (!checkTextInputKeyModState())
1392 // process text input as "classic" (with uppercase etc.) key input event
1393 HandleKey(key, KEY_PRESSED);
1394 HandleKey(key, KEY_RELEASED);
1397 void HandlePauseResumeEvent(PauseResumeEvent *event)
1399 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1403 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1409 void HandleKeyEvent(KeyEvent *event)
1411 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1412 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1413 Key key = GetEventKey(event, with_modifiers);
1414 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1416 #if DEBUG_EVENTS_KEY
1417 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1418 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1419 event->keysym.scancode,
1424 getKeyNameFromKey(key));
1427 #if defined(PLATFORM_ANDROID)
1428 if (key == KSYM_Back)
1430 // always map the "back" button to the "escape" key on Android devices
1433 else if (key == KSYM_Menu)
1435 // the "menu" button can be used to toggle displaying virtual buttons
1436 if (key_status == KEY_PRESSED)
1437 SetOverlayEnabled(!GetOverlayEnabled());
1441 // for any other "real" key event, disable virtual buttons
1442 SetOverlayEnabled(FALSE);
1446 HandleKeyModState(keymod, key_status);
1448 // only handle raw key input without text modifier keys pressed
1449 if (!checkTextInputKeyModState())
1450 HandleKey(key, key_status);
1453 static int HandleDropFileEvent(char *filename)
1455 Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
1457 // check and extract dropped zip files into correct user data directory
1458 if (!strSuffixLower(filename, ".zip"))
1460 Error(ERR_WARN, "file '%s' not supported", filename);
1462 return TREE_TYPE_UNDEFINED;
1465 TreeInfo *tree_node = NULL;
1466 int tree_type = GetZipFileTreeType(filename);
1467 char *directory = TREE_USERDIR(tree_type);
1469 if (directory == NULL)
1471 Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1473 return TREE_TYPE_UNDEFINED;
1476 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1477 game_status == GAME_MODE_LEVELS &&
1478 leveldir_current->node_parent != NULL)
1480 // extract new level set next to currently selected level set
1481 tree_node = leveldir_current;
1483 // get parent directory of currently selected level set directory
1484 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1486 // use private level directory instead of top-level package level directory
1487 if (strPrefix(directory, options.level_directory) &&
1488 strEqual(leveldir_current->node_parent->fullpath, "."))
1489 directory = getUserLevelDir(NULL);
1492 // extract level or artwork set from zip file to target directory
1493 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1495 if (top_dir == NULL)
1497 // error message already issued by "ExtractZipFileIntoDirectory()"
1499 return TREE_TYPE_UNDEFINED;
1502 // add extracted level or artwork set to tree info structure
1503 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1505 // update menu screen (and possibly change current level set)
1506 DrawScreenAfterAddingSet(top_dir, tree_type);
1511 static void HandleDropTextEvent(char *text)
1513 Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1516 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1517 int num_artwork_sets_succeeded,
1518 int num_files_failed)
1520 // only show request dialog if no other request dialog already active
1521 if (game.request_active)
1524 // this case can happen with drag-and-drop with older SDL versions
1525 if (num_level_sets_succeeded == 0 &&
1526 num_artwork_sets_succeeded == 0 &&
1527 num_files_failed == 0)
1532 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1534 char message_part1[50];
1536 sprintf(message_part1, "New %s set%s added",
1537 (num_artwork_sets_succeeded == 0 ? "level" :
1538 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1539 (num_level_sets_succeeded +
1540 num_artwork_sets_succeeded > 1 ? "s" : ""));
1542 if (num_files_failed > 0)
1543 sprintf(message, "%s, but %d dropped file%s failed!",
1544 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1546 sprintf(message, "%s!", message_part1);
1548 else if (num_files_failed > 0)
1550 sprintf(message, "Failed to process dropped file%s!",
1551 num_files_failed > 1 ? "s" : "");
1554 Request(message, REQ_CONFIRM);
1557 void HandleDropEvent(Event *event)
1559 static boolean confirm_on_drop_complete = FALSE;
1560 static int num_level_sets_succeeded = 0;
1561 static int num_artwork_sets_succeeded = 0;
1562 static int num_files_failed = 0;
1564 switch (event->type)
1568 confirm_on_drop_complete = TRUE;
1569 num_level_sets_succeeded = 0;
1570 num_artwork_sets_succeeded = 0;
1571 num_files_failed = 0;
1578 int tree_type = HandleDropFileEvent(event->drop.file);
1580 if (tree_type == TREE_TYPE_LEVEL_DIR)
1581 num_level_sets_succeeded++;
1582 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1583 tree_type == TREE_TYPE_SOUNDS_DIR ||
1584 tree_type == TREE_TYPE_MUSIC_DIR)
1585 num_artwork_sets_succeeded++;
1589 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1590 if (!confirm_on_drop_complete)
1592 // process all remaining events, including further SDL_DROPFILE events
1595 HandleDropCompleteEvent(num_level_sets_succeeded,
1596 num_artwork_sets_succeeded,
1599 num_level_sets_succeeded = 0;
1600 num_artwork_sets_succeeded = 0;
1601 num_files_failed = 0;
1609 HandleDropTextEvent(event->drop.file);
1614 case SDL_DROPCOMPLETE:
1616 HandleDropCompleteEvent(num_level_sets_succeeded,
1617 num_artwork_sets_succeeded,
1624 if (event->drop.file != NULL)
1625 SDL_free(event->drop.file);
1628 void HandleUserEvent(UserEvent *event)
1630 switch (event->code)
1632 case USEREVENT_ANIM_DELAY_ACTION:
1633 case USEREVENT_ANIM_EVENT_ACTION:
1634 // execute action functions until matching action was found
1635 if (DoKeysymAction(event->value1) ||
1636 DoGadgetAction(event->value1) ||
1637 DoScreenAction(event->value1))
1646 void HandleButton(int mx, int my, int button, int button_nr)
1648 static int old_mx = 0, old_my = 0;
1649 boolean button_hold = FALSE;
1650 boolean handle_gadgets = TRUE;
1656 button_nr = -button_nr;
1665 #if defined(PLATFORM_ANDROID)
1666 // when playing, only handle gadgets when using "follow finger" controls
1667 // or when using touch controls in combination with the MM game engine
1668 // or when using gadgets that do not overlap with virtual buttons
1670 (game_status != GAME_MODE_PLAYING ||
1671 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1672 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1673 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1674 !virtual_button_pressed));
1677 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1679 // do not handle this button event anymore
1680 return; // force mouse event not to be handled at all
1683 if (handle_gadgets && HandleGadgets(mx, my, button))
1685 // do not handle this button event anymore
1686 mx = my = -32; // force mouse event to be outside screen tiles
1689 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1692 // do not use scroll wheel button events for anything other than gadgets
1693 if (IS_WHEEL_BUTTON(button_nr))
1696 switch (game_status)
1698 case GAME_MODE_TITLE:
1699 HandleTitleScreen(mx, my, 0, 0, button);
1702 case GAME_MODE_MAIN:
1703 HandleMainMenu(mx, my, 0, 0, button);
1706 case GAME_MODE_PSEUDO_TYPENAME:
1707 HandleTypeName(0, KSYM_Return);
1710 case GAME_MODE_LEVELS:
1711 HandleChooseLevelSet(mx, my, 0, 0, button);
1714 case GAME_MODE_LEVELNR:
1715 HandleChooseLevelNr(mx, my, 0, 0, button);
1718 case GAME_MODE_SCORES:
1719 HandleHallOfFame(0, 0, 0, 0, button);
1722 case GAME_MODE_EDITOR:
1723 HandleLevelEditorIdle();
1726 case GAME_MODE_INFO:
1727 HandleInfoScreen(mx, my, 0, 0, button);
1730 case GAME_MODE_SETUP:
1731 HandleSetupScreen(mx, my, 0, 0, button);
1734 case GAME_MODE_PLAYING:
1735 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1736 HandleButtonOrFinger(mx, my, button);
1738 SetPlayerMouseAction(mx, my, button);
1741 if (button == MB_PRESSED && !motion_status && !button_hold &&
1742 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1743 DumpTileFromScreen(mx, my);
1753 static boolean is_string_suffix(char *string, char *suffix)
1755 int string_len = strlen(string);
1756 int suffix_len = strlen(suffix);
1758 if (suffix_len > string_len)
1761 return (strEqual(&string[string_len - suffix_len], suffix));
1764 #define MAX_CHEAT_INPUT_LEN 32
1766 static void HandleKeysSpecial(Key key)
1768 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1769 char letter = getCharFromKey(key);
1770 int cheat_input_len = strlen(cheat_input);
1776 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1778 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1779 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1781 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1784 cheat_input[cheat_input_len++] = letter;
1785 cheat_input[cheat_input_len] = '\0';
1787 #if DEBUG_EVENTS_KEY
1788 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1791 if (game_status == GAME_MODE_MAIN)
1793 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1794 is_string_suffix(cheat_input, ":ist"))
1796 InsertSolutionTape();
1798 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1799 is_string_suffix(cheat_input, ":pst"))
1803 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1804 is_string_suffix(cheat_input, ":rg"))
1806 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1809 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1810 is_string_suffix(cheat_input, ":rs"))
1812 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1815 else if (is_string_suffix(cheat_input, ":reload-music") ||
1816 is_string_suffix(cheat_input, ":rm"))
1818 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1821 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1822 is_string_suffix(cheat_input, ":ra"))
1824 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1825 1 << ARTWORK_TYPE_SOUNDS |
1826 1 << ARTWORK_TYPE_MUSIC);
1829 else if (is_string_suffix(cheat_input, ":dump-level") ||
1830 is_string_suffix(cheat_input, ":dl"))
1834 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1835 is_string_suffix(cheat_input, ":dt"))
1839 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1840 is_string_suffix(cheat_input, ":ft"))
1842 /* fix single-player tapes that contain player input for more than one
1843 player (due to a bug in 3.3.1.2 and earlier versions), which results
1844 in playing levels with more than one player in multi-player mode,
1845 even though the tape was originally recorded in single-player mode */
1847 // remove player input actions for all players but the first one
1848 for (i = 1; i < MAX_PLAYERS; i++)
1849 tape.player_participates[i] = FALSE;
1851 tape.changed = TRUE;
1853 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1854 is_string_suffix(cheat_input, ":snl"))
1856 SaveNativeLevel(&level);
1858 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1859 is_string_suffix(cheat_input, ":fps"))
1861 global.show_frames_per_second = !global.show_frames_per_second;
1864 else if (game_status == GAME_MODE_PLAYING)
1867 if (is_string_suffix(cheat_input, ".q"))
1868 DEBUG_SetMaximumDynamite();
1871 else if (game_status == GAME_MODE_EDITOR)
1873 if (is_string_suffix(cheat_input, ":dump-brush") ||
1874 is_string_suffix(cheat_input, ":DB"))
1878 else if (is_string_suffix(cheat_input, ":DDB"))
1883 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1885 if (letter == 'x') // copy brush to clipboard (small size)
1887 CopyBrushToClipboard_Small();
1889 else if (letter == 'c') // copy brush to clipboard (normal size)
1891 CopyBrushToClipboard();
1893 else if (letter == 'v') // paste brush from Clipboard
1895 CopyClipboardToBrush();
1900 // special key shortcuts for all game modes
1901 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1902 is_string_suffix(cheat_input, ":dea") ||
1903 is_string_suffix(cheat_input, ":DEA"))
1905 DumpGadgetIdentifiers();
1906 DumpScreenIdentifiers();
1910 boolean HandleKeysDebug(Key key, int key_status)
1915 if (key_status != KEY_PRESSED)
1918 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1920 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1922 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1924 if (key == setup.debug.frame_delay_key[i] &&
1925 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1927 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1928 setup.debug.frame_delay[i] : setup.game_frame_delay);
1930 if (!setup.debug.frame_delay_game_only)
1931 MenuFrameDelay = GameFrameDelay;
1933 SetVideoFrameDelay(GameFrameDelay);
1935 if (GameFrameDelay > ONE_SECOND_DELAY)
1936 Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
1937 else if (GameFrameDelay != 0)
1938 Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
1939 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1940 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1942 Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
1949 if (game_status == GAME_MODE_PLAYING)
1953 options.debug = !options.debug;
1955 Error(ERR_INFO, "debug mode %s",
1956 (options.debug ? "enabled" : "disabled"));
1960 else if (key == KSYM_v)
1962 Error(ERR_INFO, "currently using game engine version %d",
1963 game.engine_version);
1973 void HandleKey(Key key, int key_status)
1975 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1976 static boolean ignore_repeated_key = FALSE;
1977 static struct SetupKeyboardInfo ski;
1978 static struct SetupShortcutInfo ssi;
1987 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1988 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1989 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1990 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1991 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1992 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1997 if (HandleKeysDebug(key, key_status))
1998 return; // do not handle already processed keys again
2000 // map special keys (media keys / remote control buttons) to default keys
2001 if (key == KSYM_PlayPause)
2003 else if (key == KSYM_Select)
2006 HandleSpecialGameControllerKeys(key, key_status);
2008 if (game_status == GAME_MODE_PLAYING)
2010 // only needed for single-step tape recording mode
2011 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2014 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2016 byte key_action = 0;
2017 byte key_snap_action = 0;
2019 if (setup.input[pnr].use_joystick)
2022 ski = setup.input[pnr].key;
2024 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2025 if (key == *key_info[i].key_custom)
2026 key_action |= key_info[i].action;
2028 // use combined snap+direction keys for the first player only
2031 ssi = setup.shortcut;
2033 // also remember normal snap key when handling snap+direction keys
2034 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2036 for (i = 0; i < NUM_DIRECTIONS; i++)
2038 if (key == *key_info[i].key_snap)
2040 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2041 key_snap_action |= key_info[i].action;
2046 if (key_status == KEY_PRESSED)
2048 stored_player[pnr].action |= key_action;
2049 stored_player[pnr].snap_action |= key_snap_action;
2053 stored_player[pnr].action &= ~key_action;
2054 stored_player[pnr].snap_action &= ~key_snap_action;
2057 // restore snap action if one of several pressed snap keys was released
2058 if (stored_player[pnr].snap_action)
2059 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2061 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2063 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2065 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2067 // if snap key already pressed, keep pause mode when releasing
2068 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2069 has_snapped[pnr] = TRUE;
2071 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2073 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2075 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2076 getRedDiskReleaseFlag_SP() == 0)
2078 // add a single inactive frame before dropping starts
2079 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2080 stored_player[pnr].force_dropping = TRUE;
2083 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2085 // if snap key was pressed without direction, leave pause mode
2086 if (!has_snapped[pnr])
2087 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2089 has_snapped[pnr] = FALSE;
2092 else if (tape.recording && tape.pausing && !tape.use_mouse)
2094 // prevent key release events from un-pausing a paused game
2095 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2096 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2099 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2100 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2106 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2107 if (key == key_info[i].key_default)
2108 joy |= key_info[i].action;
2113 if (key_status == KEY_PRESSED)
2114 key_joystick_mapping |= joy;
2116 key_joystick_mapping &= ~joy;
2121 if (game_status != GAME_MODE_PLAYING)
2122 key_joystick_mapping = 0;
2124 if (key_status == KEY_RELEASED)
2126 // reset flag to ignore repeated "key pressed" events after key release
2127 ignore_repeated_key = FALSE;
2132 if ((key == KSYM_F11 ||
2133 ((key == KSYM_Return ||
2134 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2135 video.fullscreen_available &&
2136 !ignore_repeated_key)
2138 setup.fullscreen = !setup.fullscreen;
2140 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2142 if (game_status == GAME_MODE_SETUP)
2143 RedrawSetupScreenAfterFullscreenToggle();
2145 // set flag to ignore repeated "key pressed" events
2146 ignore_repeated_key = TRUE;
2151 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2152 key == KSYM_minus || key == KSYM_KP_Subtract ||
2153 key == KSYM_plus || key == KSYM_KP_Add ||
2154 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2155 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2156 video.window_scaling_available &&
2157 !video.fullscreen_enabled)
2159 if (key == KSYM_0 || key == KSYM_KP_0)
2160 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2161 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2162 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2164 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2166 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2167 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2168 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2169 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2171 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2173 if (game_status == GAME_MODE_SETUP)
2174 RedrawSetupScreenAfterFullscreenToggle();
2179 // some key events are handled like clicks for global animations
2180 boolean click = (key == KSYM_space ||
2181 key == KSYM_Return ||
2182 key == KSYM_Escape);
2184 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2186 // do not handle this key event anymore
2187 if (key != KSYM_Escape) // always allow ESC key to be handled
2191 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2192 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2199 if (game_status == GAME_MODE_MAIN &&
2200 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2202 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2207 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2209 if (key == setup.shortcut.save_game)
2211 else if (key == setup.shortcut.load_game)
2213 else if (key == setup.shortcut.toggle_pause)
2214 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2216 HandleTapeButtonKeys(key);
2217 HandleSoundButtonKeys(key);
2220 if (game_status == GAME_MODE_PLAYING && !network_playing)
2222 int centered_player_nr_next = -999;
2224 if (key == setup.shortcut.focus_player_all)
2225 centered_player_nr_next = -1;
2227 for (i = 0; i < MAX_PLAYERS; i++)
2228 if (key == setup.shortcut.focus_player[i])
2229 centered_player_nr_next = i;
2231 if (centered_player_nr_next != -999)
2233 game.centered_player_nr_next = centered_player_nr_next;
2234 game.set_centered_player = TRUE;
2238 tape.centered_player_nr_next = game.centered_player_nr_next;
2239 tape.set_centered_player = TRUE;
2244 HandleKeysSpecial(key);
2246 if (HandleGadgetsKeyInput(key))
2247 return; // do not handle already processed keys again
2249 switch (game_status)
2251 case GAME_MODE_PSEUDO_TYPENAME:
2252 HandleTypeName(0, key);
2255 case GAME_MODE_TITLE:
2256 case GAME_MODE_MAIN:
2257 case GAME_MODE_LEVELS:
2258 case GAME_MODE_LEVELNR:
2259 case GAME_MODE_SETUP:
2260 case GAME_MODE_INFO:
2261 case GAME_MODE_SCORES:
2263 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2270 if (game_status == GAME_MODE_TITLE)
2271 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2272 else if (game_status == GAME_MODE_MAIN)
2273 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2274 else if (game_status == GAME_MODE_LEVELS)
2275 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2276 else if (game_status == GAME_MODE_LEVELNR)
2277 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2278 else if (game_status == GAME_MODE_SETUP)
2279 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2280 else if (game_status == GAME_MODE_INFO)
2281 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2282 else if (game_status == GAME_MODE_SCORES)
2283 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2287 if (game_status != GAME_MODE_MAIN)
2288 FadeSkipNextFadeIn();
2290 if (game_status == GAME_MODE_TITLE)
2291 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2292 else if (game_status == GAME_MODE_LEVELS)
2293 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2294 else if (game_status == GAME_MODE_LEVELNR)
2295 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2296 else if (game_status == GAME_MODE_SETUP)
2297 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2298 else if (game_status == GAME_MODE_INFO)
2299 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2300 else if (game_status == GAME_MODE_SCORES)
2301 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2305 if (game_status == GAME_MODE_LEVELS)
2306 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2307 else if (game_status == GAME_MODE_LEVELNR)
2308 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2309 else if (game_status == GAME_MODE_SETUP)
2310 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2311 else if (game_status == GAME_MODE_INFO)
2312 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2313 else if (game_status == GAME_MODE_SCORES)
2314 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2317 case KSYM_Page_Down:
2318 if (game_status == GAME_MODE_LEVELS)
2319 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2320 else if (game_status == GAME_MODE_LEVELNR)
2321 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2322 else if (game_status == GAME_MODE_SETUP)
2323 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2324 else if (game_status == GAME_MODE_INFO)
2325 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2326 else if (game_status == GAME_MODE_SCORES)
2327 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2335 case GAME_MODE_EDITOR:
2336 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2337 HandleLevelEditorKeyInput(key);
2340 case GAME_MODE_PLAYING:
2345 RequestQuitGame(setup.ask_on_escape);
2355 if (key == KSYM_Escape)
2357 SetGameStatus(GAME_MODE_MAIN);
2366 void HandleNoEvent(void)
2368 HandleMouseCursor();
2370 switch (game_status)
2372 case GAME_MODE_PLAYING:
2373 HandleButtonOrFinger(-1, -1, -1);
2378 void HandleEventActions(void)
2380 // if (button_status && game_status != GAME_MODE_PLAYING)
2381 if (button_status && (game_status != GAME_MODE_PLAYING ||
2383 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2385 HandleButton(0, 0, button_status, -button_status);
2392 if (network.enabled)
2395 switch (game_status)
2397 case GAME_MODE_MAIN:
2398 DrawPreviewLevelAnimation();
2401 case GAME_MODE_EDITOR:
2402 HandleLevelEditorIdle();
2410 static void HandleTileCursor(int dx, int dy, int button)
2413 ClearPlayerMouseAction();
2420 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2421 (dx < 0 ? MB_LEFTBUTTON :
2422 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2424 else if (!tile_cursor.moving)
2426 int old_xpos = tile_cursor.xpos;
2427 int old_ypos = tile_cursor.ypos;
2428 int new_xpos = old_xpos;
2429 int new_ypos = old_ypos;
2431 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2432 new_xpos = old_xpos + dx;
2434 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2435 new_ypos = old_ypos + dy;
2437 SetTileCursorTargetXY(new_xpos, new_ypos);
2441 static int HandleJoystickForAllPlayers(void)
2445 boolean no_joysticks_configured = TRUE;
2446 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2447 static byte joy_action_last[MAX_PLAYERS];
2449 for (i = 0; i < MAX_PLAYERS; i++)
2450 if (setup.input[i].use_joystick)
2451 no_joysticks_configured = FALSE;
2453 // if no joysticks configured, map connected joysticks to players
2454 if (no_joysticks_configured)
2455 use_as_joystick_nr = TRUE;
2457 for (i = 0; i < MAX_PLAYERS; i++)
2459 byte joy_action = 0;
2461 joy_action = JoystickExt(i, use_as_joystick_nr);
2462 result |= joy_action;
2464 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2465 joy_action != joy_action_last[i])
2466 stored_player[i].action = joy_action;
2468 joy_action_last[i] = joy_action;
2474 void HandleJoystick(void)
2476 static unsigned int joytest_delay = 0;
2477 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2478 static int joytest_last = 0;
2479 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2480 int delay_value = GADGET_FRAME_DELAY;
2481 int joystick = HandleJoystickForAllPlayers();
2482 int keyboard = key_joystick_mapping;
2483 int joy = (joystick | keyboard);
2484 int joytest = joystick;
2485 int left = joy & JOY_LEFT;
2486 int right = joy & JOY_RIGHT;
2487 int up = joy & JOY_UP;
2488 int down = joy & JOY_DOWN;
2489 int button = joy & JOY_BUTTON;
2490 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2491 int dx = (left ? -1 : right ? 1 : 0);
2492 int dy = (up ? -1 : down ? 1 : 0);
2493 boolean use_delay_value_first = (joytest != joytest_last);
2495 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2497 // do not handle this button event anymore
2501 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2502 anyTextGadgetActive()))
2504 // leave name input in main menu or text input gadget
2505 HandleKey(KSYM_Escape, KEY_PRESSED);
2506 HandleKey(KSYM_Escape, KEY_RELEASED);
2511 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2513 if (game_status == GAME_MODE_PLAYING)
2515 // when playing MM style levels, also use delay for keyboard events
2516 joytest |= keyboard;
2518 // only use first delay value for new events, but not for changed events
2519 use_delay_value_first = (!joytest != !joytest_last);
2521 // only use delay after the initial keyboard event
2525 // for any joystick or keyboard event, enable playfield tile cursor
2526 if (dx || dy || button)
2527 SetTileCursorEnabled(TRUE);
2530 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2532 // delay joystick/keyboard actions if axes/keys continually pressed
2533 newbutton = dx = dy = 0;
2537 // first start with longer delay, then continue with shorter delay
2538 joytest_delay_value =
2539 (use_delay_value_first ? delay_value_first : delay_value);
2542 joytest_last = joytest;
2544 switch (game_status)
2546 case GAME_MODE_TITLE:
2547 case GAME_MODE_MAIN:
2548 case GAME_MODE_LEVELS:
2549 case GAME_MODE_LEVELNR:
2550 case GAME_MODE_SETUP:
2551 case GAME_MODE_INFO:
2552 case GAME_MODE_SCORES:
2554 if (anyTextGadgetActive())
2557 if (game_status == GAME_MODE_TITLE)
2558 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2559 else if (game_status == GAME_MODE_MAIN)
2560 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2561 else if (game_status == GAME_MODE_LEVELS)
2562 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2563 else if (game_status == GAME_MODE_LEVELNR)
2564 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2565 else if (game_status == GAME_MODE_SETUP)
2566 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2567 else if (game_status == GAME_MODE_INFO)
2568 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2569 else if (game_status == GAME_MODE_SCORES)
2570 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2575 case GAME_MODE_PLAYING:
2577 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2578 if (tape.playing || keyboard)
2579 newbutton = ((joy & JOY_BUTTON) != 0);
2582 if (newbutton && game.all_players_gone)
2589 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2591 if (joystick & JOY_ACTION)
2592 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2594 else if (tape.recording && tape.pausing && !tape.use_mouse)
2596 if (joystick & JOY_ACTION)
2597 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2600 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2601 HandleTileCursor(dx, dy, button);
2610 void HandleSpecialGameControllerButtons(Event *event)
2615 switch (event->type)
2617 case SDL_CONTROLLERBUTTONDOWN:
2618 key_status = KEY_PRESSED;
2621 case SDL_CONTROLLERBUTTONUP:
2622 key_status = KEY_RELEASED;
2629 switch (event->cbutton.button)
2631 case SDL_CONTROLLER_BUTTON_START:
2635 case SDL_CONTROLLER_BUTTON_BACK:
2643 HandleKey(key, key_status);
2646 void HandleSpecialGameControllerKeys(Key key, int key_status)
2648 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2649 int button = SDL_CONTROLLER_BUTTON_INVALID;
2651 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2652 if (key == KSYM_Rewind)
2653 button = SDL_CONTROLLER_BUTTON_A;
2654 else if (key == KSYM_FastForward || key == KSYM_Menu)
2655 button = SDL_CONTROLLER_BUTTON_B;
2657 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2661 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2662 SDL_CONTROLLERBUTTONUP);
2664 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2665 event.cbutton.button = button;
2666 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2669 HandleJoystickEvent(&event);
2674 boolean DoKeysymAction(int keysym)
2678 Key key = (Key)(-keysym);
2680 HandleKey(key, KEY_PRESSED);
2681 HandleKey(key, KEY_RELEASED);