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;
45 // forward declarations for internal use
46 static void ClearTouchInfo(void);
47 static void HandleNoEvent(void);
48 static void HandleEventActions(void);
51 void SetPlayfieldMouseCursorEnabled(boolean enabled)
53 special_cursor_enabled = enabled;
56 // event filter to set mouse x/y position (for pointer class global animations)
57 // (this is especially required to ensure smooth global animation mouse pointer
58 // movement when the screen is updated without handling events; this can happen
59 // when drawing door/envelope request animations, for example)
61 int FilterMouseMotionEvents(void *userdata, Event *event)
63 if (event->type == EVENT_MOTIONNOTIFY)
65 int mouse_x = ((MotionEvent *)event)->x;
66 int mouse_y = ((MotionEvent *)event)->y;
68 UpdateRawMousePosition(mouse_x, mouse_y);
74 // event filter especially needed for SDL event filtering due to
75 // delay problems with lots of mouse motion events when mouse button
76 // not pressed (X11 can handle this with 'PointerMotionHintMask')
78 // event filter addition for SDL2: as SDL2 does not have a function to enable
79 // or disable keyboard auto-repeat, filter repeated keyboard events instead
81 static int FilterEvents(const Event *event)
85 // skip repeated key press events if keyboard auto-repeat is disabled
86 if (event->type == EVENT_KEYPRESS &&
91 if (event->type == EVENT_BUTTONPRESS ||
92 event->type == EVENT_BUTTONRELEASE)
94 ((ButtonEvent *)event)->x -= video.screen_xoffset;
95 ((ButtonEvent *)event)->y -= video.screen_yoffset;
97 else if (event->type == EVENT_MOTIONNOTIFY)
99 ((MotionEvent *)event)->x -= video.screen_xoffset;
100 ((MotionEvent *)event)->y -= video.screen_yoffset;
103 if (event->type == EVENT_BUTTONPRESS ||
104 event->type == EVENT_BUTTONRELEASE ||
105 event->type == EVENT_MOTIONNOTIFY)
107 // do not reset mouse cursor before all pending events have been processed
108 if (gfx.cursor_mode == cursor_mode_last &&
109 ((game_status == GAME_MODE_TITLE &&
110 gfx.cursor_mode == CURSOR_NONE) ||
111 (game_status == GAME_MODE_PLAYING &&
112 gfx.cursor_mode == CURSOR_PLAYFIELD)))
114 SetMouseCursor(CURSOR_DEFAULT);
116 ResetDelayCounter(&special_cursor_delay);
118 cursor_mode_last = CURSOR_DEFAULT;
122 // non-motion events are directly passed to event handler functions
123 if (event->type != EVENT_MOTIONNOTIFY)
126 motion = (MotionEvent *)event;
127 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
128 motion->y >= SY && motion->y < SY + SYSIZE);
130 // set correct mouse x/y position (for pointer class global animations)
131 // (this is required in rare cases where the mouse x/y position calculated
132 // from raw values (to apply logical screen size scaling corrections) does
133 // not match the final mouse event x/y position -- this may happen because
134 // the SDL renderer's viewport position is internally represented as float,
135 // but only accessible as integer, which may lead to rounding errors)
136 gfx.mouse_x = motion->x;
137 gfx.mouse_y = motion->y;
139 // skip mouse motion events without pressed button outside level editor
140 if (button_status == MB_RELEASED &&
141 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
147 // to prevent delay problems, skip mouse motion events if the very next
148 // event is also a mouse motion event (and therefore effectively only
149 // handling the last of a row of mouse motion events in the event queue)
151 static boolean SkipPressedMouseMotionEvent(const Event *event)
153 // nothing to do if the current event is not a mouse motion event
154 if (event->type != EVENT_MOTIONNOTIFY)
157 // only skip motion events with pressed button outside the game
158 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
165 PeekEvent(&next_event);
167 // if next event is also a mouse motion event, skip the current one
168 if (next_event.type == EVENT_MOTIONNOTIFY)
175 static boolean WaitValidEvent(Event *event)
179 if (!FilterEvents(event))
182 if (SkipPressedMouseMotionEvent(event))
188 /* this is especially needed for event modifications for the Android target:
189 if mouse coordinates should be modified in the event filter function,
190 using a properly installed SDL event filter does not work, because in
191 the event filter, mouse coordinates in the event structure are still
192 physical pixel positions, not logical (scaled) screen positions, so this
193 has to be handled at a later stage in the event processing functions
194 (when device pixel positions are already converted to screen positions) */
196 boolean NextValidEvent(Event *event)
198 while (PendingEvent())
199 if (WaitValidEvent(event))
205 void StopProcessingEvents(void)
207 stop_processing_events = TRUE;
210 static void HandleEvents(void)
213 DelayCounter event_frame_delay = { GAME_FRAME_DELAY };
215 ResetDelayCounter(&event_frame_delay);
217 stop_processing_events = FALSE;
219 while (NextValidEvent(&event))
221 int game_status_last = game_status;
225 case EVENT_BUTTONPRESS:
226 case EVENT_BUTTONRELEASE:
227 HandleButtonEvent((ButtonEvent *) &event);
230 case EVENT_MOTIONNOTIFY:
231 HandleMotionEvent((MotionEvent *) &event);
234 case EVENT_WHEELMOTION:
235 HandleWheelEvent((WheelEvent *) &event);
238 case SDL_WINDOWEVENT:
239 HandleWindowEvent((WindowEvent *) &event);
242 case EVENT_FINGERPRESS:
243 case EVENT_FINGERRELEASE:
244 case EVENT_FINGERMOTION:
245 HandleFingerEvent((FingerEvent *) &event);
248 case EVENT_TEXTINPUT:
249 HandleTextEvent((TextEvent *) &event);
252 case SDL_APP_WILLENTERBACKGROUND:
253 case SDL_APP_DIDENTERBACKGROUND:
254 case SDL_APP_WILLENTERFOREGROUND:
255 case SDL_APP_DIDENTERFOREGROUND:
256 HandlePauseResumeEvent((PauseResumeEvent *) &event);
260 case EVENT_KEYRELEASE:
261 HandleKeyEvent((KeyEvent *) &event);
265 HandleUserEvent((UserEvent *) &event);
269 HandleOtherEvents(&event);
273 // always handle events within delay period if game status has changed
274 if (game_status != game_status_last)
275 ResetDelayCounter(&event_frame_delay);
277 // do not handle events for longer than standard frame delay period
278 if (DelayReached(&event_frame_delay))
281 // do not handle any further events if triggered by a special flag
282 if (stop_processing_events)
287 void HandleOtherEvents(Event *event)
291 case SDL_CONTROLLERBUTTONDOWN:
292 case SDL_CONTROLLERBUTTONUP:
293 // for any game controller button event, disable overlay buttons
294 SetOverlayEnabled(FALSE);
296 HandleSpecialGameControllerButtons(event);
299 case SDL_CONTROLLERDEVICEADDED:
300 case SDL_CONTROLLERDEVICEREMOVED:
301 case SDL_CONTROLLERAXISMOTION:
302 case SDL_JOYAXISMOTION:
303 case SDL_JOYBUTTONDOWN:
304 case SDL_JOYBUTTONUP:
305 HandleJoystickEvent(event);
309 case SDL_DROPCOMPLETE:
312 HandleDropEvent(event);
324 static void HandleMouseCursor(void)
326 if (game_status == GAME_MODE_TITLE)
328 // when showing title screens, hide mouse pointer (if not moved)
330 if (gfx.cursor_mode != CURSOR_NONE &&
331 DelayReached(&special_cursor_delay))
333 SetMouseCursor(CURSOR_NONE);
336 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
339 // when playing, display a special mouse pointer inside the playfield
341 // display normal pointer if mouse pressed
342 if (button_status != MB_RELEASED)
343 ResetDelayCounter(&special_cursor_delay);
345 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
346 cursor_inside_playfield &&
347 special_cursor_enabled &&
348 DelayReached(&special_cursor_delay))
350 SetMouseCursor(CURSOR_PLAYFIELD);
353 else if (gfx.cursor_mode != CURSOR_DEFAULT)
355 SetMouseCursor(CURSOR_DEFAULT);
358 // this is set after all pending events have been processed
359 cursor_mode_last = gfx.cursor_mode;
371 // execute event related actions after pending events have been processed
372 HandleEventActions();
374 // don't use all CPU time when idle; the main loop while playing
375 // has its own synchronization and is CPU friendly, too
377 if (game_status == GAME_MODE_PLAYING)
380 // always copy backbuffer to visible screen for every video frame
383 // reset video frame delay to default (may change again while playing)
384 SetVideoFrameDelay(MenuFrameDelay);
386 if (game_status == GAME_MODE_QUIT)
391 void ClearAutoRepeatKeyEvents(void)
393 while (PendingEvent())
397 PeekEvent(&next_event);
399 // if event is repeated key press event, remove it from event queue
400 if (next_event.type == EVENT_KEYPRESS &&
401 next_event.key.repeat)
402 WaitEvent(&next_event);
408 void ClearEventQueue(void)
412 while (NextValidEvent(&event))
416 case EVENT_BUTTONRELEASE:
417 button_status = MB_RELEASED;
420 case EVENT_FINGERRELEASE:
421 case EVENT_KEYRELEASE:
425 case SDL_CONTROLLERBUTTONUP:
426 HandleJoystickEvent(&event);
431 HandleOtherEvents(&event);
437 static void ClearPlayerMouseAction(void)
439 local_player->mouse_action.lx = 0;
440 local_player->mouse_action.ly = 0;
441 local_player->mouse_action.button = 0;
444 void ClearPlayerAction(void)
448 // simulate key release events for still pressed keys
449 key_joystick_mapping = 0;
450 for (i = 0; i < MAX_PLAYERS; i++)
452 stored_player[i].action = 0;
453 stored_player[i].snap_action = 0;
456 // simulate finger release events for still pressed virtual buttons
457 overlay.grid_button_action = JOY_NO_ACTION;
460 ClearJoystickState();
461 ClearPlayerMouseAction();
464 static void SetPlayerMouseAction(int mx, int my, int button)
466 int lx = getLevelFromScreenX(mx);
467 int ly = getLevelFromScreenY(my);
468 int new_button = (!local_player->mouse_action.button && button);
470 if (local_player->mouse_action.button_hint)
471 button = local_player->mouse_action.button_hint;
473 ClearPlayerMouseAction();
475 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
478 local_player->mouse_action.lx = lx;
479 local_player->mouse_action.ly = ly;
480 local_player->mouse_action.button = button;
482 if (tape.recording && tape.pausing && tape.use_mouse_actions)
484 // un-pause a paused game only if mouse button was newly pressed down
486 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
489 SetTileCursorXY(lx, ly);
492 static Key GetKeyFromGridButton(int grid_button)
494 return (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
495 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
496 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
497 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
498 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
499 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
503 #if defined(PLATFORM_ANDROID)
504 static boolean CheckVirtualButtonPressed(int mx, int my, int button)
506 float touch_x = (float)(mx + video.screen_xoffset) / video.screen_width;
507 float touch_y = (float)(my + video.screen_yoffset) / video.screen_height;
508 int x = touch_x * overlay.grid_xsize;
509 int y = touch_y * overlay.grid_ysize;
510 int grid_button = overlay.grid_button[x][y];
511 Key key = GetKeyFromGridButton(grid_button);
512 int key_status = (button == MB_RELEASED ? KEY_RELEASED : KEY_PRESSED);
514 return (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
518 void HandleButtonEvent(ButtonEvent *event)
520 #if DEBUG_EVENTS_BUTTON
521 Debug("event:button", "button %d %s, x/y %d/%d\n",
523 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
527 // for any mouse button event, disable playfield tile cursor
528 SetTileCursorEnabled(FALSE);
530 // for any mouse button event, disable playfield mouse cursor
531 if (cursor_inside_playfield)
532 SetPlayfieldMouseCursorEnabled(FALSE);
534 #if defined(HAS_SCREEN_KEYBOARD)
535 if (video.shifted_up)
536 event->y += video.shifted_up_pos;
539 motion_status = FALSE;
541 if (event->type == EVENT_BUTTONPRESS)
542 button_status = event->button;
544 button_status = MB_RELEASED;
546 HandleButton(event->x, event->y, button_status, event->button);
549 void HandleMotionEvent(MotionEvent *event)
551 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
554 motion_status = TRUE;
556 #if DEBUG_EVENTS_MOTION
557 Debug("event:motion", "button %d moved, x/y %d/%d\n",
558 button_status, event->x, event->y);
561 HandleButton(event->x, event->y, button_status, button_status);
564 void HandleWheelEvent(WheelEvent *event)
568 #if DEBUG_EVENTS_WHEEL
570 Debug("event:wheel", "mouse == %d, x/y == %d/%d\n",
571 event->which, event->x, event->y);
573 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
574 Debug("event:wheel", "mouse == %d, x/y == %d/%d, direction == %s\n",
575 event->which, event->x, event->y,
576 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
577 "SDL_MOUSEWHEEL_FLIPPED"));
581 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
582 event->x > 0 ? MB_WHEEL_RIGHT :
583 event->y < 0 ? MB_WHEEL_DOWN :
584 event->y > 0 ? MB_WHEEL_UP : 0);
586 #if defined(PLATFORM_WINDOWS) || defined(PLATFORM_MAC)
587 // accelerated mouse wheel available on Mac and Windows
588 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
590 // no accelerated mouse wheel available on Unix/Linux
591 wheel_steps = DEFAULT_WHEEL_STEPS;
594 motion_status = FALSE;
596 button_status = button_nr;
597 HandleButton(0, 0, button_status, -button_nr);
599 button_status = MB_RELEASED;
600 HandleButton(0, 0, button_status, -button_nr);
603 void HandleWindowEvent(WindowEvent *event)
605 #if DEBUG_EVENTS_WINDOW
606 int subtype = event->event;
609 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
610 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
611 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
612 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
613 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
614 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
615 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
616 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
617 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
618 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
619 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
620 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
621 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
622 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
625 Debug("event:window", "name: '%s', data1: %ld, data2: %ld",
626 event_name, event->data1, event->data2);
630 // (not needed, as the screen gets redrawn every 20 ms anyway)
631 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
632 event->event == SDL_WINDOWEVENT_RESIZED ||
633 event->event == SDL_WINDOWEVENT_EXPOSED)
637 if (event->event == SDL_WINDOWEVENT_RESIZED)
639 if (!video.fullscreen_enabled)
641 int new_window_width = event->data1;
642 int new_window_height = event->data2;
644 // if window size has changed after resizing, calculate new scaling factor
645 if (new_window_width != video.window_width ||
646 new_window_height != video.window_height)
648 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
649 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
651 // (extreme window scaling allowed, but cannot be saved permanently)
652 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
653 setup.window_scaling_percent =
654 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
655 MAX_WINDOW_SCALING_PERCENT);
657 video.window_width = new_window_width;
658 video.window_height = new_window_height;
660 if (game_status == GAME_MODE_SETUP)
661 RedrawSetupScreenAfterFullscreenToggle();
663 UpdateMousePosition();
668 #if defined(PLATFORM_ANDROID)
671 int new_display_width = event->data1;
672 int new_display_height = event->data2;
674 // if fullscreen display size has changed, device has been rotated
675 if (new_display_width != video.display_width ||
676 new_display_height != video.display_height)
678 int nr = GRID_ACTIVE_NR(); // previous screen orientation
680 video.display_width = new_display_width;
681 video.display_height = new_display_height;
683 SDLSetScreenProperties();
684 SetGadgetsPosition_OverlayTouchButtons();
686 // check if screen orientation has changed (should always be true here)
687 if (nr != GRID_ACTIVE_NR())
689 if (game_status == GAME_MODE_SETUP)
690 RedrawSetupScreenAfterScreenRotation(nr);
692 SetOverlayGridSizeAndButtons();
700 #define NUM_TOUCH_FINGERS 3
705 SDL_FingerID finger_id;
709 } touch_info[NUM_TOUCH_FINGERS];
711 static void SetTouchInfo(int pos, SDL_FingerID finger_id, int counter,
712 Key key, byte action)
714 touch_info[pos].touched = (action != JOY_NO_ACTION);
715 touch_info[pos].finger_id = finger_id;
716 touch_info[pos].counter = counter;
717 touch_info[pos].key = key;
718 touch_info[pos].action = action;
721 static void ClearTouchInfo(void)
725 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
726 SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
729 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
731 int x = event->x * overlay.grid_xsize;
732 int y = event->y * overlay.grid_ysize;
733 int grid_button = overlay.grid_button[x][y];
734 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
735 Key key = GetKeyFromGridButton(grid_button);
736 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
738 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
742 // for any touch input event, enable overlay buttons (if activated)
743 SetOverlayEnabled(TRUE);
745 Debug("event:finger", "key '%s' was '%s' [fingerId: %lld]",
746 getKeyNameFromKey(key), key_status_name, event->fingerId);
748 if (key_status == KEY_PRESSED)
749 overlay.grid_button_action |= grid_button_action;
751 overlay.grid_button_action &= ~grid_button_action;
753 // check if we already know this touch event's finger id
754 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
756 if (touch_info[i].touched &&
757 touch_info[i].finger_id == event->fingerId)
759 // Debug("event:finger", "MARK 1: %d", i);
765 if (i >= NUM_TOUCH_FINGERS)
767 if (key_status == KEY_PRESSED)
769 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
771 // unknown finger id -- get new, empty slot, if available
772 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
774 if (touch_info[i].counter < oldest_counter)
777 oldest_counter = touch_info[i].counter;
779 // Debug("event:finger", "MARK 2: %d", i);
782 if (!touch_info[i].touched)
784 // Debug("event:finger", "MARK 3: %d", i);
790 if (i >= NUM_TOUCH_FINGERS)
792 // all slots allocated -- use oldest slot
795 // Debug("event:finger", "MARK 4: %d", i);
800 // release of previously unknown key (should not happen)
802 if (key != KSYM_UNDEFINED)
804 HandleKey(key, KEY_RELEASED);
806 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [1]",
807 getKeyNameFromKey(key), "KEY_RELEASED", i);
812 if (i < NUM_TOUCH_FINGERS)
814 if (key_status == KEY_PRESSED)
816 if (touch_info[i].key != key)
818 if (touch_info[i].key != KSYM_UNDEFINED)
820 HandleKey(touch_info[i].key, KEY_RELEASED);
822 // undraw previous grid button when moving finger away
823 overlay.grid_button_action &= ~touch_info[i].action;
825 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [2]",
826 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
829 if (key != KSYM_UNDEFINED)
831 HandleKey(key, KEY_PRESSED);
833 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [3]",
834 getKeyNameFromKey(key), "KEY_PRESSED", i);
838 SetTouchInfo(i, event->fingerId, Counter(), key, grid_button_action);
842 if (touch_info[i].key != KSYM_UNDEFINED)
844 HandleKey(touch_info[i].key, KEY_RELEASED);
846 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [4]",
847 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
850 SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
855 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
857 static Key motion_key_x = KSYM_UNDEFINED;
858 static Key motion_key_y = KSYM_UNDEFINED;
859 static Key button_key = KSYM_UNDEFINED;
860 static float motion_x1, motion_y1;
861 static float button_x1, button_y1;
862 static SDL_FingerID motion_id = -1;
863 static SDL_FingerID button_id = -1;
864 int move_trigger_distance_percent = setup.touch.move_distance;
865 int drop_trigger_distance_percent = setup.touch.drop_distance;
866 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
867 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
868 float event_x = event->x;
869 float event_y = event->y;
871 if (event->type == EVENT_FINGERPRESS)
873 if (event_x > 1.0 / 3.0)
877 motion_id = event->fingerId;
882 motion_key_x = KSYM_UNDEFINED;
883 motion_key_y = KSYM_UNDEFINED;
885 Debug("event:finger", "---------- MOVE STARTED (WAIT) ----------");
891 button_id = event->fingerId;
896 button_key = setup.input[0].key.snap;
898 HandleKey(button_key, KEY_PRESSED);
900 Debug("event:finger", "---------- SNAP STARTED ----------");
903 else if (event->type == EVENT_FINGERRELEASE)
905 if (event->fingerId == motion_id)
909 if (motion_key_x != KSYM_UNDEFINED)
910 HandleKey(motion_key_x, KEY_RELEASED);
911 if (motion_key_y != KSYM_UNDEFINED)
912 HandleKey(motion_key_y, KEY_RELEASED);
914 motion_key_x = KSYM_UNDEFINED;
915 motion_key_y = KSYM_UNDEFINED;
917 Debug("event:finger", "---------- MOVE STOPPED ----------");
919 else if (event->fingerId == button_id)
923 if (button_key != KSYM_UNDEFINED)
924 HandleKey(button_key, KEY_RELEASED);
926 button_key = KSYM_UNDEFINED;
928 Debug("event:finger", "---------- SNAP STOPPED ----------");
931 else if (event->type == EVENT_FINGERMOTION)
933 if (event->fingerId == motion_id)
935 float distance_x = ABS(event_x - motion_x1);
936 float distance_y = ABS(event_y - motion_y1);
937 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
938 event_x > motion_x1 ? setup.input[0].key.right :
940 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
941 event_y > motion_y1 ? setup.input[0].key.down :
944 if (distance_x < move_trigger_distance / 2 ||
945 distance_x < distance_y)
946 new_motion_key_x = KSYM_UNDEFINED;
948 if (distance_y < move_trigger_distance / 2 ||
949 distance_y < distance_x)
950 new_motion_key_y = KSYM_UNDEFINED;
952 if (distance_x > move_trigger_distance ||
953 distance_y > move_trigger_distance)
955 if (new_motion_key_x != motion_key_x)
957 if (motion_key_x != KSYM_UNDEFINED)
958 HandleKey(motion_key_x, KEY_RELEASED);
959 if (new_motion_key_x != KSYM_UNDEFINED)
960 HandleKey(new_motion_key_x, KEY_PRESSED);
963 if (new_motion_key_y != motion_key_y)
965 if (motion_key_y != KSYM_UNDEFINED)
966 HandleKey(motion_key_y, KEY_RELEASED);
967 if (new_motion_key_y != KSYM_UNDEFINED)
968 HandleKey(new_motion_key_y, KEY_PRESSED);
974 motion_key_x = new_motion_key_x;
975 motion_key_y = new_motion_key_y;
977 Debug("event:finger", "---------- MOVE STARTED (MOVE) ----------");
980 else if (event->fingerId == button_id)
982 float distance_x = ABS(event_x - button_x1);
983 float distance_y = ABS(event_y - button_y1);
985 if (distance_x < drop_trigger_distance / 2 &&
986 distance_y > drop_trigger_distance)
988 if (button_key == setup.input[0].key.snap)
989 HandleKey(button_key, KEY_RELEASED);
994 button_key = setup.input[0].key.drop;
996 HandleKey(button_key, KEY_PRESSED);
998 Debug("event:finger", "---------- DROP STARTED ----------");
1004 void HandleFingerEvent(FingerEvent *event)
1006 #if DEBUG_EVENTS_FINGER
1007 Debug("event:finger", "finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
1008 event->type == EVENT_FINGERPRESS ? "pressed" :
1009 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
1013 event->dx, event->dy,
1017 runtime.uses_touch_device = TRUE;
1019 if (game_status != GAME_MODE_PLAYING)
1022 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1024 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1025 local_player->mouse_action.button_hint =
1026 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1027 event->x < 0.5 ? MB_LEFTBUTTON :
1028 event->x > 0.5 ? MB_RIGHTBUTTON :
1034 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1035 HandleFingerEvent_VirtualButtons(event);
1036 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1037 HandleFingerEvent_WipeGestures(event);
1040 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1042 static int old_mx = 0, old_my = 0;
1043 static int last_button = MB_LEFTBUTTON;
1044 static boolean touched = FALSE;
1045 static boolean tapped = FALSE;
1047 // screen tile was tapped (but finger not touching the screen anymore)
1048 // (this point will also be reached without receiving a touch event)
1049 if (tapped && !touched)
1051 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1056 // stop here if this function was not triggered by a touch event
1060 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1062 // finger started touching the screen
1072 ClearPlayerMouseAction();
1074 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1077 else if (button == MB_RELEASED && touched)
1079 // finger stopped touching the screen
1084 SetPlayerMouseAction(old_mx, old_my, last_button);
1086 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1088 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1093 // finger moved while touching the screen
1095 int old_x = getLevelFromScreenX(old_mx);
1096 int old_y = getLevelFromScreenY(old_my);
1097 int new_x = getLevelFromScreenX(mx);
1098 int new_y = getLevelFromScreenY(my);
1100 if (new_x != old_x || new_y != old_y)
1105 // finger moved left or right from (horizontal) starting position
1107 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1109 SetPlayerMouseAction(old_mx, old_my, button_nr);
1111 last_button = button_nr;
1113 Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1117 // finger stays at or returned to (horizontal) starting position
1119 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1121 Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1126 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1128 static int old_mx = 0, old_my = 0;
1129 static int last_button = MB_LEFTBUTTON;
1130 static boolean touched = FALSE;
1131 static boolean tapped = FALSE;
1133 // screen tile was tapped (but finger not touching the screen anymore)
1134 // (this point will also be reached without receiving a touch event)
1135 if (tapped && !touched)
1137 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1142 // stop here if this function was not triggered by a touch event
1146 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1148 // finger started touching the screen
1158 ClearPlayerMouseAction();
1160 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1163 else if (button == MB_RELEASED && touched)
1165 // finger stopped touching the screen
1170 SetPlayerMouseAction(old_mx, old_my, last_button);
1172 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1174 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1179 // finger moved while touching the screen
1181 int old_x = getLevelFromScreenX(old_mx);
1182 int old_y = getLevelFromScreenY(old_my);
1183 int new_x = getLevelFromScreenX(mx);
1184 int new_y = getLevelFromScreenY(my);
1186 if (new_x != old_x || new_y != old_y)
1188 // finger moved away from starting position
1190 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1192 // quickly alternate between clicking and releasing for maximum speed
1193 if (FrameCounter % 2 == 0)
1194 button_nr = MB_RELEASED;
1196 SetPlayerMouseAction(old_mx, old_my, button_nr);
1199 last_button = button_nr;
1203 Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1207 // finger stays at or returned to starting position
1209 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1211 Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1216 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1218 static int old_mx = 0, old_my = 0;
1219 static Key motion_key_x = KSYM_UNDEFINED;
1220 static Key motion_key_y = KSYM_UNDEFINED;
1221 static boolean touched = FALSE;
1222 static boolean started_on_player = FALSE;
1223 static boolean player_is_dropping = FALSE;
1224 static int player_drop_count = 0;
1225 static int last_player_x = -1;
1226 static int last_player_y = -1;
1228 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1237 started_on_player = FALSE;
1238 player_is_dropping = FALSE;
1239 player_drop_count = 0;
1243 motion_key_x = KSYM_UNDEFINED;
1244 motion_key_y = KSYM_UNDEFINED;
1246 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1249 else if (button == MB_RELEASED && touched)
1256 if (motion_key_x != KSYM_UNDEFINED)
1257 HandleKey(motion_key_x, KEY_RELEASED);
1258 if (motion_key_y != KSYM_UNDEFINED)
1259 HandleKey(motion_key_y, KEY_RELEASED);
1261 if (started_on_player)
1263 if (player_is_dropping)
1265 Debug("event:finger", "---------- DROP STOPPED ----------");
1267 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1271 Debug("event:finger", "---------- SNAP STOPPED ----------");
1273 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1277 motion_key_x = KSYM_UNDEFINED;
1278 motion_key_y = KSYM_UNDEFINED;
1280 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1285 int src_x = local_player->jx;
1286 int src_y = local_player->jy;
1287 int dst_x = getLevelFromScreenX(old_mx);
1288 int dst_y = getLevelFromScreenY(old_my);
1289 int dx = dst_x - src_x;
1290 int dy = dst_y - src_y;
1291 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1292 dx > 0 ? setup.input[0].key.right :
1294 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1295 dy > 0 ? setup.input[0].key.down :
1298 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1299 (last_player_x != local_player->jx ||
1300 last_player_y != local_player->jy))
1302 // in case of asymmetric diagonal movement, use "preferred" direction
1304 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1306 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1307 game_em.ply[0]->last_move_dir = last_move_dir;
1309 local_player->last_move_dir = last_move_dir;
1311 // (required to prevent accidentally forcing direction for next movement)
1312 last_player_x = local_player->jx;
1313 last_player_y = local_player->jy;
1316 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1318 started_on_player = TRUE;
1319 player_drop_count = getPlayerInventorySize(0);
1320 player_is_dropping = (player_drop_count > 0);
1322 if (player_is_dropping)
1324 Debug("event:finger", "---------- DROP STARTED ----------");
1326 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1330 Debug("event:finger", "---------- SNAP STARTED ----------");
1332 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1335 else if (dx != 0 || dy != 0)
1337 if (player_is_dropping &&
1338 player_drop_count == getPlayerInventorySize(0))
1340 Debug("event:finger", "---------- DROP -> SNAP ----------");
1342 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1343 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1345 player_is_dropping = FALSE;
1349 if (new_motion_key_x != motion_key_x)
1351 Debug("event:finger", "---------- %s %s ----------",
1352 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1353 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1355 if (motion_key_x != KSYM_UNDEFINED)
1356 HandleKey(motion_key_x, KEY_RELEASED);
1357 if (new_motion_key_x != KSYM_UNDEFINED)
1358 HandleKey(new_motion_key_x, KEY_PRESSED);
1361 if (new_motion_key_y != motion_key_y)
1363 Debug("event:finger", "---------- %s %s ----------",
1364 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1365 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1367 if (motion_key_y != KSYM_UNDEFINED)
1368 HandleKey(motion_key_y, KEY_RELEASED);
1369 if (new_motion_key_y != KSYM_UNDEFINED)
1370 HandleKey(new_motion_key_y, KEY_PRESSED);
1373 motion_key_x = new_motion_key_x;
1374 motion_key_y = new_motion_key_y;
1378 static void HandleButtonOrFinger(int mx, int my, int button)
1380 boolean valid_mouse_event = (mx != -1 && my != -1 && button != -1);
1382 if (game_status != GAME_MODE_PLAYING)
1385 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1387 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1388 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1389 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1390 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1391 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1392 SetPlayerMouseAction(mx, my, button); // special case
1396 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1397 HandleButtonOrFinger_FollowFinger(mx, my, button);
1398 else if (game.use_mouse_actions && valid_mouse_event)
1399 SetPlayerMouseAction(mx, my, button);
1403 static boolean checkTextInputKey(Key key)
1405 // when playing, only handle raw key events and ignore text input
1406 if (game_status == GAME_MODE_PLAYING)
1409 // if Shift or right Alt key is pressed, handle key as text input
1410 if ((GetKeyModState() & KMOD_TextInput) != KMOD_None)
1413 // ignore raw keys as text input when not in text input mode
1414 if (KSYM_RAW(key) && !textinput_status)
1417 // else handle all printable keys as text input
1418 return KSYM_PRINTABLE(key);
1421 void HandleTextEvent(TextEvent *event)
1423 char *text = event->text;
1424 Key key = getKeyFromKeyName(text);
1426 #if DEBUG_EVENTS_TEXT
1427 Debug("event:text", "text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1430 text[0], (int)(text[0]),
1432 getKeyNameFromKey(key),
1436 if (checkTextInputKey(key))
1438 // process printable keys (with uppercase etc.) in text input mode
1439 HandleKey(key, KEY_PRESSED);
1440 HandleKey(key, KEY_RELEASED);
1444 void HandlePauseResumeEvent(PauseResumeEvent *event)
1446 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1450 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1456 void HandleKeyEvent(KeyEvent *event)
1458 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1459 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1460 Key key = GetEventKey(event, with_modifiers);
1461 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1463 #if DEBUG_EVENTS_KEY
1464 Debug("event:key", "key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1465 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1466 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(keymod, 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 // update menu screen (and possibly change current level set)
1556 DrawScreenAfterAddingSet(top_dir, tree_type);
1561 static void HandleDropTextEvent(char *text)
1563 Debug("event:droptext", "text == '%s'", text);
1566 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1567 int num_artwork_sets_succeeded,
1568 int num_files_failed)
1570 // only show request dialog if no other request dialog already active
1571 if (game.request_active)
1574 // this case can happen with drag-and-drop with older SDL versions
1575 if (num_level_sets_succeeded == 0 &&
1576 num_artwork_sets_succeeded == 0 &&
1577 num_files_failed == 0)
1582 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1584 char message_part1[50];
1586 sprintf(message_part1, "New %s set%s added",
1587 (num_artwork_sets_succeeded == 0 ? "level" :
1588 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1589 (num_level_sets_succeeded +
1590 num_artwork_sets_succeeded > 1 ? "s" : ""));
1592 if (num_files_failed > 0)
1593 sprintf(message, "%s, but %d dropped file%s failed!",
1594 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1596 sprintf(message, "%s!", message_part1);
1598 else if (num_files_failed > 0)
1600 sprintf(message, "Failed to process dropped file%s!",
1601 num_files_failed > 1 ? "s" : "");
1604 Request(message, REQ_CONFIRM);
1607 void HandleDropEvent(Event *event)
1609 static boolean confirm_on_drop_complete = FALSE;
1610 static int num_level_sets_succeeded = 0;
1611 static int num_artwork_sets_succeeded = 0;
1612 static int num_files_failed = 0;
1614 switch (event->type)
1618 confirm_on_drop_complete = TRUE;
1619 num_level_sets_succeeded = 0;
1620 num_artwork_sets_succeeded = 0;
1621 num_files_failed = 0;
1628 int tree_type = HandleDropFileEvent(event->drop.file);
1630 if (tree_type == TREE_TYPE_LEVEL_DIR)
1631 num_level_sets_succeeded++;
1632 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1633 tree_type == TREE_TYPE_SOUNDS_DIR ||
1634 tree_type == TREE_TYPE_MUSIC_DIR)
1635 num_artwork_sets_succeeded++;
1639 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1640 if (!confirm_on_drop_complete)
1642 // process all remaining events, including further SDL_DROPFILE events
1645 HandleDropCompleteEvent(num_level_sets_succeeded,
1646 num_artwork_sets_succeeded,
1649 num_level_sets_succeeded = 0;
1650 num_artwork_sets_succeeded = 0;
1651 num_files_failed = 0;
1659 HandleDropTextEvent(event->drop.file);
1664 case SDL_DROPCOMPLETE:
1666 HandleDropCompleteEvent(num_level_sets_succeeded,
1667 num_artwork_sets_succeeded,
1674 if (event->drop.file != NULL)
1675 SDL_free(event->drop.file);
1678 void HandleUserEvent(UserEvent *event)
1680 switch (event->code)
1682 case USEREVENT_ANIM_DELAY_ACTION:
1683 case USEREVENT_ANIM_EVENT_ACTION:
1684 // execute action functions until matching action was found
1685 if (DoKeysymAction(event->value1) ||
1686 DoGadgetAction(event->value1) ||
1687 DoScreenAction(event->value1))
1696 void HandleButton(int mx, int my, int button, int button_nr)
1698 static int old_mx = 0, old_my = 0;
1699 boolean button_hold = FALSE;
1700 boolean handle_gadgets = TRUE;
1706 button_nr = -button_nr;
1715 #if defined(PLATFORM_ANDROID)
1716 // when playing, only handle gadgets when using "follow finger" controls
1717 // or when using touch controls in combination with the MM game engine
1718 // or when using gadgets that do not overlap with virtual buttons
1719 // or when touch controls are disabled (e.g., with mouse-only levels)
1721 (game_status != GAME_MODE_PLAYING ||
1722 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1723 strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF) ||
1724 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1725 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1726 !CheckVirtualButtonPressed(mx, my, button)));
1728 // always recognize potentially releasing already pressed gadgets
1729 if (button == MB_RELEASED)
1730 handle_gadgets = TRUE;
1732 // always recognize pressing or releasing overlay touch buttons
1733 if (CheckPosition_OverlayTouchButtons(mx, my, button) && !motion_status)
1734 handle_gadgets = TRUE;
1737 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1739 // do not handle this button event anymore
1740 return; // force mouse event not to be handled at all
1743 if (handle_gadgets && HandleGadgets(mx, my, button))
1745 // do not handle this button event anymore
1746 mx = my = -32; // force mouse event to be outside screen tiles
1749 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1752 // do not use scroll wheel button events for anything other than gadgets
1753 if (IS_WHEEL_BUTTON(button_nr))
1756 switch (game_status)
1758 case GAME_MODE_TITLE:
1759 HandleTitleScreen(mx, my, 0, 0, button);
1762 case GAME_MODE_MAIN:
1763 HandleMainMenu(mx, my, 0, 0, button);
1766 case GAME_MODE_PSEUDO_TYPENAME:
1767 case GAME_MODE_PSEUDO_TYPENAMES:
1768 HandleTypeName(KSYM_Return);
1771 case GAME_MODE_NAMES:
1772 HandleChoosePlayerName(mx, my, 0, 0, button);
1775 case GAME_MODE_LEVELS:
1776 HandleChooseLevelSet(mx, my, 0, 0, button);
1779 case GAME_MODE_LEVELNR:
1780 HandleChooseLevelNr(mx, my, 0, 0, button);
1783 case GAME_MODE_SCORES:
1784 HandleHallOfFame(mx, my, 0, 0, button);
1787 case GAME_MODE_SCOREINFO:
1788 HandleScoreInfo(mx, my, 0, 0, button);
1791 case GAME_MODE_EDITOR:
1792 HandleLevelEditorIdle();
1795 case GAME_MODE_INFO:
1796 HandleInfoScreen(mx, my, 0, 0, button);
1799 case GAME_MODE_SETUP:
1800 HandleSetupScreen(mx, my, 0, 0, button);
1803 case GAME_MODE_PLAYING:
1804 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1805 HandleButtonOrFinger(mx, my, button);
1807 SetPlayerMouseAction(mx, my, button);
1810 if (button == MB_PRESSED && !motion_status && !button_hold &&
1811 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1812 DumpTileFromScreen(mx, my);
1822 #define MAX_CHEAT_INPUT_LEN 32
1824 static void HandleKeysSpecial(Key key)
1826 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1827 char letter = getCharFromKey(key);
1828 int cheat_input_len = strlen(cheat_input);
1834 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1836 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1837 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1839 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1842 cheat_input[cheat_input_len++] = letter;
1843 cheat_input[cheat_input_len] = '\0';
1845 #if DEBUG_EVENTS_KEY
1846 Debug("event:key:special", "'%s' [%d]", cheat_input, cheat_input_len);
1849 if (game_status == GAME_MODE_MAIN)
1851 if (strSuffix(cheat_input, ":insert-solution-tape") ||
1852 strSuffix(cheat_input, ":ist"))
1854 InsertSolutionTape();
1856 else if (strSuffix(cheat_input, ":play-solution-tape") ||
1857 strSuffix(cheat_input, ":pst"))
1861 else if (strSuffix(cheat_input, ":reload-graphics") ||
1862 strSuffix(cheat_input, ":rg"))
1864 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1867 else if (strSuffix(cheat_input, ":reload-sounds") ||
1868 strSuffix(cheat_input, ":rs"))
1870 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1873 else if (strSuffix(cheat_input, ":reload-music") ||
1874 strSuffix(cheat_input, ":rm"))
1876 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1879 else if (strSuffix(cheat_input, ":reload-artwork") ||
1880 strSuffix(cheat_input, ":ra"))
1882 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1883 1 << ARTWORK_TYPE_SOUNDS |
1884 1 << ARTWORK_TYPE_MUSIC);
1887 else if (strSuffix(cheat_input, ":dump-level") ||
1888 strSuffix(cheat_input, ":dl"))
1892 else if (strSuffix(cheat_input, ":dump-tape") ||
1893 strSuffix(cheat_input, ":dt"))
1897 else if (strSuffix(cheat_input, ":undo-tape") ||
1898 strSuffix(cheat_input, ":ut"))
1902 else if (strSuffix(cheat_input, ":fix-tape") ||
1903 strSuffix(cheat_input, ":ft"))
1905 FixTape_ForceSinglePlayer();
1907 else if (strSuffix(cheat_input, ":save-native-level") ||
1908 strSuffix(cheat_input, ":snl"))
1910 SaveNativeLevel(&level);
1912 else if (strSuffix(cheat_input, ":frames-per-second") ||
1913 strSuffix(cheat_input, ":fps"))
1915 global.show_frames_per_second = !global.show_frames_per_second;
1917 else if (strSuffix(cheat_input, ":xsn"))
1919 tile_cursor.xsn_debug = TRUE;
1922 else if (game_status == GAME_MODE_PLAYING)
1925 if (strSuffix(cheat_input, ".q"))
1926 DEBUG_SetMaximumDynamite();
1929 else if (game_status == GAME_MODE_EDITOR)
1931 if (strSuffix(cheat_input, ":dump-brush") ||
1932 strSuffix(cheat_input, ":DB"))
1936 else if (strSuffix(cheat_input, ":DDB"))
1941 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1943 if (letter == 'x') // copy brush to clipboard (small size)
1945 CopyBrushToClipboard_Small();
1947 else if (letter == 'c') // copy brush to clipboard (normal size)
1949 CopyBrushToClipboard();
1951 else if (letter == 'v') // paste brush from Clipboard
1953 CopyClipboardToBrush();
1955 else if (letter == 'z') // undo or redo last operation
1957 if (GetKeyModState() & KMOD_Shift)
1958 RedoLevelEditorOperation();
1960 UndoLevelEditorOperation();
1965 // special key shortcuts for all game modes
1966 if (strSuffix(cheat_input, ":dump-event-actions") ||
1967 strSuffix(cheat_input, ":dea") ||
1968 strSuffix(cheat_input, ":DEA"))
1970 DumpGadgetIdentifiers();
1971 DumpScreenIdentifiers();
1975 boolean HandleKeysDebug(Key key, int key_status)
1980 if (key_status != KEY_PRESSED)
1983 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1985 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1987 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1989 if (key == setup.debug.frame_delay_key[i] &&
1990 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1992 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1993 setup.debug.frame_delay[i] : setup.game_frame_delay);
1995 if (!setup.debug.frame_delay_game_only)
1996 MenuFrameDelay = GameFrameDelay;
1998 SetVideoFrameDelay(GameFrameDelay);
2000 if (GameFrameDelay > ONE_SECOND_DELAY)
2001 Debug("event:key:debug", "frame delay == %d ms", GameFrameDelay);
2002 else if (GameFrameDelay != 0)
2003 Debug("event:key:debug", "frame delay == %d ms (max. %d fps / %d %%)",
2004 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
2005 GAME_FRAME_DELAY * 100 / GameFrameDelay);
2007 Debug("event:key:debug", "frame delay == 0 ms (maximum speed)");
2014 if (game_status == GAME_MODE_PLAYING)
2018 options.debug = !options.debug;
2020 Debug("event:key:debug", "debug mode %s",
2021 (options.debug ? "enabled" : "disabled"));
2025 else if (key == KSYM_v)
2027 Debug("event:key:debug", "currently using game engine version %d",
2028 game.engine_version);
2038 void HandleKey(Key key, int key_status)
2040 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2041 static boolean ignore_repeated_key = FALSE;
2042 static struct SetupKeyboardInfo ski;
2043 static struct SetupShortcutInfo ssi;
2052 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
2053 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
2054 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
2055 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
2056 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2057 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2062 if (HandleKeysDebug(key, key_status))
2063 return; // do not handle already processed keys again
2065 // map special keys (media keys / remote control buttons) to default keys
2066 if (key == KSYM_PlayPause)
2068 else if (key == KSYM_Select)
2071 HandleSpecialGameControllerKeys(key, key_status);
2073 if (game_status == GAME_MODE_PLAYING)
2075 // only needed for single-step tape recording mode
2076 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2079 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2081 byte key_action = 0;
2082 byte key_snap_action = 0;
2084 if (setup.input[pnr].use_joystick)
2087 ski = setup.input[pnr].key;
2089 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2090 if (key == *key_info[i].key_custom)
2091 key_action |= key_info[i].action;
2093 // use combined snap+direction keys for the first player only
2096 ssi = setup.shortcut;
2098 // also remember normal snap key when handling snap+direction keys
2099 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2101 for (i = 0; i < NUM_DIRECTIONS; i++)
2103 if (key == *key_info[i].key_snap)
2105 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2106 key_snap_action |= key_info[i].action;
2108 tape.property_bits |= TAPE_PROPERTY_TAS_KEYS;
2113 if (key_status == KEY_PRESSED)
2115 stored_player[pnr].action |= key_action;
2116 stored_player[pnr].snap_action |= key_snap_action;
2120 stored_player[pnr].action &= ~key_action;
2121 stored_player[pnr].snap_action &= ~key_snap_action;
2124 // restore snap action if one of several pressed snap keys was released
2125 if (stored_player[pnr].snap_action)
2126 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2128 if (tape.recording && tape.pausing && tape.use_key_actions)
2130 if (tape.single_step)
2132 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2134 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2136 // if snap key already pressed, keep pause mode when releasing
2137 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2138 has_snapped[pnr] = TRUE;
2140 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2142 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2144 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2145 getRedDiskReleaseFlag_SP() == 0)
2147 // add a single inactive frame before dropping starts
2148 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2149 stored_player[pnr].force_dropping = TRUE;
2152 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2154 // if snap key was pressed without direction, leave pause mode
2155 if (!has_snapped[pnr])
2156 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2158 has_snapped[pnr] = FALSE;
2163 // prevent key release events from un-pausing a paused game
2164 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2165 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2169 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2170 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2173 // for any keyboard event, enable playfield mouse cursor
2174 if (key_action && key_status == KEY_PRESSED)
2175 SetPlayfieldMouseCursorEnabled(TRUE);
2180 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2181 if (key == key_info[i].key_default)
2182 joy |= key_info[i].action;
2187 if (key_status == KEY_PRESSED)
2188 key_joystick_mapping |= joy;
2190 key_joystick_mapping &= ~joy;
2195 if (game_status != GAME_MODE_PLAYING)
2196 key_joystick_mapping = 0;
2198 if (key_status == KEY_RELEASED)
2200 // reset flag to ignore repeated "key pressed" events after key release
2201 ignore_repeated_key = FALSE;
2206 if ((key == KSYM_F11 ||
2207 ((key == KSYM_Return ||
2208 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2209 video.fullscreen_available &&
2210 !ignore_repeated_key)
2212 setup.fullscreen = !setup.fullscreen;
2214 ToggleFullscreenIfNeeded();
2216 if (game_status == GAME_MODE_SETUP)
2217 RedrawSetupScreenAfterFullscreenToggle();
2219 UpdateMousePosition();
2221 // set flag to ignore repeated "key pressed" events
2222 ignore_repeated_key = TRUE;
2227 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2228 key == KSYM_minus || key == KSYM_KP_Subtract ||
2229 key == KSYM_plus || key == KSYM_KP_Add ||
2230 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2231 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2232 video.window_scaling_available &&
2233 !video.fullscreen_enabled)
2235 if (key == KSYM_0 || key == KSYM_KP_0)
2236 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2237 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2238 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2240 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2242 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2243 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2244 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2245 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2247 ChangeWindowScalingIfNeeded();
2249 if (game_status == GAME_MODE_SETUP)
2250 RedrawSetupScreenAfterFullscreenToggle();
2252 UpdateMousePosition();
2257 // some key events are handled like clicks for global animations
2258 boolean click = (key == KSYM_space ||
2259 key == KSYM_Return ||
2260 key == KSYM_Escape);
2262 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2264 // do not handle this key event anymore
2265 if (key != KSYM_Escape) // always allow ESC key to be handled
2269 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2270 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2277 if (game_status == GAME_MODE_MAIN &&
2278 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2280 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2285 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2287 if (key == setup.shortcut.save_game)
2289 else if (key == setup.shortcut.load_game)
2291 else if (key == setup.shortcut.restart_game)
2293 else if (key == setup.shortcut.pause_before_end)
2294 TapeReplayAndPauseBeforeEnd();
2295 else if (key == setup.shortcut.toggle_pause)
2296 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2298 HandleTapeButtonKeys(key);
2299 HandleSoundButtonKeys(key);
2302 if (game_status == GAME_MODE_SCOREINFO)
2304 HandleScreenGadgetKeys(key);
2307 if (game_status == GAME_MODE_PLAYING && !network_playing)
2309 int centered_player_nr_next = -999;
2311 if (key == setup.shortcut.focus_player_all)
2312 centered_player_nr_next = -1;
2314 for (i = 0; i < MAX_PLAYERS; i++)
2315 if (key == setup.shortcut.focus_player[i])
2316 centered_player_nr_next = i;
2318 if (centered_player_nr_next != -999)
2320 game.centered_player_nr_next = centered_player_nr_next;
2321 game.set_centered_player = TRUE;
2325 tape.centered_player_nr_next = game.centered_player_nr_next;
2326 tape.set_centered_player = TRUE;
2331 HandleKeysSpecial(key);
2333 if (HandleGadgetsKeyInput(key))
2334 return; // do not handle already processed keys again
2336 // special case: on "space" key, either continue playing or go to main menu
2337 if (game_status == GAME_MODE_SCORES && key == KSYM_space)
2339 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CONTINUE);
2344 switch (game_status)
2346 case GAME_MODE_PSEUDO_TYPENAME:
2347 case GAME_MODE_PSEUDO_TYPENAMES:
2348 HandleTypeName(key);
2351 case GAME_MODE_TITLE:
2352 case GAME_MODE_MAIN:
2353 case GAME_MODE_NAMES:
2354 case GAME_MODE_LEVELS:
2355 case GAME_MODE_LEVELNR:
2356 case GAME_MODE_SETUP:
2357 case GAME_MODE_INFO:
2358 case GAME_MODE_SCORES:
2359 case GAME_MODE_SCOREINFO:
2361 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2368 if (game_status == GAME_MODE_TITLE)
2369 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2370 else if (game_status == GAME_MODE_MAIN)
2371 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2372 else if (game_status == GAME_MODE_NAMES)
2373 HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_CHOICE);
2374 else if (game_status == GAME_MODE_LEVELS)
2375 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2376 else if (game_status == GAME_MODE_LEVELNR)
2377 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2378 else if (game_status == GAME_MODE_SETUP)
2379 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2380 else if (game_status == GAME_MODE_INFO)
2381 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2382 else if (game_status == GAME_MODE_SCORES)
2383 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2384 else if (game_status == GAME_MODE_SCOREINFO)
2385 HandleScoreInfo(0, 0, 0, 0, MB_MENU_CHOICE);
2389 if (game_status != GAME_MODE_MAIN)
2390 FadeSkipNextFadeIn();
2392 if (game_status == GAME_MODE_TITLE)
2393 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2394 else if (game_status == GAME_MODE_NAMES)
2395 HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_LEAVE);
2396 else if (game_status == GAME_MODE_LEVELS)
2397 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2398 else if (game_status == GAME_MODE_LEVELNR)
2399 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2400 else if (game_status == GAME_MODE_SETUP)
2401 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2402 else if (game_status == GAME_MODE_INFO)
2403 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2404 else if (game_status == GAME_MODE_SCORES)
2405 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2406 else if (game_status == GAME_MODE_SCOREINFO)
2407 HandleScoreInfo(0, 0, 0, 0, MB_MENU_LEAVE);
2411 if (game_status == GAME_MODE_NAMES)
2412 HandleChoosePlayerName(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2413 else if (game_status == GAME_MODE_LEVELS)
2414 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2415 else if (game_status == GAME_MODE_LEVELNR)
2416 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2417 else if (game_status == GAME_MODE_SETUP)
2418 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2419 else if (game_status == GAME_MODE_INFO)
2420 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2421 else if (game_status == GAME_MODE_SCORES)
2422 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2423 else if (game_status == GAME_MODE_SCOREINFO)
2424 HandleScoreInfo(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2427 case KSYM_Page_Down:
2428 if (game_status == GAME_MODE_NAMES)
2429 HandleChoosePlayerName(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2430 else if (game_status == GAME_MODE_LEVELS)
2431 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2432 else if (game_status == GAME_MODE_LEVELNR)
2433 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2434 else if (game_status == GAME_MODE_SETUP)
2435 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2436 else if (game_status == GAME_MODE_INFO)
2437 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2438 else if (game_status == GAME_MODE_SCORES)
2439 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2440 else if (game_status == GAME_MODE_SCOREINFO)
2441 HandleScoreInfo(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2449 case GAME_MODE_EDITOR:
2450 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2451 HandleLevelEditorKeyInput(key);
2454 case GAME_MODE_PLAYING:
2459 RequestQuitGame(TRUE);
2469 if (key == KSYM_Escape)
2471 SetGameStatus(GAME_MODE_MAIN);
2480 void HandleNoEvent(void)
2482 HandleMouseCursor();
2484 switch (game_status)
2486 case GAME_MODE_PLAYING:
2487 HandleButtonOrFinger(-1, -1, -1);
2492 void HandleEventActions(void)
2494 // if (button_status && game_status != GAME_MODE_PLAYING)
2495 if (button_status && (game_status != GAME_MODE_PLAYING ||
2497 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2499 HandleButton(0, 0, button_status, -button_status);
2506 if (network.enabled)
2509 switch (game_status)
2511 case GAME_MODE_MAIN:
2512 DrawPreviewLevelAnimation();
2515 case GAME_MODE_EDITOR:
2516 HandleLevelEditorIdle();
2524 static void HandleTileCursor(int dx, int dy, int button)
2527 ClearPlayerMouseAction();
2534 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2535 (dx < 0 ? MB_LEFTBUTTON :
2536 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2538 else if (!tile_cursor.moving)
2540 int old_xpos = tile_cursor.xpos;
2541 int old_ypos = tile_cursor.ypos;
2542 int new_xpos = old_xpos;
2543 int new_ypos = old_ypos;
2545 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2546 new_xpos = old_xpos + dx;
2548 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2549 new_ypos = old_ypos + dy;
2551 SetTileCursorTargetXY(new_xpos, new_ypos);
2555 static int HandleJoystickForAllPlayers(void)
2559 boolean no_joysticks_configured = TRUE;
2560 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2561 static byte joy_action_last[MAX_PLAYERS];
2563 for (i = 0; i < MAX_PLAYERS; i++)
2564 if (setup.input[i].use_joystick)
2565 no_joysticks_configured = FALSE;
2567 // if no joysticks configured, map connected joysticks to players
2568 if (no_joysticks_configured)
2569 use_as_joystick_nr = TRUE;
2571 for (i = 0; i < MAX_PLAYERS; i++)
2573 byte joy_action = 0;
2575 joy_action = JoystickExt(i, use_as_joystick_nr);
2576 result |= joy_action;
2578 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2579 joy_action != joy_action_last[i])
2580 stored_player[i].action = joy_action;
2582 joy_action_last[i] = joy_action;
2588 void HandleJoystick(void)
2590 static DelayCounter joytest_delay = { GADGET_FRAME_DELAY };
2591 static int joytest_last = 0;
2592 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2593 int delay_value = GADGET_FRAME_DELAY;
2594 int joystick = HandleJoystickForAllPlayers();
2595 int keyboard = key_joystick_mapping;
2596 int joy = (joystick | keyboard);
2597 int joytest = joystick;
2598 int left = joy & JOY_LEFT;
2599 int right = joy & JOY_RIGHT;
2600 int up = joy & JOY_UP;
2601 int down = joy & JOY_DOWN;
2602 int button = joy & JOY_BUTTON;
2603 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2604 int dx = (left ? -1 : right ? 1 : 0);
2605 int dy = (up ? -1 : down ? 1 : 0);
2606 boolean use_delay_value_first = (joytest != joytest_last);
2608 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2610 // do not handle this button event anymore
2614 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2615 game_status == GAME_MODE_PSEUDO_TYPENAMES ||
2616 anyTextGadgetActive()))
2618 // leave name input in main menu or text input gadget
2619 HandleKey(KSYM_Escape, KEY_PRESSED);
2620 HandleKey(KSYM_Escape, KEY_RELEASED);
2625 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2627 if (game_status == GAME_MODE_PLAYING)
2629 // when playing MM style levels, also use delay for keyboard events
2630 joytest |= keyboard;
2632 // only use first delay value for new events, but not for changed events
2633 use_delay_value_first = (!joytest != !joytest_last);
2635 // only use delay after the initial keyboard event
2639 // for any joystick or keyboard event, enable playfield tile cursor
2640 if (dx || dy || button)
2641 SetTileCursorEnabled(TRUE);
2644 // for any joystick event, enable playfield mouse cursor
2645 if (dx || dy || button)
2646 SetPlayfieldMouseCursorEnabled(TRUE);
2648 if (joytest && !button && !DelayReached(&joytest_delay))
2650 // delay joystick/keyboard actions if axes/keys continually pressed
2651 newbutton = dx = dy = 0;
2655 // first start with longer delay, then continue with shorter delay
2656 joytest_delay.value =
2657 (use_delay_value_first ? delay_value_first : delay_value);
2660 joytest_last = joytest;
2662 switch (game_status)
2664 case GAME_MODE_TITLE:
2665 case GAME_MODE_MAIN:
2666 case GAME_MODE_NAMES:
2667 case GAME_MODE_LEVELS:
2668 case GAME_MODE_LEVELNR:
2669 case GAME_MODE_SETUP:
2670 case GAME_MODE_INFO:
2671 case GAME_MODE_SCORES:
2672 case GAME_MODE_SCOREINFO:
2674 if (anyTextGadgetActive())
2677 if (game_status == GAME_MODE_TITLE)
2678 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2679 else if (game_status == GAME_MODE_MAIN)
2680 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2681 else if (game_status == GAME_MODE_NAMES)
2682 HandleChoosePlayerName(0,0,dx,dy,newbutton?MB_MENU_CHOICE:MB_MENU_MARK);
2683 else if (game_status == GAME_MODE_LEVELS)
2684 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2685 else if (game_status == GAME_MODE_LEVELNR)
2686 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2687 else if (game_status == GAME_MODE_SETUP)
2688 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2689 else if (game_status == GAME_MODE_INFO)
2690 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2691 else if (game_status == GAME_MODE_SCORES)
2692 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2693 else if (game_status == GAME_MODE_SCOREINFO)
2694 HandleScoreInfo(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2699 case GAME_MODE_PLAYING:
2701 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2702 if (tape.playing || keyboard)
2703 newbutton = ((joy & JOY_BUTTON) != 0);
2706 if (newbutton && game.all_players_gone)
2713 if (tape.recording && tape.pausing && tape.use_key_actions)
2715 if (tape.single_step)
2717 if (joystick & JOY_ACTION)
2718 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2722 if (joystick & JOY_ACTION)
2723 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2727 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2728 HandleTileCursor(dx, dy, button);
2737 void HandleSpecialGameControllerButtons(Event *event)
2742 switch (event->type)
2744 case SDL_CONTROLLERBUTTONDOWN:
2745 key_status = KEY_PRESSED;
2748 case SDL_CONTROLLERBUTTONUP:
2749 key_status = KEY_RELEASED;
2756 switch (event->cbutton.button)
2758 case SDL_CONTROLLER_BUTTON_START:
2762 case SDL_CONTROLLER_BUTTON_BACK:
2770 HandleKey(key, key_status);
2773 void HandleSpecialGameControllerKeys(Key key, int key_status)
2775 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2776 int button = SDL_CONTROLLER_BUTTON_INVALID;
2778 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2779 if (key == KSYM_Rewind)
2780 button = SDL_CONTROLLER_BUTTON_A;
2781 else if (key == KSYM_FastForward || key == KSYM_Menu)
2782 button = SDL_CONTROLLER_BUTTON_B;
2784 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2788 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2789 SDL_CONTROLLERBUTTONUP);
2791 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2792 event.cbutton.button = button;
2793 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2796 HandleJoystickEvent(&event);
2801 boolean DoKeysymAction(int keysym)
2805 Key key = (Key)(-keysym);
2807 HandleKey(key, KEY_PRESSED);
2808 HandleKey(key, KEY_RELEASED);