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 unsigned int special_cursor_delay = 0;
40 static unsigned int special_cursor_delay_value = 1000;
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 // event filter to set mouse x/y position (for pointer class global animations)
52 // (this is especially required to ensure smooth global animation mouse pointer
53 // movement when the screen is updated without handling events; this can happen
54 // when drawing door/envelope request animations, for example)
56 int FilterMouseMotionEvents(void *userdata, Event *event)
58 if (event->type == EVENT_MOTIONNOTIFY)
60 int mouse_x = ((MotionEvent *)event)->x;
61 int mouse_y = ((MotionEvent *)event)->y;
63 UpdateRawMousePosition(mouse_x, mouse_y);
69 // event filter especially needed for SDL event filtering due to
70 // delay problems with lots of mouse motion events when mouse button
71 // not pressed (X11 can handle this with 'PointerMotionHintMask')
73 // event filter addition for SDL2: as SDL2 does not have a function to enable
74 // or disable keyboard auto-repeat, filter repeated keyboard events instead
76 static int FilterEvents(const Event *event)
80 // skip repeated key press events if keyboard auto-repeat is disabled
81 if (event->type == EVENT_KEYPRESS &&
86 if (event->type == EVENT_BUTTONPRESS ||
87 event->type == EVENT_BUTTONRELEASE)
89 ((ButtonEvent *)event)->x -= video.screen_xoffset;
90 ((ButtonEvent *)event)->y -= video.screen_yoffset;
92 else if (event->type == EVENT_MOTIONNOTIFY)
94 ((MotionEvent *)event)->x -= video.screen_xoffset;
95 ((MotionEvent *)event)->y -= video.screen_yoffset;
98 if (event->type == EVENT_BUTTONPRESS ||
99 event->type == EVENT_BUTTONRELEASE ||
100 event->type == EVENT_MOTIONNOTIFY)
102 // do not reset mouse cursor before all pending events have been processed
103 if (gfx.cursor_mode == cursor_mode_last &&
104 ((game_status == GAME_MODE_TITLE &&
105 gfx.cursor_mode == CURSOR_NONE) ||
106 (game_status == GAME_MODE_PLAYING &&
107 gfx.cursor_mode == CURSOR_PLAYFIELD)))
109 SetMouseCursor(CURSOR_DEFAULT);
111 ResetDelayCounter(&special_cursor_delay);
113 cursor_mode_last = CURSOR_DEFAULT;
117 // non-motion events are directly passed to event handler functions
118 if (event->type != EVENT_MOTIONNOTIFY)
121 motion = (MotionEvent *)event;
122 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
123 motion->y >= SY && motion->y < SY + SYSIZE);
125 // set correct mouse x/y position (for pointer class global animations)
126 // (this is required in rare cases where the mouse x/y position calculated
127 // from raw values (to apply logical screen size scaling corrections) does
128 // not match the final mouse event x/y position -- this may happen because
129 // the SDL renderer's viewport position is internally represented as float,
130 // but only accessible as integer, which may lead to rounding errors)
131 gfx.mouse_x = motion->x;
132 gfx.mouse_y = motion->y;
134 // skip mouse motion events without pressed button outside level editor
135 if (button_status == MB_RELEASED &&
136 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
142 // to prevent delay problems, skip mouse motion events if the very next
143 // event is also a mouse motion event (and therefore effectively only
144 // handling the last of a row of mouse motion events in the event queue)
146 static boolean SkipPressedMouseMotionEvent(const Event *event)
148 // nothing to do if the current event is not a mouse motion event
149 if (event->type != EVENT_MOTIONNOTIFY)
152 // only skip motion events with pressed button outside the game
153 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
160 PeekEvent(&next_event);
162 // if next event is also a mouse motion event, skip the current one
163 if (next_event.type == EVENT_MOTIONNOTIFY)
170 static boolean WaitValidEvent(Event *event)
174 if (!FilterEvents(event))
177 if (SkipPressedMouseMotionEvent(event))
183 /* this is especially needed for event modifications for the Android target:
184 if mouse coordinates should be modified in the event filter function,
185 using a properly installed SDL event filter does not work, because in
186 the event filter, mouse coordinates in the event structure are still
187 physical pixel positions, not logical (scaled) screen positions, so this
188 has to be handled at a later stage in the event processing functions
189 (when device pixel positions are already converted to screen positions) */
191 boolean NextValidEvent(Event *event)
193 while (PendingEvent())
194 if (WaitValidEvent(event))
200 void StopProcessingEvents(void)
202 stop_processing_events = TRUE;
205 static void HandleEvents(void)
208 unsigned int event_frame_delay = 0;
209 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
211 ResetDelayCounter(&event_frame_delay);
213 stop_processing_events = FALSE;
215 while (NextValidEvent(&event))
217 int game_status_last = game_status;
221 case EVENT_BUTTONPRESS:
222 case EVENT_BUTTONRELEASE:
223 HandleButtonEvent((ButtonEvent *) &event);
226 case EVENT_MOTIONNOTIFY:
227 HandleMotionEvent((MotionEvent *) &event);
230 case EVENT_WHEELMOTION:
231 HandleWheelEvent((WheelEvent *) &event);
234 case SDL_WINDOWEVENT:
235 HandleWindowEvent((WindowEvent *) &event);
238 case EVENT_FINGERPRESS:
239 case EVENT_FINGERRELEASE:
240 case EVENT_FINGERMOTION:
241 HandleFingerEvent((FingerEvent *) &event);
244 case EVENT_TEXTINPUT:
245 HandleTextEvent((TextEvent *) &event);
248 case SDL_APP_WILLENTERBACKGROUND:
249 case SDL_APP_DIDENTERBACKGROUND:
250 case SDL_APP_WILLENTERFOREGROUND:
251 case SDL_APP_DIDENTERFOREGROUND:
252 HandlePauseResumeEvent((PauseResumeEvent *) &event);
256 case EVENT_KEYRELEASE:
257 HandleKeyEvent((KeyEvent *) &event);
261 HandleUserEvent((UserEvent *) &event);
265 HandleOtherEvents(&event);
269 // always handle events within delay period if game status has changed
270 if (game_status != game_status_last)
271 ResetDelayCounter(&event_frame_delay);
273 // do not handle events for longer than standard frame delay period
274 if (DelayReached(&event_frame_delay, event_frame_delay_value))
277 // do not handle any further events if triggered by a special flag
278 if (stop_processing_events)
283 void HandleOtherEvents(Event *event)
287 case SDL_CONTROLLERBUTTONDOWN:
288 case SDL_CONTROLLERBUTTONUP:
289 // for any game controller button event, disable overlay buttons
290 SetOverlayEnabled(FALSE);
292 HandleSpecialGameControllerButtons(event);
295 case SDL_CONTROLLERDEVICEADDED:
296 case SDL_CONTROLLERDEVICEREMOVED:
297 case SDL_CONTROLLERAXISMOTION:
298 case SDL_JOYAXISMOTION:
299 case SDL_JOYBUTTONDOWN:
300 case SDL_JOYBUTTONUP:
301 HandleJoystickEvent(event);
305 case SDL_DROPCOMPLETE:
308 HandleDropEvent(event);
320 static void HandleMouseCursor(void)
322 if (game_status == GAME_MODE_TITLE)
324 // when showing title screens, hide mouse pointer (if not moved)
326 if (gfx.cursor_mode != CURSOR_NONE &&
327 DelayReached(&special_cursor_delay, special_cursor_delay_value))
329 SetMouseCursor(CURSOR_NONE);
332 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
335 // when playing, display a special mouse pointer inside the playfield
337 // display normal pointer if mouse pressed
338 if (button_status != MB_RELEASED)
339 ResetDelayCounter(&special_cursor_delay);
341 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
342 cursor_inside_playfield &&
343 DelayReached(&special_cursor_delay, special_cursor_delay_value))
345 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
347 SetMouseCursor(CURSOR_PLAYFIELD);
350 else if (gfx.cursor_mode != CURSOR_DEFAULT)
352 SetMouseCursor(CURSOR_DEFAULT);
355 // this is set after all pending events have been processed
356 cursor_mode_last = gfx.cursor_mode;
368 // execute event related actions after pending events have been processed
369 HandleEventActions();
371 // don't use all CPU time when idle; the main loop while playing
372 // has its own synchronization and is CPU friendly, too
374 if (game_status == GAME_MODE_PLAYING)
377 // always copy backbuffer to visible screen for every video frame
380 // reset video frame delay to default (may change again while playing)
381 SetVideoFrameDelay(MenuFrameDelay);
383 if (game_status == GAME_MODE_QUIT)
388 void ClearAutoRepeatKeyEvents(void)
390 while (PendingEvent())
394 PeekEvent(&next_event);
396 // if event is repeated key press event, remove it from event queue
397 if (next_event.type == EVENT_KEYPRESS &&
398 next_event.key.repeat)
399 WaitEvent(&next_event);
405 void ClearEventQueue(void)
409 while (NextValidEvent(&event))
413 case EVENT_BUTTONRELEASE:
414 button_status = MB_RELEASED;
417 case EVENT_FINGERRELEASE:
418 case EVENT_KEYRELEASE:
422 case SDL_CONTROLLERBUTTONUP:
423 HandleJoystickEvent(&event);
428 HandleOtherEvents(&event);
434 static void ClearPlayerMouseAction(void)
436 local_player->mouse_action.lx = 0;
437 local_player->mouse_action.ly = 0;
438 local_player->mouse_action.button = 0;
441 void ClearPlayerAction(void)
445 // simulate key release events for still pressed keys
446 key_joystick_mapping = 0;
447 for (i = 0; i < MAX_PLAYERS; i++)
449 stored_player[i].action = 0;
450 stored_player[i].snap_action = 0;
453 // simulate finger release events for still pressed virtual buttons
454 overlay.grid_button_action = JOY_NO_ACTION;
457 ClearJoystickState();
458 ClearPlayerMouseAction();
461 static void SetPlayerMouseAction(int mx, int my, int button)
463 int lx = getLevelFromScreenX(mx);
464 int ly = getLevelFromScreenY(my);
465 int new_button = (!local_player->mouse_action.button && button);
467 if (local_player->mouse_action.button_hint)
468 button = local_player->mouse_action.button_hint;
470 ClearPlayerMouseAction();
472 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
475 local_player->mouse_action.lx = lx;
476 local_player->mouse_action.ly = ly;
477 local_player->mouse_action.button = button;
479 if (tape.recording && tape.pausing && tape.use_mouse_actions)
481 // un-pause a paused game only if mouse button was newly pressed down
483 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
486 SetTileCursorXY(lx, ly);
489 static Key GetKeyFromGridButton(int grid_button)
491 return (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
492 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
493 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
494 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
495 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
496 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
500 #if defined(PLATFORM_ANDROID)
501 static boolean CheckVirtualButtonPressed(int mx, int my, int button)
503 float touch_x = (float)(mx + video.screen_xoffset) / video.screen_width;
504 float touch_y = (float)(my + video.screen_yoffset) / video.screen_height;
505 int x = touch_x * overlay.grid_xsize;
506 int y = touch_y * overlay.grid_ysize;
507 int grid_button = overlay.grid_button[x][y];
508 Key key = GetKeyFromGridButton(grid_button);
509 int key_status = (button == MB_RELEASED ? KEY_RELEASED : KEY_PRESSED);
511 return (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
515 void HandleButtonEvent(ButtonEvent *event)
517 #if DEBUG_EVENTS_BUTTON
518 Debug("event:button", "button %d %s, x/y %d/%d\n",
520 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
524 // for any mouse button event, disable playfield tile cursor
525 SetTileCursorEnabled(FALSE);
527 #if defined(HAS_SCREEN_KEYBOARD)
528 if (video.shifted_up)
529 event->y += video.shifted_up_pos;
532 motion_status = FALSE;
534 if (event->type == EVENT_BUTTONPRESS)
535 button_status = event->button;
537 button_status = MB_RELEASED;
539 HandleButton(event->x, event->y, button_status, event->button);
542 void HandleMotionEvent(MotionEvent *event)
544 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
547 motion_status = TRUE;
549 #if DEBUG_EVENTS_MOTION
550 Debug("event:motion", "button %d moved, x/y %d/%d\n",
551 button_status, event->x, event->y);
554 HandleButton(event->x, event->y, button_status, button_status);
557 void HandleWheelEvent(WheelEvent *event)
561 #if DEBUG_EVENTS_WHEEL
563 Debug("event:wheel", "mouse == %d, x/y == %d/%d\n",
564 event->which, event->x, event->y);
566 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
567 Debug("event:wheel", "mouse == %d, x/y == %d/%d, direction == %s\n",
568 event->which, event->x, event->y,
569 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
570 "SDL_MOUSEWHEEL_FLIPPED"));
574 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
575 event->x > 0 ? MB_WHEEL_RIGHT :
576 event->y < 0 ? MB_WHEEL_DOWN :
577 event->y > 0 ? MB_WHEEL_UP : 0);
579 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
580 // accelerated mouse wheel available on Mac and Windows
581 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
583 // no accelerated mouse wheel available on Unix/Linux
584 wheel_steps = DEFAULT_WHEEL_STEPS;
587 motion_status = FALSE;
589 button_status = button_nr;
590 HandleButton(0, 0, button_status, -button_nr);
592 button_status = MB_RELEASED;
593 HandleButton(0, 0, button_status, -button_nr);
596 void HandleWindowEvent(WindowEvent *event)
598 #if DEBUG_EVENTS_WINDOW
599 int subtype = event->event;
602 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
603 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
604 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
605 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
606 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
607 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
608 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
609 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
610 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
611 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
612 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
613 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
614 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
615 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
618 Debug("event:window", "name: '%s', data1: %ld, data2: %ld",
619 event_name, event->data1, event->data2);
623 // (not needed, as the screen gets redrawn every 20 ms anyway)
624 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
625 event->event == SDL_WINDOWEVENT_RESIZED ||
626 event->event == SDL_WINDOWEVENT_EXPOSED)
630 if (event->event == SDL_WINDOWEVENT_RESIZED)
632 if (!video.fullscreen_enabled)
634 int new_window_width = event->data1;
635 int new_window_height = event->data2;
637 // if window size has changed after resizing, calculate new scaling factor
638 if (new_window_width != video.window_width ||
639 new_window_height != video.window_height)
641 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
642 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
644 // (extreme window scaling allowed, but cannot be saved permanently)
645 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
646 setup.window_scaling_percent =
647 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
648 MAX_WINDOW_SCALING_PERCENT);
650 video.window_width = new_window_width;
651 video.window_height = new_window_height;
653 if (game_status == GAME_MODE_SETUP)
654 RedrawSetupScreenAfterFullscreenToggle();
656 UpdateMousePosition();
661 #if defined(PLATFORM_ANDROID)
664 int new_display_width = event->data1;
665 int new_display_height = event->data2;
667 // if fullscreen display size has changed, device has been rotated
668 if (new_display_width != video.display_width ||
669 new_display_height != video.display_height)
671 int nr = GRID_ACTIVE_NR(); // previous screen orientation
673 video.display_width = new_display_width;
674 video.display_height = new_display_height;
676 SDLSetScreenProperties();
677 SetGadgetsPosition_OverlayTouchButtons();
679 // check if screen orientation has changed (should always be true here)
680 if (nr != GRID_ACTIVE_NR())
682 if (game_status == GAME_MODE_SETUP)
683 RedrawSetupScreenAfterScreenRotation(nr);
685 SetOverlayGridSizeAndButtons();
693 #define NUM_TOUCH_FINGERS 3
698 SDL_FingerID finger_id;
702 } touch_info[NUM_TOUCH_FINGERS];
704 static void SetTouchInfo(int pos, SDL_FingerID finger_id, int counter,
705 Key key, byte action)
707 touch_info[pos].touched = (action != JOY_NO_ACTION);
708 touch_info[pos].finger_id = finger_id;
709 touch_info[pos].counter = counter;
710 touch_info[pos].key = key;
711 touch_info[pos].action = action;
714 static void ClearTouchInfo(void)
718 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
719 SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
722 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
724 int x = event->x * overlay.grid_xsize;
725 int y = event->y * overlay.grid_ysize;
726 int grid_button = overlay.grid_button[x][y];
727 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
728 Key key = GetKeyFromGridButton(grid_button);
729 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
731 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
735 // for any touch input event, enable overlay buttons (if activated)
736 SetOverlayEnabled(TRUE);
738 Debug("event:finger", "key '%s' was '%s' [fingerId: %lld]",
739 getKeyNameFromKey(key), key_status_name, event->fingerId);
741 if (key_status == KEY_PRESSED)
742 overlay.grid_button_action |= grid_button_action;
744 overlay.grid_button_action &= ~grid_button_action;
746 // check if we already know this touch event's finger id
747 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
749 if (touch_info[i].touched &&
750 touch_info[i].finger_id == event->fingerId)
752 // Debug("event:finger", "MARK 1: %d", i);
758 if (i >= NUM_TOUCH_FINGERS)
760 if (key_status == KEY_PRESSED)
762 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
764 // unknown finger id -- get new, empty slot, if available
765 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
767 if (touch_info[i].counter < oldest_counter)
770 oldest_counter = touch_info[i].counter;
772 // Debug("event:finger", "MARK 2: %d", i);
775 if (!touch_info[i].touched)
777 // Debug("event:finger", "MARK 3: %d", i);
783 if (i >= NUM_TOUCH_FINGERS)
785 // all slots allocated -- use oldest slot
788 // Debug("event:finger", "MARK 4: %d", i);
793 // release of previously unknown key (should not happen)
795 if (key != KSYM_UNDEFINED)
797 HandleKey(key, KEY_RELEASED);
799 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [1]",
800 getKeyNameFromKey(key), "KEY_RELEASED", i);
805 if (i < NUM_TOUCH_FINGERS)
807 if (key_status == KEY_PRESSED)
809 if (touch_info[i].key != key)
811 if (touch_info[i].key != KSYM_UNDEFINED)
813 HandleKey(touch_info[i].key, KEY_RELEASED);
815 // undraw previous grid button when moving finger away
816 overlay.grid_button_action &= ~touch_info[i].action;
818 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [2]",
819 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
822 if (key != KSYM_UNDEFINED)
824 HandleKey(key, KEY_PRESSED);
826 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [3]",
827 getKeyNameFromKey(key), "KEY_PRESSED", i);
831 SetTouchInfo(i, event->fingerId, Counter(), key, grid_button_action);
835 if (touch_info[i].key != KSYM_UNDEFINED)
837 HandleKey(touch_info[i].key, KEY_RELEASED);
839 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [4]",
840 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
843 SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
848 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
850 static Key motion_key_x = KSYM_UNDEFINED;
851 static Key motion_key_y = KSYM_UNDEFINED;
852 static Key button_key = KSYM_UNDEFINED;
853 static float motion_x1, motion_y1;
854 static float button_x1, button_y1;
855 static SDL_FingerID motion_id = -1;
856 static SDL_FingerID button_id = -1;
857 int move_trigger_distance_percent = setup.touch.move_distance;
858 int drop_trigger_distance_percent = setup.touch.drop_distance;
859 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
860 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
861 float event_x = event->x;
862 float event_y = event->y;
864 if (event->type == EVENT_FINGERPRESS)
866 if (event_x > 1.0 / 3.0)
870 motion_id = event->fingerId;
875 motion_key_x = KSYM_UNDEFINED;
876 motion_key_y = KSYM_UNDEFINED;
878 Debug("event:finger", "---------- MOVE STARTED (WAIT) ----------");
884 button_id = event->fingerId;
889 button_key = setup.input[0].key.snap;
891 HandleKey(button_key, KEY_PRESSED);
893 Debug("event:finger", "---------- SNAP STARTED ----------");
896 else if (event->type == EVENT_FINGERRELEASE)
898 if (event->fingerId == motion_id)
902 if (motion_key_x != KSYM_UNDEFINED)
903 HandleKey(motion_key_x, KEY_RELEASED);
904 if (motion_key_y != KSYM_UNDEFINED)
905 HandleKey(motion_key_y, KEY_RELEASED);
907 motion_key_x = KSYM_UNDEFINED;
908 motion_key_y = KSYM_UNDEFINED;
910 Debug("event:finger", "---------- MOVE STOPPED ----------");
912 else if (event->fingerId == button_id)
916 if (button_key != KSYM_UNDEFINED)
917 HandleKey(button_key, KEY_RELEASED);
919 button_key = KSYM_UNDEFINED;
921 Debug("event:finger", "---------- SNAP STOPPED ----------");
924 else if (event->type == EVENT_FINGERMOTION)
926 if (event->fingerId == motion_id)
928 float distance_x = ABS(event_x - motion_x1);
929 float distance_y = ABS(event_y - motion_y1);
930 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
931 event_x > motion_x1 ? setup.input[0].key.right :
933 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
934 event_y > motion_y1 ? setup.input[0].key.down :
937 if (distance_x < move_trigger_distance / 2 ||
938 distance_x < distance_y)
939 new_motion_key_x = KSYM_UNDEFINED;
941 if (distance_y < move_trigger_distance / 2 ||
942 distance_y < distance_x)
943 new_motion_key_y = KSYM_UNDEFINED;
945 if (distance_x > move_trigger_distance ||
946 distance_y > move_trigger_distance)
948 if (new_motion_key_x != motion_key_x)
950 if (motion_key_x != KSYM_UNDEFINED)
951 HandleKey(motion_key_x, KEY_RELEASED);
952 if (new_motion_key_x != KSYM_UNDEFINED)
953 HandleKey(new_motion_key_x, KEY_PRESSED);
956 if (new_motion_key_y != motion_key_y)
958 if (motion_key_y != KSYM_UNDEFINED)
959 HandleKey(motion_key_y, KEY_RELEASED);
960 if (new_motion_key_y != KSYM_UNDEFINED)
961 HandleKey(new_motion_key_y, KEY_PRESSED);
967 motion_key_x = new_motion_key_x;
968 motion_key_y = new_motion_key_y;
970 Debug("event:finger", "---------- MOVE STARTED (MOVE) ----------");
973 else if (event->fingerId == button_id)
975 float distance_x = ABS(event_x - button_x1);
976 float distance_y = ABS(event_y - button_y1);
978 if (distance_x < drop_trigger_distance / 2 &&
979 distance_y > drop_trigger_distance)
981 if (button_key == setup.input[0].key.snap)
982 HandleKey(button_key, KEY_RELEASED);
987 button_key = setup.input[0].key.drop;
989 HandleKey(button_key, KEY_PRESSED);
991 Debug("event:finger", "---------- DROP STARTED ----------");
997 void HandleFingerEvent(FingerEvent *event)
999 #if DEBUG_EVENTS_FINGER
1000 Debug("event:finger", "finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
1001 event->type == EVENT_FINGERPRESS ? "pressed" :
1002 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
1006 event->dx, event->dy,
1010 runtime.uses_touch_device = TRUE;
1012 if (game_status != GAME_MODE_PLAYING)
1015 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1017 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1018 local_player->mouse_action.button_hint =
1019 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1020 event->x < 0.5 ? MB_LEFTBUTTON :
1021 event->x > 0.5 ? MB_RIGHTBUTTON :
1027 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1028 HandleFingerEvent_VirtualButtons(event);
1029 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1030 HandleFingerEvent_WipeGestures(event);
1033 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1035 static int old_mx = 0, old_my = 0;
1036 static int last_button = MB_LEFTBUTTON;
1037 static boolean touched = FALSE;
1038 static boolean tapped = FALSE;
1040 // screen tile was tapped (but finger not touching the screen anymore)
1041 // (this point will also be reached without receiving a touch event)
1042 if (tapped && !touched)
1044 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1049 // stop here if this function was not triggered by a touch event
1053 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1055 // finger started touching the screen
1065 ClearPlayerMouseAction();
1067 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1070 else if (button == MB_RELEASED && touched)
1072 // finger stopped touching the screen
1077 SetPlayerMouseAction(old_mx, old_my, last_button);
1079 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1081 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1086 // finger moved while touching the screen
1088 int old_x = getLevelFromScreenX(old_mx);
1089 int old_y = getLevelFromScreenY(old_my);
1090 int new_x = getLevelFromScreenX(mx);
1091 int new_y = getLevelFromScreenY(my);
1093 if (new_x != old_x || new_y != old_y)
1098 // finger moved left or right from (horizontal) starting position
1100 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1102 SetPlayerMouseAction(old_mx, old_my, button_nr);
1104 last_button = button_nr;
1106 Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1110 // finger stays at or returned to (horizontal) starting position
1112 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1114 Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1119 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1121 static int old_mx = 0, old_my = 0;
1122 static int last_button = MB_LEFTBUTTON;
1123 static boolean touched = FALSE;
1124 static boolean tapped = FALSE;
1126 // screen tile was tapped (but finger not touching the screen anymore)
1127 // (this point will also be reached without receiving a touch event)
1128 if (tapped && !touched)
1130 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1135 // stop here if this function was not triggered by a touch event
1139 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1141 // finger started touching the screen
1151 ClearPlayerMouseAction();
1153 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1156 else if (button == MB_RELEASED && touched)
1158 // finger stopped touching the screen
1163 SetPlayerMouseAction(old_mx, old_my, last_button);
1165 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1167 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1172 // finger moved while touching the screen
1174 int old_x = getLevelFromScreenX(old_mx);
1175 int old_y = getLevelFromScreenY(old_my);
1176 int new_x = getLevelFromScreenX(mx);
1177 int new_y = getLevelFromScreenY(my);
1179 if (new_x != old_x || new_y != old_y)
1181 // finger moved away from starting position
1183 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1185 // quickly alternate between clicking and releasing for maximum speed
1186 if (FrameCounter % 2 == 0)
1187 button_nr = MB_RELEASED;
1189 SetPlayerMouseAction(old_mx, old_my, button_nr);
1192 last_button = button_nr;
1196 Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1200 // finger stays at or returned to starting position
1202 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1204 Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1209 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1211 static int old_mx = 0, old_my = 0;
1212 static Key motion_key_x = KSYM_UNDEFINED;
1213 static Key motion_key_y = KSYM_UNDEFINED;
1214 static boolean touched = FALSE;
1215 static boolean started_on_player = FALSE;
1216 static boolean player_is_dropping = FALSE;
1217 static int player_drop_count = 0;
1218 static int last_player_x = -1;
1219 static int last_player_y = -1;
1221 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1230 started_on_player = FALSE;
1231 player_is_dropping = FALSE;
1232 player_drop_count = 0;
1236 motion_key_x = KSYM_UNDEFINED;
1237 motion_key_y = KSYM_UNDEFINED;
1239 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1242 else if (button == MB_RELEASED && touched)
1249 if (motion_key_x != KSYM_UNDEFINED)
1250 HandleKey(motion_key_x, KEY_RELEASED);
1251 if (motion_key_y != KSYM_UNDEFINED)
1252 HandleKey(motion_key_y, KEY_RELEASED);
1254 if (started_on_player)
1256 if (player_is_dropping)
1258 Debug("event:finger", "---------- DROP STOPPED ----------");
1260 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1264 Debug("event:finger", "---------- SNAP STOPPED ----------");
1266 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1270 motion_key_x = KSYM_UNDEFINED;
1271 motion_key_y = KSYM_UNDEFINED;
1273 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1278 int src_x = local_player->jx;
1279 int src_y = local_player->jy;
1280 int dst_x = getLevelFromScreenX(old_mx);
1281 int dst_y = getLevelFromScreenY(old_my);
1282 int dx = dst_x - src_x;
1283 int dy = dst_y - src_y;
1284 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1285 dx > 0 ? setup.input[0].key.right :
1287 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1288 dy > 0 ? setup.input[0].key.down :
1291 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1292 (last_player_x != local_player->jx ||
1293 last_player_y != local_player->jy))
1295 // in case of asymmetric diagonal movement, use "preferred" direction
1297 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1299 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1300 game_em.ply[0]->last_move_dir = last_move_dir;
1302 local_player->last_move_dir = last_move_dir;
1304 // (required to prevent accidentally forcing direction for next movement)
1305 last_player_x = local_player->jx;
1306 last_player_y = local_player->jy;
1309 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1311 started_on_player = TRUE;
1312 player_drop_count = getPlayerInventorySize(0);
1313 player_is_dropping = (player_drop_count > 0);
1315 if (player_is_dropping)
1317 Debug("event:finger", "---------- DROP STARTED ----------");
1319 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1323 Debug("event:finger", "---------- SNAP STARTED ----------");
1325 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1328 else if (dx != 0 || dy != 0)
1330 if (player_is_dropping &&
1331 player_drop_count == getPlayerInventorySize(0))
1333 Debug("event:finger", "---------- DROP -> SNAP ----------");
1335 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1336 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1338 player_is_dropping = FALSE;
1342 if (new_motion_key_x != motion_key_x)
1344 Debug("event:finger", "---------- %s %s ----------",
1345 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1346 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1348 if (motion_key_x != KSYM_UNDEFINED)
1349 HandleKey(motion_key_x, KEY_RELEASED);
1350 if (new_motion_key_x != KSYM_UNDEFINED)
1351 HandleKey(new_motion_key_x, KEY_PRESSED);
1354 if (new_motion_key_y != motion_key_y)
1356 Debug("event:finger", "---------- %s %s ----------",
1357 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1358 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1360 if (motion_key_y != KSYM_UNDEFINED)
1361 HandleKey(motion_key_y, KEY_RELEASED);
1362 if (new_motion_key_y != KSYM_UNDEFINED)
1363 HandleKey(new_motion_key_y, KEY_PRESSED);
1366 motion_key_x = new_motion_key_x;
1367 motion_key_y = new_motion_key_y;
1371 static void HandleButtonOrFinger(int mx, int my, int button)
1373 boolean valid_mouse_event = (mx != -1 && my != -1 && button != -1);
1375 if (game_status != GAME_MODE_PLAYING)
1378 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1380 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1381 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1382 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1383 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1384 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1385 SetPlayerMouseAction(mx, my, button); // special case
1389 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1390 HandleButtonOrFinger_FollowFinger(mx, my, button);
1391 else if (game.use_mouse_actions && valid_mouse_event)
1392 SetPlayerMouseAction(mx, my, button);
1396 static boolean checkTextInputKey(Key key)
1398 // when playing, only handle raw key events and ignore text input
1399 if (game_status == GAME_MODE_PLAYING)
1402 // if Shift or right Alt key is pressed, handle key as text input
1403 if ((GetKeyModState() & KMOD_TextInput) != KMOD_None)
1406 // ignore raw keys as text input when not in text input mode
1407 if (KSYM_RAW(key) && !textinput_status)
1410 // else handle all printable keys as text input
1411 return KSYM_PRINTABLE(key);
1414 void HandleTextEvent(TextEvent *event)
1416 char *text = event->text;
1417 Key key = getKeyFromKeyName(text);
1419 #if DEBUG_EVENTS_TEXT
1420 Debug("event:text", "text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1423 text[0], (int)(text[0]),
1425 getKeyNameFromKey(key),
1429 if (checkTextInputKey(key))
1431 // process printable keys (with uppercase etc.) in text input mode
1432 HandleKey(key, KEY_PRESSED);
1433 HandleKey(key, KEY_RELEASED);
1437 void HandlePauseResumeEvent(PauseResumeEvent *event)
1439 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1443 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1449 void HandleKeyEvent(KeyEvent *event)
1451 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1452 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1453 Key key = GetEventKey(event, with_modifiers);
1454 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1456 #if DEBUG_EVENTS_KEY
1457 Debug("event:key", "key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1458 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1459 event->keysym.scancode,
1464 getKeyNameFromKey(key));
1467 #if defined(PLATFORM_ANDROID)
1468 if (key == KSYM_Back)
1470 // always map the "back" button to the "escape" key on Android devices
1473 else if (key == KSYM_Menu)
1475 // the "menu" button can be used to toggle displaying virtual buttons
1476 if (key_status == KEY_PRESSED)
1477 SetOverlayEnabled(!GetOverlayEnabled());
1479 else if (!textinput_status)
1481 // for any other "real" key event, disable virtual buttons
1482 SetOverlayEnabled(FALSE);
1484 // for any other "real" key event, disable overlay touch buttons
1485 runtime.uses_touch_device = FALSE;
1489 HandleKeyModState(keymod, key_status);
1491 // process all keys if not in text input mode or if non-printable keys
1492 if (!checkTextInputKey(key))
1493 HandleKey(key, key_status);
1496 static int HandleDropFileEvent(char *filename)
1498 Debug("event:dropfile", "filename == '%s'", filename);
1500 // check and extract dropped zip files into correct user data directory
1501 if (!strSuffixLower(filename, ".zip"))
1503 Warn("file '%s' not supported", filename);
1505 return TREE_TYPE_UNDEFINED;
1508 TreeInfo *tree_node = NULL;
1509 int tree_type = GetZipFileTreeType(filename);
1510 char *directory = TREE_USERDIR(tree_type);
1512 if (directory == NULL)
1514 Warn("zip file '%s' has invalid content!", filename);
1516 return TREE_TYPE_UNDEFINED;
1519 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1520 game_status == GAME_MODE_LEVELS &&
1521 leveldir_current->node_parent != NULL)
1523 // extract new level set next to currently selected level set
1524 tree_node = leveldir_current;
1526 // get parent directory of currently selected level set directory
1527 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1529 // use private level directory instead of top-level package level directory
1530 if (strPrefix(directory, options.level_directory) &&
1531 strEqual(leveldir_current->node_parent->fullpath, "."))
1532 directory = getUserLevelDir(NULL);
1535 // extract level or artwork set from zip file to target directory
1536 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1538 if (top_dir == NULL)
1540 // error message already issued by "ExtractZipFileIntoDirectory()"
1542 return TREE_TYPE_UNDEFINED;
1545 // add extracted level or artwork set to tree info structure
1546 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1548 // update menu screen (and possibly change current level set)
1549 DrawScreenAfterAddingSet(top_dir, tree_type);
1554 static void HandleDropTextEvent(char *text)
1556 Debug("event:droptext", "text == '%s'", text);
1559 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1560 int num_artwork_sets_succeeded,
1561 int num_files_failed)
1563 // only show request dialog if no other request dialog already active
1564 if (game.request_active)
1567 // this case can happen with drag-and-drop with older SDL versions
1568 if (num_level_sets_succeeded == 0 &&
1569 num_artwork_sets_succeeded == 0 &&
1570 num_files_failed == 0)
1575 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1577 char message_part1[50];
1579 sprintf(message_part1, "New %s set%s added",
1580 (num_artwork_sets_succeeded == 0 ? "level" :
1581 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1582 (num_level_sets_succeeded +
1583 num_artwork_sets_succeeded > 1 ? "s" : ""));
1585 if (num_files_failed > 0)
1586 sprintf(message, "%s, but %d dropped file%s failed!",
1587 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1589 sprintf(message, "%s!", message_part1);
1591 else if (num_files_failed > 0)
1593 sprintf(message, "Failed to process dropped file%s!",
1594 num_files_failed > 1 ? "s" : "");
1597 Request(message, REQ_CONFIRM);
1600 void HandleDropEvent(Event *event)
1602 static boolean confirm_on_drop_complete = FALSE;
1603 static int num_level_sets_succeeded = 0;
1604 static int num_artwork_sets_succeeded = 0;
1605 static int num_files_failed = 0;
1607 switch (event->type)
1611 confirm_on_drop_complete = TRUE;
1612 num_level_sets_succeeded = 0;
1613 num_artwork_sets_succeeded = 0;
1614 num_files_failed = 0;
1621 int tree_type = HandleDropFileEvent(event->drop.file);
1623 if (tree_type == TREE_TYPE_LEVEL_DIR)
1624 num_level_sets_succeeded++;
1625 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1626 tree_type == TREE_TYPE_SOUNDS_DIR ||
1627 tree_type == TREE_TYPE_MUSIC_DIR)
1628 num_artwork_sets_succeeded++;
1632 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1633 if (!confirm_on_drop_complete)
1635 // process all remaining events, including further SDL_DROPFILE events
1638 HandleDropCompleteEvent(num_level_sets_succeeded,
1639 num_artwork_sets_succeeded,
1642 num_level_sets_succeeded = 0;
1643 num_artwork_sets_succeeded = 0;
1644 num_files_failed = 0;
1652 HandleDropTextEvent(event->drop.file);
1657 case SDL_DROPCOMPLETE:
1659 HandleDropCompleteEvent(num_level_sets_succeeded,
1660 num_artwork_sets_succeeded,
1667 if (event->drop.file != NULL)
1668 SDL_free(event->drop.file);
1671 void HandleUserEvent(UserEvent *event)
1673 switch (event->code)
1675 case USEREVENT_ANIM_DELAY_ACTION:
1676 case USEREVENT_ANIM_EVENT_ACTION:
1677 // execute action functions until matching action was found
1678 if (DoKeysymAction(event->value1) ||
1679 DoGadgetAction(event->value1) ||
1680 DoScreenAction(event->value1))
1689 void HandleButton(int mx, int my, int button, int button_nr)
1691 static int old_mx = 0, old_my = 0;
1692 boolean button_hold = FALSE;
1693 boolean handle_gadgets = TRUE;
1699 button_nr = -button_nr;
1708 #if defined(PLATFORM_ANDROID)
1709 // when playing, only handle gadgets when using "follow finger" controls
1710 // or when using touch controls in combination with the MM game engine
1711 // or when using gadgets that do not overlap with virtual buttons
1713 (game_status != GAME_MODE_PLAYING ||
1714 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1715 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1716 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1717 !CheckVirtualButtonPressed(mx, my, button)));
1719 // always recognize potentially releasing already pressed gadgets
1720 if (button == MB_RELEASED)
1721 handle_gadgets = TRUE;
1723 // always recognize pressing or releasing overlay touch buttons
1724 if (CheckPosition_OverlayTouchButtons(mx, my, button) && !motion_status)
1725 handle_gadgets = TRUE;
1728 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1730 // do not handle this button event anymore
1731 return; // force mouse event not to be handled at all
1734 if (handle_gadgets && HandleGadgets(mx, my, button))
1736 // do not handle this button event anymore
1737 mx = my = -32; // force mouse event to be outside screen tiles
1740 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1743 // do not use scroll wheel button events for anything other than gadgets
1744 if (IS_WHEEL_BUTTON(button_nr))
1747 switch (game_status)
1749 case GAME_MODE_TITLE:
1750 HandleTitleScreen(mx, my, 0, 0, button);
1753 case GAME_MODE_MAIN:
1754 HandleMainMenu(mx, my, 0, 0, button);
1757 case GAME_MODE_PSEUDO_TYPENAME:
1758 case GAME_MODE_PSEUDO_TYPENAMES:
1759 HandleTypeName(KSYM_Return);
1762 case GAME_MODE_NAMES:
1763 HandleChoosePlayerName(mx, my, 0, 0, button);
1766 case GAME_MODE_LEVELS:
1767 HandleChooseLevelSet(mx, my, 0, 0, button);
1770 case GAME_MODE_LEVELNR:
1771 HandleChooseLevelNr(mx, my, 0, 0, button);
1774 case GAME_MODE_SCORES:
1775 HandleHallOfFame(0, 0, 0, 0, button);
1778 case GAME_MODE_EDITOR:
1779 HandleLevelEditorIdle();
1782 case GAME_MODE_INFO:
1783 HandleInfoScreen(mx, my, 0, 0, button);
1786 case GAME_MODE_SETUP:
1787 HandleSetupScreen(mx, my, 0, 0, button);
1790 case GAME_MODE_PLAYING:
1791 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1792 HandleButtonOrFinger(mx, my, button);
1794 SetPlayerMouseAction(mx, my, button);
1797 if (button == MB_PRESSED && !motion_status && !button_hold &&
1798 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1799 DumpTileFromScreen(mx, my);
1809 #define MAX_CHEAT_INPUT_LEN 32
1811 static void HandleKeysSpecial(Key key)
1813 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1814 char letter = getCharFromKey(key);
1815 int cheat_input_len = strlen(cheat_input);
1821 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1823 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1824 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1826 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1829 cheat_input[cheat_input_len++] = letter;
1830 cheat_input[cheat_input_len] = '\0';
1832 #if DEBUG_EVENTS_KEY
1833 Debug("event:key:special", "'%s' [%d]", cheat_input, cheat_input_len);
1836 if (game_status == GAME_MODE_MAIN)
1838 if (strSuffix(cheat_input, ":insert-solution-tape") ||
1839 strSuffix(cheat_input, ":ist"))
1841 InsertSolutionTape();
1843 else if (strSuffix(cheat_input, ":play-solution-tape") ||
1844 strSuffix(cheat_input, ":pst"))
1848 else if (strSuffix(cheat_input, ":reload-graphics") ||
1849 strSuffix(cheat_input, ":rg"))
1851 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1854 else if (strSuffix(cheat_input, ":reload-sounds") ||
1855 strSuffix(cheat_input, ":rs"))
1857 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1860 else if (strSuffix(cheat_input, ":reload-music") ||
1861 strSuffix(cheat_input, ":rm"))
1863 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1866 else if (strSuffix(cheat_input, ":reload-artwork") ||
1867 strSuffix(cheat_input, ":ra"))
1869 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1870 1 << ARTWORK_TYPE_SOUNDS |
1871 1 << ARTWORK_TYPE_MUSIC);
1874 else if (strSuffix(cheat_input, ":dump-level") ||
1875 strSuffix(cheat_input, ":dl"))
1879 else if (strSuffix(cheat_input, ":dump-tape") ||
1880 strSuffix(cheat_input, ":dt"))
1884 else if (strSuffix(cheat_input, ":undo-tape") ||
1885 strSuffix(cheat_input, ":ut"))
1889 else if (strSuffix(cheat_input, ":fix-tape") ||
1890 strSuffix(cheat_input, ":ft"))
1892 FixTape_ForceSinglePlayer();
1894 else if (strSuffix(cheat_input, ":save-native-level") ||
1895 strSuffix(cheat_input, ":snl"))
1897 SaveNativeLevel(&level);
1899 else if (strSuffix(cheat_input, ":frames-per-second") ||
1900 strSuffix(cheat_input, ":fps"))
1902 global.show_frames_per_second = !global.show_frames_per_second;
1904 else if (strSuffix(cheat_input, ":xsn"))
1906 tile_cursor.xsn_debug = TRUE;
1909 else if (game_status == GAME_MODE_PLAYING)
1912 if (strSuffix(cheat_input, ".q"))
1913 DEBUG_SetMaximumDynamite();
1916 else if (game_status == GAME_MODE_EDITOR)
1918 if (strSuffix(cheat_input, ":dump-brush") ||
1919 strSuffix(cheat_input, ":DB"))
1923 else if (strSuffix(cheat_input, ":DDB"))
1928 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1930 if (letter == 'x') // copy brush to clipboard (small size)
1932 CopyBrushToClipboard_Small();
1934 else if (letter == 'c') // copy brush to clipboard (normal size)
1936 CopyBrushToClipboard();
1938 else if (letter == 'v') // paste brush from Clipboard
1940 CopyClipboardToBrush();
1942 else if (letter == 'z') // undo or redo last operation
1944 if (GetKeyModState() & KMOD_Shift)
1945 RedoLevelEditorOperation();
1947 UndoLevelEditorOperation();
1952 // special key shortcuts for all game modes
1953 if (strSuffix(cheat_input, ":dump-event-actions") ||
1954 strSuffix(cheat_input, ":dea") ||
1955 strSuffix(cheat_input, ":DEA"))
1957 DumpGadgetIdentifiers();
1958 DumpScreenIdentifiers();
1962 boolean HandleKeysDebug(Key key, int key_status)
1967 if (key_status != KEY_PRESSED)
1970 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1972 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1974 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1976 if (key == setup.debug.frame_delay_key[i] &&
1977 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1979 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1980 setup.debug.frame_delay[i] : setup.game_frame_delay);
1982 if (!setup.debug.frame_delay_game_only)
1983 MenuFrameDelay = GameFrameDelay;
1985 SetVideoFrameDelay(GameFrameDelay);
1987 if (GameFrameDelay > ONE_SECOND_DELAY)
1988 Debug("event:key:debug", "frame delay == %d ms", GameFrameDelay);
1989 else if (GameFrameDelay != 0)
1990 Debug("event:key:debug", "frame delay == %d ms (max. %d fps / %d %%)",
1991 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1992 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1994 Debug("event:key:debug", "frame delay == 0 ms (maximum speed)");
2001 if (game_status == GAME_MODE_PLAYING)
2005 options.debug = !options.debug;
2007 Debug("event:key:debug", "debug mode %s",
2008 (options.debug ? "enabled" : "disabled"));
2012 else if (key == KSYM_v)
2014 Debug("event:key:debug", "currently using game engine version %d",
2015 game.engine_version);
2025 void HandleKey(Key key, int key_status)
2027 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2028 static boolean ignore_repeated_key = FALSE;
2029 static struct SetupKeyboardInfo ski;
2030 static struct SetupShortcutInfo ssi;
2039 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
2040 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
2041 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
2042 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
2043 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2044 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2049 if (HandleKeysDebug(key, key_status))
2050 return; // do not handle already processed keys again
2052 // map special keys (media keys / remote control buttons) to default keys
2053 if (key == KSYM_PlayPause)
2055 else if (key == KSYM_Select)
2058 HandleSpecialGameControllerKeys(key, key_status);
2060 if (game_status == GAME_MODE_PLAYING)
2062 // only needed for single-step tape recording mode
2063 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2066 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2068 byte key_action = 0;
2069 byte key_snap_action = 0;
2071 if (setup.input[pnr].use_joystick)
2074 ski = setup.input[pnr].key;
2076 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2077 if (key == *key_info[i].key_custom)
2078 key_action |= key_info[i].action;
2080 // use combined snap+direction keys for the first player only
2083 ssi = setup.shortcut;
2085 // also remember normal snap key when handling snap+direction keys
2086 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2088 for (i = 0; i < NUM_DIRECTIONS; i++)
2090 if (key == *key_info[i].key_snap)
2092 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2093 key_snap_action |= key_info[i].action;
2095 tape.property_bits |= TAPE_PROPERTY_TAS_KEYS;
2100 if (key_status == KEY_PRESSED)
2102 stored_player[pnr].action |= key_action;
2103 stored_player[pnr].snap_action |= key_snap_action;
2107 stored_player[pnr].action &= ~key_action;
2108 stored_player[pnr].snap_action &= ~key_snap_action;
2111 // restore snap action if one of several pressed snap keys was released
2112 if (stored_player[pnr].snap_action)
2113 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2115 if (tape.recording && tape.pausing && tape.use_key_actions)
2117 if (tape.single_step)
2119 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2121 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2123 // if snap key already pressed, keep pause mode when releasing
2124 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2125 has_snapped[pnr] = TRUE;
2127 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2129 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2131 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2132 getRedDiskReleaseFlag_SP() == 0)
2134 // add a single inactive frame before dropping starts
2135 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2136 stored_player[pnr].force_dropping = TRUE;
2139 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2141 // if snap key was pressed without direction, leave pause mode
2142 if (!has_snapped[pnr])
2143 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2145 has_snapped[pnr] = FALSE;
2150 // prevent key release events from un-pausing a paused game
2151 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2152 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2156 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2157 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2163 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2164 if (key == key_info[i].key_default)
2165 joy |= key_info[i].action;
2170 if (key_status == KEY_PRESSED)
2171 key_joystick_mapping |= joy;
2173 key_joystick_mapping &= ~joy;
2178 if (game_status != GAME_MODE_PLAYING)
2179 key_joystick_mapping = 0;
2181 if (key_status == KEY_RELEASED)
2183 // reset flag to ignore repeated "key pressed" events after key release
2184 ignore_repeated_key = FALSE;
2189 if ((key == KSYM_F11 ||
2190 ((key == KSYM_Return ||
2191 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2192 video.fullscreen_available &&
2193 !ignore_repeated_key)
2195 setup.fullscreen = !setup.fullscreen;
2197 ToggleFullscreenIfNeeded();
2199 if (game_status == GAME_MODE_SETUP)
2200 RedrawSetupScreenAfterFullscreenToggle();
2202 UpdateMousePosition();
2204 // set flag to ignore repeated "key pressed" events
2205 ignore_repeated_key = TRUE;
2210 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2211 key == KSYM_minus || key == KSYM_KP_Subtract ||
2212 key == KSYM_plus || key == KSYM_KP_Add ||
2213 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2214 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2215 video.window_scaling_available &&
2216 !video.fullscreen_enabled)
2218 if (key == KSYM_0 || key == KSYM_KP_0)
2219 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2220 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2221 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2223 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2225 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2226 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2227 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2228 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2230 ChangeWindowScalingIfNeeded();
2232 if (game_status == GAME_MODE_SETUP)
2233 RedrawSetupScreenAfterFullscreenToggle();
2235 UpdateMousePosition();
2240 // some key events are handled like clicks for global animations
2241 boolean click = (key == KSYM_space ||
2242 key == KSYM_Return ||
2243 key == KSYM_Escape);
2245 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2247 // do not handle this key event anymore
2248 if (key != KSYM_Escape) // always allow ESC key to be handled
2252 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2253 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2260 if (game_status == GAME_MODE_MAIN &&
2261 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2263 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2268 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2270 if (key == setup.shortcut.save_game)
2272 else if (key == setup.shortcut.load_game)
2274 else if (key == setup.shortcut.toggle_pause)
2275 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2277 HandleTapeButtonKeys(key);
2278 HandleSoundButtonKeys(key);
2281 if (game_status == GAME_MODE_PLAYING && !network_playing)
2283 int centered_player_nr_next = -999;
2285 if (key == setup.shortcut.focus_player_all)
2286 centered_player_nr_next = -1;
2288 for (i = 0; i < MAX_PLAYERS; i++)
2289 if (key == setup.shortcut.focus_player[i])
2290 centered_player_nr_next = i;
2292 if (centered_player_nr_next != -999)
2294 game.centered_player_nr_next = centered_player_nr_next;
2295 game.set_centered_player = TRUE;
2299 tape.centered_player_nr_next = game.centered_player_nr_next;
2300 tape.set_centered_player = TRUE;
2305 HandleKeysSpecial(key);
2307 if (HandleGadgetsKeyInput(key))
2308 return; // do not handle already processed keys again
2310 switch (game_status)
2312 case GAME_MODE_PSEUDO_TYPENAME:
2313 case GAME_MODE_PSEUDO_TYPENAMES:
2314 HandleTypeName(key);
2317 case GAME_MODE_TITLE:
2318 case GAME_MODE_MAIN:
2319 case GAME_MODE_NAMES:
2320 case GAME_MODE_LEVELS:
2321 case GAME_MODE_LEVELNR:
2322 case GAME_MODE_SETUP:
2323 case GAME_MODE_INFO:
2324 case GAME_MODE_SCORES:
2326 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2333 if (game_status == GAME_MODE_TITLE)
2334 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2335 else if (game_status == GAME_MODE_MAIN)
2336 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2337 else if (game_status == GAME_MODE_NAMES)
2338 HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_CHOICE);
2339 else if (game_status == GAME_MODE_LEVELS)
2340 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2341 else if (game_status == GAME_MODE_LEVELNR)
2342 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2343 else if (game_status == GAME_MODE_SETUP)
2344 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2345 else if (game_status == GAME_MODE_INFO)
2346 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2347 else if (game_status == GAME_MODE_SCORES)
2348 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2352 if (game_status != GAME_MODE_MAIN)
2353 FadeSkipNextFadeIn();
2355 if (game_status == GAME_MODE_TITLE)
2356 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2357 else if (game_status == GAME_MODE_NAMES)
2358 HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_LEAVE);
2359 else if (game_status == GAME_MODE_LEVELS)
2360 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2361 else if (game_status == GAME_MODE_LEVELNR)
2362 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2363 else if (game_status == GAME_MODE_SETUP)
2364 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2365 else if (game_status == GAME_MODE_INFO)
2366 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2367 else if (game_status == GAME_MODE_SCORES)
2368 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2372 if (game_status == GAME_MODE_NAMES)
2373 HandleChoosePlayerName(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2374 else if (game_status == GAME_MODE_LEVELS)
2375 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2376 else if (game_status == GAME_MODE_LEVELNR)
2377 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2378 else if (game_status == GAME_MODE_SETUP)
2379 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2380 else if (game_status == GAME_MODE_INFO)
2381 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2382 else if (game_status == GAME_MODE_SCORES)
2383 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2386 case KSYM_Page_Down:
2387 if (game_status == GAME_MODE_NAMES)
2388 HandleChoosePlayerName(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2389 else if (game_status == GAME_MODE_LEVELS)
2390 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2391 else if (game_status == GAME_MODE_LEVELNR)
2392 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2393 else if (game_status == GAME_MODE_SETUP)
2394 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2395 else if (game_status == GAME_MODE_INFO)
2396 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2397 else if (game_status == GAME_MODE_SCORES)
2398 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2406 case GAME_MODE_EDITOR:
2407 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2408 HandleLevelEditorKeyInput(key);
2411 case GAME_MODE_PLAYING:
2416 RequestQuitGame(TRUE);
2426 if (key == KSYM_Escape)
2428 SetGameStatus(GAME_MODE_MAIN);
2437 void HandleNoEvent(void)
2439 HandleMouseCursor();
2441 switch (game_status)
2443 case GAME_MODE_PLAYING:
2444 HandleButtonOrFinger(-1, -1, -1);
2449 void HandleEventActions(void)
2451 // if (button_status && game_status != GAME_MODE_PLAYING)
2452 if (button_status && (game_status != GAME_MODE_PLAYING ||
2454 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2456 HandleButton(0, 0, button_status, -button_status);
2463 if (network.enabled)
2466 switch (game_status)
2468 case GAME_MODE_MAIN:
2469 DrawPreviewLevelAnimation();
2472 case GAME_MODE_EDITOR:
2473 HandleLevelEditorIdle();
2481 static void HandleTileCursor(int dx, int dy, int button)
2484 ClearPlayerMouseAction();
2491 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2492 (dx < 0 ? MB_LEFTBUTTON :
2493 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2495 else if (!tile_cursor.moving)
2497 int old_xpos = tile_cursor.xpos;
2498 int old_ypos = tile_cursor.ypos;
2499 int new_xpos = old_xpos;
2500 int new_ypos = old_ypos;
2502 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2503 new_xpos = old_xpos + dx;
2505 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2506 new_ypos = old_ypos + dy;
2508 SetTileCursorTargetXY(new_xpos, new_ypos);
2512 static int HandleJoystickForAllPlayers(void)
2516 boolean no_joysticks_configured = TRUE;
2517 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2518 static byte joy_action_last[MAX_PLAYERS];
2520 for (i = 0; i < MAX_PLAYERS; i++)
2521 if (setup.input[i].use_joystick)
2522 no_joysticks_configured = FALSE;
2524 // if no joysticks configured, map connected joysticks to players
2525 if (no_joysticks_configured)
2526 use_as_joystick_nr = TRUE;
2528 for (i = 0; i < MAX_PLAYERS; i++)
2530 byte joy_action = 0;
2532 joy_action = JoystickExt(i, use_as_joystick_nr);
2533 result |= joy_action;
2535 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2536 joy_action != joy_action_last[i])
2537 stored_player[i].action = joy_action;
2539 joy_action_last[i] = joy_action;
2545 void HandleJoystick(void)
2547 static unsigned int joytest_delay = 0;
2548 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2549 static int joytest_last = 0;
2550 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2551 int delay_value = GADGET_FRAME_DELAY;
2552 int joystick = HandleJoystickForAllPlayers();
2553 int keyboard = key_joystick_mapping;
2554 int joy = (joystick | keyboard);
2555 int joytest = joystick;
2556 int left = joy & JOY_LEFT;
2557 int right = joy & JOY_RIGHT;
2558 int up = joy & JOY_UP;
2559 int down = joy & JOY_DOWN;
2560 int button = joy & JOY_BUTTON;
2561 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2562 int dx = (left ? -1 : right ? 1 : 0);
2563 int dy = (up ? -1 : down ? 1 : 0);
2564 boolean use_delay_value_first = (joytest != joytest_last);
2566 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2568 // do not handle this button event anymore
2572 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2573 game_status == GAME_MODE_PSEUDO_TYPENAMES ||
2574 anyTextGadgetActive()))
2576 // leave name input in main menu or text input gadget
2577 HandleKey(KSYM_Escape, KEY_PRESSED);
2578 HandleKey(KSYM_Escape, KEY_RELEASED);
2583 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2585 if (game_status == GAME_MODE_PLAYING)
2587 // when playing MM style levels, also use delay for keyboard events
2588 joytest |= keyboard;
2590 // only use first delay value for new events, but not for changed events
2591 use_delay_value_first = (!joytest != !joytest_last);
2593 // only use delay after the initial keyboard event
2597 // for any joystick or keyboard event, enable playfield tile cursor
2598 if (dx || dy || button)
2599 SetTileCursorEnabled(TRUE);
2602 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2604 // delay joystick/keyboard actions if axes/keys continually pressed
2605 newbutton = dx = dy = 0;
2609 // first start with longer delay, then continue with shorter delay
2610 joytest_delay_value =
2611 (use_delay_value_first ? delay_value_first : delay_value);
2614 joytest_last = joytest;
2616 switch (game_status)
2618 case GAME_MODE_TITLE:
2619 case GAME_MODE_MAIN:
2620 case GAME_MODE_NAMES:
2621 case GAME_MODE_LEVELS:
2622 case GAME_MODE_LEVELNR:
2623 case GAME_MODE_SETUP:
2624 case GAME_MODE_INFO:
2625 case GAME_MODE_SCORES:
2627 if (anyTextGadgetActive())
2630 if (game_status == GAME_MODE_TITLE)
2631 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2632 else if (game_status == GAME_MODE_MAIN)
2633 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2634 else if (game_status == GAME_MODE_NAMES)
2635 HandleChoosePlayerName(0,0,dx,dy,newbutton?MB_MENU_CHOICE:MB_MENU_MARK);
2636 else if (game_status == GAME_MODE_LEVELS)
2637 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2638 else if (game_status == GAME_MODE_LEVELNR)
2639 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2640 else if (game_status == GAME_MODE_SETUP)
2641 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2642 else if (game_status == GAME_MODE_INFO)
2643 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2644 else if (game_status == GAME_MODE_SCORES)
2645 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2650 case GAME_MODE_PLAYING:
2652 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2653 if (tape.playing || keyboard)
2654 newbutton = ((joy & JOY_BUTTON) != 0);
2657 if (newbutton && game.all_players_gone)
2664 if (tape.recording && tape.pausing && tape.use_key_actions)
2666 if (tape.single_step)
2668 if (joystick & JOY_ACTION)
2669 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2673 if (joystick & JOY_ACTION)
2674 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2678 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2679 HandleTileCursor(dx, dy, button);
2688 void HandleSpecialGameControllerButtons(Event *event)
2693 switch (event->type)
2695 case SDL_CONTROLLERBUTTONDOWN:
2696 key_status = KEY_PRESSED;
2699 case SDL_CONTROLLERBUTTONUP:
2700 key_status = KEY_RELEASED;
2707 switch (event->cbutton.button)
2709 case SDL_CONTROLLER_BUTTON_START:
2713 case SDL_CONTROLLER_BUTTON_BACK:
2721 HandleKey(key, key_status);
2724 void HandleSpecialGameControllerKeys(Key key, int key_status)
2726 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2727 int button = SDL_CONTROLLER_BUTTON_INVALID;
2729 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2730 if (key == KSYM_Rewind)
2731 button = SDL_CONTROLLER_BUTTON_A;
2732 else if (key == KSYM_FastForward || key == KSYM_Menu)
2733 button = SDL_CONTROLLER_BUTTON_B;
2735 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2739 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2740 SDL_CONTROLLERBUTTONUP);
2742 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2743 event.cbutton.button = button;
2744 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2747 HandleJoystickEvent(&event);
2752 boolean DoKeysymAction(int keysym)
2756 Key key = (Key)(-keysym);
2758 HandleKey(key, KEY_PRESSED);
2759 HandleKey(key, KEY_RELEASED);