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") &&
1509 !strPrefixLower(filename, "fd:"))
1511 Warn("file '%s' not supported", filename);
1513 return TREE_TYPE_UNDEFINED;
1516 TreeInfo *tree_node = NULL;
1517 int tree_type = GetZipFileTreeType(filename);
1518 char *directory = TREE_USERDIR(tree_type);
1520 if (directory == NULL)
1522 Warn("zip file '%s' has invalid content!", filename);
1524 return TREE_TYPE_UNDEFINED;
1527 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1528 game_status == GAME_MODE_LEVELS &&
1529 leveldir_current->node_parent != NULL)
1531 // extract new level set next to currently selected level set
1532 tree_node = leveldir_current;
1534 // get parent directory of currently selected level set directory
1535 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1537 // use private level directory instead of top-level package level directory
1538 if (strPrefix(directory, options.level_directory) &&
1539 strEqual(leveldir_current->node_parent->fullpath, "."))
1540 directory = getUserLevelDir(NULL);
1543 // extract level or artwork set from zip file to target directory
1544 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1546 if (top_dir == NULL)
1548 // error message already issued by "ExtractZipFileIntoDirectory()"
1550 return TREE_TYPE_UNDEFINED;
1553 // add extracted level or artwork set to tree info structure
1554 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1556 // force restart after adding level collection
1557 if (getTreeInfoFromIdentifier(TREE_FIRST_NODE(tree_type), top_dir) == NULL)
1559 Request("Program must be restarted after adding a new level collection!",
1565 // update menu screen (and possibly change current level set)
1566 DrawScreenAfterAddingSet(top_dir, tree_type);
1571 static void HandleDropTextEvent(char *text)
1573 Debug("event:droptext", "text == '%s'", text);
1576 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1577 int num_artwork_sets_succeeded,
1578 int num_files_failed)
1580 // only show request dialog if no other request dialog already active
1581 if (game.request_active)
1584 // this case can happen with drag-and-drop with older SDL versions
1585 if (num_level_sets_succeeded == 0 &&
1586 num_artwork_sets_succeeded == 0 &&
1587 num_files_failed == 0)
1592 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1594 char message_part1[50];
1596 sprintf(message_part1, "New %s set%s added",
1597 (num_artwork_sets_succeeded == 0 ? "level" :
1598 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1599 (num_level_sets_succeeded +
1600 num_artwork_sets_succeeded > 1 ? "s" : ""));
1602 if (num_files_failed > 0)
1603 sprintf(message, "%s, but %d dropped file%s failed!",
1604 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1606 sprintf(message, "%s!", message_part1);
1608 else if (num_files_failed > 0)
1610 sprintf(message, "Failed to process dropped file%s!",
1611 num_files_failed > 1 ? "s" : "");
1614 Request(message, REQ_CONFIRM);
1617 void HandleDropEvent(Event *event)
1619 Debug("event:drop", (event->type == SDL_DROPBEGIN ? "SDL_DROPBEGIN" :
1620 event->type == SDL_DROPFILE ? "SDL_DROPFILE" :
1621 event->type == SDL_DROPTEXT ? "SDL_DROPTEXT" :
1622 event->type == SDL_DROPCOMPLETE ? "SDL_DROPCOMPLETE" :
1623 "(unknown drop event type)"));
1625 static boolean confirm_on_drop_complete = FALSE;
1626 static int num_level_sets_succeeded = 0;
1627 static int num_artwork_sets_succeeded = 0;
1628 static int num_files_failed = 0;
1630 switch (event->type)
1634 confirm_on_drop_complete = TRUE;
1635 num_level_sets_succeeded = 0;
1636 num_artwork_sets_succeeded = 0;
1637 num_files_failed = 0;
1644 int tree_type = HandleDropFileEvent(event->drop.file);
1646 if (tree_type == TREE_TYPE_LEVEL_DIR)
1647 num_level_sets_succeeded++;
1648 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1649 tree_type == TREE_TYPE_SOUNDS_DIR ||
1650 tree_type == TREE_TYPE_MUSIC_DIR)
1651 num_artwork_sets_succeeded++;
1655 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1656 if (!confirm_on_drop_complete)
1658 // process all remaining events, including further SDL_DROPFILE events
1661 HandleDropCompleteEvent(num_level_sets_succeeded,
1662 num_artwork_sets_succeeded,
1665 num_level_sets_succeeded = 0;
1666 num_artwork_sets_succeeded = 0;
1667 num_files_failed = 0;
1675 HandleDropTextEvent(event->drop.file);
1680 case SDL_DROPCOMPLETE:
1682 HandleDropCompleteEvent(num_level_sets_succeeded,
1683 num_artwork_sets_succeeded,
1690 if (event->drop.file != NULL)
1691 SDL_free(event->drop.file);
1694 void HandleUserEvent(UserEvent *event)
1696 switch (event->code)
1698 case USEREVENT_ANIM_DELAY_ACTION:
1699 case USEREVENT_ANIM_EVENT_ACTION:
1700 // execute action functions until matching action was found
1701 if (DoKeysymAction(event->value1) ||
1702 DoGadgetAction(event->value1) ||
1703 DoScreenAction(event->value1))
1712 void HandleButton(int mx, int my, int button, int button_nr)
1714 static int old_mx = 0, old_my = 0;
1715 boolean button_hold = FALSE;
1716 boolean handle_gadgets = TRUE;
1717 int game_status_last = game_status;
1723 button_nr = -button_nr;
1732 #if defined(PLATFORM_ANDROID)
1733 // when playing, only handle gadgets when using "follow finger" controls
1734 // or when using touch controls in combination with the MM game engine
1735 // or when using gadgets that do not overlap with virtual buttons
1736 // or when touch controls are disabled (e.g., with mouse-only levels)
1738 (game_status != GAME_MODE_PLAYING ||
1739 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1740 strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF) ||
1741 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1742 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1743 !CheckVirtualButtonPressed(mx, my, button)));
1745 // always recognize potentially releasing already pressed gadgets
1746 if (button == MB_RELEASED)
1747 handle_gadgets = TRUE;
1749 // always recognize pressing or releasing overlay touch buttons
1750 if (CheckPosition_OverlayTouchButtons(mx, my, button) && !motion_status)
1751 handle_gadgets = TRUE;
1754 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1756 // do not handle this button event anymore
1757 return; // force mouse event not to be handled at all
1760 if (handle_gadgets && HandleGadgets(mx, my, button))
1762 // do not handle this button event anymore with position on screen
1763 mx = my = -32; // force mouse event to be outside screen tiles
1765 // do not handle this button event anymore if game status has changed
1766 if (game_status != game_status_last)
1770 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1773 // do not use scroll wheel button events for anything other than gadgets
1774 if (IS_WHEEL_BUTTON(button_nr))
1777 switch (game_status)
1779 case GAME_MODE_TITLE:
1780 HandleTitleScreen(mx, my, 0, 0, button);
1783 case GAME_MODE_MAIN:
1784 HandleMainMenu(mx, my, 0, 0, button);
1787 case GAME_MODE_PSEUDO_TYPENAME:
1788 case GAME_MODE_PSEUDO_TYPENAMES:
1789 HandleTypeName(KSYM_Return);
1792 case GAME_MODE_NAMES:
1793 HandleChoosePlayerName(mx, my, 0, 0, button);
1796 case GAME_MODE_LEVELS:
1797 HandleChooseLevelSet(mx, my, 0, 0, button);
1800 case GAME_MODE_LEVELNR:
1801 HandleChooseLevelNr(mx, my, 0, 0, button);
1804 case GAME_MODE_SCORES:
1805 HandleHallOfFame(mx, my, 0, 0, button);
1808 case GAME_MODE_SCOREINFO:
1809 HandleScoreInfo(mx, my, 0, 0, button);
1812 case GAME_MODE_EDITOR:
1813 HandleLevelEditorIdle();
1816 case GAME_MODE_INFO:
1817 HandleInfoScreen(mx, my, 0, 0, button);
1820 case GAME_MODE_SETUP:
1821 HandleSetupScreen(mx, my, 0, 0, button);
1824 case GAME_MODE_PLAYING:
1825 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1826 HandleButtonOrFinger(mx, my, button);
1828 SetPlayerMouseAction(mx, my, button);
1831 if (button == MB_PRESSED && !motion_status && !button_hold &&
1832 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1833 DumpTileFromScreen(mx, my);
1843 #define MAX_CHEAT_INPUT_LEN 32
1845 static void HandleKeysSpecial(Key key)
1847 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1848 char letter = getCharFromKey(key);
1849 int cheat_input_len = strlen(cheat_input);
1855 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1857 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1858 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1860 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1863 cheat_input[cheat_input_len++] = letter;
1864 cheat_input[cheat_input_len] = '\0';
1866 #if DEBUG_EVENTS_KEY
1867 Debug("event:key:special", "'%s' [%d]", cheat_input, cheat_input_len);
1870 if (game_status == GAME_MODE_MAIN)
1872 if (strSuffix(cheat_input, ":insert-solution-tape") ||
1873 strSuffix(cheat_input, ":ist"))
1875 InsertSolutionTape();
1877 else if (strSuffix(cheat_input, ":play-solution-tape") ||
1878 strSuffix(cheat_input, ":pst"))
1882 else if (strSuffix(cheat_input, ":reload-graphics") ||
1883 strSuffix(cheat_input, ":rg"))
1885 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1888 else if (strSuffix(cheat_input, ":reload-sounds") ||
1889 strSuffix(cheat_input, ":rs"))
1891 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1894 else if (strSuffix(cheat_input, ":reload-music") ||
1895 strSuffix(cheat_input, ":rm"))
1897 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1900 else if (strSuffix(cheat_input, ":reload-artwork") ||
1901 strSuffix(cheat_input, ":ra"))
1903 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1904 1 << ARTWORK_TYPE_SOUNDS |
1905 1 << ARTWORK_TYPE_MUSIC);
1908 else if (strSuffix(cheat_input, ":dump-level") ||
1909 strSuffix(cheat_input, ":dl"))
1913 else if (strSuffix(cheat_input, ":dump-tape") ||
1914 strSuffix(cheat_input, ":dt"))
1918 else if (strSuffix(cheat_input, ":undo-tape") ||
1919 strSuffix(cheat_input, ":ut"))
1923 else if (strSuffix(cheat_input, ":fix-tape") ||
1924 strSuffix(cheat_input, ":ft"))
1926 FixTape_ForceSinglePlayer();
1928 else if (strSuffix(cheat_input, ":save-native-level") ||
1929 strSuffix(cheat_input, ":snl"))
1931 SaveNativeLevel(&level);
1933 else if (strSuffix(cheat_input, ":frames-per-second") ||
1934 strSuffix(cheat_input, ":fps"))
1936 global.show_frames_per_second = !global.show_frames_per_second;
1938 else if (strSuffix(cheat_input, ":xsn"))
1940 tile_cursor.xsn_debug = TRUE;
1943 else if (game_status == GAME_MODE_PLAYING)
1946 if (strSuffix(cheat_input, ".q"))
1947 DEBUG_SetMaximumDynamite();
1950 else if (game_status == GAME_MODE_EDITOR)
1952 if (strSuffix(cheat_input, ":dump-brush") ||
1953 strSuffix(cheat_input, ":DB"))
1957 else if (strSuffix(cheat_input, ":DDB"))
1962 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1964 if (letter == 'x') // copy brush to clipboard (small size)
1966 CopyBrushToClipboard_Small();
1968 else if (letter == 'c') // copy brush to clipboard (normal size)
1970 CopyBrushToClipboard();
1972 else if (letter == 'v') // paste brush from Clipboard
1974 CopyClipboardToBrush();
1976 else if (letter == 'z') // undo or redo last operation
1978 if (GetKeyModState() & KMOD_Shift)
1979 RedoLevelEditorOperation();
1981 UndoLevelEditorOperation();
1986 // special key shortcuts for all game modes
1987 if (strSuffix(cheat_input, ":dump-event-actions") ||
1988 strSuffix(cheat_input, ":dea") ||
1989 strSuffix(cheat_input, ":DEA"))
1991 DumpGadgetIdentifiers();
1992 DumpScreenIdentifiers();
1996 static boolean HandleKeysSpeed(Key key, int key_status)
1998 if (game_status == GAME_MODE_PLAYING)
2000 if (key == setup.shortcut.speed_fast ||
2001 key == setup.shortcut.speed_slow)
2003 int speed_factor = 4;
2005 GameFrameDelay = (key_status != KEY_PRESSED ? setup.game_frame_delay :
2006 key == setup.shortcut.speed_fast ? setup.game_frame_delay / speed_factor :
2007 key == setup.shortcut.speed_slow ? setup.game_frame_delay * speed_factor :
2008 setup.game_frame_delay);
2010 GameFrameDelay = MIN(MAX(1, GameFrameDelay), 1000);
2012 SetVideoFrameDelay(GameFrameDelay);
2021 boolean HandleKeysDebug(Key key, int key_status)
2026 if (key_status != KEY_PRESSED)
2029 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
2031 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
2033 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
2035 if (key == setup.debug.frame_delay_key[i] &&
2036 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
2038 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
2039 setup.debug.frame_delay[i] : setup.game_frame_delay);
2041 if (!setup.debug.frame_delay_game_only)
2042 MenuFrameDelay = GameFrameDelay;
2044 SetVideoFrameDelay(GameFrameDelay);
2046 if (GameFrameDelay > ONE_SECOND_DELAY)
2047 Debug("event:key:debug", "frame delay == %d ms", GameFrameDelay);
2048 else if (GameFrameDelay != 0)
2049 Debug("event:key:debug", "frame delay == %d ms (max. %d fps / %d %%)",
2050 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
2051 GAME_FRAME_DELAY * 100 / GameFrameDelay);
2053 Debug("event:key:debug", "frame delay == 0 ms (maximum speed)");
2060 if (game_status == GAME_MODE_PLAYING)
2064 options.debug = !options.debug;
2066 Debug("event:key:debug", "debug mode %s",
2067 (options.debug ? "enabled" : "disabled"));
2071 else if (key == KSYM_v)
2073 Debug("event:key:debug", "currently using game engine version %d",
2074 game.engine_version);
2084 void HandleKey(Key key, int key_status)
2086 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2087 static boolean ignore_repeated_key = FALSE;
2088 static struct SetupKeyboardInfo ski;
2089 static struct SetupShortcutInfo ssi;
2098 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
2099 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
2100 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
2101 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
2102 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2103 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2108 if (HandleKeysSpeed(key, key_status))
2109 return; // do not handle already processed keys again
2111 if (HandleKeysDebug(key, key_status))
2112 return; // do not handle already processed keys again
2114 // map special keys (media keys / remote control buttons) to default keys
2115 if (key == KSYM_PlayPause)
2117 else if (key == KSYM_Select)
2120 HandleSpecialGameControllerKeys(key, key_status);
2122 if (game_status == GAME_MODE_PLAYING)
2124 // only needed for single-step tape recording mode
2125 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2128 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2130 byte key_action = 0;
2131 byte key_snap_action = 0;
2133 if (setup.input[pnr].use_joystick)
2136 ski = setup.input[pnr].key;
2138 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2139 if (key == *key_info[i].key_custom)
2140 key_action |= key_info[i].action;
2142 // use combined snap+direction keys for the first player only
2145 ssi = setup.shortcut;
2147 // also remember normal snap key when handling snap+direction keys
2148 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2150 for (i = 0; i < NUM_DIRECTIONS; i++)
2152 if (key == *key_info[i].key_snap)
2154 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2155 key_snap_action |= key_info[i].action;
2157 tape.property_bits |= TAPE_PROPERTY_TAS_KEYS;
2162 if (key_status == KEY_PRESSED)
2164 stored_player[pnr].action |= key_action;
2165 stored_player[pnr].snap_action |= key_snap_action;
2169 stored_player[pnr].action &= ~key_action;
2170 stored_player[pnr].snap_action &= ~key_snap_action;
2173 // restore snap action if one of several pressed snap keys was released
2174 if (stored_player[pnr].snap_action)
2175 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2177 if (tape.recording && tape.pausing && tape.use_key_actions)
2179 if (tape.single_step)
2181 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2183 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2185 // if snap key already pressed, keep pause mode when releasing
2186 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2187 has_snapped[pnr] = TRUE;
2189 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2191 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2193 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2194 getRedDiskReleaseFlag_SP() == 0)
2196 // add a single inactive frame before dropping starts
2197 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2198 stored_player[pnr].force_dropping = TRUE;
2201 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2203 // if snap key was pressed without direction, leave pause mode
2204 if (!has_snapped[pnr])
2205 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2207 has_snapped[pnr] = FALSE;
2212 // prevent key release events from un-pausing a paused game
2213 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2214 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2218 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2219 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2222 // for any keyboard event, enable playfield mouse cursor
2223 if (key_action && key_status == KEY_PRESSED)
2224 SetPlayfieldMouseCursorEnabled(TRUE);
2229 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2230 if (key == key_info[i].key_default)
2231 joy |= key_info[i].action;
2236 if (key_status == KEY_PRESSED)
2237 key_joystick_mapping |= joy;
2239 key_joystick_mapping &= ~joy;
2244 if (game_status != GAME_MODE_PLAYING)
2245 key_joystick_mapping = 0;
2247 if (key_status == KEY_RELEASED)
2249 // reset flag to ignore repeated "key pressed" events after key release
2250 ignore_repeated_key = FALSE;
2252 // send key release event to global animation event handling
2253 if (!is_global_anim_event)
2254 HandleGlobalAnimClicks(-1, -1, KEY_RELEASED, FALSE);
2259 if ((key == KSYM_F11 ||
2260 ((key == KSYM_Return ||
2261 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2262 video.fullscreen_available &&
2263 !ignore_repeated_key)
2265 setup.fullscreen = !setup.fullscreen;
2267 ToggleFullscreenIfNeeded();
2269 if (game_status == GAME_MODE_SETUP)
2270 RedrawSetupScreenAfterFullscreenToggle();
2272 UpdateMousePosition();
2274 // set flag to ignore repeated "key pressed" events
2275 ignore_repeated_key = TRUE;
2280 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2281 key == KSYM_minus || key == KSYM_KP_Subtract ||
2282 key == KSYM_plus || key == KSYM_KP_Add ||
2283 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2284 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2285 video.window_scaling_available &&
2286 !video.fullscreen_enabled)
2288 if (key == KSYM_0 || key == KSYM_KP_0)
2289 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2290 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2291 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2293 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2295 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2296 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2297 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2298 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2300 ChangeWindowScalingIfNeeded();
2302 if (game_status == GAME_MODE_SETUP)
2303 RedrawSetupScreenAfterFullscreenToggle();
2305 UpdateMousePosition();
2310 // some key events are handled like clicks for global animations
2311 boolean click = (!is_global_anim_event && (key == KSYM_space ||
2312 key == KSYM_Return ||
2313 key == KSYM_Escape));
2315 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2317 // do not handle this key event anymore
2318 if (key != KSYM_Escape) // always allow ESC key to be handled
2322 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2323 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2330 if (game_status == GAME_MODE_MAIN &&
2331 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2333 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2338 if (game_status == GAME_MODE_MAIN &&
2339 (setup.internal.info_screens_from_main ||
2340 leveldir_current->info_screens_from_main) &&
2341 (key >= KSYM_KP_1 && key <= KSYM_KP_9))
2343 DrawInfoScreen_FromMainMenu(key - KSYM_KP_1 + 1);
2348 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2350 if (key == setup.shortcut.save_game)
2352 else if (key == setup.shortcut.load_game)
2354 else if (key == setup.shortcut.restart_game)
2356 else if (key == setup.shortcut.pause_before_end)
2357 TapeReplayAndPauseBeforeEnd();
2358 else if (key == setup.shortcut.toggle_pause)
2359 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2361 HandleTapeButtonKeys(key);
2362 HandleSoundButtonKeys(key);
2365 if (game_status == GAME_MODE_SCOREINFO)
2367 HandleScreenGadgetKeys(key);
2370 if (game_status == GAME_MODE_PLAYING && !network_playing)
2372 int centered_player_nr_next = -999;
2374 if (key == setup.shortcut.focus_player_all)
2375 centered_player_nr_next = -1;
2377 for (i = 0; i < MAX_PLAYERS; i++)
2378 if (key == setup.shortcut.focus_player[i])
2379 centered_player_nr_next = i;
2381 if (centered_player_nr_next != -999)
2383 game.centered_player_nr_next = centered_player_nr_next;
2384 game.set_centered_player = TRUE;
2388 tape.centered_player_nr_next = game.centered_player_nr_next;
2389 tape.set_centered_player = TRUE;
2394 HandleKeysSpecial(key);
2396 if (HandleGadgetsKeyInput(key))
2397 return; // do not handle already processed keys again
2399 // special case: on "space" key, either continue playing or go to main menu
2400 if (game_status == GAME_MODE_SCORES && key == KSYM_space)
2402 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CONTINUE);
2407 switch (game_status)
2409 case GAME_MODE_PSEUDO_TYPENAME:
2410 case GAME_MODE_PSEUDO_TYPENAMES:
2411 HandleTypeName(key);
2414 case GAME_MODE_TITLE:
2415 case GAME_MODE_MAIN:
2416 case GAME_MODE_NAMES:
2417 case GAME_MODE_LEVELS:
2418 case GAME_MODE_LEVELNR:
2419 case GAME_MODE_SETUP:
2420 case GAME_MODE_INFO:
2421 case GAME_MODE_SCORES:
2422 case GAME_MODE_SCOREINFO:
2424 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2431 if (game_status == GAME_MODE_TITLE)
2432 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2433 else if (game_status == GAME_MODE_MAIN)
2434 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2435 else if (game_status == GAME_MODE_NAMES)
2436 HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_CHOICE);
2437 else if (game_status == GAME_MODE_LEVELS)
2438 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2439 else if (game_status == GAME_MODE_LEVELNR)
2440 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2441 else if (game_status == GAME_MODE_SETUP)
2442 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2443 else if (game_status == GAME_MODE_INFO)
2444 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2445 else if (game_status == GAME_MODE_SCORES)
2446 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2447 else if (game_status == GAME_MODE_SCOREINFO)
2448 HandleScoreInfo(0, 0, 0, 0, MB_MENU_CHOICE);
2452 if (game_status != GAME_MODE_MAIN)
2453 FadeSkipNextFadeIn();
2455 if (game_status == GAME_MODE_TITLE)
2456 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2457 else if (game_status == GAME_MODE_NAMES)
2458 HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_LEAVE);
2459 else if (game_status == GAME_MODE_LEVELS)
2460 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2461 else if (game_status == GAME_MODE_LEVELNR)
2462 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2463 else if (game_status == GAME_MODE_SETUP)
2464 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2465 else if (game_status == GAME_MODE_INFO)
2466 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2467 else if (game_status == GAME_MODE_SCORES)
2468 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2469 else if (game_status == GAME_MODE_SCOREINFO)
2470 HandleScoreInfo(0, 0, 0, 0, MB_MENU_LEAVE);
2474 if (game_status == GAME_MODE_NAMES)
2475 HandleChoosePlayerName(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2476 else if (game_status == GAME_MODE_LEVELS)
2477 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2478 else if (game_status == GAME_MODE_LEVELNR)
2479 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2480 else if (game_status == GAME_MODE_SETUP)
2481 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2482 else if (game_status == GAME_MODE_INFO)
2483 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2484 else if (game_status == GAME_MODE_SCORES)
2485 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2486 else if (game_status == GAME_MODE_SCOREINFO)
2487 HandleScoreInfo(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2490 case KSYM_Page_Down:
2491 if (game_status == GAME_MODE_NAMES)
2492 HandleChoosePlayerName(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2493 else if (game_status == GAME_MODE_LEVELS)
2494 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2495 else if (game_status == GAME_MODE_LEVELNR)
2496 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2497 else if (game_status == GAME_MODE_SETUP)
2498 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2499 else if (game_status == GAME_MODE_INFO)
2500 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2501 else if (game_status == GAME_MODE_SCORES)
2502 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2503 else if (game_status == GAME_MODE_SCOREINFO)
2504 HandleScoreInfo(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2512 case GAME_MODE_EDITOR:
2513 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2514 HandleLevelEditorKeyInput(key);
2517 case GAME_MODE_PLAYING:
2522 RequestQuitGame(TRUE);
2532 if (key == KSYM_Escape)
2534 SetGameStatus(GAME_MODE_MAIN);
2543 void HandleNoEvent(void)
2545 HandleMouseCursor();
2547 switch (game_status)
2549 case GAME_MODE_PLAYING:
2550 HandleButtonOrFinger(-1, -1, -1);
2555 void HandleEventActions(void)
2557 // if (button_status && game_status != GAME_MODE_PLAYING)
2558 if (button_status && (game_status != GAME_MODE_PLAYING ||
2560 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2562 HandleButton(0, 0, button_status, -button_status);
2569 if (network.enabled)
2572 switch (game_status)
2574 case GAME_MODE_MAIN:
2575 DrawPreviewLevelAnimation();
2578 case GAME_MODE_EDITOR:
2579 HandleLevelEditorIdle();
2587 static void HandleTileCursor(int dx, int dy, int button)
2590 ClearPlayerMouseAction();
2597 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2598 (dx < 0 ? MB_LEFTBUTTON :
2599 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2601 else if (!tile_cursor.moving)
2603 int old_xpos = tile_cursor.xpos;
2604 int old_ypos = tile_cursor.ypos;
2605 int new_xpos = tile_cursor.xpos + dx;
2606 int new_ypos = tile_cursor.ypos + dy;
2608 if (!IN_LEV_FIELD(new_xpos, old_ypos) || !IN_SCR_FIELD(new_xpos, old_ypos))
2609 new_xpos = old_xpos;
2611 if (!IN_LEV_FIELD(old_xpos, new_ypos) || !IN_SCR_FIELD(old_xpos, new_ypos))
2612 new_ypos = old_ypos;
2614 SetTileCursorTargetXY(new_xpos, new_ypos);
2618 static int HandleJoystickForAllPlayers(void)
2622 boolean no_joysticks_configured = TRUE;
2623 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2624 static byte joy_action_last[MAX_PLAYERS];
2626 for (i = 0; i < MAX_PLAYERS; i++)
2627 if (setup.input[i].use_joystick)
2628 no_joysticks_configured = FALSE;
2630 // if no joysticks configured, map connected joysticks to players
2631 if (no_joysticks_configured)
2632 use_as_joystick_nr = TRUE;
2634 for (i = 0; i < MAX_PLAYERS; i++)
2636 byte joy_action = 0;
2638 joy_action = JoystickExt(i, use_as_joystick_nr);
2639 result |= joy_action;
2641 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2642 joy_action != joy_action_last[i])
2643 stored_player[i].action = joy_action;
2645 joy_action_last[i] = joy_action;
2651 void HandleJoystick(void)
2653 static DelayCounter joytest_delay = { GADGET_FRAME_DELAY };
2654 static int joytest_last = 0;
2655 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2656 int delay_value = GADGET_FRAME_DELAY;
2657 int joystick = HandleJoystickForAllPlayers();
2658 int keyboard = key_joystick_mapping;
2659 int joy = (joystick | keyboard);
2660 int joytest = joystick;
2661 int left = joy & JOY_LEFT;
2662 int right = joy & JOY_RIGHT;
2663 int up = joy & JOY_UP;
2664 int down = joy & JOY_DOWN;
2665 int button = joy & JOY_BUTTON;
2666 int anybutton = AnyJoystickButton();
2667 int newbutton = (anybutton == JOY_BUTTON_NEW_PRESSED);
2668 int dx = (left ? -1 : right ? 1 : 0);
2669 int dy = (up ? -1 : down ? 1 : 0);
2670 boolean use_delay_value_first = (joytest != joytest_last);
2671 boolean new_button_event = (anybutton == JOY_BUTTON_NEW_PRESSED ||
2672 anybutton == JOY_BUTTON_NEW_RELEASED);
2674 if (new_button_event && HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2676 // do not handle this button event anymore
2680 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2681 game_status == GAME_MODE_PSEUDO_TYPENAMES ||
2682 anyTextGadgetActive()))
2684 // leave name input in main menu or text input gadget
2685 HandleKey(KSYM_Escape, KEY_PRESSED);
2686 HandleKey(KSYM_Escape, KEY_RELEASED);
2691 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2693 if (game_status == GAME_MODE_PLAYING)
2695 // when playing MM style levels, also use delay for keyboard events
2696 joytest |= keyboard;
2698 // only use first delay value for new events, but not for changed events
2699 use_delay_value_first = (!joytest != !joytest_last);
2701 // only use delay after the initial keyboard event
2705 // for any joystick or keyboard event, enable playfield tile cursor
2706 if (dx || dy || button)
2707 SetTileCursorEnabled(TRUE);
2710 // for any joystick event, enable playfield mouse cursor
2711 if (dx || dy || button)
2712 SetPlayfieldMouseCursorEnabled(TRUE);
2714 if (joytest && !button && !DelayReached(&joytest_delay))
2716 // delay joystick/keyboard actions if axes/keys continually pressed
2717 newbutton = dx = dy = 0;
2721 // first start with longer delay, then continue with shorter delay
2722 joytest_delay.value =
2723 (use_delay_value_first ? delay_value_first : delay_value);
2726 joytest_last = joytest;
2728 switch (game_status)
2730 case GAME_MODE_TITLE:
2731 case GAME_MODE_MAIN:
2732 case GAME_MODE_NAMES:
2733 case GAME_MODE_LEVELS:
2734 case GAME_MODE_LEVELNR:
2735 case GAME_MODE_SETUP:
2736 case GAME_MODE_INFO:
2737 case GAME_MODE_SCORES:
2738 case GAME_MODE_SCOREINFO:
2740 if (anyTextGadgetActive())
2743 if (game_status == GAME_MODE_TITLE)
2744 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2745 else if (game_status == GAME_MODE_MAIN)
2746 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2747 else if (game_status == GAME_MODE_NAMES)
2748 HandleChoosePlayerName(0,0,dx,dy,newbutton?MB_MENU_CHOICE:MB_MENU_MARK);
2749 else if (game_status == GAME_MODE_LEVELS)
2750 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2751 else if (game_status == GAME_MODE_LEVELNR)
2752 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2753 else if (game_status == GAME_MODE_SETUP)
2754 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2755 else if (game_status == GAME_MODE_INFO)
2756 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2757 else if (game_status == GAME_MODE_SCORES)
2758 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2759 else if (game_status == GAME_MODE_SCOREINFO)
2760 HandleScoreInfo(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2765 case GAME_MODE_PLAYING:
2767 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2768 if (tape.playing || keyboard)
2769 newbutton = ((joy & JOY_BUTTON) != 0);
2772 if (newbutton && game.all_players_gone)
2779 if (tape.recording && tape.pausing && tape.use_key_actions)
2781 if (tape.single_step)
2783 if (joystick & JOY_ACTION)
2784 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2788 if (joystick & JOY_ACTION)
2789 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2793 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2794 HandleTileCursor(dx, dy, button);
2803 void HandleSpecialGameControllerButtons(Event *event)
2808 switch (event->type)
2810 case SDL_CONTROLLERBUTTONDOWN:
2811 key_status = KEY_PRESSED;
2814 case SDL_CONTROLLERBUTTONUP:
2815 key_status = KEY_RELEASED;
2822 switch (event->cbutton.button)
2824 case SDL_CONTROLLER_BUTTON_START:
2828 case SDL_CONTROLLER_BUTTON_BACK:
2836 HandleKey(key, key_status);
2839 void HandleSpecialGameControllerKeys(Key key, int key_status)
2841 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2842 int button = SDL_CONTROLLER_BUTTON_INVALID;
2844 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2845 if (key == KSYM_Rewind)
2846 button = SDL_CONTROLLER_BUTTON_A;
2847 else if (key == KSYM_FastForward || key == KSYM_Menu)
2848 button = SDL_CONTROLLER_BUTTON_B;
2850 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2854 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2855 SDL_CONTROLLERBUTTONUP);
2857 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2858 event.cbutton.button = button;
2859 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2862 HandleJoystickEvent(&event);
2867 boolean DoKeysymAction(int keysym)
2871 Key key = (Key)(-keysym);
2873 is_global_anim_event = TRUE;
2875 HandleKey(key, KEY_PRESSED);
2876 HandleKey(key, KEY_RELEASED);
2878 is_global_anim_event = FALSE;