1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://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 DelayCounter special_cursor_delay = { 1000 };
40 static boolean special_cursor_enabled = FALSE;
42 static boolean stop_processing_events = FALSE;
43 static boolean is_global_anim_event = FALSE;
46 // forward declarations for internal use
47 static void ClearTouchInfo(void);
48 static void HandleNoEvent(void);
49 static void HandleEventActions(void);
52 void SetPlayfieldMouseCursorEnabled(boolean enabled)
54 special_cursor_enabled = enabled;
57 // event filter to set mouse x/y position (for pointer class global animations)
58 // (this is especially required to ensure smooth global animation mouse pointer
59 // movement when the screen is updated without handling events; this can happen
60 // when drawing door/envelope request animations, for example)
62 int FilterMouseMotionEvents(void *userdata, Event *event)
64 if (event->type == EVENT_MOTIONNOTIFY)
66 int mouse_x = ((MotionEvent *)event)->x;
67 int mouse_y = ((MotionEvent *)event)->y;
69 UpdateRawMousePosition(mouse_x, 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 if (event->type == EVENT_BUTTONPRESS ||
105 event->type == EVENT_BUTTONRELEASE ||
106 event->type == EVENT_MOTIONNOTIFY)
108 // do not reset mouse cursor before all pending events have been processed
109 if (gfx.cursor_mode == cursor_mode_last &&
110 ((game_status == GAME_MODE_TITLE &&
111 gfx.cursor_mode == CURSOR_NONE) ||
112 (game_status == GAME_MODE_PLAYING &&
113 gfx.cursor_mode == CURSOR_PLAYFIELD)))
115 SetMouseCursor(CURSOR_DEFAULT);
117 ResetDelayCounter(&special_cursor_delay);
119 cursor_mode_last = CURSOR_DEFAULT;
123 // non-motion events are directly passed to event handler functions
124 if (event->type != EVENT_MOTIONNOTIFY)
127 motion = (MotionEvent *)event;
128 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
129 motion->y >= SY && motion->y < SY + SYSIZE);
131 // set correct mouse x/y position (for pointer class global animations)
132 // (this is required in rare cases where the mouse x/y position calculated
133 // from raw values (to apply logical screen size scaling corrections) does
134 // not match the final mouse event x/y position -- this may happen because
135 // the SDL renderer's viewport position is internally represented as float,
136 // but only accessible as integer, which may lead to rounding errors)
137 gfx.mouse_x = motion->x;
138 gfx.mouse_y = motion->y;
140 // skip mouse motion events without pressed button outside level editor
141 if (button_status == MB_RELEASED &&
142 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
148 // to prevent delay problems, skip mouse motion events if the very next
149 // event is also a mouse motion event (and therefore effectively only
150 // handling the last of a row of mouse motion events in the event queue)
152 static boolean SkipPressedMouseMotionEvent(const Event *event)
154 // nothing to do if the current event is not a mouse motion event
155 if (event->type != EVENT_MOTIONNOTIFY)
158 // only skip motion events with pressed button outside the game
159 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
166 PeekEvent(&next_event);
168 // if next event is also a mouse motion event, skip the current one
169 if (next_event.type == EVENT_MOTIONNOTIFY)
176 static boolean WaitValidEvent(Event *event)
180 if (!FilterEvents(event))
183 if (SkipPressedMouseMotionEvent(event))
189 /* this is especially needed for event modifications for the Android target:
190 if mouse coordinates should be modified in the event filter function,
191 using a properly installed SDL event filter does not work, because in
192 the event filter, mouse coordinates in the event structure are still
193 physical pixel positions, not logical (scaled) screen positions, so this
194 has to be handled at a later stage in the event processing functions
195 (when device pixel positions are already converted to screen positions) */
197 boolean NextValidEvent(Event *event)
199 while (PendingEvent())
200 if (WaitValidEvent(event))
206 void StopProcessingEvents(void)
208 stop_processing_events = TRUE;
211 static void HandleEvents(void)
214 DelayCounter event_frame_delay = { GAME_FRAME_DELAY };
216 ResetDelayCounter(&event_frame_delay);
218 stop_processing_events = FALSE;
220 while (NextValidEvent(&event))
222 int game_status_last = game_status;
226 case EVENT_BUTTONPRESS:
227 case EVENT_BUTTONRELEASE:
228 HandleButtonEvent((ButtonEvent *) &event);
231 case EVENT_MOTIONNOTIFY:
232 HandleMotionEvent((MotionEvent *) &event);
235 case EVENT_WHEELMOTION:
236 HandleWheelEvent((WheelEvent *) &event);
239 case SDL_WINDOWEVENT:
240 HandleWindowEvent((WindowEvent *) &event);
243 case EVENT_FINGERPRESS:
244 case EVENT_FINGERRELEASE:
245 case EVENT_FINGERMOTION:
246 HandleFingerEvent((FingerEvent *) &event);
249 case EVENT_TEXTINPUT:
250 HandleTextEvent((TextEvent *) &event);
253 case SDL_APP_WILLENTERBACKGROUND:
254 case SDL_APP_DIDENTERBACKGROUND:
255 case SDL_APP_WILLENTERFOREGROUND:
256 case SDL_APP_DIDENTERFOREGROUND:
257 HandlePauseResumeEvent((PauseResumeEvent *) &event);
261 case EVENT_KEYRELEASE:
262 HandleKeyEvent((KeyEvent *) &event);
266 HandleUserEvent((UserEvent *) &event);
270 HandleOtherEvents(&event);
274 // always handle events within delay period if game status has changed
275 if (game_status != game_status_last)
276 ResetDelayCounter(&event_frame_delay);
278 // do not handle events for longer than standard frame delay period
279 if (DelayReached(&event_frame_delay))
282 // do not handle any further events if triggered by a special flag
283 if (stop_processing_events)
288 void HandleOtherEvents(Event *event)
292 case SDL_CONTROLLERBUTTONDOWN:
293 case SDL_CONTROLLERBUTTONUP:
294 // for any game controller button event, disable overlay buttons
295 SetOverlayEnabled(FALSE);
297 HandleSpecialGameControllerButtons(event);
300 case SDL_CONTROLLERDEVICEADDED:
301 case SDL_CONTROLLERDEVICEREMOVED:
302 case SDL_CONTROLLERAXISMOTION:
303 case SDL_JOYAXISMOTION:
304 case SDL_JOYBUTTONDOWN:
305 case SDL_JOYBUTTONUP:
306 HandleJoystickEvent(event);
310 case SDL_DROPCOMPLETE:
313 HandleDropEvent(event);
325 static void HandleMouseCursor(void)
327 if (game_status == GAME_MODE_TITLE)
329 // when showing title screens, hide mouse pointer (if not moved)
331 if (gfx.cursor_mode != CURSOR_NONE &&
332 DelayReached(&special_cursor_delay))
334 SetMouseCursor(CURSOR_NONE);
337 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
340 // when playing, display a special mouse pointer inside the playfield
342 // display normal pointer if mouse pressed
343 if (button_status != MB_RELEASED)
344 ResetDelayCounter(&special_cursor_delay);
346 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
347 cursor_inside_playfield &&
348 special_cursor_enabled &&
349 DelayReached(&special_cursor_delay))
351 SetMouseCursor(CURSOR_PLAYFIELD);
354 else if (gfx.cursor_mode != CURSOR_DEFAULT)
356 SetMouseCursor(CURSOR_DEFAULT);
359 // this is set after all pending events have been processed
360 cursor_mode_last = gfx.cursor_mode;
372 // execute event related actions after pending events have been processed
373 HandleEventActions();
375 // don't use all CPU time when idle; the main loop while playing
376 // has its own synchronization and is CPU friendly, too
378 if (game_status == GAME_MODE_PLAYING)
381 // always copy backbuffer to visible screen for every video frame
384 // reset video frame delay to default (may change again while playing)
385 SetVideoFrameDelay(MenuFrameDelay);
387 if (game_status == GAME_MODE_QUIT)
392 void ClearAutoRepeatKeyEvents(void)
394 while (PendingEvent())
398 PeekEvent(&next_event);
400 // if event is repeated key press event, remove it from event queue
401 if (next_event.type == EVENT_KEYPRESS &&
402 next_event.key.repeat)
403 WaitEvent(&next_event);
409 void ClearEventQueue(void)
413 while (NextValidEvent(&event))
417 case EVENT_BUTTONRELEASE:
418 button_status = MB_RELEASED;
421 case EVENT_FINGERRELEASE:
422 case EVENT_KEYRELEASE:
426 case SDL_CONTROLLERBUTTONUP:
427 HandleJoystickEvent(&event);
432 HandleOtherEvents(&event);
438 static void ClearPlayerMouseAction(void)
440 local_player->mouse_action.lx = 0;
441 local_player->mouse_action.ly = 0;
442 local_player->mouse_action.button = 0;
445 void ClearPlayerAction(void)
449 // simulate key release events for still pressed keys
450 key_joystick_mapping = 0;
451 for (i = 0; i < MAX_PLAYERS; i++)
453 stored_player[i].action = 0;
454 stored_player[i].snap_action = 0;
457 // simulate finger release events for still pressed virtual buttons
458 overlay.grid_button_action = JOY_NO_ACTION;
461 ClearJoystickState();
462 ClearPlayerMouseAction();
465 static void SetPlayerMouseAction(int mx, int my, int button)
467 int lx = getLevelFromScreenX(mx);
468 int ly = getLevelFromScreenY(my);
469 int new_button = (!local_player->mouse_action.button && button);
471 if (local_player->mouse_action.button_hint)
472 button = local_player->mouse_action.button_hint;
474 ClearPlayerMouseAction();
476 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
479 local_player->mouse_action.lx = lx;
480 local_player->mouse_action.ly = ly;
481 local_player->mouse_action.button = button;
483 if (tape.recording && tape.pausing && tape.use_mouse_actions)
485 // un-pause a paused game only if mouse button was newly pressed down
487 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
490 SetTileCursorXY(lx, ly);
493 static Key GetKeyFromGridButton(int grid_button)
495 return (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
496 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
497 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
498 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
499 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
500 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
504 #if defined(PLATFORM_ANDROID)
505 static boolean CheckVirtualButtonPressed(int mx, int my, int button)
507 float touch_x = (float)(mx + video.screen_xoffset) / video.screen_width;
508 float touch_y = (float)(my + video.screen_yoffset) / video.screen_height;
509 int x = touch_x * overlay.grid_xsize;
510 int y = touch_y * overlay.grid_ysize;
511 int grid_button = overlay.grid_button[x][y];
512 Key key = GetKeyFromGridButton(grid_button);
513 int key_status = (button == MB_RELEASED ? KEY_RELEASED : KEY_PRESSED);
515 return (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
519 void HandleButtonEvent(ButtonEvent *event)
521 #if DEBUG_EVENTS_BUTTON
522 Debug("event:button", "button %d %s, x/y %d/%d\n",
524 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
528 // for any mouse button event, disable playfield tile cursor
529 SetTileCursorEnabled(FALSE);
531 // for any mouse button event, disable playfield mouse cursor
532 if (cursor_inside_playfield)
533 SetPlayfieldMouseCursorEnabled(FALSE);
535 #if defined(HAS_SCREEN_KEYBOARD)
536 if (video.shifted_up)
537 event->y += video.shifted_up_pos;
540 motion_status = FALSE;
542 if (event->type == EVENT_BUTTONPRESS)
543 button_status = event->button;
545 button_status = MB_RELEASED;
547 HandleButton(event->x, event->y, button_status, event->button);
550 void HandleMotionEvent(MotionEvent *event)
552 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
555 motion_status = TRUE;
557 #if DEBUG_EVENTS_MOTION
558 Debug("event:motion", "button %d moved, x/y %d/%d\n",
559 button_status, event->x, event->y);
562 HandleButton(event->x, event->y, button_status, button_status);
565 void HandleWheelEvent(WheelEvent *event)
569 #if DEBUG_EVENTS_WHEEL
571 Debug("event:wheel", "mouse == %d, x/y == %d/%d\n",
572 event->which, event->x, event->y);
574 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
575 Debug("event:wheel", "mouse == %d, x/y == %d/%d, direction == %s\n",
576 event->which, event->x, event->y,
577 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
578 "SDL_MOUSEWHEEL_FLIPPED"));
582 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
583 event->x > 0 ? MB_WHEEL_RIGHT :
584 event->y < 0 ? MB_WHEEL_DOWN :
585 event->y > 0 ? MB_WHEEL_UP : 0);
587 #if defined(PLATFORM_WINDOWS) || defined(PLATFORM_MAC)
588 // accelerated mouse wheel available on Mac and Windows
589 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
591 // no accelerated mouse wheel available on Unix/Linux
592 wheel_steps = DEFAULT_WHEEL_STEPS;
595 motion_status = FALSE;
597 button_status = button_nr;
598 HandleButton(0, 0, button_status, -button_nr);
600 button_status = MB_RELEASED;
601 HandleButton(0, 0, button_status, -button_nr);
604 void HandleWindowEvent(WindowEvent *event)
606 #if DEBUG_EVENTS_WINDOW
607 int subtype = event->event;
610 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
611 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
612 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
613 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
614 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
615 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
616 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
617 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
618 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
619 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
620 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
621 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
622 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
623 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
624 subtype == SDL_WINDOWEVENT_TAKE_FOCUS ? "SDL_WINDOWEVENT_TAKE_FOCUS" :
625 subtype == SDL_WINDOWEVENT_HIT_TEST ? "SDL_WINDOWEVENT_HIT_TEST" :
628 Debug("event:window", "name: '%s', data1: %ld, data2: %ld",
629 event_name, event->data1, event->data2);
633 // (not needed, as the screen gets redrawn every 20 ms anyway)
634 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
635 event->event == SDL_WINDOWEVENT_RESIZED ||
636 event->event == SDL_WINDOWEVENT_EXPOSED)
640 if (event->event == SDL_WINDOWEVENT_RESIZED)
642 if (!video.fullscreen_enabled)
644 int new_window_width = event->data1;
645 int new_window_height = event->data2;
647 // if window size has changed after resizing, calculate new scaling factor
648 if (new_window_width != video.window_width ||
649 new_window_height != video.window_height)
651 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
652 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
654 // (extreme window scaling allowed, but cannot be saved permanently)
655 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
656 setup.window_scaling_percent =
657 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
658 MAX_WINDOW_SCALING_PERCENT);
660 video.window_width = new_window_width;
661 video.window_height = new_window_height;
663 if (game_status == GAME_MODE_SETUP)
664 RedrawSetupScreenAfterFullscreenToggle();
666 UpdateMousePosition();
671 #if defined(PLATFORM_ANDROID)
674 int new_display_width = event->data1;
675 int new_display_height = event->data2;
677 // if fullscreen display size has changed, device has been rotated
678 if (new_display_width != video.display_width ||
679 new_display_height != video.display_height)
681 int nr = GRID_ACTIVE_NR(); // previous screen orientation
683 video.display_width = new_display_width;
684 video.display_height = new_display_height;
686 SDLSetScreenProperties();
687 SetGadgetsPosition_OverlayTouchButtons();
689 // check if screen orientation has changed (should always be true here)
690 if (nr != GRID_ACTIVE_NR())
692 if (game_status == GAME_MODE_SETUP)
693 RedrawSetupScreenAfterScreenRotation(nr);
695 SetOverlayGridSizeAndButtons();
703 #define NUM_TOUCH_FINGERS 3
708 SDL_FingerID finger_id;
712 } touch_info[NUM_TOUCH_FINGERS];
714 static void SetTouchInfo(int pos, SDL_FingerID finger_id, int counter,
715 Key key, byte action)
717 touch_info[pos].touched = (action != JOY_NO_ACTION);
718 touch_info[pos].finger_id = finger_id;
719 touch_info[pos].counter = counter;
720 touch_info[pos].key = key;
721 touch_info[pos].action = action;
724 static void ClearTouchInfo(void)
728 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
729 SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
732 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
734 int x = event->x * overlay.grid_xsize;
735 int y = event->y * overlay.grid_ysize;
736 int grid_button = overlay.grid_button[x][y];
737 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
738 Key key = GetKeyFromGridButton(grid_button);
739 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
741 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
745 // for any touch input event, enable overlay buttons (if activated)
746 SetOverlayEnabled(TRUE);
748 Debug("event:finger", "key '%s' was '%s' [fingerId: %lld]",
749 getKeyNameFromKey(key), key_status_name, event->fingerId);
751 if (key_status == KEY_PRESSED)
752 overlay.grid_button_action |= grid_button_action;
754 overlay.grid_button_action &= ~grid_button_action;
756 // check if we already know this touch event's finger id
757 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
759 if (touch_info[i].touched &&
760 touch_info[i].finger_id == event->fingerId)
762 // Debug("event:finger", "MARK 1: %d", i);
768 if (i >= NUM_TOUCH_FINGERS)
770 if (key_status == KEY_PRESSED)
772 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
774 // unknown finger id -- get new, empty slot, if available
775 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
777 if (touch_info[i].counter < oldest_counter)
780 oldest_counter = touch_info[i].counter;
782 // Debug("event:finger", "MARK 2: %d", i);
785 if (!touch_info[i].touched)
787 // Debug("event:finger", "MARK 3: %d", i);
793 if (i >= NUM_TOUCH_FINGERS)
795 // all slots allocated -- use oldest slot
798 // Debug("event:finger", "MARK 4: %d", i);
803 // release of previously unknown key (should not happen)
805 if (key != KSYM_UNDEFINED)
807 HandleKey(key, KEY_RELEASED);
809 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [1]",
810 getKeyNameFromKey(key), "KEY_RELEASED", i);
815 if (i < NUM_TOUCH_FINGERS)
817 if (key_status == KEY_PRESSED)
819 if (touch_info[i].key != key)
821 if (touch_info[i].key != KSYM_UNDEFINED)
823 HandleKey(touch_info[i].key, KEY_RELEASED);
825 // undraw previous grid button when moving finger away
826 overlay.grid_button_action &= ~touch_info[i].action;
828 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [2]",
829 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
832 if (key != KSYM_UNDEFINED)
834 HandleKey(key, KEY_PRESSED);
836 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [3]",
837 getKeyNameFromKey(key), "KEY_PRESSED", i);
841 SetTouchInfo(i, event->fingerId, Counter(), key, grid_button_action);
845 if (touch_info[i].key != KSYM_UNDEFINED)
847 HandleKey(touch_info[i].key, KEY_RELEASED);
849 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [4]",
850 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
853 SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
858 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
860 static Key motion_key_x = KSYM_UNDEFINED;
861 static Key motion_key_y = KSYM_UNDEFINED;
862 static Key button_key = KSYM_UNDEFINED;
863 static float motion_x1, motion_y1;
864 static float button_x1, button_y1;
865 static SDL_FingerID motion_id = -1;
866 static SDL_FingerID button_id = -1;
867 int move_trigger_distance_percent = setup.touch.move_distance;
868 int drop_trigger_distance_percent = setup.touch.drop_distance;
869 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
870 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
871 float event_x = event->x;
872 float event_y = event->y;
874 if (event->type == EVENT_FINGERPRESS)
876 if (event_x > 1.0 / 3.0)
880 motion_id = event->fingerId;
885 motion_key_x = KSYM_UNDEFINED;
886 motion_key_y = KSYM_UNDEFINED;
888 Debug("event:finger", "---------- MOVE STARTED (WAIT) ----------");
894 button_id = event->fingerId;
899 button_key = setup.input[0].key.snap;
901 HandleKey(button_key, KEY_PRESSED);
903 Debug("event:finger", "---------- SNAP STARTED ----------");
906 else if (event->type == EVENT_FINGERRELEASE)
908 if (event->fingerId == motion_id)
912 if (motion_key_x != KSYM_UNDEFINED)
913 HandleKey(motion_key_x, KEY_RELEASED);
914 if (motion_key_y != KSYM_UNDEFINED)
915 HandleKey(motion_key_y, KEY_RELEASED);
917 motion_key_x = KSYM_UNDEFINED;
918 motion_key_y = KSYM_UNDEFINED;
920 Debug("event:finger", "---------- MOVE STOPPED ----------");
922 else if (event->fingerId == button_id)
926 if (button_key != KSYM_UNDEFINED)
927 HandleKey(button_key, KEY_RELEASED);
929 button_key = KSYM_UNDEFINED;
931 Debug("event:finger", "---------- SNAP STOPPED ----------");
934 else if (event->type == EVENT_FINGERMOTION)
936 if (event->fingerId == motion_id)
938 float distance_x = ABS(event_x - motion_x1);
939 float distance_y = ABS(event_y - motion_y1);
940 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
941 event_x > motion_x1 ? setup.input[0].key.right :
943 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
944 event_y > motion_y1 ? setup.input[0].key.down :
947 if (distance_x < move_trigger_distance / 2 ||
948 distance_x < distance_y)
949 new_motion_key_x = KSYM_UNDEFINED;
951 if (distance_y < move_trigger_distance / 2 ||
952 distance_y < distance_x)
953 new_motion_key_y = KSYM_UNDEFINED;
955 if (distance_x > move_trigger_distance ||
956 distance_y > move_trigger_distance)
958 if (new_motion_key_x != motion_key_x)
960 if (motion_key_x != KSYM_UNDEFINED)
961 HandleKey(motion_key_x, KEY_RELEASED);
962 if (new_motion_key_x != KSYM_UNDEFINED)
963 HandleKey(new_motion_key_x, KEY_PRESSED);
966 if (new_motion_key_y != motion_key_y)
968 if (motion_key_y != KSYM_UNDEFINED)
969 HandleKey(motion_key_y, KEY_RELEASED);
970 if (new_motion_key_y != KSYM_UNDEFINED)
971 HandleKey(new_motion_key_y, KEY_PRESSED);
977 motion_key_x = new_motion_key_x;
978 motion_key_y = new_motion_key_y;
980 Debug("event:finger", "---------- MOVE STARTED (MOVE) ----------");
983 else if (event->fingerId == button_id)
985 float distance_x = ABS(event_x - button_x1);
986 float distance_y = ABS(event_y - button_y1);
988 if (distance_x < drop_trigger_distance / 2 &&
989 distance_y > drop_trigger_distance)
991 if (button_key == setup.input[0].key.snap)
992 HandleKey(button_key, KEY_RELEASED);
997 button_key = setup.input[0].key.drop;
999 HandleKey(button_key, KEY_PRESSED);
1001 Debug("event:finger", "---------- DROP STARTED ----------");
1007 void HandleFingerEvent(FingerEvent *event)
1009 #if DEBUG_EVENTS_FINGER
1010 Debug("event:finger", "finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
1011 event->type == EVENT_FINGERPRESS ? "pressed" :
1012 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
1016 event->dx, event->dy,
1020 runtime.uses_touch_device = TRUE;
1022 if (game_status != GAME_MODE_PLAYING)
1025 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1027 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1028 local_player->mouse_action.button_hint =
1029 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1030 event->x < 0.5 ? MB_LEFTBUTTON :
1031 event->x > 0.5 ? MB_RIGHTBUTTON :
1037 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1038 HandleFingerEvent_VirtualButtons(event);
1039 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1040 HandleFingerEvent_WipeGestures(event);
1043 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1045 static int old_mx = 0, old_my = 0;
1046 static int last_button = MB_LEFTBUTTON;
1047 static boolean touched = FALSE;
1048 static boolean tapped = FALSE;
1050 // screen tile was tapped (but finger not touching the screen anymore)
1051 // (this point will also be reached without receiving a touch event)
1052 if (tapped && !touched)
1054 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1059 // stop here if this function was not triggered by a touch event
1063 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1065 // finger started touching the screen
1075 ClearPlayerMouseAction();
1077 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1080 else if (button == MB_RELEASED && touched)
1082 // finger stopped touching the screen
1087 SetPlayerMouseAction(old_mx, old_my, last_button);
1089 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1091 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1096 // finger moved while touching the screen
1098 int old_x = getLevelFromScreenX(old_mx);
1099 int old_y = getLevelFromScreenY(old_my);
1100 int new_x = getLevelFromScreenX(mx);
1101 int new_y = getLevelFromScreenY(my);
1103 if (new_x != old_x || new_y != old_y)
1108 // finger moved left or right from (horizontal) starting position
1110 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1112 SetPlayerMouseAction(old_mx, old_my, button_nr);
1114 last_button = button_nr;
1116 Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1120 // finger stays at or returned to (horizontal) starting position
1122 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1124 Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1129 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1131 static int old_mx = 0, old_my = 0;
1132 static int last_button = MB_LEFTBUTTON;
1133 static boolean touched = FALSE;
1134 static boolean tapped = FALSE;
1136 // screen tile was tapped (but finger not touching the screen anymore)
1137 // (this point will also be reached without receiving a touch event)
1138 if (tapped && !touched)
1140 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1145 // stop here if this function was not triggered by a touch event
1149 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1151 // finger started touching the screen
1161 ClearPlayerMouseAction();
1163 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1166 else if (button == MB_RELEASED && touched)
1168 // finger stopped touching the screen
1173 SetPlayerMouseAction(old_mx, old_my, last_button);
1175 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1177 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1182 // finger moved while touching the screen
1184 int old_x = getLevelFromScreenX(old_mx);
1185 int old_y = getLevelFromScreenY(old_my);
1186 int new_x = getLevelFromScreenX(mx);
1187 int new_y = getLevelFromScreenY(my);
1189 if (new_x != old_x || new_y != old_y)
1191 // finger moved away from starting position
1193 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1195 // quickly alternate between clicking and releasing for maximum speed
1196 if (FrameCounter % 2 == 0)
1197 button_nr = MB_RELEASED;
1199 SetPlayerMouseAction(old_mx, old_my, button_nr);
1202 last_button = button_nr;
1206 Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1210 // finger stays at or returned to starting position
1212 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1214 Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1219 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1221 static int old_mx = 0, old_my = 0;
1222 static Key motion_key_x = KSYM_UNDEFINED;
1223 static Key motion_key_y = KSYM_UNDEFINED;
1224 static boolean touched = FALSE;
1225 static boolean started_on_player = FALSE;
1226 static boolean player_is_dropping = FALSE;
1227 static int player_drop_count = 0;
1228 static int last_player_x = -1;
1229 static int last_player_y = -1;
1231 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1240 started_on_player = FALSE;
1241 player_is_dropping = FALSE;
1242 player_drop_count = 0;
1246 motion_key_x = KSYM_UNDEFINED;
1247 motion_key_y = KSYM_UNDEFINED;
1249 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1252 else if (button == MB_RELEASED && touched)
1259 if (motion_key_x != KSYM_UNDEFINED)
1260 HandleKey(motion_key_x, KEY_RELEASED);
1261 if (motion_key_y != KSYM_UNDEFINED)
1262 HandleKey(motion_key_y, KEY_RELEASED);
1264 if (started_on_player)
1266 if (player_is_dropping)
1268 Debug("event:finger", "---------- DROP STOPPED ----------");
1270 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1274 Debug("event:finger", "---------- SNAP STOPPED ----------");
1276 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1280 motion_key_x = KSYM_UNDEFINED;
1281 motion_key_y = KSYM_UNDEFINED;
1283 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1288 int src_x = local_player->jx;
1289 int src_y = local_player->jy;
1290 int dst_x = getLevelFromScreenX(old_mx);
1291 int dst_y = getLevelFromScreenY(old_my);
1292 int dx = dst_x - src_x;
1293 int dy = dst_y - src_y;
1294 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1295 dx > 0 ? setup.input[0].key.right :
1297 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1298 dy > 0 ? setup.input[0].key.down :
1301 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1302 (last_player_x != local_player->jx ||
1303 last_player_y != local_player->jy))
1305 // in case of asymmetric diagonal movement, use "preferred" direction
1307 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1309 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1310 game_em.ply[0]->last_move_dir = last_move_dir;
1312 local_player->last_move_dir = last_move_dir;
1314 // (required to prevent accidentally forcing direction for next movement)
1315 last_player_x = local_player->jx;
1316 last_player_y = local_player->jy;
1319 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1321 started_on_player = TRUE;
1322 player_drop_count = getPlayerInventorySize(0);
1323 player_is_dropping = (player_drop_count > 0);
1325 if (player_is_dropping)
1327 Debug("event:finger", "---------- DROP STARTED ----------");
1329 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1333 Debug("event:finger", "---------- SNAP STARTED ----------");
1335 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1338 else if (dx != 0 || dy != 0)
1340 if (player_is_dropping &&
1341 player_drop_count == getPlayerInventorySize(0))
1343 Debug("event:finger", "---------- DROP -> SNAP ----------");
1345 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1346 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1348 player_is_dropping = FALSE;
1352 if (new_motion_key_x != motion_key_x)
1354 Debug("event:finger", "---------- %s %s ----------",
1355 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1356 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1358 if (motion_key_x != KSYM_UNDEFINED)
1359 HandleKey(motion_key_x, KEY_RELEASED);
1360 if (new_motion_key_x != KSYM_UNDEFINED)
1361 HandleKey(new_motion_key_x, KEY_PRESSED);
1364 if (new_motion_key_y != motion_key_y)
1366 Debug("event:finger", "---------- %s %s ----------",
1367 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1368 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1370 if (motion_key_y != KSYM_UNDEFINED)
1371 HandleKey(motion_key_y, KEY_RELEASED);
1372 if (new_motion_key_y != KSYM_UNDEFINED)
1373 HandleKey(new_motion_key_y, KEY_PRESSED);
1376 motion_key_x = new_motion_key_x;
1377 motion_key_y = new_motion_key_y;
1381 static void HandleButtonOrFinger(int mx, int my, int button)
1383 boolean valid_mouse_event = (mx != -1 && my != -1 && button != -1);
1385 if (game_status != GAME_MODE_PLAYING)
1388 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1390 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1391 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1392 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1393 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1394 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1395 SetPlayerMouseAction(mx, my, button); // special case
1399 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1400 HandleButtonOrFinger_FollowFinger(mx, my, button);
1401 else if (game.use_mouse_actions && valid_mouse_event)
1402 SetPlayerMouseAction(mx, my, button);
1406 static boolean checkTextInputKey(Key key)
1408 // when playing, only handle raw key events and ignore text input
1409 if (game_status == GAME_MODE_PLAYING)
1412 // if Shift or right Alt key is pressed, handle key as text input
1413 if ((GetKeyModState() & KMOD_TextInput) != KMOD_None)
1416 // ignore raw keys as text input when not in text input mode
1417 if (KSYM_RAW(key) && !textinput_status)
1420 // else handle all printable keys as text input
1421 return KSYM_PRINTABLE(key);
1424 void HandleTextEvent(TextEvent *event)
1426 char *text = event->text;
1427 Key key = getKeyFromKeyName(text);
1429 #if DEBUG_EVENTS_TEXT
1430 Debug("event:text", "text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1433 text[0], (int)(text[0]),
1435 getKeyNameFromKey(key),
1439 if (checkTextInputKey(key))
1441 // process printable keys (with uppercase etc.) in text input mode
1442 HandleKey(key, KEY_PRESSED);
1443 HandleKey(key, KEY_RELEASED);
1447 void HandlePauseResumeEvent(PauseResumeEvent *event)
1449 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1453 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1459 void HandleKeyEvent(KeyEvent *event)
1461 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1462 Key key = GetEventKey(event);
1464 #if DEBUG_EVENTS_KEY
1465 Debug("event:key", "key was %s, keysym.scancode == %d, keysym.sym == %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1466 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1467 event->keysym.scancode,
1471 getKeyNameFromKey(key));
1474 #if defined(PLATFORM_ANDROID)
1475 if (key == KSYM_Back)
1477 // always map the "back" button to the "escape" key on Android devices
1480 else if (key == KSYM_Menu)
1482 // the "menu" button can be used to toggle displaying virtual buttons
1483 if (key_status == KEY_PRESSED)
1484 SetOverlayEnabled(!GetOverlayEnabled());
1486 else if (!textinput_status)
1488 // for any other "real" key event, disable virtual buttons
1489 SetOverlayEnabled(FALSE);
1491 // for any other "real" key event, disable overlay touch buttons
1492 runtime.uses_touch_device = FALSE;
1496 HandleKeyModState(key, key_status);
1498 // process all keys if not in text input mode or if non-printable keys
1499 if (!checkTextInputKey(key))
1500 HandleKey(key, key_status);
1503 static int HandleDropFileEvent(char *filename)
1505 Debug("event:dropfile", "filename == '%s'", filename);
1507 // check and extract dropped zip files into correct user data directory
1508 if (!strSuffixLower(filename, ".zip"))
1510 Warn("file '%s' not supported", filename);
1512 return TREE_TYPE_UNDEFINED;
1515 TreeInfo *tree_node = NULL;
1516 int tree_type = GetZipFileTreeType(filename);
1517 char *directory = TREE_USERDIR(tree_type);
1519 if (directory == NULL)
1521 Warn("zip file '%s' has invalid content!", filename);
1523 return TREE_TYPE_UNDEFINED;
1526 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1527 game_status == GAME_MODE_LEVELS &&
1528 leveldir_current->node_parent != NULL)
1530 // extract new level set next to currently selected level set
1531 tree_node = leveldir_current;
1533 // get parent directory of currently selected level set directory
1534 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1536 // use private level directory instead of top-level package level directory
1537 if (strPrefix(directory, options.level_directory) &&
1538 strEqual(leveldir_current->node_parent->fullpath, "."))
1539 directory = getUserLevelDir(NULL);
1542 // extract level or artwork set from zip file to target directory
1543 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1545 if (top_dir == NULL)
1547 // error message already issued by "ExtractZipFileIntoDirectory()"
1549 return TREE_TYPE_UNDEFINED;
1552 // add extracted level or artwork set to tree info structure
1553 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1555 // force restart after adding level collection
1556 if (getTreeInfoFromIdentifier(TREE_FIRST_NODE(tree_type), top_dir) == NULL)
1558 Request("Program must be restarted after adding a new level collection!",
1564 // update menu screen (and possibly change current level set)
1565 DrawScreenAfterAddingSet(top_dir, tree_type);
1570 static void HandleDropTextEvent(char *text)
1572 Debug("event:droptext", "text == '%s'", text);
1575 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1576 int num_artwork_sets_succeeded,
1577 int num_files_failed)
1579 // only show request dialog if no other request dialog already active
1580 if (game.request_active)
1583 // this case can happen with drag-and-drop with older SDL versions
1584 if (num_level_sets_succeeded == 0 &&
1585 num_artwork_sets_succeeded == 0 &&
1586 num_files_failed == 0)
1591 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1593 char message_part1[50];
1595 sprintf(message_part1, "New %s set%s added",
1596 (num_artwork_sets_succeeded == 0 ? "level" :
1597 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1598 (num_level_sets_succeeded +
1599 num_artwork_sets_succeeded > 1 ? "s" : ""));
1601 if (num_files_failed > 0)
1602 sprintf(message, "%s, but %d dropped file%s failed!",
1603 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1605 sprintf(message, "%s!", message_part1);
1607 else if (num_files_failed > 0)
1609 sprintf(message, "Failed to process dropped file%s!",
1610 num_files_failed > 1 ? "s" : "");
1613 Request(message, REQ_CONFIRM);
1616 void HandleDropEvent(Event *event)
1618 static boolean confirm_on_drop_complete = FALSE;
1619 static int num_level_sets_succeeded = 0;
1620 static int num_artwork_sets_succeeded = 0;
1621 static int num_files_failed = 0;
1623 switch (event->type)
1627 confirm_on_drop_complete = TRUE;
1628 num_level_sets_succeeded = 0;
1629 num_artwork_sets_succeeded = 0;
1630 num_files_failed = 0;
1637 int tree_type = HandleDropFileEvent(event->drop.file);
1639 if (tree_type == TREE_TYPE_LEVEL_DIR)
1640 num_level_sets_succeeded++;
1641 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1642 tree_type == TREE_TYPE_SOUNDS_DIR ||
1643 tree_type == TREE_TYPE_MUSIC_DIR)
1644 num_artwork_sets_succeeded++;
1648 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1649 if (!confirm_on_drop_complete)
1651 // process all remaining events, including further SDL_DROPFILE events
1654 HandleDropCompleteEvent(num_level_sets_succeeded,
1655 num_artwork_sets_succeeded,
1658 num_level_sets_succeeded = 0;
1659 num_artwork_sets_succeeded = 0;
1660 num_files_failed = 0;
1668 HandleDropTextEvent(event->drop.file);
1673 case SDL_DROPCOMPLETE:
1675 HandleDropCompleteEvent(num_level_sets_succeeded,
1676 num_artwork_sets_succeeded,
1683 if (event->drop.file != NULL)
1684 SDL_free(event->drop.file);
1687 void HandleUserEvent(UserEvent *event)
1689 switch (event->code)
1691 case USEREVENT_ANIM_DELAY_ACTION:
1692 case USEREVENT_ANIM_EVENT_ACTION:
1693 // execute action functions until matching action was found
1694 if (DoKeysymAction(event->value1) ||
1695 DoGadgetAction(event->value1) ||
1696 DoScreenAction(event->value1))
1705 void HandleButton(int mx, int my, int button, int button_nr)
1707 static int old_mx = 0, old_my = 0;
1708 boolean button_hold = FALSE;
1709 boolean handle_gadgets = TRUE;
1710 int game_status_last = game_status;
1716 button_nr = -button_nr;
1725 #if defined(PLATFORM_ANDROID)
1726 // when playing, only handle gadgets when using "follow finger" controls
1727 // or when using touch controls in combination with the MM game engine
1728 // or when using gadgets that do not overlap with virtual buttons
1729 // or when touch controls are disabled (e.g., with mouse-only levels)
1731 (game_status != GAME_MODE_PLAYING ||
1732 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1733 strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF) ||
1734 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1735 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1736 !CheckVirtualButtonPressed(mx, my, button)));
1738 // always recognize potentially releasing already pressed gadgets
1739 if (button == MB_RELEASED)
1740 handle_gadgets = TRUE;
1742 // always recognize pressing or releasing overlay touch buttons
1743 if (CheckPosition_OverlayTouchButtons(mx, my, button) && !motion_status)
1744 handle_gadgets = TRUE;
1747 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1749 // do not handle this button event anymore
1750 return; // force mouse event not to be handled at all
1753 if (handle_gadgets && HandleGadgets(mx, my, button))
1755 // do not handle this button event anymore with position on screen
1756 mx = my = -32; // force mouse event to be outside screen tiles
1758 // do not handle this button event anymore if game status has changed
1759 if (game_status != game_status_last)
1763 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1766 // do not use scroll wheel button events for anything other than gadgets
1767 if (IS_WHEEL_BUTTON(button_nr))
1770 switch (game_status)
1772 case GAME_MODE_TITLE:
1773 HandleTitleScreen(mx, my, 0, 0, button);
1776 case GAME_MODE_MAIN:
1777 HandleMainMenu(mx, my, 0, 0, button);
1780 case GAME_MODE_PSEUDO_TYPENAME:
1781 case GAME_MODE_PSEUDO_TYPENAMES:
1782 HandleTypeName(KSYM_Return);
1785 case GAME_MODE_NAMES:
1786 HandleChoosePlayerName(mx, my, 0, 0, button);
1789 case GAME_MODE_LEVELS:
1790 HandleChooseLevelSet(mx, my, 0, 0, button);
1793 case GAME_MODE_LEVELNR:
1794 HandleChooseLevelNr(mx, my, 0, 0, button);
1797 case GAME_MODE_SCORES:
1798 HandleHallOfFame(mx, my, 0, 0, button);
1801 case GAME_MODE_SCOREINFO:
1802 HandleScoreInfo(mx, my, 0, 0, button);
1805 case GAME_MODE_EDITOR:
1806 HandleLevelEditorIdle();
1809 case GAME_MODE_INFO:
1810 HandleInfoScreen(mx, my, 0, 0, button);
1813 case GAME_MODE_SETUP:
1814 HandleSetupScreen(mx, my, 0, 0, button);
1817 case GAME_MODE_PLAYING:
1818 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1819 HandleButtonOrFinger(mx, my, button);
1821 SetPlayerMouseAction(mx, my, button);
1824 if (button == MB_PRESSED && !motion_status && !button_hold &&
1825 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1826 DumpTileFromScreen(mx, my);
1836 #define MAX_CHEAT_INPUT_LEN 32
1838 static void HandleKeysSpecial(Key key)
1840 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1841 char letter = getCharFromKey(key);
1842 int cheat_input_len = strlen(cheat_input);
1848 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1850 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1851 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1853 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1856 cheat_input[cheat_input_len++] = letter;
1857 cheat_input[cheat_input_len] = '\0';
1859 #if DEBUG_EVENTS_KEY
1860 Debug("event:key:special", "'%s' [%d]", cheat_input, cheat_input_len);
1863 if (game_status == GAME_MODE_MAIN)
1865 if (strSuffix(cheat_input, ":insert-solution-tape") ||
1866 strSuffix(cheat_input, ":ist"))
1868 InsertSolutionTape();
1870 else if (strSuffix(cheat_input, ":play-solution-tape") ||
1871 strSuffix(cheat_input, ":pst"))
1875 else if (strSuffix(cheat_input, ":reload-graphics") ||
1876 strSuffix(cheat_input, ":rg"))
1878 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1881 else if (strSuffix(cheat_input, ":reload-sounds") ||
1882 strSuffix(cheat_input, ":rs"))
1884 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1887 else if (strSuffix(cheat_input, ":reload-music") ||
1888 strSuffix(cheat_input, ":rm"))
1890 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1893 else if (strSuffix(cheat_input, ":reload-artwork") ||
1894 strSuffix(cheat_input, ":ra"))
1896 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1897 1 << ARTWORK_TYPE_SOUNDS |
1898 1 << ARTWORK_TYPE_MUSIC);
1901 else if (strSuffix(cheat_input, ":dump-level") ||
1902 strSuffix(cheat_input, ":dl"))
1906 else if (strSuffix(cheat_input, ":dump-tape") ||
1907 strSuffix(cheat_input, ":dt"))
1911 else if (strSuffix(cheat_input, ":undo-tape") ||
1912 strSuffix(cheat_input, ":ut"))
1916 else if (strSuffix(cheat_input, ":fix-tape") ||
1917 strSuffix(cheat_input, ":ft"))
1919 FixTape_ForceSinglePlayer();
1921 else if (strSuffix(cheat_input, ":save-native-level") ||
1922 strSuffix(cheat_input, ":snl"))
1924 SaveNativeLevel(&level);
1926 else if (strSuffix(cheat_input, ":frames-per-second") ||
1927 strSuffix(cheat_input, ":fps"))
1929 global.show_frames_per_second = !global.show_frames_per_second;
1931 else if (strSuffix(cheat_input, ":xsn"))
1933 tile_cursor.xsn_debug = TRUE;
1936 else if (game_status == GAME_MODE_PLAYING)
1939 if (strSuffix(cheat_input, ".q"))
1940 DEBUG_SetMaximumDynamite();
1943 else if (game_status == GAME_MODE_EDITOR)
1945 if (strSuffix(cheat_input, ":dump-brush") ||
1946 strSuffix(cheat_input, ":DB"))
1950 else if (strSuffix(cheat_input, ":DDB"))
1955 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1957 if (letter == 'x') // copy brush to clipboard (small size)
1959 CopyBrushToClipboard_Small();
1961 else if (letter == 'c') // copy brush to clipboard (normal size)
1963 CopyBrushToClipboard();
1965 else if (letter == 'v') // paste brush from Clipboard
1967 CopyClipboardToBrush();
1969 else if (letter == 'z') // undo or redo last operation
1971 if (GetKeyModState() & KMOD_Shift)
1972 RedoLevelEditorOperation();
1974 UndoLevelEditorOperation();
1979 // special key shortcuts for all game modes
1980 if (strSuffix(cheat_input, ":dump-event-actions") ||
1981 strSuffix(cheat_input, ":dea") ||
1982 strSuffix(cheat_input, ":DEA"))
1984 DumpGadgetIdentifiers();
1985 DumpScreenIdentifiers();
1989 boolean HandleKeysDebug(Key key, int key_status)
1994 if (key_status != KEY_PRESSED)
1997 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1999 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
2001 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
2003 if (key == setup.debug.frame_delay_key[i] &&
2004 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
2006 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
2007 setup.debug.frame_delay[i] : setup.game_frame_delay);
2009 if (!setup.debug.frame_delay_game_only)
2010 MenuFrameDelay = GameFrameDelay;
2012 SetVideoFrameDelay(GameFrameDelay);
2014 if (GameFrameDelay > ONE_SECOND_DELAY)
2015 Debug("event:key:debug", "frame delay == %d ms", GameFrameDelay);
2016 else if (GameFrameDelay != 0)
2017 Debug("event:key:debug", "frame delay == %d ms (max. %d fps / %d %%)",
2018 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
2019 GAME_FRAME_DELAY * 100 / GameFrameDelay);
2021 Debug("event:key:debug", "frame delay == 0 ms (maximum speed)");
2028 if (game_status == GAME_MODE_PLAYING)
2032 options.debug = !options.debug;
2034 Debug("event:key:debug", "debug mode %s",
2035 (options.debug ? "enabled" : "disabled"));
2039 else if (key == KSYM_v)
2041 Debug("event:key:debug", "currently using game engine version %d",
2042 game.engine_version);
2052 void HandleKey(Key key, int key_status)
2054 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2055 static boolean ignore_repeated_key = FALSE;
2056 static struct SetupKeyboardInfo ski;
2057 static struct SetupShortcutInfo ssi;
2066 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
2067 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
2068 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
2069 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
2070 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2071 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2076 if (HandleKeysDebug(key, key_status))
2077 return; // do not handle already processed keys again
2079 // map special keys (media keys / remote control buttons) to default keys
2080 if (key == KSYM_PlayPause)
2082 else if (key == KSYM_Select)
2085 HandleSpecialGameControllerKeys(key, key_status);
2087 if (game_status == GAME_MODE_PLAYING)
2089 // only needed for single-step tape recording mode
2090 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2093 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2095 byte key_action = 0;
2096 byte key_snap_action = 0;
2098 if (setup.input[pnr].use_joystick)
2101 ski = setup.input[pnr].key;
2103 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2104 if (key == *key_info[i].key_custom)
2105 key_action |= key_info[i].action;
2107 // use combined snap+direction keys for the first player only
2110 ssi = setup.shortcut;
2112 // also remember normal snap key when handling snap+direction keys
2113 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2115 for (i = 0; i < NUM_DIRECTIONS; i++)
2117 if (key == *key_info[i].key_snap)
2119 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2120 key_snap_action |= key_info[i].action;
2122 tape.property_bits |= TAPE_PROPERTY_TAS_KEYS;
2127 if (key_status == KEY_PRESSED)
2129 stored_player[pnr].action |= key_action;
2130 stored_player[pnr].snap_action |= key_snap_action;
2134 stored_player[pnr].action &= ~key_action;
2135 stored_player[pnr].snap_action &= ~key_snap_action;
2138 // restore snap action if one of several pressed snap keys was released
2139 if (stored_player[pnr].snap_action)
2140 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2142 if (tape.recording && tape.pausing && tape.use_key_actions)
2144 if (tape.single_step)
2146 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2148 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2150 // if snap key already pressed, keep pause mode when releasing
2151 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2152 has_snapped[pnr] = TRUE;
2154 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2156 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2158 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2159 getRedDiskReleaseFlag_SP() == 0)
2161 // add a single inactive frame before dropping starts
2162 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2163 stored_player[pnr].force_dropping = TRUE;
2166 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2168 // if snap key was pressed without direction, leave pause mode
2169 if (!has_snapped[pnr])
2170 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2172 has_snapped[pnr] = FALSE;
2177 // prevent key release events from un-pausing a paused game
2178 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2179 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2183 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2184 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2187 // for any keyboard event, enable playfield mouse cursor
2188 if (key_action && key_status == KEY_PRESSED)
2189 SetPlayfieldMouseCursorEnabled(TRUE);
2194 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2195 if (key == key_info[i].key_default)
2196 joy |= key_info[i].action;
2201 if (key_status == KEY_PRESSED)
2202 key_joystick_mapping |= joy;
2204 key_joystick_mapping &= ~joy;
2209 if (game_status != GAME_MODE_PLAYING)
2210 key_joystick_mapping = 0;
2212 if (key_status == KEY_RELEASED)
2214 // reset flag to ignore repeated "key pressed" events after key release
2215 ignore_repeated_key = FALSE;
2217 // send key release event to global animation event handling
2218 if (!is_global_anim_event)
2219 HandleGlobalAnimClicks(-1, -1, KEY_RELEASED, FALSE);
2224 if ((key == KSYM_F11 ||
2225 ((key == KSYM_Return ||
2226 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2227 video.fullscreen_available &&
2228 !ignore_repeated_key)
2230 setup.fullscreen = !setup.fullscreen;
2232 ToggleFullscreenIfNeeded();
2234 if (game_status == GAME_MODE_SETUP)
2235 RedrawSetupScreenAfterFullscreenToggle();
2237 UpdateMousePosition();
2239 // set flag to ignore repeated "key pressed" events
2240 ignore_repeated_key = TRUE;
2245 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2246 key == KSYM_minus || key == KSYM_KP_Subtract ||
2247 key == KSYM_plus || key == KSYM_KP_Add ||
2248 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2249 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2250 video.window_scaling_available &&
2251 !video.fullscreen_enabled)
2253 if (key == KSYM_0 || key == KSYM_KP_0)
2254 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2255 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2256 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2258 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2260 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2261 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2262 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2263 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2265 ChangeWindowScalingIfNeeded();
2267 if (game_status == GAME_MODE_SETUP)
2268 RedrawSetupScreenAfterFullscreenToggle();
2270 UpdateMousePosition();
2275 // some key events are handled like clicks for global animations
2276 boolean click = (!is_global_anim_event && (key == KSYM_space ||
2277 key == KSYM_Return ||
2278 key == KSYM_Escape));
2280 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2282 // do not handle this key event anymore
2283 if (key != KSYM_Escape) // always allow ESC key to be handled
2287 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2288 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2295 if (game_status == GAME_MODE_MAIN &&
2296 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2298 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2303 if (game_status == GAME_MODE_MAIN &&
2304 (setup.internal.info_screens_from_main ||
2305 leveldir_current->info_screens_from_main) &&
2306 (key >= KSYM_KP_1 && key <= KSYM_KP_9))
2308 DrawInfoScreen_FromMainMenu(key - KSYM_KP_1 + 1);
2313 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2315 if (key == setup.shortcut.save_game)
2317 else if (key == setup.shortcut.load_game)
2319 else if (key == setup.shortcut.restart_game)
2321 else if (key == setup.shortcut.pause_before_end)
2322 TapeReplayAndPauseBeforeEnd();
2323 else if (key == setup.shortcut.toggle_pause)
2324 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2326 HandleTapeButtonKeys(key);
2327 HandleSoundButtonKeys(key);
2330 if (game_status == GAME_MODE_SCOREINFO)
2332 HandleScreenGadgetKeys(key);
2335 if (game_status == GAME_MODE_PLAYING && !network_playing)
2337 int centered_player_nr_next = -999;
2339 if (key == setup.shortcut.focus_player_all)
2340 centered_player_nr_next = -1;
2342 for (i = 0; i < MAX_PLAYERS; i++)
2343 if (key == setup.shortcut.focus_player[i])
2344 centered_player_nr_next = i;
2346 if (centered_player_nr_next != -999)
2348 game.centered_player_nr_next = centered_player_nr_next;
2349 game.set_centered_player = TRUE;
2353 tape.centered_player_nr_next = game.centered_player_nr_next;
2354 tape.set_centered_player = TRUE;
2359 HandleKeysSpecial(key);
2361 if (HandleGadgetsKeyInput(key))
2362 return; // do not handle already processed keys again
2364 // special case: on "space" key, either continue playing or go to main menu
2365 if (game_status == GAME_MODE_SCORES && key == KSYM_space)
2367 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CONTINUE);
2372 switch (game_status)
2374 case GAME_MODE_PSEUDO_TYPENAME:
2375 case GAME_MODE_PSEUDO_TYPENAMES:
2376 HandleTypeName(key);
2379 case GAME_MODE_TITLE:
2380 case GAME_MODE_MAIN:
2381 case GAME_MODE_NAMES:
2382 case GAME_MODE_LEVELS:
2383 case GAME_MODE_LEVELNR:
2384 case GAME_MODE_SETUP:
2385 case GAME_MODE_INFO:
2386 case GAME_MODE_SCORES:
2387 case GAME_MODE_SCOREINFO:
2389 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2396 if (game_status == GAME_MODE_TITLE)
2397 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2398 else if (game_status == GAME_MODE_MAIN)
2399 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2400 else if (game_status == GAME_MODE_NAMES)
2401 HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_CHOICE);
2402 else if (game_status == GAME_MODE_LEVELS)
2403 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2404 else if (game_status == GAME_MODE_LEVELNR)
2405 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2406 else if (game_status == GAME_MODE_SETUP)
2407 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2408 else if (game_status == GAME_MODE_INFO)
2409 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2410 else if (game_status == GAME_MODE_SCORES)
2411 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2412 else if (game_status == GAME_MODE_SCOREINFO)
2413 HandleScoreInfo(0, 0, 0, 0, MB_MENU_CHOICE);
2417 if (game_status != GAME_MODE_MAIN)
2418 FadeSkipNextFadeIn();
2420 if (game_status == GAME_MODE_TITLE)
2421 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2422 else if (game_status == GAME_MODE_NAMES)
2423 HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_LEAVE);
2424 else if (game_status == GAME_MODE_LEVELS)
2425 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2426 else if (game_status == GAME_MODE_LEVELNR)
2427 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2428 else if (game_status == GAME_MODE_SETUP)
2429 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2430 else if (game_status == GAME_MODE_INFO)
2431 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2432 else if (game_status == GAME_MODE_SCORES)
2433 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2434 else if (game_status == GAME_MODE_SCOREINFO)
2435 HandleScoreInfo(0, 0, 0, 0, MB_MENU_LEAVE);
2439 if (game_status == GAME_MODE_NAMES)
2440 HandleChoosePlayerName(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2441 else if (game_status == GAME_MODE_LEVELS)
2442 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2443 else if (game_status == GAME_MODE_LEVELNR)
2444 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2445 else if (game_status == GAME_MODE_SETUP)
2446 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2447 else if (game_status == GAME_MODE_INFO)
2448 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2449 else if (game_status == GAME_MODE_SCORES)
2450 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2451 else if (game_status == GAME_MODE_SCOREINFO)
2452 HandleScoreInfo(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2455 case KSYM_Page_Down:
2456 if (game_status == GAME_MODE_NAMES)
2457 HandleChoosePlayerName(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2458 else if (game_status == GAME_MODE_LEVELS)
2459 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2460 else if (game_status == GAME_MODE_LEVELNR)
2461 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2462 else if (game_status == GAME_MODE_SETUP)
2463 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2464 else if (game_status == GAME_MODE_INFO)
2465 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2466 else if (game_status == GAME_MODE_SCORES)
2467 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2468 else if (game_status == GAME_MODE_SCOREINFO)
2469 HandleScoreInfo(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2477 case GAME_MODE_EDITOR:
2478 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2479 HandleLevelEditorKeyInput(key);
2482 case GAME_MODE_PLAYING:
2487 RequestQuitGame(TRUE);
2497 if (key == KSYM_Escape)
2499 SetGameStatus(GAME_MODE_MAIN);
2508 void HandleNoEvent(void)
2510 HandleMouseCursor();
2512 switch (game_status)
2514 case GAME_MODE_PLAYING:
2515 HandleButtonOrFinger(-1, -1, -1);
2520 void HandleEventActions(void)
2522 // if (button_status && game_status != GAME_MODE_PLAYING)
2523 if (button_status && (game_status != GAME_MODE_PLAYING ||
2525 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2527 HandleButton(0, 0, button_status, -button_status);
2534 if (network.enabled)
2537 switch (game_status)
2539 case GAME_MODE_MAIN:
2540 DrawPreviewLevelAnimation();
2543 case GAME_MODE_EDITOR:
2544 HandleLevelEditorIdle();
2552 static void HandleTileCursor(int dx, int dy, int button)
2555 ClearPlayerMouseAction();
2562 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2563 (dx < 0 ? MB_LEFTBUTTON :
2564 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2566 else if (!tile_cursor.moving)
2568 int old_xpos = tile_cursor.xpos;
2569 int old_ypos = tile_cursor.ypos;
2570 int new_xpos = tile_cursor.xpos + dx;
2571 int new_ypos = tile_cursor.ypos + dy;
2573 if (!IN_LEV_FIELD(new_xpos, old_ypos) || !IN_SCR_FIELD(new_xpos, old_ypos))
2574 new_xpos = old_xpos;
2576 if (!IN_LEV_FIELD(old_xpos, new_ypos) || !IN_SCR_FIELD(old_xpos, new_ypos))
2577 new_ypos = old_ypos;
2579 SetTileCursorTargetXY(new_xpos, new_ypos);
2583 static int HandleJoystickForAllPlayers(void)
2587 boolean no_joysticks_configured = TRUE;
2588 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2589 static byte joy_action_last[MAX_PLAYERS];
2591 for (i = 0; i < MAX_PLAYERS; i++)
2592 if (setup.input[i].use_joystick)
2593 no_joysticks_configured = FALSE;
2595 // if no joysticks configured, map connected joysticks to players
2596 if (no_joysticks_configured)
2597 use_as_joystick_nr = TRUE;
2599 for (i = 0; i < MAX_PLAYERS; i++)
2601 byte joy_action = 0;
2603 joy_action = JoystickExt(i, use_as_joystick_nr);
2604 result |= joy_action;
2606 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2607 joy_action != joy_action_last[i])
2608 stored_player[i].action = joy_action;
2610 joy_action_last[i] = joy_action;
2616 void HandleJoystick(void)
2618 static DelayCounter joytest_delay = { GADGET_FRAME_DELAY };
2619 static int joytest_last = 0;
2620 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2621 int delay_value = GADGET_FRAME_DELAY;
2622 int joystick = HandleJoystickForAllPlayers();
2623 int keyboard = key_joystick_mapping;
2624 int joy = (joystick | keyboard);
2625 int joytest = joystick;
2626 int left = joy & JOY_LEFT;
2627 int right = joy & JOY_RIGHT;
2628 int up = joy & JOY_UP;
2629 int down = joy & JOY_DOWN;
2630 int button = joy & JOY_BUTTON;
2631 int anybutton = AnyJoystickButton();
2632 int newbutton = (anybutton == JOY_BUTTON_NEW_PRESSED);
2633 int dx = (left ? -1 : right ? 1 : 0);
2634 int dy = (up ? -1 : down ? 1 : 0);
2635 boolean use_delay_value_first = (joytest != joytest_last);
2636 boolean new_button_event = (anybutton == JOY_BUTTON_NEW_PRESSED ||
2637 anybutton == JOY_BUTTON_NEW_RELEASED);
2639 if (new_button_event && HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2641 // do not handle this button event anymore
2645 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2646 game_status == GAME_MODE_PSEUDO_TYPENAMES ||
2647 anyTextGadgetActive()))
2649 // leave name input in main menu or text input gadget
2650 HandleKey(KSYM_Escape, KEY_PRESSED);
2651 HandleKey(KSYM_Escape, KEY_RELEASED);
2656 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2658 if (game_status == GAME_MODE_PLAYING)
2660 // when playing MM style levels, also use delay for keyboard events
2661 joytest |= keyboard;
2663 // only use first delay value for new events, but not for changed events
2664 use_delay_value_first = (!joytest != !joytest_last);
2666 // only use delay after the initial keyboard event
2670 // for any joystick or keyboard event, enable playfield tile cursor
2671 if (dx || dy || button)
2672 SetTileCursorEnabled(TRUE);
2675 // for any joystick event, enable playfield mouse cursor
2676 if (dx || dy || button)
2677 SetPlayfieldMouseCursorEnabled(TRUE);
2679 if (joytest && !button && !DelayReached(&joytest_delay))
2681 // delay joystick/keyboard actions if axes/keys continually pressed
2682 newbutton = dx = dy = 0;
2686 // first start with longer delay, then continue with shorter delay
2687 joytest_delay.value =
2688 (use_delay_value_first ? delay_value_first : delay_value);
2691 joytest_last = joytest;
2693 switch (game_status)
2695 case GAME_MODE_TITLE:
2696 case GAME_MODE_MAIN:
2697 case GAME_MODE_NAMES:
2698 case GAME_MODE_LEVELS:
2699 case GAME_MODE_LEVELNR:
2700 case GAME_MODE_SETUP:
2701 case GAME_MODE_INFO:
2702 case GAME_MODE_SCORES:
2703 case GAME_MODE_SCOREINFO:
2705 if (anyTextGadgetActive())
2708 if (game_status == GAME_MODE_TITLE)
2709 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2710 else if (game_status == GAME_MODE_MAIN)
2711 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2712 else if (game_status == GAME_MODE_NAMES)
2713 HandleChoosePlayerName(0,0,dx,dy,newbutton?MB_MENU_CHOICE:MB_MENU_MARK);
2714 else if (game_status == GAME_MODE_LEVELS)
2715 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2716 else if (game_status == GAME_MODE_LEVELNR)
2717 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2718 else if (game_status == GAME_MODE_SETUP)
2719 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2720 else if (game_status == GAME_MODE_INFO)
2721 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2722 else if (game_status == GAME_MODE_SCORES)
2723 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2724 else if (game_status == GAME_MODE_SCOREINFO)
2725 HandleScoreInfo(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2730 case GAME_MODE_PLAYING:
2732 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2733 if (tape.playing || keyboard)
2734 newbutton = ((joy & JOY_BUTTON) != 0);
2737 if (newbutton && game.all_players_gone)
2744 if (tape.recording && tape.pausing && tape.use_key_actions)
2746 if (tape.single_step)
2748 if (joystick & JOY_ACTION)
2749 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2753 if (joystick & JOY_ACTION)
2754 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2758 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2759 HandleTileCursor(dx, dy, button);
2768 void HandleSpecialGameControllerButtons(Event *event)
2773 switch (event->type)
2775 case SDL_CONTROLLERBUTTONDOWN:
2776 key_status = KEY_PRESSED;
2779 case SDL_CONTROLLERBUTTONUP:
2780 key_status = KEY_RELEASED;
2787 switch (event->cbutton.button)
2789 case SDL_CONTROLLER_BUTTON_START:
2793 case SDL_CONTROLLER_BUTTON_BACK:
2801 HandleKey(key, key_status);
2804 void HandleSpecialGameControllerKeys(Key key, int key_status)
2806 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2807 int button = SDL_CONTROLLER_BUTTON_INVALID;
2809 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2810 if (key == KSYM_Rewind)
2811 button = SDL_CONTROLLER_BUTTON_A;
2812 else if (key == KSYM_FastForward || key == KSYM_Menu)
2813 button = SDL_CONTROLLER_BUTTON_B;
2815 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2819 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2820 SDL_CONTROLLERBUTTONUP);
2822 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2823 event.cbutton.button = button;
2824 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2827 HandleJoystickEvent(&event);
2832 boolean DoKeysymAction(int keysym)
2836 Key key = (Key)(-keysym);
2838 is_global_anim_event = TRUE;
2840 HandleKey(key, KEY_PRESSED);
2841 HandleKey(key, KEY_RELEASED);
2843 is_global_anim_event = FALSE;