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)
59 int mouse_x = ((MotionEvent *)event)->x;
60 int mouse_y = ((MotionEvent *)event)->y;
62 UpdateRawMousePosition(mouse_x, mouse_y);
68 // event filter especially needed for SDL event filtering due to
69 // delay problems with lots of mouse motion events when mouse button
70 // not pressed (X11 can handle this with 'PointerMotionHintMask')
72 // event filter addition for SDL2: as SDL2 does not have a function to enable
73 // or disable keyboard auto-repeat, filter repeated keyboard events instead
75 static int FilterEvents(const Event *event)
79 // skip repeated key press events if keyboard auto-repeat is disabled
80 if (event->type == EVENT_KEYPRESS &&
85 if (event->type == EVENT_BUTTONPRESS ||
86 event->type == EVENT_BUTTONRELEASE)
88 ((ButtonEvent *)event)->x -= video.screen_xoffset;
89 ((ButtonEvent *)event)->y -= video.screen_yoffset;
91 else if (event->type == EVENT_MOTIONNOTIFY)
93 ((MotionEvent *)event)->x -= video.screen_xoffset;
94 ((MotionEvent *)event)->y -= video.screen_yoffset;
97 // non-motion events are directly passed to event handler functions
98 if (event->type != EVENT_MOTIONNOTIFY)
101 motion = (MotionEvent *)event;
102 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
103 motion->y >= SY && motion->y < SY + SYSIZE);
105 // do no reset mouse cursor before all pending events have been processed
106 if (gfx.cursor_mode == cursor_mode_last &&
107 ((game_status == GAME_MODE_TITLE &&
108 gfx.cursor_mode == CURSOR_NONE) ||
109 (game_status == GAME_MODE_PLAYING &&
110 gfx.cursor_mode == CURSOR_PLAYFIELD)))
112 SetMouseCursor(CURSOR_DEFAULT);
114 DelayReached(&special_cursor_delay, 0);
116 cursor_mode_last = CURSOR_DEFAULT;
119 // skip mouse motion events without pressed button outside level editor
120 if (button_status == MB_RELEASED &&
121 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
127 // to prevent delay problems, skip mouse motion events if the very next
128 // event is also a mouse motion event (and therefore effectively only
129 // handling the last of a row of mouse motion events in the event queue)
131 static boolean SkipPressedMouseMotionEvent(const Event *event)
133 // nothing to do if the current event is not a mouse motion event
134 if (event->type != EVENT_MOTIONNOTIFY)
137 // only skip motion events with pressed button outside the game
138 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
145 PeekEvent(&next_event);
147 // if next event is also a mouse motion event, skip the current one
148 if (next_event.type == EVENT_MOTIONNOTIFY)
155 static boolean WaitValidEvent(Event *event)
159 if (!FilterEvents(event))
162 if (SkipPressedMouseMotionEvent(event))
168 /* this is especially needed for event modifications for the Android target:
169 if mouse coordinates should be modified in the event filter function,
170 using a properly installed SDL event filter does not work, because in
171 the event filter, mouse coordinates in the event structure are still
172 physical pixel positions, not logical (scaled) screen positions, so this
173 has to be handled at a later stage in the event processing functions
174 (when device pixel positions are already converted to screen positions) */
176 boolean NextValidEvent(Event *event)
178 while (PendingEvent())
179 if (WaitValidEvent(event))
185 static void HandleEvents(void)
188 unsigned int event_frame_delay = 0;
189 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
191 ResetDelayCounter(&event_frame_delay);
193 while (NextValidEvent(&event))
197 case EVENT_BUTTONPRESS:
198 case EVENT_BUTTONRELEASE:
199 HandleButtonEvent((ButtonEvent *) &event);
202 case EVENT_MOTIONNOTIFY:
203 HandleMotionEvent((MotionEvent *) &event);
206 case EVENT_WHEELMOTION:
207 HandleWheelEvent((WheelEvent *) &event);
210 case SDL_WINDOWEVENT:
211 HandleWindowEvent((WindowEvent *) &event);
214 case EVENT_FINGERPRESS:
215 case EVENT_FINGERRELEASE:
216 case EVENT_FINGERMOTION:
217 HandleFingerEvent((FingerEvent *) &event);
220 case EVENT_TEXTINPUT:
221 HandleTextEvent((TextEvent *) &event);
224 case SDL_APP_WILLENTERBACKGROUND:
225 case SDL_APP_DIDENTERBACKGROUND:
226 case SDL_APP_WILLENTERFOREGROUND:
227 case SDL_APP_DIDENTERFOREGROUND:
228 HandlePauseResumeEvent((PauseResumeEvent *) &event);
232 case EVENT_KEYRELEASE:
233 HandleKeyEvent((KeyEvent *) &event);
237 HandleUserEvent((UserEvent *) &event);
241 HandleOtherEvents(&event);
245 // do not handle events for longer than standard frame delay period
246 if (DelayReached(&event_frame_delay, event_frame_delay_value))
251 void HandleOtherEvents(Event *event)
255 case SDL_CONTROLLERBUTTONDOWN:
256 case SDL_CONTROLLERBUTTONUP:
257 // for any game controller button event, disable overlay buttons
258 SetOverlayEnabled(FALSE);
260 HandleSpecialGameControllerButtons(event);
263 case SDL_CONTROLLERDEVICEADDED:
264 case SDL_CONTROLLERDEVICEREMOVED:
265 case SDL_CONTROLLERAXISMOTION:
266 case SDL_JOYAXISMOTION:
267 case SDL_JOYBUTTONDOWN:
268 case SDL_JOYBUTTONUP:
269 HandleJoystickEvent(event);
273 case SDL_DROPCOMPLETE:
276 HandleDropEvent(event);
288 static void HandleMouseCursor(void)
290 if (game_status == GAME_MODE_TITLE)
292 // when showing title screens, hide mouse pointer (if not moved)
294 if (gfx.cursor_mode != CURSOR_NONE &&
295 DelayReached(&special_cursor_delay, special_cursor_delay_value))
297 SetMouseCursor(CURSOR_NONE);
300 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
303 // when playing, display a special mouse pointer inside the playfield
305 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
306 cursor_inside_playfield &&
307 DelayReached(&special_cursor_delay, special_cursor_delay_value))
309 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
311 SetMouseCursor(CURSOR_PLAYFIELD);
314 else if (gfx.cursor_mode != CURSOR_DEFAULT)
316 SetMouseCursor(CURSOR_DEFAULT);
319 // this is set after all pending events have been processed
320 cursor_mode_last = gfx.cursor_mode;
332 // execute event related actions after pending events have been processed
333 HandleEventActions();
335 // don't use all CPU time when idle; the main loop while playing
336 // has its own synchronization and is CPU friendly, too
338 if (game_status == GAME_MODE_PLAYING)
341 // always copy backbuffer to visible screen for every video frame
344 // reset video frame delay to default (may change again while playing)
345 SetVideoFrameDelay(MenuFrameDelay);
347 if (game_status == GAME_MODE_QUIT)
352 void ClearAutoRepeatKeyEvents(void)
354 while (PendingEvent())
358 PeekEvent(&next_event);
360 // if event is repeated key press event, remove it from event queue
361 if (next_event.type == EVENT_KEYPRESS &&
362 next_event.key.repeat)
363 WaitEvent(&next_event);
369 void ClearEventQueue(void)
373 while (NextValidEvent(&event))
377 case EVENT_BUTTONRELEASE:
378 button_status = MB_RELEASED;
381 case EVENT_KEYRELEASE:
385 case SDL_CONTROLLERBUTTONUP:
386 HandleJoystickEvent(&event);
391 HandleOtherEvents(&event);
397 static void ClearPlayerMouseAction(void)
399 local_player->mouse_action.lx = 0;
400 local_player->mouse_action.ly = 0;
401 local_player->mouse_action.button = 0;
404 void ClearPlayerAction(void)
408 // simulate key release events for still pressed keys
409 key_joystick_mapping = 0;
410 for (i = 0; i < MAX_PLAYERS; i++)
412 stored_player[i].action = 0;
413 stored_player[i].snap_action = 0;
416 ClearJoystickState();
417 ClearPlayerMouseAction();
420 static void SetPlayerMouseAction(int mx, int my, int button)
422 int lx = getLevelFromScreenX(mx);
423 int ly = getLevelFromScreenY(my);
424 int new_button = (!local_player->mouse_action.button && button);
426 if (local_player->mouse_action.button_hint)
427 button = local_player->mouse_action.button_hint;
429 ClearPlayerMouseAction();
431 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
434 local_player->mouse_action.lx = lx;
435 local_player->mouse_action.ly = ly;
436 local_player->mouse_action.button = button;
438 if (tape.recording && tape.pausing && tape.use_mouse)
440 // un-pause a paused game only if mouse button was newly pressed down
442 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
445 SetTileCursorXY(lx, ly);
448 void HandleButtonEvent(ButtonEvent *event)
450 #if DEBUG_EVENTS_BUTTON
451 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
453 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
457 // for any mouse button event, disable playfield tile cursor
458 SetTileCursorEnabled(FALSE);
460 #if defined(HAS_SCREEN_KEYBOARD)
461 if (video.shifted_up)
462 event->y += video.shifted_up_pos;
465 motion_status = FALSE;
467 if (event->type == EVENT_BUTTONPRESS)
468 button_status = event->button;
470 button_status = MB_RELEASED;
472 HandleButton(event->x, event->y, button_status, event->button);
475 void HandleMotionEvent(MotionEvent *event)
477 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
480 motion_status = TRUE;
482 #if DEBUG_EVENTS_MOTION
483 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
484 button_status, event->x, event->y);
487 HandleButton(event->x, event->y, button_status, button_status);
490 void HandleWheelEvent(WheelEvent *event)
494 #if DEBUG_EVENTS_WHEEL
496 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
497 event->which, event->x, event->y);
499 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
500 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
501 event->which, event->x, event->y,
502 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
503 "SDL_MOUSEWHEEL_FLIPPED"));
507 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
508 event->x > 0 ? MB_WHEEL_RIGHT :
509 event->y < 0 ? MB_WHEEL_DOWN :
510 event->y > 0 ? MB_WHEEL_UP : 0);
512 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
513 // accelerated mouse wheel available on Mac and Windows
514 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
516 // no accelerated mouse wheel available on Unix/Linux
517 wheel_steps = DEFAULT_WHEEL_STEPS;
520 motion_status = FALSE;
522 button_status = button_nr;
523 HandleButton(0, 0, button_status, -button_nr);
525 button_status = MB_RELEASED;
526 HandleButton(0, 0, button_status, -button_nr);
529 void HandleWindowEvent(WindowEvent *event)
531 #if DEBUG_EVENTS_WINDOW
532 int subtype = event->event;
535 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
536 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
537 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
538 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
539 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
540 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
541 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
542 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
543 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
544 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
545 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
546 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
547 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
548 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
551 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
552 event_name, event->data1, event->data2);
556 // (not needed, as the screen gets redrawn every 20 ms anyway)
557 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
558 event->event == SDL_WINDOWEVENT_RESIZED ||
559 event->event == SDL_WINDOWEVENT_EXPOSED)
563 if (event->event == SDL_WINDOWEVENT_RESIZED)
565 if (!video.fullscreen_enabled)
567 int new_window_width = event->data1;
568 int new_window_height = event->data2;
570 // if window size has changed after resizing, calculate new scaling factor
571 if (new_window_width != video.window_width ||
572 new_window_height != video.window_height)
574 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
575 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
577 // (extreme window scaling allowed, but cannot be saved permanently)
578 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
579 setup.window_scaling_percent =
580 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
581 MAX_WINDOW_SCALING_PERCENT);
583 video.window_width = new_window_width;
584 video.window_height = new_window_height;
586 if (game_status == GAME_MODE_SETUP)
587 RedrawSetupScreenAfterFullscreenToggle();
589 UpdateMousePosition();
594 #if defined(PLATFORM_ANDROID)
597 int new_display_width = event->data1;
598 int new_display_height = event->data2;
600 // if fullscreen display size has changed, device has been rotated
601 if (new_display_width != video.display_width ||
602 new_display_height != video.display_height)
604 int nr = GRID_ACTIVE_NR(); // previous screen orientation
606 video.display_width = new_display_width;
607 video.display_height = new_display_height;
609 SDLSetScreenProperties();
611 // check if screen orientation has changed (should always be true here)
612 if (nr != GRID_ACTIVE_NR())
616 if (game_status == GAME_MODE_SETUP)
617 RedrawSetupScreenAfterScreenRotation(nr);
619 nr = GRID_ACTIVE_NR();
621 overlay.grid_xsize = setup.touch.grid_xsize[nr];
622 overlay.grid_ysize = setup.touch.grid_ysize[nr];
624 for (x = 0; x < MAX_GRID_XSIZE; x++)
625 for (y = 0; y < MAX_GRID_YSIZE; y++)
626 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
634 #define NUM_TOUCH_FINGERS 3
639 SDL_FingerID finger_id;
643 } touch_info[NUM_TOUCH_FINGERS];
645 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
648 int x = event->x * overlay.grid_xsize;
649 int y = event->y * overlay.grid_ysize;
650 int grid_button = overlay.grid_button[x][y];
651 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
652 Key key = (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
653 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
654 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
655 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
656 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
657 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
660 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
661 float event_x = (event->x);
662 float event_y = (event->y - ypos) / (1 - ypos);
663 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
664 event_y > 2.0 / 3.0 && event_y < 1 ?
665 setup.input[0].key.snap :
666 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
667 event_y > 2.0 / 3.0 && event_y < 1 ?
668 setup.input[0].key.drop :
669 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
670 event_y > 0 && event_y < 1.0 / 3.0 ?
671 setup.input[0].key.up :
672 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
673 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
674 setup.input[0].key.left :
675 event_x > 8.0 / 9.0 && event_x < 1 &&
676 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
677 setup.input[0].key.right :
678 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
679 event_y > 2.0 / 3.0 && event_y < 1 ?
680 setup.input[0].key.down :
683 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
685 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
689 virtual_button_pressed = (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
691 // for any touch input event, enable overlay buttons (if activated)
692 SetOverlayEnabled(TRUE);
694 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
695 getKeyNameFromKey(key), key_status_name, event->fingerId);
697 if (key_status == KEY_PRESSED)
698 overlay.grid_button_action |= grid_button_action;
700 overlay.grid_button_action &= ~grid_button_action;
702 // check if we already know this touch event's finger id
703 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
705 if (touch_info[i].touched &&
706 touch_info[i].finger_id == event->fingerId)
708 // Error(ERR_DEBUG, "MARK 1: %d", i);
714 if (i >= NUM_TOUCH_FINGERS)
716 if (key_status == KEY_PRESSED)
718 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
720 // unknown finger id -- get new, empty slot, if available
721 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
723 if (touch_info[i].counter < oldest_counter)
726 oldest_counter = touch_info[i].counter;
728 // Error(ERR_DEBUG, "MARK 2: %d", i);
731 if (!touch_info[i].touched)
733 // Error(ERR_DEBUG, "MARK 3: %d", i);
739 if (i >= NUM_TOUCH_FINGERS)
741 // all slots allocated -- use oldest slot
744 // Error(ERR_DEBUG, "MARK 4: %d", i);
749 // release of previously unknown key (should not happen)
751 if (key != KSYM_UNDEFINED)
753 HandleKey(key, KEY_RELEASED);
755 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
756 getKeyNameFromKey(key), "KEY_RELEASED", i);
761 if (i < NUM_TOUCH_FINGERS)
763 if (key_status == KEY_PRESSED)
765 if (touch_info[i].key != key)
767 if (touch_info[i].key != KSYM_UNDEFINED)
769 HandleKey(touch_info[i].key, KEY_RELEASED);
771 // undraw previous grid button when moving finger away
772 overlay.grid_button_action &= ~touch_info[i].action;
774 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
775 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
778 if (key != KSYM_UNDEFINED)
780 HandleKey(key, KEY_PRESSED);
782 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
783 getKeyNameFromKey(key), "KEY_PRESSED", i);
787 touch_info[i].touched = TRUE;
788 touch_info[i].finger_id = event->fingerId;
789 touch_info[i].counter = Counter();
790 touch_info[i].key = key;
791 touch_info[i].action = grid_button_action;
795 if (touch_info[i].key != KSYM_UNDEFINED)
797 HandleKey(touch_info[i].key, KEY_RELEASED);
799 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
800 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
803 touch_info[i].touched = FALSE;
804 touch_info[i].finger_id = 0;
805 touch_info[i].counter = 0;
806 touch_info[i].key = 0;
807 touch_info[i].action = JOY_NO_ACTION;
812 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
814 static Key motion_key_x = KSYM_UNDEFINED;
815 static Key motion_key_y = KSYM_UNDEFINED;
816 static Key button_key = KSYM_UNDEFINED;
817 static float motion_x1, motion_y1;
818 static float button_x1, button_y1;
819 static SDL_FingerID motion_id = -1;
820 static SDL_FingerID button_id = -1;
821 int move_trigger_distance_percent = setup.touch.move_distance;
822 int drop_trigger_distance_percent = setup.touch.drop_distance;
823 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
824 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
825 float event_x = event->x;
826 float event_y = event->y;
828 if (event->type == EVENT_FINGERPRESS)
830 if (event_x > 1.0 / 3.0)
834 motion_id = event->fingerId;
839 motion_key_x = KSYM_UNDEFINED;
840 motion_key_y = KSYM_UNDEFINED;
842 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
848 button_id = event->fingerId;
853 button_key = setup.input[0].key.snap;
855 HandleKey(button_key, KEY_PRESSED);
857 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
860 else if (event->type == EVENT_FINGERRELEASE)
862 if (event->fingerId == motion_id)
866 if (motion_key_x != KSYM_UNDEFINED)
867 HandleKey(motion_key_x, KEY_RELEASED);
868 if (motion_key_y != KSYM_UNDEFINED)
869 HandleKey(motion_key_y, KEY_RELEASED);
871 motion_key_x = KSYM_UNDEFINED;
872 motion_key_y = KSYM_UNDEFINED;
874 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
876 else if (event->fingerId == button_id)
880 if (button_key != KSYM_UNDEFINED)
881 HandleKey(button_key, KEY_RELEASED);
883 button_key = KSYM_UNDEFINED;
885 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
888 else if (event->type == EVENT_FINGERMOTION)
890 if (event->fingerId == motion_id)
892 float distance_x = ABS(event_x - motion_x1);
893 float distance_y = ABS(event_y - motion_y1);
894 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
895 event_x > motion_x1 ? setup.input[0].key.right :
897 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
898 event_y > motion_y1 ? setup.input[0].key.down :
901 if (distance_x < move_trigger_distance / 2 ||
902 distance_x < distance_y)
903 new_motion_key_x = KSYM_UNDEFINED;
905 if (distance_y < move_trigger_distance / 2 ||
906 distance_y < distance_x)
907 new_motion_key_y = KSYM_UNDEFINED;
909 if (distance_x > move_trigger_distance ||
910 distance_y > move_trigger_distance)
912 if (new_motion_key_x != motion_key_x)
914 if (motion_key_x != KSYM_UNDEFINED)
915 HandleKey(motion_key_x, KEY_RELEASED);
916 if (new_motion_key_x != KSYM_UNDEFINED)
917 HandleKey(new_motion_key_x, KEY_PRESSED);
920 if (new_motion_key_y != motion_key_y)
922 if (motion_key_y != KSYM_UNDEFINED)
923 HandleKey(motion_key_y, KEY_RELEASED);
924 if (new_motion_key_y != KSYM_UNDEFINED)
925 HandleKey(new_motion_key_y, KEY_PRESSED);
931 motion_key_x = new_motion_key_x;
932 motion_key_y = new_motion_key_y;
934 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
937 else if (event->fingerId == button_id)
939 float distance_x = ABS(event_x - button_x1);
940 float distance_y = ABS(event_y - button_y1);
942 if (distance_x < drop_trigger_distance / 2 &&
943 distance_y > drop_trigger_distance)
945 if (button_key == setup.input[0].key.snap)
946 HandleKey(button_key, KEY_RELEASED);
951 button_key = setup.input[0].key.drop;
953 HandleKey(button_key, KEY_PRESSED);
955 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
961 void HandleFingerEvent(FingerEvent *event)
963 #if DEBUG_EVENTS_FINGER
964 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
965 event->type == EVENT_FINGERPRESS ? "pressed" :
966 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
970 event->dx, event->dy,
974 runtime.uses_touch_device = TRUE;
976 if (game_status != GAME_MODE_PLAYING)
979 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
981 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
982 local_player->mouse_action.button_hint =
983 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
984 event->x < 0.5 ? MB_LEFTBUTTON :
985 event->x > 0.5 ? MB_RIGHTBUTTON :
991 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
992 HandleFingerEvent_VirtualButtons(event);
993 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
994 HandleFingerEvent_WipeGestures(event);
997 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
999 static int old_mx = 0, old_my = 0;
1000 static int last_button = MB_LEFTBUTTON;
1001 static boolean touched = FALSE;
1002 static boolean tapped = FALSE;
1004 // screen tile was tapped (but finger not touching the screen anymore)
1005 // (this point will also be reached without receiving a touch event)
1006 if (tapped && !touched)
1008 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1013 // stop here if this function was not triggered by a touch event
1017 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1019 // finger started touching the screen
1029 ClearPlayerMouseAction();
1031 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1034 else if (button == MB_RELEASED && touched)
1036 // finger stopped touching the screen
1041 SetPlayerMouseAction(old_mx, old_my, last_button);
1043 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1045 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1050 // finger moved while touching the screen
1052 int old_x = getLevelFromScreenX(old_mx);
1053 int old_y = getLevelFromScreenY(old_my);
1054 int new_x = getLevelFromScreenX(mx);
1055 int new_y = getLevelFromScreenY(my);
1057 if (new_x != old_x || new_y != old_y)
1062 // finger moved left or right from (horizontal) starting position
1064 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1066 SetPlayerMouseAction(old_mx, old_my, button_nr);
1068 last_button = button_nr;
1070 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1074 // finger stays at or returned to (horizontal) starting position
1076 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1078 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1083 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1085 static int old_mx = 0, old_my = 0;
1086 static int last_button = MB_LEFTBUTTON;
1087 static boolean touched = FALSE;
1088 static boolean tapped = FALSE;
1090 // screen tile was tapped (but finger not touching the screen anymore)
1091 // (this point will also be reached without receiving a touch event)
1092 if (tapped && !touched)
1094 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1099 // stop here if this function was not triggered by a touch event
1103 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1105 // finger started touching the screen
1115 ClearPlayerMouseAction();
1117 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1120 else if (button == MB_RELEASED && touched)
1122 // finger stopped touching the screen
1127 SetPlayerMouseAction(old_mx, old_my, last_button);
1129 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1131 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1136 // finger moved while touching the screen
1138 int old_x = getLevelFromScreenX(old_mx);
1139 int old_y = getLevelFromScreenY(old_my);
1140 int new_x = getLevelFromScreenX(mx);
1141 int new_y = getLevelFromScreenY(my);
1143 if (new_x != old_x || new_y != old_y)
1145 // finger moved away from starting position
1147 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1149 // quickly alternate between clicking and releasing for maximum speed
1150 if (FrameCounter % 2 == 0)
1151 button_nr = MB_RELEASED;
1153 SetPlayerMouseAction(old_mx, old_my, button_nr);
1156 last_button = button_nr;
1160 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1164 // finger stays at or returned to starting position
1166 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1168 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1173 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1175 static int old_mx = 0, old_my = 0;
1176 static Key motion_key_x = KSYM_UNDEFINED;
1177 static Key motion_key_y = KSYM_UNDEFINED;
1178 static boolean touched = FALSE;
1179 static boolean started_on_player = FALSE;
1180 static boolean player_is_dropping = FALSE;
1181 static int player_drop_count = 0;
1182 static int last_player_x = -1;
1183 static int last_player_y = -1;
1185 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1194 started_on_player = FALSE;
1195 player_is_dropping = FALSE;
1196 player_drop_count = 0;
1200 motion_key_x = KSYM_UNDEFINED;
1201 motion_key_y = KSYM_UNDEFINED;
1203 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1206 else if (button == MB_RELEASED && touched)
1213 if (motion_key_x != KSYM_UNDEFINED)
1214 HandleKey(motion_key_x, KEY_RELEASED);
1215 if (motion_key_y != KSYM_UNDEFINED)
1216 HandleKey(motion_key_y, KEY_RELEASED);
1218 if (started_on_player)
1220 if (player_is_dropping)
1222 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1224 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1228 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1230 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1234 motion_key_x = KSYM_UNDEFINED;
1235 motion_key_y = KSYM_UNDEFINED;
1237 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1242 int src_x = local_player->jx;
1243 int src_y = local_player->jy;
1244 int dst_x = getLevelFromScreenX(old_mx);
1245 int dst_y = getLevelFromScreenY(old_my);
1246 int dx = dst_x - src_x;
1247 int dy = dst_y - src_y;
1248 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1249 dx > 0 ? setup.input[0].key.right :
1251 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1252 dy > 0 ? setup.input[0].key.down :
1255 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1256 (last_player_x != local_player->jx ||
1257 last_player_y != local_player->jy))
1259 // in case of asymmetric diagonal movement, use "preferred" direction
1261 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1263 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1264 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1266 local_player->last_move_dir = last_move_dir;
1268 // (required to prevent accidentally forcing direction for next movement)
1269 last_player_x = local_player->jx;
1270 last_player_y = local_player->jy;
1273 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1275 started_on_player = TRUE;
1276 player_drop_count = getPlayerInventorySize(0);
1277 player_is_dropping = (player_drop_count > 0);
1279 if (player_is_dropping)
1281 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1283 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1287 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1289 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1292 else if (dx != 0 || dy != 0)
1294 if (player_is_dropping &&
1295 player_drop_count == getPlayerInventorySize(0))
1297 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1299 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1300 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1302 player_is_dropping = FALSE;
1306 if (new_motion_key_x != motion_key_x)
1308 Error(ERR_DEBUG, "---------- %s %s ----------",
1309 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1310 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1312 if (motion_key_x != KSYM_UNDEFINED)
1313 HandleKey(motion_key_x, KEY_RELEASED);
1314 if (new_motion_key_x != KSYM_UNDEFINED)
1315 HandleKey(new_motion_key_x, KEY_PRESSED);
1318 if (new_motion_key_y != motion_key_y)
1320 Error(ERR_DEBUG, "---------- %s %s ----------",
1321 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1322 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1324 if (motion_key_y != KSYM_UNDEFINED)
1325 HandleKey(motion_key_y, KEY_RELEASED);
1326 if (new_motion_key_y != KSYM_UNDEFINED)
1327 HandleKey(new_motion_key_y, KEY_PRESSED);
1330 motion_key_x = new_motion_key_x;
1331 motion_key_y = new_motion_key_y;
1335 static void HandleButtonOrFinger(int mx, int my, int button)
1337 if (game_status != GAME_MODE_PLAYING)
1340 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1342 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1343 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1344 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1345 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1346 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1347 SetPlayerMouseAction(mx, my, button); // special case
1351 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1352 HandleButtonOrFinger_FollowFinger(mx, my, button);
1356 static boolean checkTextInputKeyModState(void)
1358 // when playing, only handle raw key events and ignore text input
1359 if (game_status == GAME_MODE_PLAYING)
1362 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1365 void HandleTextEvent(TextEvent *event)
1367 char *text = event->text;
1368 Key key = getKeyFromKeyName(text);
1370 #if DEBUG_EVENTS_TEXT
1371 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1374 text[0], (int)(text[0]),
1376 getKeyNameFromKey(key),
1380 #if !defined(HAS_SCREEN_KEYBOARD)
1381 // non-mobile devices: only handle key input with modifier keys pressed here
1382 // (every other key input is handled directly as physical key input event)
1383 if (!checkTextInputKeyModState())
1387 // process text input as "classic" (with uppercase etc.) key input event
1388 HandleKey(key, KEY_PRESSED);
1389 HandleKey(key, KEY_RELEASED);
1392 void HandlePauseResumeEvent(PauseResumeEvent *event)
1394 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1398 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1404 void HandleKeyEvent(KeyEvent *event)
1406 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1407 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1408 Key key = GetEventKey(event, with_modifiers);
1409 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1411 #if DEBUG_EVENTS_KEY
1412 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1413 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1414 event->keysym.scancode,
1419 getKeyNameFromKey(key));
1422 #if defined(PLATFORM_ANDROID)
1423 if (key == KSYM_Back)
1425 // always map the "back" button to the "escape" key on Android devices
1428 else if (key == KSYM_Menu)
1430 // the "menu" button can be used to toggle displaying virtual buttons
1431 if (key_status == KEY_PRESSED)
1432 SetOverlayEnabled(!GetOverlayEnabled());
1436 // for any other "real" key event, disable virtual buttons
1437 SetOverlayEnabled(FALSE);
1441 HandleKeyModState(keymod, key_status);
1443 // only handle raw key input without text modifier keys pressed
1444 if (!checkTextInputKeyModState())
1445 HandleKey(key, key_status);
1448 static int HandleDropFileEvent(char *filename)
1450 Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
1452 // check and extract dropped zip files into correct user data directory
1453 if (!strSuffixLower(filename, ".zip"))
1455 Error(ERR_WARN, "file '%s' not supported", filename);
1457 return TREE_TYPE_UNDEFINED;
1460 TreeInfo *tree_node = NULL;
1461 int tree_type = GetZipFileTreeType(filename);
1462 char *directory = TREE_USERDIR(tree_type);
1464 if (directory == NULL)
1466 Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1468 return TREE_TYPE_UNDEFINED;
1471 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1472 game_status == GAME_MODE_LEVELS &&
1473 leveldir_current->node_parent != NULL)
1475 // extract new level set next to currently selected level set
1476 tree_node = leveldir_current;
1478 // get parent directory of currently selected level set directory
1479 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1481 // use private level directory instead of top-level package level directory
1482 if (strPrefix(directory, options.level_directory) &&
1483 strEqual(leveldir_current->node_parent->fullpath, "."))
1484 directory = getUserLevelDir(NULL);
1487 // extract level or artwork set from zip file to target directory
1488 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1490 if (top_dir == NULL)
1492 // error message already issued by "ExtractZipFileIntoDirectory()"
1494 return TREE_TYPE_UNDEFINED;
1497 // add extracted level or artwork set to tree info structure
1498 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1500 // update menu screen (and possibly change current level set)
1501 DrawScreenAfterAddingSet(top_dir, tree_type);
1506 static void HandleDropTextEvent(char *text)
1508 Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1511 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1512 int num_artwork_sets_succeeded,
1513 int num_files_failed)
1515 // only show request dialog if no other request dialog already active
1516 if (game.request_active)
1519 // this case can happen with drag-and-drop with older SDL versions
1520 if (num_level_sets_succeeded == 0 &&
1521 num_artwork_sets_succeeded == 0 &&
1522 num_files_failed == 0)
1527 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1529 char message_part1[50];
1531 sprintf(message_part1, "New %s set%s added",
1532 (num_artwork_sets_succeeded == 0 ? "level" :
1533 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1534 (num_level_sets_succeeded +
1535 num_artwork_sets_succeeded > 1 ? "s" : ""));
1537 if (num_files_failed > 0)
1538 sprintf(message, "%s, but %d dropped file%s failed!",
1539 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1541 sprintf(message, "%s!", message_part1);
1543 else if (num_files_failed > 0)
1545 sprintf(message, "Failed to process dropped file%s!",
1546 num_files_failed > 1 ? "s" : "");
1549 Request(message, REQ_CONFIRM);
1552 void HandleDropEvent(Event *event)
1554 static boolean confirm_on_drop_complete = FALSE;
1555 static int num_level_sets_succeeded = 0;
1556 static int num_artwork_sets_succeeded = 0;
1557 static int num_files_failed = 0;
1559 switch (event->type)
1563 confirm_on_drop_complete = TRUE;
1564 num_level_sets_succeeded = 0;
1565 num_artwork_sets_succeeded = 0;
1566 num_files_failed = 0;
1573 int tree_type = HandleDropFileEvent(event->drop.file);
1575 if (tree_type == TREE_TYPE_LEVEL_DIR)
1576 num_level_sets_succeeded++;
1577 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1578 tree_type == TREE_TYPE_SOUNDS_DIR ||
1579 tree_type == TREE_TYPE_MUSIC_DIR)
1580 num_artwork_sets_succeeded++;
1584 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1585 if (!confirm_on_drop_complete)
1587 // process all remaining events, including further SDL_DROPFILE events
1590 HandleDropCompleteEvent(num_level_sets_succeeded,
1591 num_artwork_sets_succeeded,
1594 num_level_sets_succeeded = 0;
1595 num_artwork_sets_succeeded = 0;
1596 num_files_failed = 0;
1604 HandleDropTextEvent(event->drop.file);
1609 case SDL_DROPCOMPLETE:
1611 HandleDropCompleteEvent(num_level_sets_succeeded,
1612 num_artwork_sets_succeeded,
1619 if (event->drop.file != NULL)
1620 SDL_free(event->drop.file);
1623 void HandleUserEvent(UserEvent *event)
1625 switch (event->code)
1627 case USEREVENT_ANIM_DELAY_ACTION:
1628 case USEREVENT_ANIM_EVENT_ACTION:
1629 // execute action functions until matching action was found
1630 if (DoKeysymAction(event->value1) ||
1631 DoGadgetAction(event->value1) ||
1632 DoScreenAction(event->value1))
1641 void HandleButton(int mx, int my, int button, int button_nr)
1643 static int old_mx = 0, old_my = 0;
1644 boolean button_hold = FALSE;
1645 boolean handle_gadgets = TRUE;
1651 button_nr = -button_nr;
1660 #if defined(PLATFORM_ANDROID)
1661 // when playing, only handle gadgets when using "follow finger" controls
1662 // or when using touch controls in combination with the MM game engine
1663 // or when using gadgets that do not overlap with virtual buttons
1665 (game_status != GAME_MODE_PLAYING ||
1666 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1667 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1668 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1669 !virtual_button_pressed));
1672 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1674 // do not handle this button event anymore
1675 return; // force mouse event not to be handled at all
1678 if (handle_gadgets && HandleGadgets(mx, my, button))
1680 // do not handle this button event anymore
1681 mx = my = -32; // force mouse event to be outside screen tiles
1684 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1687 // do not use scroll wheel button events for anything other than gadgets
1688 if (IS_WHEEL_BUTTON(button_nr))
1691 switch (game_status)
1693 case GAME_MODE_TITLE:
1694 HandleTitleScreen(mx, my, 0, 0, button);
1697 case GAME_MODE_MAIN:
1698 HandleMainMenu(mx, my, 0, 0, button);
1701 case GAME_MODE_PSEUDO_TYPENAME:
1702 HandleTypeName(0, KSYM_Return);
1705 case GAME_MODE_LEVELS:
1706 HandleChooseLevelSet(mx, my, 0, 0, button);
1709 case GAME_MODE_LEVELNR:
1710 HandleChooseLevelNr(mx, my, 0, 0, button);
1713 case GAME_MODE_SCORES:
1714 HandleHallOfFame(0, 0, 0, 0, button);
1717 case GAME_MODE_EDITOR:
1718 HandleLevelEditorIdle();
1721 case GAME_MODE_INFO:
1722 HandleInfoScreen(mx, my, 0, 0, button);
1725 case GAME_MODE_SETUP:
1726 HandleSetupScreen(mx, my, 0, 0, button);
1729 case GAME_MODE_PLAYING:
1730 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1731 HandleButtonOrFinger(mx, my, button);
1733 SetPlayerMouseAction(mx, my, button);
1736 if (button == MB_PRESSED && !motion_status && !button_hold &&
1737 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1738 DumpTileFromScreen(mx, my);
1748 static boolean is_string_suffix(char *string, char *suffix)
1750 int string_len = strlen(string);
1751 int suffix_len = strlen(suffix);
1753 if (suffix_len > string_len)
1756 return (strEqual(&string[string_len - suffix_len], suffix));
1759 #define MAX_CHEAT_INPUT_LEN 32
1761 static void HandleKeysSpecial(Key key)
1763 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1764 char letter = getCharFromKey(key);
1765 int cheat_input_len = strlen(cheat_input);
1771 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1773 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1774 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1776 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1779 cheat_input[cheat_input_len++] = letter;
1780 cheat_input[cheat_input_len] = '\0';
1782 #if DEBUG_EVENTS_KEY
1783 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1786 if (game_status == GAME_MODE_MAIN)
1788 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1789 is_string_suffix(cheat_input, ":ist"))
1791 InsertSolutionTape();
1793 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1794 is_string_suffix(cheat_input, ":pst"))
1798 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1799 is_string_suffix(cheat_input, ":rg"))
1801 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1804 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1805 is_string_suffix(cheat_input, ":rs"))
1807 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1810 else if (is_string_suffix(cheat_input, ":reload-music") ||
1811 is_string_suffix(cheat_input, ":rm"))
1813 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1816 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1817 is_string_suffix(cheat_input, ":ra"))
1819 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1820 1 << ARTWORK_TYPE_SOUNDS |
1821 1 << ARTWORK_TYPE_MUSIC);
1824 else if (is_string_suffix(cheat_input, ":dump-level") ||
1825 is_string_suffix(cheat_input, ":dl"))
1829 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1830 is_string_suffix(cheat_input, ":dt"))
1834 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1835 is_string_suffix(cheat_input, ":ft"))
1837 /* fix single-player tapes that contain player input for more than one
1838 player (due to a bug in 3.3.1.2 and earlier versions), which results
1839 in playing levels with more than one player in multi-player mode,
1840 even though the tape was originally recorded in single-player mode */
1842 // remove player input actions for all players but the first one
1843 for (i = 1; i < MAX_PLAYERS; i++)
1844 tape.player_participates[i] = FALSE;
1846 tape.changed = TRUE;
1848 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1849 is_string_suffix(cheat_input, ":snl"))
1851 SaveNativeLevel(&level);
1853 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1854 is_string_suffix(cheat_input, ":fps"))
1856 global.show_frames_per_second = !global.show_frames_per_second;
1859 else if (game_status == GAME_MODE_PLAYING)
1862 if (is_string_suffix(cheat_input, ".q"))
1863 DEBUG_SetMaximumDynamite();
1866 else if (game_status == GAME_MODE_EDITOR)
1868 if (is_string_suffix(cheat_input, ":dump-brush") ||
1869 is_string_suffix(cheat_input, ":DB"))
1873 else if (is_string_suffix(cheat_input, ":DDB"))
1878 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1880 if (letter == 'x') // copy brush to clipboard (small size)
1882 CopyBrushToClipboard_Small();
1884 else if (letter == 'c') // copy brush to clipboard (normal size)
1886 CopyBrushToClipboard();
1888 else if (letter == 'v') // paste brush from Clipboard
1890 CopyClipboardToBrush();
1895 // special key shortcuts for all game modes
1896 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1897 is_string_suffix(cheat_input, ":dea") ||
1898 is_string_suffix(cheat_input, ":DEA"))
1900 DumpGadgetIdentifiers();
1901 DumpScreenIdentifiers();
1905 boolean HandleKeysDebug(Key key, int key_status)
1910 if (key_status != KEY_PRESSED)
1913 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1915 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1917 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1919 if (key == setup.debug.frame_delay_key[i] &&
1920 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1922 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1923 setup.debug.frame_delay[i] : setup.game_frame_delay);
1925 if (!setup.debug.frame_delay_game_only)
1926 MenuFrameDelay = GameFrameDelay;
1928 SetVideoFrameDelay(GameFrameDelay);
1930 if (GameFrameDelay > ONE_SECOND_DELAY)
1931 Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
1932 else if (GameFrameDelay != 0)
1933 Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
1934 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1935 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1937 Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
1944 if (game_status == GAME_MODE_PLAYING)
1948 options.debug = !options.debug;
1950 Error(ERR_INFO, "debug mode %s",
1951 (options.debug ? "enabled" : "disabled"));
1955 else if (key == KSYM_v)
1957 Error(ERR_INFO, "currently using game engine version %d",
1958 game.engine_version);
1968 void HandleKey(Key key, int key_status)
1970 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1971 static boolean ignore_repeated_key = FALSE;
1972 static struct SetupKeyboardInfo ski;
1973 static struct SetupShortcutInfo ssi;
1982 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1983 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1984 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1985 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1986 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1987 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1992 if (HandleKeysDebug(key, key_status))
1993 return; // do not handle already processed keys again
1995 // map special keys (media keys / remote control buttons) to default keys
1996 if (key == KSYM_PlayPause)
1998 else if (key == KSYM_Select)
2001 HandleSpecialGameControllerKeys(key, key_status);
2003 if (game_status == GAME_MODE_PLAYING)
2005 // only needed for single-step tape recording mode
2006 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2009 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2011 byte key_action = 0;
2012 byte key_snap_action = 0;
2014 if (setup.input[pnr].use_joystick)
2017 ski = setup.input[pnr].key;
2019 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2020 if (key == *key_info[i].key_custom)
2021 key_action |= key_info[i].action;
2023 // use combined snap+direction keys for the first player only
2026 ssi = setup.shortcut;
2028 // also remember normal snap key when handling snap+direction keys
2029 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2031 for (i = 0; i < NUM_DIRECTIONS; i++)
2033 if (key == *key_info[i].key_snap)
2035 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2036 key_snap_action |= key_info[i].action;
2041 if (key_status == KEY_PRESSED)
2043 stored_player[pnr].action |= key_action;
2044 stored_player[pnr].snap_action |= key_snap_action;
2048 stored_player[pnr].action &= ~key_action;
2049 stored_player[pnr].snap_action &= ~key_snap_action;
2052 // restore snap action if one of several pressed snap keys was released
2053 if (stored_player[pnr].snap_action)
2054 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2056 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2058 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2060 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2062 // if snap key already pressed, keep pause mode when releasing
2063 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2064 has_snapped[pnr] = TRUE;
2066 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2068 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2070 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2071 getRedDiskReleaseFlag_SP() == 0)
2073 // add a single inactive frame before dropping starts
2074 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2075 stored_player[pnr].force_dropping = TRUE;
2078 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2080 // if snap key was pressed without direction, leave pause mode
2081 if (!has_snapped[pnr])
2082 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2084 has_snapped[pnr] = FALSE;
2087 else if (tape.recording && tape.pausing && !tape.use_mouse)
2089 // prevent key release events from un-pausing a paused game
2090 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2091 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2094 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2095 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2101 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2102 if (key == key_info[i].key_default)
2103 joy |= key_info[i].action;
2108 if (key_status == KEY_PRESSED)
2109 key_joystick_mapping |= joy;
2111 key_joystick_mapping &= ~joy;
2116 if (game_status != GAME_MODE_PLAYING)
2117 key_joystick_mapping = 0;
2119 if (key_status == KEY_RELEASED)
2121 // reset flag to ignore repeated "key pressed" events after key release
2122 ignore_repeated_key = FALSE;
2127 if ((key == KSYM_F11 ||
2128 ((key == KSYM_Return ||
2129 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2130 video.fullscreen_available &&
2131 !ignore_repeated_key)
2133 setup.fullscreen = !setup.fullscreen;
2135 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2137 if (game_status == GAME_MODE_SETUP)
2138 RedrawSetupScreenAfterFullscreenToggle();
2140 UpdateMousePosition();
2142 // set flag to ignore repeated "key pressed" events
2143 ignore_repeated_key = TRUE;
2148 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2149 key == KSYM_minus || key == KSYM_KP_Subtract ||
2150 key == KSYM_plus || key == KSYM_KP_Add ||
2151 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2152 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2153 video.window_scaling_available &&
2154 !video.fullscreen_enabled)
2156 if (key == KSYM_0 || key == KSYM_KP_0)
2157 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2158 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2159 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2161 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2163 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2164 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2165 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2166 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2168 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2170 if (game_status == GAME_MODE_SETUP)
2171 RedrawSetupScreenAfterFullscreenToggle();
2173 UpdateMousePosition();
2178 // some key events are handled like clicks for global animations
2179 boolean click = (key == KSYM_space ||
2180 key == KSYM_Return ||
2181 key == KSYM_Escape);
2183 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2185 // do not handle this key event anymore
2186 if (key != KSYM_Escape) // always allow ESC key to be handled
2190 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2191 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2198 if (game_status == GAME_MODE_MAIN &&
2199 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2201 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2206 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2208 if (key == setup.shortcut.save_game)
2210 else if (key == setup.shortcut.load_game)
2212 else if (key == setup.shortcut.toggle_pause)
2213 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2215 HandleTapeButtonKeys(key);
2216 HandleSoundButtonKeys(key);
2219 if (game_status == GAME_MODE_PLAYING && !network_playing)
2221 int centered_player_nr_next = -999;
2223 if (key == setup.shortcut.focus_player_all)
2224 centered_player_nr_next = -1;
2226 for (i = 0; i < MAX_PLAYERS; i++)
2227 if (key == setup.shortcut.focus_player[i])
2228 centered_player_nr_next = i;
2230 if (centered_player_nr_next != -999)
2232 game.centered_player_nr_next = centered_player_nr_next;
2233 game.set_centered_player = TRUE;
2237 tape.centered_player_nr_next = game.centered_player_nr_next;
2238 tape.set_centered_player = TRUE;
2243 HandleKeysSpecial(key);
2245 if (HandleGadgetsKeyInput(key))
2246 return; // do not handle already processed keys again
2248 switch (game_status)
2250 case GAME_MODE_PSEUDO_TYPENAME:
2251 HandleTypeName(0, key);
2254 case GAME_MODE_TITLE:
2255 case GAME_MODE_MAIN:
2256 case GAME_MODE_LEVELS:
2257 case GAME_MODE_LEVELNR:
2258 case GAME_MODE_SETUP:
2259 case GAME_MODE_INFO:
2260 case GAME_MODE_SCORES:
2262 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2269 if (game_status == GAME_MODE_TITLE)
2270 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2271 else if (game_status == GAME_MODE_MAIN)
2272 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2273 else if (game_status == GAME_MODE_LEVELS)
2274 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2275 else if (game_status == GAME_MODE_LEVELNR)
2276 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2277 else if (game_status == GAME_MODE_SETUP)
2278 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2279 else if (game_status == GAME_MODE_INFO)
2280 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2281 else if (game_status == GAME_MODE_SCORES)
2282 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2286 if (game_status != GAME_MODE_MAIN)
2287 FadeSkipNextFadeIn();
2289 if (game_status == GAME_MODE_TITLE)
2290 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2291 else if (game_status == GAME_MODE_LEVELS)
2292 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2293 else if (game_status == GAME_MODE_LEVELNR)
2294 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2295 else if (game_status == GAME_MODE_SETUP)
2296 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2297 else if (game_status == GAME_MODE_INFO)
2298 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2299 else if (game_status == GAME_MODE_SCORES)
2300 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2304 if (game_status == GAME_MODE_LEVELS)
2305 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2306 else if (game_status == GAME_MODE_LEVELNR)
2307 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2308 else if (game_status == GAME_MODE_SETUP)
2309 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2310 else if (game_status == GAME_MODE_INFO)
2311 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2312 else if (game_status == GAME_MODE_SCORES)
2313 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2316 case KSYM_Page_Down:
2317 if (game_status == GAME_MODE_LEVELS)
2318 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2319 else if (game_status == GAME_MODE_LEVELNR)
2320 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2321 else if (game_status == GAME_MODE_SETUP)
2322 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2323 else if (game_status == GAME_MODE_INFO)
2324 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2325 else if (game_status == GAME_MODE_SCORES)
2326 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2334 case GAME_MODE_EDITOR:
2335 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2336 HandleLevelEditorKeyInput(key);
2339 case GAME_MODE_PLAYING:
2344 RequestQuitGame(setup.ask_on_escape);
2354 if (key == KSYM_Escape)
2356 SetGameStatus(GAME_MODE_MAIN);
2365 void HandleNoEvent(void)
2367 HandleMouseCursor();
2369 switch (game_status)
2371 case GAME_MODE_PLAYING:
2372 HandleButtonOrFinger(-1, -1, -1);
2377 void HandleEventActions(void)
2379 // if (button_status && game_status != GAME_MODE_PLAYING)
2380 if (button_status && (game_status != GAME_MODE_PLAYING ||
2382 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2384 HandleButton(0, 0, button_status, -button_status);
2391 if (network.enabled)
2394 switch (game_status)
2396 case GAME_MODE_MAIN:
2397 DrawPreviewLevelAnimation();
2400 case GAME_MODE_EDITOR:
2401 HandleLevelEditorIdle();
2409 static void HandleTileCursor(int dx, int dy, int button)
2412 ClearPlayerMouseAction();
2419 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2420 (dx < 0 ? MB_LEFTBUTTON :
2421 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2423 else if (!tile_cursor.moving)
2425 int old_xpos = tile_cursor.xpos;
2426 int old_ypos = tile_cursor.ypos;
2427 int new_xpos = old_xpos;
2428 int new_ypos = old_ypos;
2430 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2431 new_xpos = old_xpos + dx;
2433 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2434 new_ypos = old_ypos + dy;
2436 SetTileCursorTargetXY(new_xpos, new_ypos);
2440 static int HandleJoystickForAllPlayers(void)
2444 boolean no_joysticks_configured = TRUE;
2445 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2446 static byte joy_action_last[MAX_PLAYERS];
2448 for (i = 0; i < MAX_PLAYERS; i++)
2449 if (setup.input[i].use_joystick)
2450 no_joysticks_configured = FALSE;
2452 // if no joysticks configured, map connected joysticks to players
2453 if (no_joysticks_configured)
2454 use_as_joystick_nr = TRUE;
2456 for (i = 0; i < MAX_PLAYERS; i++)
2458 byte joy_action = 0;
2460 joy_action = JoystickExt(i, use_as_joystick_nr);
2461 result |= joy_action;
2463 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2464 joy_action != joy_action_last[i])
2465 stored_player[i].action = joy_action;
2467 joy_action_last[i] = joy_action;
2473 void HandleJoystick(void)
2475 static unsigned int joytest_delay = 0;
2476 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2477 static int joytest_last = 0;
2478 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2479 int delay_value = GADGET_FRAME_DELAY;
2480 int joystick = HandleJoystickForAllPlayers();
2481 int keyboard = key_joystick_mapping;
2482 int joy = (joystick | keyboard);
2483 int joytest = joystick;
2484 int left = joy & JOY_LEFT;
2485 int right = joy & JOY_RIGHT;
2486 int up = joy & JOY_UP;
2487 int down = joy & JOY_DOWN;
2488 int button = joy & JOY_BUTTON;
2489 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2490 int dx = (left ? -1 : right ? 1 : 0);
2491 int dy = (up ? -1 : down ? 1 : 0);
2492 boolean use_delay_value_first = (joytest != joytest_last);
2494 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2496 // do not handle this button event anymore
2500 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2501 anyTextGadgetActive()))
2503 // leave name input in main menu or text input gadget
2504 HandleKey(KSYM_Escape, KEY_PRESSED);
2505 HandleKey(KSYM_Escape, KEY_RELEASED);
2510 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2512 if (game_status == GAME_MODE_PLAYING)
2514 // when playing MM style levels, also use delay for keyboard events
2515 joytest |= keyboard;
2517 // only use first delay value for new events, but not for changed events
2518 use_delay_value_first = (!joytest != !joytest_last);
2520 // only use delay after the initial keyboard event
2524 // for any joystick or keyboard event, enable playfield tile cursor
2525 if (dx || dy || button)
2526 SetTileCursorEnabled(TRUE);
2529 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2531 // delay joystick/keyboard actions if axes/keys continually pressed
2532 newbutton = dx = dy = 0;
2536 // first start with longer delay, then continue with shorter delay
2537 joytest_delay_value =
2538 (use_delay_value_first ? delay_value_first : delay_value);
2541 joytest_last = joytest;
2543 switch (game_status)
2545 case GAME_MODE_TITLE:
2546 case GAME_MODE_MAIN:
2547 case GAME_MODE_LEVELS:
2548 case GAME_MODE_LEVELNR:
2549 case GAME_MODE_SETUP:
2550 case GAME_MODE_INFO:
2551 case GAME_MODE_SCORES:
2553 if (anyTextGadgetActive())
2556 if (game_status == GAME_MODE_TITLE)
2557 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2558 else if (game_status == GAME_MODE_MAIN)
2559 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2560 else if (game_status == GAME_MODE_LEVELS)
2561 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2562 else if (game_status == GAME_MODE_LEVELNR)
2563 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2564 else if (game_status == GAME_MODE_SETUP)
2565 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2566 else if (game_status == GAME_MODE_INFO)
2567 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2568 else if (game_status == GAME_MODE_SCORES)
2569 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2574 case GAME_MODE_PLAYING:
2576 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2577 if (tape.playing || keyboard)
2578 newbutton = ((joy & JOY_BUTTON) != 0);
2581 if (newbutton && game.all_players_gone)
2588 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2590 if (joystick & JOY_ACTION)
2591 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2593 else if (tape.recording && tape.pausing && !tape.use_mouse)
2595 if (joystick & JOY_ACTION)
2596 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2599 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2600 HandleTileCursor(dx, dy, button);
2609 void HandleSpecialGameControllerButtons(Event *event)
2614 switch (event->type)
2616 case SDL_CONTROLLERBUTTONDOWN:
2617 key_status = KEY_PRESSED;
2620 case SDL_CONTROLLERBUTTONUP:
2621 key_status = KEY_RELEASED;
2628 switch (event->cbutton.button)
2630 case SDL_CONTROLLER_BUTTON_START:
2634 case SDL_CONTROLLER_BUTTON_BACK:
2642 HandleKey(key, key_status);
2645 void HandleSpecialGameControllerKeys(Key key, int key_status)
2647 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2648 int button = SDL_CONTROLLER_BUTTON_INVALID;
2650 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2651 if (key == KSYM_Rewind)
2652 button = SDL_CONTROLLER_BUTTON_A;
2653 else if (key == KSYM_FastForward || key == KSYM_Menu)
2654 button = SDL_CONTROLLER_BUTTON_B;
2656 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2660 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2661 SDL_CONTROLLERBUTTONUP);
2663 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2664 event.cbutton.button = button;
2665 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2668 HandleJoystickEvent(&event);
2673 boolean DoKeysymAction(int keysym)
2677 Key key = (Key)(-keysym);
2679 HandleKey(key, KEY_PRESSED);
2680 HandleKey(key, KEY_RELEASED);