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 DelayReached(&special_cursor_delay, 0);
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))
219 case EVENT_BUTTONPRESS:
220 case EVENT_BUTTONRELEASE:
221 HandleButtonEvent((ButtonEvent *) &event);
224 case EVENT_MOTIONNOTIFY:
225 HandleMotionEvent((MotionEvent *) &event);
228 case EVENT_WHEELMOTION:
229 HandleWheelEvent((WheelEvent *) &event);
232 case SDL_WINDOWEVENT:
233 HandleWindowEvent((WindowEvent *) &event);
236 case EVENT_FINGERPRESS:
237 case EVENT_FINGERRELEASE:
238 case EVENT_FINGERMOTION:
239 HandleFingerEvent((FingerEvent *) &event);
242 case EVENT_TEXTINPUT:
243 HandleTextEvent((TextEvent *) &event);
246 case SDL_APP_WILLENTERBACKGROUND:
247 case SDL_APP_DIDENTERBACKGROUND:
248 case SDL_APP_WILLENTERFOREGROUND:
249 case SDL_APP_DIDENTERFOREGROUND:
250 HandlePauseResumeEvent((PauseResumeEvent *) &event);
254 case EVENT_KEYRELEASE:
255 HandleKeyEvent((KeyEvent *) &event);
259 HandleUserEvent((UserEvent *) &event);
263 HandleOtherEvents(&event);
267 // do not handle events for longer than standard frame delay period
268 if (DelayReached(&event_frame_delay, event_frame_delay_value))
271 // do not handle any further events if triggered by a special flag
272 if (stop_processing_events)
277 void HandleOtherEvents(Event *event)
281 case SDL_CONTROLLERBUTTONDOWN:
282 case SDL_CONTROLLERBUTTONUP:
283 // for any game controller button event, disable overlay buttons
284 SetOverlayEnabled(FALSE);
286 HandleSpecialGameControllerButtons(event);
289 case SDL_CONTROLLERDEVICEADDED:
290 case SDL_CONTROLLERDEVICEREMOVED:
291 case SDL_CONTROLLERAXISMOTION:
292 case SDL_JOYAXISMOTION:
293 case SDL_JOYBUTTONDOWN:
294 case SDL_JOYBUTTONUP:
295 HandleJoystickEvent(event);
299 case SDL_DROPCOMPLETE:
302 HandleDropEvent(event);
314 static void HandleMouseCursor(void)
316 if (game_status == GAME_MODE_TITLE)
318 // when showing title screens, hide mouse pointer (if not moved)
320 if (gfx.cursor_mode != CURSOR_NONE &&
321 DelayReached(&special_cursor_delay, special_cursor_delay_value))
323 SetMouseCursor(CURSOR_NONE);
326 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
329 // when playing, display a special mouse pointer inside the playfield
331 // display normal pointer if mouse pressed
332 if (button_status != MB_RELEASED)
333 DelayReached(&special_cursor_delay, 0);
335 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
336 cursor_inside_playfield &&
337 DelayReached(&special_cursor_delay, special_cursor_delay_value))
339 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
341 SetMouseCursor(CURSOR_PLAYFIELD);
344 else if (gfx.cursor_mode != CURSOR_DEFAULT)
346 SetMouseCursor(CURSOR_DEFAULT);
349 // this is set after all pending events have been processed
350 cursor_mode_last = gfx.cursor_mode;
362 // execute event related actions after pending events have been processed
363 HandleEventActions();
365 // don't use all CPU time when idle; the main loop while playing
366 // has its own synchronization and is CPU friendly, too
368 if (game_status == GAME_MODE_PLAYING)
371 // always copy backbuffer to visible screen for every video frame
374 // reset video frame delay to default (may change again while playing)
375 SetVideoFrameDelay(MenuFrameDelay);
377 if (game_status == GAME_MODE_QUIT)
382 void ClearAutoRepeatKeyEvents(void)
384 while (PendingEvent())
388 PeekEvent(&next_event);
390 // if event is repeated key press event, remove it from event queue
391 if (next_event.type == EVENT_KEYPRESS &&
392 next_event.key.repeat)
393 WaitEvent(&next_event);
399 void ClearEventQueue(void)
403 while (NextValidEvent(&event))
407 case EVENT_BUTTONRELEASE:
408 button_status = MB_RELEASED;
411 case EVENT_FINGERRELEASE:
412 case EVENT_KEYRELEASE:
416 case SDL_CONTROLLERBUTTONUP:
417 HandleJoystickEvent(&event);
422 HandleOtherEvents(&event);
428 static void ClearPlayerMouseAction(void)
430 local_player->mouse_action.lx = 0;
431 local_player->mouse_action.ly = 0;
432 local_player->mouse_action.button = 0;
435 void ClearPlayerAction(void)
439 // simulate key release events for still pressed keys
440 key_joystick_mapping = 0;
441 for (i = 0; i < MAX_PLAYERS; i++)
443 stored_player[i].action = 0;
444 stored_player[i].snap_action = 0;
447 // simulate finger release events for still pressed virtual buttons
448 overlay.grid_button_action = JOY_NO_ACTION;
451 ClearJoystickState();
452 ClearPlayerMouseAction();
455 static void SetPlayerMouseAction(int mx, int my, int button)
457 int lx = getLevelFromScreenX(mx);
458 int ly = getLevelFromScreenY(my);
459 int new_button = (!local_player->mouse_action.button && button);
461 if (local_player->mouse_action.button_hint)
462 button = local_player->mouse_action.button_hint;
464 ClearPlayerMouseAction();
466 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
469 local_player->mouse_action.lx = lx;
470 local_player->mouse_action.ly = ly;
471 local_player->mouse_action.button = button;
473 if (tape.recording && tape.pausing && tape.use_mouse_actions)
475 // un-pause a paused game only if mouse button was newly pressed down
477 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
480 SetTileCursorXY(lx, ly);
483 static Key GetKeyFromGridButton(int grid_button)
485 return (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
486 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
487 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
488 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
489 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
490 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
494 #if defined(PLATFORM_ANDROID)
495 static boolean CheckVirtualButtonPressed(int mx, int my, int button)
497 float touch_x = (float)(mx + video.screen_xoffset) / video.screen_width;
498 float touch_y = (float)(my + video.screen_yoffset) / video.screen_height;
499 int x = touch_x * overlay.grid_xsize;
500 int y = touch_y * overlay.grid_ysize;
501 int grid_button = overlay.grid_button[x][y];
502 Key key = GetKeyFromGridButton(grid_button);
503 int key_status = (button == MB_RELEASED ? KEY_RELEASED : KEY_PRESSED);
505 return (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
509 void HandleButtonEvent(ButtonEvent *event)
511 #if DEBUG_EVENTS_BUTTON
512 Debug("event:button", "button %d %s, x/y %d/%d\n",
514 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
518 // for any mouse button event, disable playfield tile cursor
519 SetTileCursorEnabled(FALSE);
521 #if defined(HAS_SCREEN_KEYBOARD)
522 if (video.shifted_up)
523 event->y += video.shifted_up_pos;
526 motion_status = FALSE;
528 if (event->type == EVENT_BUTTONPRESS)
529 button_status = event->button;
531 button_status = MB_RELEASED;
533 HandleButton(event->x, event->y, button_status, event->button);
536 void HandleMotionEvent(MotionEvent *event)
538 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
541 motion_status = TRUE;
543 #if DEBUG_EVENTS_MOTION
544 Debug("event:motion", "button %d moved, x/y %d/%d\n",
545 button_status, event->x, event->y);
548 HandleButton(event->x, event->y, button_status, button_status);
551 void HandleWheelEvent(WheelEvent *event)
555 #if DEBUG_EVENTS_WHEEL
557 Debug("event:wheel", "mouse == %d, x/y == %d/%d\n",
558 event->which, event->x, event->y);
560 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
561 Debug("event:wheel", "mouse == %d, x/y == %d/%d, direction == %s\n",
562 event->which, event->x, event->y,
563 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
564 "SDL_MOUSEWHEEL_FLIPPED"));
568 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
569 event->x > 0 ? MB_WHEEL_RIGHT :
570 event->y < 0 ? MB_WHEEL_DOWN :
571 event->y > 0 ? MB_WHEEL_UP : 0);
573 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
574 // accelerated mouse wheel available on Mac and Windows
575 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
577 // no accelerated mouse wheel available on Unix/Linux
578 wheel_steps = DEFAULT_WHEEL_STEPS;
581 motion_status = FALSE;
583 button_status = button_nr;
584 HandleButton(0, 0, button_status, -button_nr);
586 button_status = MB_RELEASED;
587 HandleButton(0, 0, button_status, -button_nr);
590 void HandleWindowEvent(WindowEvent *event)
592 #if DEBUG_EVENTS_WINDOW
593 int subtype = event->event;
596 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
597 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
598 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
599 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
600 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
601 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
602 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
603 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
604 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
605 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
606 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
607 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
608 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
609 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
612 Debug("event:window", "name: '%s', data1: %ld, data2: %ld",
613 event_name, event->data1, event->data2);
617 // (not needed, as the screen gets redrawn every 20 ms anyway)
618 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
619 event->event == SDL_WINDOWEVENT_RESIZED ||
620 event->event == SDL_WINDOWEVENT_EXPOSED)
624 if (event->event == SDL_WINDOWEVENT_RESIZED)
626 if (!video.fullscreen_enabled)
628 int new_window_width = event->data1;
629 int new_window_height = event->data2;
631 // if window size has changed after resizing, calculate new scaling factor
632 if (new_window_width != video.window_width ||
633 new_window_height != video.window_height)
635 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
636 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
638 // (extreme window scaling allowed, but cannot be saved permanently)
639 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
640 setup.window_scaling_percent =
641 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
642 MAX_WINDOW_SCALING_PERCENT);
644 video.window_width = new_window_width;
645 video.window_height = new_window_height;
647 if (game_status == GAME_MODE_SETUP)
648 RedrawSetupScreenAfterFullscreenToggle();
650 UpdateMousePosition();
655 #if defined(PLATFORM_ANDROID)
658 int new_display_width = event->data1;
659 int new_display_height = event->data2;
661 // if fullscreen display size has changed, device has been rotated
662 if (new_display_width != video.display_width ||
663 new_display_height != video.display_height)
665 int nr = GRID_ACTIVE_NR(); // previous screen orientation
667 video.display_width = new_display_width;
668 video.display_height = new_display_height;
670 SDLSetScreenProperties();
671 SetGadgetsPosition_OverlayTouchButtons();
673 // check if screen orientation has changed (should always be true here)
674 if (nr != GRID_ACTIVE_NR())
676 if (game_status == GAME_MODE_SETUP)
677 RedrawSetupScreenAfterScreenRotation(nr);
679 SetOverlayGridSizeAndButtons();
687 #define NUM_TOUCH_FINGERS 3
692 SDL_FingerID finger_id;
696 } touch_info[NUM_TOUCH_FINGERS];
698 static void SetTouchInfo(int pos, SDL_FingerID finger_id, int counter,
699 Key key, byte action)
701 touch_info[pos].touched = (action != JOY_NO_ACTION);
702 touch_info[pos].finger_id = finger_id;
703 touch_info[pos].counter = counter;
704 touch_info[pos].key = key;
705 touch_info[pos].action = action;
708 static void ClearTouchInfo(void)
712 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
713 SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
716 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
718 int x = event->x * overlay.grid_xsize;
719 int y = event->y * overlay.grid_ysize;
720 int grid_button = overlay.grid_button[x][y];
721 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
722 Key key = GetKeyFromGridButton(grid_button);
723 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
725 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
729 // for any touch input event, enable overlay buttons (if activated)
730 SetOverlayEnabled(TRUE);
732 Debug("event:finger", "key '%s' was '%s' [fingerId: %lld]",
733 getKeyNameFromKey(key), key_status_name, event->fingerId);
735 if (key_status == KEY_PRESSED)
736 overlay.grid_button_action |= grid_button_action;
738 overlay.grid_button_action &= ~grid_button_action;
740 // check if we already know this touch event's finger id
741 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
743 if (touch_info[i].touched &&
744 touch_info[i].finger_id == event->fingerId)
746 // Debug("event:finger", "MARK 1: %d", i);
752 if (i >= NUM_TOUCH_FINGERS)
754 if (key_status == KEY_PRESSED)
756 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
758 // unknown finger id -- get new, empty slot, if available
759 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
761 if (touch_info[i].counter < oldest_counter)
764 oldest_counter = touch_info[i].counter;
766 // Debug("event:finger", "MARK 2: %d", i);
769 if (!touch_info[i].touched)
771 // Debug("event:finger", "MARK 3: %d", i);
777 if (i >= NUM_TOUCH_FINGERS)
779 // all slots allocated -- use oldest slot
782 // Debug("event:finger", "MARK 4: %d", i);
787 // release of previously unknown key (should not happen)
789 if (key != KSYM_UNDEFINED)
791 HandleKey(key, KEY_RELEASED);
793 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [1]",
794 getKeyNameFromKey(key), "KEY_RELEASED", i);
799 if (i < NUM_TOUCH_FINGERS)
801 if (key_status == KEY_PRESSED)
803 if (touch_info[i].key != key)
805 if (touch_info[i].key != KSYM_UNDEFINED)
807 HandleKey(touch_info[i].key, KEY_RELEASED);
809 // undraw previous grid button when moving finger away
810 overlay.grid_button_action &= ~touch_info[i].action;
812 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [2]",
813 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
816 if (key != KSYM_UNDEFINED)
818 HandleKey(key, KEY_PRESSED);
820 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [3]",
821 getKeyNameFromKey(key), "KEY_PRESSED", i);
825 SetTouchInfo(i, event->fingerId, Counter(), key, grid_button_action);
829 if (touch_info[i].key != KSYM_UNDEFINED)
831 HandleKey(touch_info[i].key, KEY_RELEASED);
833 Debug("event:finger", "key == '%s', key_status == '%s' [slot %d] [4]",
834 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
837 SetTouchInfo(i, 0, 0, 0, JOY_NO_ACTION);
842 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
844 static Key motion_key_x = KSYM_UNDEFINED;
845 static Key motion_key_y = KSYM_UNDEFINED;
846 static Key button_key = KSYM_UNDEFINED;
847 static float motion_x1, motion_y1;
848 static float button_x1, button_y1;
849 static SDL_FingerID motion_id = -1;
850 static SDL_FingerID button_id = -1;
851 int move_trigger_distance_percent = setup.touch.move_distance;
852 int drop_trigger_distance_percent = setup.touch.drop_distance;
853 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
854 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
855 float event_x = event->x;
856 float event_y = event->y;
858 if (event->type == EVENT_FINGERPRESS)
860 if (event_x > 1.0 / 3.0)
864 motion_id = event->fingerId;
869 motion_key_x = KSYM_UNDEFINED;
870 motion_key_y = KSYM_UNDEFINED;
872 Debug("event:finger", "---------- MOVE STARTED (WAIT) ----------");
878 button_id = event->fingerId;
883 button_key = setup.input[0].key.snap;
885 HandleKey(button_key, KEY_PRESSED);
887 Debug("event:finger", "---------- SNAP STARTED ----------");
890 else if (event->type == EVENT_FINGERRELEASE)
892 if (event->fingerId == motion_id)
896 if (motion_key_x != KSYM_UNDEFINED)
897 HandleKey(motion_key_x, KEY_RELEASED);
898 if (motion_key_y != KSYM_UNDEFINED)
899 HandleKey(motion_key_y, KEY_RELEASED);
901 motion_key_x = KSYM_UNDEFINED;
902 motion_key_y = KSYM_UNDEFINED;
904 Debug("event:finger", "---------- MOVE STOPPED ----------");
906 else if (event->fingerId == button_id)
910 if (button_key != KSYM_UNDEFINED)
911 HandleKey(button_key, KEY_RELEASED);
913 button_key = KSYM_UNDEFINED;
915 Debug("event:finger", "---------- SNAP STOPPED ----------");
918 else if (event->type == EVENT_FINGERMOTION)
920 if (event->fingerId == motion_id)
922 float distance_x = ABS(event_x - motion_x1);
923 float distance_y = ABS(event_y - motion_y1);
924 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
925 event_x > motion_x1 ? setup.input[0].key.right :
927 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
928 event_y > motion_y1 ? setup.input[0].key.down :
931 if (distance_x < move_trigger_distance / 2 ||
932 distance_x < distance_y)
933 new_motion_key_x = KSYM_UNDEFINED;
935 if (distance_y < move_trigger_distance / 2 ||
936 distance_y < distance_x)
937 new_motion_key_y = KSYM_UNDEFINED;
939 if (distance_x > move_trigger_distance ||
940 distance_y > move_trigger_distance)
942 if (new_motion_key_x != motion_key_x)
944 if (motion_key_x != KSYM_UNDEFINED)
945 HandleKey(motion_key_x, KEY_RELEASED);
946 if (new_motion_key_x != KSYM_UNDEFINED)
947 HandleKey(new_motion_key_x, KEY_PRESSED);
950 if (new_motion_key_y != motion_key_y)
952 if (motion_key_y != KSYM_UNDEFINED)
953 HandleKey(motion_key_y, KEY_RELEASED);
954 if (new_motion_key_y != KSYM_UNDEFINED)
955 HandleKey(new_motion_key_y, KEY_PRESSED);
961 motion_key_x = new_motion_key_x;
962 motion_key_y = new_motion_key_y;
964 Debug("event:finger", "---------- MOVE STARTED (MOVE) ----------");
967 else if (event->fingerId == button_id)
969 float distance_x = ABS(event_x - button_x1);
970 float distance_y = ABS(event_y - button_y1);
972 if (distance_x < drop_trigger_distance / 2 &&
973 distance_y > drop_trigger_distance)
975 if (button_key == setup.input[0].key.snap)
976 HandleKey(button_key, KEY_RELEASED);
981 button_key = setup.input[0].key.drop;
983 HandleKey(button_key, KEY_PRESSED);
985 Debug("event:finger", "---------- DROP STARTED ----------");
991 void HandleFingerEvent(FingerEvent *event)
993 #if DEBUG_EVENTS_FINGER
994 Debug("event:finger", "finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
995 event->type == EVENT_FINGERPRESS ? "pressed" :
996 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
1000 event->dx, event->dy,
1004 runtime.uses_touch_device = TRUE;
1006 if (game_status != GAME_MODE_PLAYING)
1009 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1011 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1012 local_player->mouse_action.button_hint =
1013 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1014 event->x < 0.5 ? MB_LEFTBUTTON :
1015 event->x > 0.5 ? MB_RIGHTBUTTON :
1021 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1022 HandleFingerEvent_VirtualButtons(event);
1023 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1024 HandleFingerEvent_WipeGestures(event);
1027 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1029 static int old_mx = 0, old_my = 0;
1030 static int last_button = MB_LEFTBUTTON;
1031 static boolean touched = FALSE;
1032 static boolean tapped = FALSE;
1034 // screen tile was tapped (but finger not touching the screen anymore)
1035 // (this point will also be reached without receiving a touch event)
1036 if (tapped && !touched)
1038 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1043 // stop here if this function was not triggered by a touch event
1047 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1049 // finger started touching the screen
1059 ClearPlayerMouseAction();
1061 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1064 else if (button == MB_RELEASED && touched)
1066 // finger stopped touching the screen
1071 SetPlayerMouseAction(old_mx, old_my, last_button);
1073 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1075 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1080 // finger moved while touching the screen
1082 int old_x = getLevelFromScreenX(old_mx);
1083 int old_y = getLevelFromScreenY(old_my);
1084 int new_x = getLevelFromScreenX(mx);
1085 int new_y = getLevelFromScreenY(my);
1087 if (new_x != old_x || new_y != old_y)
1092 // finger moved left or right from (horizontal) starting position
1094 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1096 SetPlayerMouseAction(old_mx, old_my, button_nr);
1098 last_button = button_nr;
1100 Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1104 // finger stays at or returned to (horizontal) starting position
1106 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1108 Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1113 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1115 static int old_mx = 0, old_my = 0;
1116 static int last_button = MB_LEFTBUTTON;
1117 static boolean touched = FALSE;
1118 static boolean tapped = FALSE;
1120 // screen tile was tapped (but finger not touching the screen anymore)
1121 // (this point will also be reached without receiving a touch event)
1122 if (tapped && !touched)
1124 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1129 // stop here if this function was not triggered by a touch event
1133 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1135 // finger started touching the screen
1145 ClearPlayerMouseAction();
1147 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1150 else if (button == MB_RELEASED && touched)
1152 // finger stopped touching the screen
1157 SetPlayerMouseAction(old_mx, old_my, last_button);
1159 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1161 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1166 // finger moved while touching the screen
1168 int old_x = getLevelFromScreenX(old_mx);
1169 int old_y = getLevelFromScreenY(old_my);
1170 int new_x = getLevelFromScreenX(mx);
1171 int new_y = getLevelFromScreenY(my);
1173 if (new_x != old_x || new_y != old_y)
1175 // finger moved away from starting position
1177 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1179 // quickly alternate between clicking and releasing for maximum speed
1180 if (FrameCounter % 2 == 0)
1181 button_nr = MB_RELEASED;
1183 SetPlayerMouseAction(old_mx, old_my, button_nr);
1186 last_button = button_nr;
1190 Debug("event:finger", "---------- TOUCH ACTION: ROTATING ----------");
1194 // finger stays at or returned to starting position
1196 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1198 Debug("event:finger", "---------- TOUCH ACTION PAUSED ----------");
1203 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1205 static int old_mx = 0, old_my = 0;
1206 static Key motion_key_x = KSYM_UNDEFINED;
1207 static Key motion_key_y = KSYM_UNDEFINED;
1208 static boolean touched = FALSE;
1209 static boolean started_on_player = FALSE;
1210 static boolean player_is_dropping = FALSE;
1211 static int player_drop_count = 0;
1212 static int last_player_x = -1;
1213 static int last_player_y = -1;
1215 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1224 started_on_player = FALSE;
1225 player_is_dropping = FALSE;
1226 player_drop_count = 0;
1230 motion_key_x = KSYM_UNDEFINED;
1231 motion_key_y = KSYM_UNDEFINED;
1233 Debug("event:finger", "---------- TOUCH ACTION STARTED ----------");
1236 else if (button == MB_RELEASED && touched)
1243 if (motion_key_x != KSYM_UNDEFINED)
1244 HandleKey(motion_key_x, KEY_RELEASED);
1245 if (motion_key_y != KSYM_UNDEFINED)
1246 HandleKey(motion_key_y, KEY_RELEASED);
1248 if (started_on_player)
1250 if (player_is_dropping)
1252 Debug("event:finger", "---------- DROP STOPPED ----------");
1254 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1258 Debug("event:finger", "---------- SNAP STOPPED ----------");
1260 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1264 motion_key_x = KSYM_UNDEFINED;
1265 motion_key_y = KSYM_UNDEFINED;
1267 Debug("event:finger", "---------- TOUCH ACTION STOPPED ----------");
1272 int src_x = local_player->jx;
1273 int src_y = local_player->jy;
1274 int dst_x = getLevelFromScreenX(old_mx);
1275 int dst_y = getLevelFromScreenY(old_my);
1276 int dx = dst_x - src_x;
1277 int dy = dst_y - src_y;
1278 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1279 dx > 0 ? setup.input[0].key.right :
1281 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1282 dy > 0 ? setup.input[0].key.down :
1285 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1286 (last_player_x != local_player->jx ||
1287 last_player_y != local_player->jy))
1289 // in case of asymmetric diagonal movement, use "preferred" direction
1291 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1293 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1294 game_em.ply[0]->last_move_dir = last_move_dir;
1296 local_player->last_move_dir = last_move_dir;
1298 // (required to prevent accidentally forcing direction for next movement)
1299 last_player_x = local_player->jx;
1300 last_player_y = local_player->jy;
1303 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1305 started_on_player = TRUE;
1306 player_drop_count = getPlayerInventorySize(0);
1307 player_is_dropping = (player_drop_count > 0);
1309 if (player_is_dropping)
1311 Debug("event:finger", "---------- DROP STARTED ----------");
1313 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1317 Debug("event:finger", "---------- SNAP STARTED ----------");
1319 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1322 else if (dx != 0 || dy != 0)
1324 if (player_is_dropping &&
1325 player_drop_count == getPlayerInventorySize(0))
1327 Debug("event:finger", "---------- DROP -> SNAP ----------");
1329 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1330 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1332 player_is_dropping = FALSE;
1336 if (new_motion_key_x != motion_key_x)
1338 Debug("event:finger", "---------- %s %s ----------",
1339 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1340 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1342 if (motion_key_x != KSYM_UNDEFINED)
1343 HandleKey(motion_key_x, KEY_RELEASED);
1344 if (new_motion_key_x != KSYM_UNDEFINED)
1345 HandleKey(new_motion_key_x, KEY_PRESSED);
1348 if (new_motion_key_y != motion_key_y)
1350 Debug("event:finger", "---------- %s %s ----------",
1351 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1352 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1354 if (motion_key_y != KSYM_UNDEFINED)
1355 HandleKey(motion_key_y, KEY_RELEASED);
1356 if (new_motion_key_y != KSYM_UNDEFINED)
1357 HandleKey(new_motion_key_y, KEY_PRESSED);
1360 motion_key_x = new_motion_key_x;
1361 motion_key_y = new_motion_key_y;
1365 static void HandleButtonOrFinger(int mx, int my, int button)
1367 boolean valid_mouse_event = (mx != -1 && my != -1 && button != -1);
1369 if (game_status != GAME_MODE_PLAYING)
1372 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1374 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1375 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1376 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1377 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1378 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1379 SetPlayerMouseAction(mx, my, button); // special case
1383 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1384 HandleButtonOrFinger_FollowFinger(mx, my, button);
1385 else if (game.use_mouse_actions && valid_mouse_event)
1386 SetPlayerMouseAction(mx, my, button);
1390 static boolean checkTextInputKey(Key key)
1392 // when playing, only handle raw key events and ignore text input
1393 if (game_status == GAME_MODE_PLAYING)
1396 // if Shift or right Alt key is pressed, handle key as text input
1397 if ((GetKeyModState() & KMOD_TextInput) != KMOD_None)
1400 // ignore raw keys as text input when not in text input mode
1401 if (KSYM_RAW(key) && !textinput_status)
1404 // else handle all printable keys as text input
1405 return KSYM_PRINTABLE(key);
1408 void HandleTextEvent(TextEvent *event)
1410 char *text = event->text;
1411 Key key = getKeyFromKeyName(text);
1413 #if DEBUG_EVENTS_TEXT
1414 Debug("event:text", "text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1417 text[0], (int)(text[0]),
1419 getKeyNameFromKey(key),
1423 if (checkTextInputKey(key))
1425 // process printable keys (with uppercase etc.) in text input mode
1426 HandleKey(key, KEY_PRESSED);
1427 HandleKey(key, KEY_RELEASED);
1431 void HandlePauseResumeEvent(PauseResumeEvent *event)
1433 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1437 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1443 void HandleKeyEvent(KeyEvent *event)
1445 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1446 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1447 Key key = GetEventKey(event, with_modifiers);
1448 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1450 #if DEBUG_EVENTS_KEY
1451 Debug("event:key", "key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1452 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1453 event->keysym.scancode,
1458 getKeyNameFromKey(key));
1461 #if defined(PLATFORM_ANDROID)
1462 if (key == KSYM_Back)
1464 // always map the "back" button to the "escape" key on Android devices
1467 else if (key == KSYM_Menu)
1469 // the "menu" button can be used to toggle displaying virtual buttons
1470 if (key_status == KEY_PRESSED)
1471 SetOverlayEnabled(!GetOverlayEnabled());
1475 // for any other "real" key event, disable virtual buttons
1476 SetOverlayEnabled(FALSE);
1478 // for any other "real" key event, disable overlay touch buttons
1479 runtime.uses_touch_device = FALSE;
1483 HandleKeyModState(keymod, key_status);
1485 // process all keys if not in text input mode or if non-printable keys
1486 if (!checkTextInputKey(key))
1487 HandleKey(key, key_status);
1490 static int HandleDropFileEvent(char *filename)
1492 Debug("event:dropfile", "filename == '%s'", filename);
1494 // check and extract dropped zip files into correct user data directory
1495 if (!strSuffixLower(filename, ".zip"))
1497 Warn("file '%s' not supported", filename);
1499 return TREE_TYPE_UNDEFINED;
1502 TreeInfo *tree_node = NULL;
1503 int tree_type = GetZipFileTreeType(filename);
1504 char *directory = TREE_USERDIR(tree_type);
1506 if (directory == NULL)
1508 Warn("zip file '%s' has invalid content!", filename);
1510 return TREE_TYPE_UNDEFINED;
1513 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1514 game_status == GAME_MODE_LEVELS &&
1515 leveldir_current->node_parent != NULL)
1517 // extract new level set next to currently selected level set
1518 tree_node = leveldir_current;
1520 // get parent directory of currently selected level set directory
1521 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1523 // use private level directory instead of top-level package level directory
1524 if (strPrefix(directory, options.level_directory) &&
1525 strEqual(leveldir_current->node_parent->fullpath, "."))
1526 directory = getUserLevelDir(NULL);
1529 // extract level or artwork set from zip file to target directory
1530 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1532 if (top_dir == NULL)
1534 // error message already issued by "ExtractZipFileIntoDirectory()"
1536 return TREE_TYPE_UNDEFINED;
1539 // add extracted level or artwork set to tree info structure
1540 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1542 // update menu screen (and possibly change current level set)
1543 DrawScreenAfterAddingSet(top_dir, tree_type);
1548 static void HandleDropTextEvent(char *text)
1550 Debug("event:droptext", "text == '%s'", text);
1553 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1554 int num_artwork_sets_succeeded,
1555 int num_files_failed)
1557 // only show request dialog if no other request dialog already active
1558 if (game.request_active)
1561 // this case can happen with drag-and-drop with older SDL versions
1562 if (num_level_sets_succeeded == 0 &&
1563 num_artwork_sets_succeeded == 0 &&
1564 num_files_failed == 0)
1569 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1571 char message_part1[50];
1573 sprintf(message_part1, "New %s set%s added",
1574 (num_artwork_sets_succeeded == 0 ? "level" :
1575 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1576 (num_level_sets_succeeded +
1577 num_artwork_sets_succeeded > 1 ? "s" : ""));
1579 if (num_files_failed > 0)
1580 sprintf(message, "%s, but %d dropped file%s failed!",
1581 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1583 sprintf(message, "%s!", message_part1);
1585 else if (num_files_failed > 0)
1587 sprintf(message, "Failed to process dropped file%s!",
1588 num_files_failed > 1 ? "s" : "");
1591 Request(message, REQ_CONFIRM);
1594 void HandleDropEvent(Event *event)
1596 static boolean confirm_on_drop_complete = FALSE;
1597 static int num_level_sets_succeeded = 0;
1598 static int num_artwork_sets_succeeded = 0;
1599 static int num_files_failed = 0;
1601 switch (event->type)
1605 confirm_on_drop_complete = TRUE;
1606 num_level_sets_succeeded = 0;
1607 num_artwork_sets_succeeded = 0;
1608 num_files_failed = 0;
1615 int tree_type = HandleDropFileEvent(event->drop.file);
1617 if (tree_type == TREE_TYPE_LEVEL_DIR)
1618 num_level_sets_succeeded++;
1619 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1620 tree_type == TREE_TYPE_SOUNDS_DIR ||
1621 tree_type == TREE_TYPE_MUSIC_DIR)
1622 num_artwork_sets_succeeded++;
1626 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1627 if (!confirm_on_drop_complete)
1629 // process all remaining events, including further SDL_DROPFILE events
1632 HandleDropCompleteEvent(num_level_sets_succeeded,
1633 num_artwork_sets_succeeded,
1636 num_level_sets_succeeded = 0;
1637 num_artwork_sets_succeeded = 0;
1638 num_files_failed = 0;
1646 HandleDropTextEvent(event->drop.file);
1651 case SDL_DROPCOMPLETE:
1653 HandleDropCompleteEvent(num_level_sets_succeeded,
1654 num_artwork_sets_succeeded,
1661 if (event->drop.file != NULL)
1662 SDL_free(event->drop.file);
1665 void HandleUserEvent(UserEvent *event)
1667 switch (event->code)
1669 case USEREVENT_ANIM_DELAY_ACTION:
1670 case USEREVENT_ANIM_EVENT_ACTION:
1671 // execute action functions until matching action was found
1672 if (DoKeysymAction(event->value1) ||
1673 DoGadgetAction(event->value1) ||
1674 DoScreenAction(event->value1))
1683 void HandleButton(int mx, int my, int button, int button_nr)
1685 static int old_mx = 0, old_my = 0;
1686 boolean button_hold = FALSE;
1687 boolean handle_gadgets = TRUE;
1693 button_nr = -button_nr;
1702 #if defined(PLATFORM_ANDROID)
1703 // when playing, only handle gadgets when using "follow finger" controls
1704 // or when using touch controls in combination with the MM game engine
1705 // or when using gadgets that do not overlap with virtual buttons
1707 (game_status != GAME_MODE_PLAYING ||
1708 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1709 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1710 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1711 !CheckVirtualButtonPressed(mx, my, button)));
1713 // always recognize potentially releasing already pressed gadgets
1714 if (button == MB_RELEASED)
1715 handle_gadgets = TRUE;
1717 // always recognize pressing or releasing overlay touch buttons
1718 if (CheckPosition_OverlayTouchButtons(mx, my, button) && !motion_status)
1719 handle_gadgets = TRUE;
1722 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1724 // do not handle this button event anymore
1725 return; // force mouse event not to be handled at all
1728 if (handle_gadgets && HandleGadgets(mx, my, button))
1730 // do not handle this button event anymore
1731 mx = my = -32; // force mouse event to be outside screen tiles
1734 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1737 // do not use scroll wheel button events for anything other than gadgets
1738 if (IS_WHEEL_BUTTON(button_nr))
1741 switch (game_status)
1743 case GAME_MODE_TITLE:
1744 HandleTitleScreen(mx, my, 0, 0, button);
1747 case GAME_MODE_MAIN:
1748 HandleMainMenu(mx, my, 0, 0, button);
1751 case GAME_MODE_PSEUDO_TYPENAME:
1752 case GAME_MODE_PSEUDO_TYPENAMES:
1753 HandleTypeName(KSYM_Return);
1756 case GAME_MODE_NAMES:
1757 HandleChoosePlayerName(mx, my, 0, 0, button);
1760 case GAME_MODE_LEVELS:
1761 HandleChooseLevelSet(mx, my, 0, 0, button);
1764 case GAME_MODE_LEVELNR:
1765 HandleChooseLevelNr(mx, my, 0, 0, button);
1768 case GAME_MODE_SCORES:
1769 HandleHallOfFame(0, 0, 0, 0, button);
1772 case GAME_MODE_EDITOR:
1773 HandleLevelEditorIdle();
1776 case GAME_MODE_INFO:
1777 HandleInfoScreen(mx, my, 0, 0, button);
1780 case GAME_MODE_SETUP:
1781 HandleSetupScreen(mx, my, 0, 0, button);
1784 case GAME_MODE_PLAYING:
1785 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1786 HandleButtonOrFinger(mx, my, button);
1788 SetPlayerMouseAction(mx, my, button);
1791 if (button == MB_PRESSED && !motion_status && !button_hold &&
1792 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1793 DumpTileFromScreen(mx, my);
1803 #define MAX_CHEAT_INPUT_LEN 32
1805 static void HandleKeysSpecial(Key key)
1807 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1808 char letter = getCharFromKey(key);
1809 int cheat_input_len = strlen(cheat_input);
1815 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1817 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1818 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1820 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1823 cheat_input[cheat_input_len++] = letter;
1824 cheat_input[cheat_input_len] = '\0';
1826 #if DEBUG_EVENTS_KEY
1827 Debug("event:key:special", "'%s' [%d]", cheat_input, cheat_input_len);
1830 if (game_status == GAME_MODE_MAIN)
1832 if (strSuffix(cheat_input, ":insert-solution-tape") ||
1833 strSuffix(cheat_input, ":ist"))
1835 InsertSolutionTape();
1837 else if (strSuffix(cheat_input, ":play-solution-tape") ||
1838 strSuffix(cheat_input, ":pst"))
1842 else if (strSuffix(cheat_input, ":reload-graphics") ||
1843 strSuffix(cheat_input, ":rg"))
1845 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1848 else if (strSuffix(cheat_input, ":reload-sounds") ||
1849 strSuffix(cheat_input, ":rs"))
1851 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1854 else if (strSuffix(cheat_input, ":reload-music") ||
1855 strSuffix(cheat_input, ":rm"))
1857 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1860 else if (strSuffix(cheat_input, ":reload-artwork") ||
1861 strSuffix(cheat_input, ":ra"))
1863 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1864 1 << ARTWORK_TYPE_SOUNDS |
1865 1 << ARTWORK_TYPE_MUSIC);
1868 else if (strSuffix(cheat_input, ":dump-level") ||
1869 strSuffix(cheat_input, ":dl"))
1873 else if (strSuffix(cheat_input, ":dump-tape") ||
1874 strSuffix(cheat_input, ":dt"))
1878 else if (strSuffix(cheat_input, ":undo-tape") ||
1879 strSuffix(cheat_input, ":ut"))
1883 else if (strSuffix(cheat_input, ":fix-tape") ||
1884 strSuffix(cheat_input, ":ft"))
1886 FixTape_ForceSinglePlayer();
1888 else if (strSuffix(cheat_input, ":save-native-level") ||
1889 strSuffix(cheat_input, ":snl"))
1891 SaveNativeLevel(&level);
1893 else if (strSuffix(cheat_input, ":frames-per-second") ||
1894 strSuffix(cheat_input, ":fps"))
1896 global.show_frames_per_second = !global.show_frames_per_second;
1898 else if (strSuffix(cheat_input, ":xsn"))
1900 tile_cursor.xsn_debug = TRUE;
1903 else if (game_status == GAME_MODE_PLAYING)
1906 if (strSuffix(cheat_input, ".q"))
1907 DEBUG_SetMaximumDynamite();
1910 else if (game_status == GAME_MODE_EDITOR)
1912 if (strSuffix(cheat_input, ":dump-brush") ||
1913 strSuffix(cheat_input, ":DB"))
1917 else if (strSuffix(cheat_input, ":DDB"))
1922 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1924 if (letter == 'x') // copy brush to clipboard (small size)
1926 CopyBrushToClipboard_Small();
1928 else if (letter == 'c') // copy brush to clipboard (normal size)
1930 CopyBrushToClipboard();
1932 else if (letter == 'v') // paste brush from Clipboard
1934 CopyClipboardToBrush();
1936 else if (letter == 'z') // undo or redo last operation
1938 if (GetKeyModState() & KMOD_Shift)
1939 RedoLevelEditorOperation();
1941 UndoLevelEditorOperation();
1946 // special key shortcuts for all game modes
1947 if (strSuffix(cheat_input, ":dump-event-actions") ||
1948 strSuffix(cheat_input, ":dea") ||
1949 strSuffix(cheat_input, ":DEA"))
1951 DumpGadgetIdentifiers();
1952 DumpScreenIdentifiers();
1956 boolean HandleKeysDebug(Key key, int key_status)
1961 if (key_status != KEY_PRESSED)
1964 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1966 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1968 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1970 if (key == setup.debug.frame_delay_key[i] &&
1971 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1973 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1974 setup.debug.frame_delay[i] : setup.game_frame_delay);
1976 if (!setup.debug.frame_delay_game_only)
1977 MenuFrameDelay = GameFrameDelay;
1979 SetVideoFrameDelay(GameFrameDelay);
1981 if (GameFrameDelay > ONE_SECOND_DELAY)
1982 Debug("event:key:debug", "frame delay == %d ms", GameFrameDelay);
1983 else if (GameFrameDelay != 0)
1984 Debug("event:key:debug", "frame delay == %d ms (max. %d fps / %d %%)",
1985 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1986 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1988 Debug("event:key:debug", "frame delay == 0 ms (maximum speed)");
1995 if (game_status == GAME_MODE_PLAYING)
1999 options.debug = !options.debug;
2001 Debug("event:key:debug", "debug mode %s",
2002 (options.debug ? "enabled" : "disabled"));
2006 else if (key == KSYM_v)
2008 Debug("event:key:debug", "currently using game engine version %d",
2009 game.engine_version);
2019 void HandleKey(Key key, int key_status)
2021 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2022 static boolean ignore_repeated_key = FALSE;
2023 static struct SetupKeyboardInfo ski;
2024 static struct SetupShortcutInfo ssi;
2033 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
2034 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
2035 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
2036 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
2037 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2038 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2043 if (HandleKeysDebug(key, key_status))
2044 return; // do not handle already processed keys again
2046 // map special keys (media keys / remote control buttons) to default keys
2047 if (key == KSYM_PlayPause)
2049 else if (key == KSYM_Select)
2052 HandleSpecialGameControllerKeys(key, key_status);
2054 if (game_status == GAME_MODE_PLAYING)
2056 // only needed for single-step tape recording mode
2057 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2060 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2062 byte key_action = 0;
2063 byte key_snap_action = 0;
2065 if (setup.input[pnr].use_joystick)
2068 ski = setup.input[pnr].key;
2070 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2071 if (key == *key_info[i].key_custom)
2072 key_action |= key_info[i].action;
2074 // use combined snap+direction keys for the first player only
2077 ssi = setup.shortcut;
2079 // also remember normal snap key when handling snap+direction keys
2080 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2082 for (i = 0; i < NUM_DIRECTIONS; i++)
2084 if (key == *key_info[i].key_snap)
2086 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2087 key_snap_action |= key_info[i].action;
2092 if (key_status == KEY_PRESSED)
2094 stored_player[pnr].action |= key_action;
2095 stored_player[pnr].snap_action |= key_snap_action;
2099 stored_player[pnr].action &= ~key_action;
2100 stored_player[pnr].snap_action &= ~key_snap_action;
2103 // restore snap action if one of several pressed snap keys was released
2104 if (stored_player[pnr].snap_action)
2105 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2107 if (tape.recording && tape.pausing && tape.use_key_actions)
2109 if (tape.single_step)
2111 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2113 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2115 // if snap key already pressed, keep pause mode when releasing
2116 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2117 has_snapped[pnr] = TRUE;
2119 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2121 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2123 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2124 getRedDiskReleaseFlag_SP() == 0)
2126 // add a single inactive frame before dropping starts
2127 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2128 stored_player[pnr].force_dropping = TRUE;
2131 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2133 // if snap key was pressed without direction, leave pause mode
2134 if (!has_snapped[pnr])
2135 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2137 has_snapped[pnr] = FALSE;
2142 // prevent key release events from un-pausing a paused game
2143 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2144 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2148 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2149 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2155 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2156 if (key == key_info[i].key_default)
2157 joy |= key_info[i].action;
2162 if (key_status == KEY_PRESSED)
2163 key_joystick_mapping |= joy;
2165 key_joystick_mapping &= ~joy;
2170 if (game_status != GAME_MODE_PLAYING)
2171 key_joystick_mapping = 0;
2173 if (key_status == KEY_RELEASED)
2175 // reset flag to ignore repeated "key pressed" events after key release
2176 ignore_repeated_key = FALSE;
2181 if ((key == KSYM_F11 ||
2182 ((key == KSYM_Return ||
2183 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2184 video.fullscreen_available &&
2185 !ignore_repeated_key)
2187 setup.fullscreen = !setup.fullscreen;
2189 ToggleFullscreenIfNeeded();
2191 if (game_status == GAME_MODE_SETUP)
2192 RedrawSetupScreenAfterFullscreenToggle();
2194 UpdateMousePosition();
2196 // set flag to ignore repeated "key pressed" events
2197 ignore_repeated_key = TRUE;
2202 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2203 key == KSYM_minus || key == KSYM_KP_Subtract ||
2204 key == KSYM_plus || key == KSYM_KP_Add ||
2205 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2206 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2207 video.window_scaling_available &&
2208 !video.fullscreen_enabled)
2210 if (key == KSYM_0 || key == KSYM_KP_0)
2211 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2212 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2213 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2215 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2217 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2218 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2219 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2220 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2222 ChangeWindowScalingIfNeeded();
2224 if (game_status == GAME_MODE_SETUP)
2225 RedrawSetupScreenAfterFullscreenToggle();
2227 UpdateMousePosition();
2232 // some key events are handled like clicks for global animations
2233 boolean click = (key == KSYM_space ||
2234 key == KSYM_Return ||
2235 key == KSYM_Escape);
2237 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2239 // do not handle this key event anymore
2240 if (key != KSYM_Escape) // always allow ESC key to be handled
2244 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2245 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2252 if (game_status == GAME_MODE_MAIN &&
2253 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2255 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2260 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2262 if (key == setup.shortcut.save_game)
2264 else if (key == setup.shortcut.load_game)
2266 else if (key == setup.shortcut.toggle_pause)
2267 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2269 HandleTapeButtonKeys(key);
2270 HandleSoundButtonKeys(key);
2273 if (game_status == GAME_MODE_PLAYING && !network_playing)
2275 int centered_player_nr_next = -999;
2277 if (key == setup.shortcut.focus_player_all)
2278 centered_player_nr_next = -1;
2280 for (i = 0; i < MAX_PLAYERS; i++)
2281 if (key == setup.shortcut.focus_player[i])
2282 centered_player_nr_next = i;
2284 if (centered_player_nr_next != -999)
2286 game.centered_player_nr_next = centered_player_nr_next;
2287 game.set_centered_player = TRUE;
2291 tape.centered_player_nr_next = game.centered_player_nr_next;
2292 tape.set_centered_player = TRUE;
2297 HandleKeysSpecial(key);
2299 if (HandleGadgetsKeyInput(key))
2300 return; // do not handle already processed keys again
2302 switch (game_status)
2304 case GAME_MODE_PSEUDO_TYPENAME:
2305 case GAME_MODE_PSEUDO_TYPENAMES:
2306 HandleTypeName(key);
2309 case GAME_MODE_TITLE:
2310 case GAME_MODE_MAIN:
2311 case GAME_MODE_NAMES:
2312 case GAME_MODE_LEVELS:
2313 case GAME_MODE_LEVELNR:
2314 case GAME_MODE_SETUP:
2315 case GAME_MODE_INFO:
2316 case GAME_MODE_SCORES:
2318 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2325 if (game_status == GAME_MODE_TITLE)
2326 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2327 else if (game_status == GAME_MODE_MAIN)
2328 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2329 else if (game_status == GAME_MODE_NAMES)
2330 HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_CHOICE);
2331 else if (game_status == GAME_MODE_LEVELS)
2332 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2333 else if (game_status == GAME_MODE_LEVELNR)
2334 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2335 else if (game_status == GAME_MODE_SETUP)
2336 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2337 else if (game_status == GAME_MODE_INFO)
2338 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2339 else if (game_status == GAME_MODE_SCORES)
2340 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2344 if (game_status != GAME_MODE_MAIN)
2345 FadeSkipNextFadeIn();
2347 if (game_status == GAME_MODE_TITLE)
2348 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2349 else if (game_status == GAME_MODE_NAMES)
2350 HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_LEAVE);
2351 else if (game_status == GAME_MODE_LEVELS)
2352 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2353 else if (game_status == GAME_MODE_LEVELNR)
2354 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2355 else if (game_status == GAME_MODE_SETUP)
2356 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2357 else if (game_status == GAME_MODE_INFO)
2358 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2359 else if (game_status == GAME_MODE_SCORES)
2360 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2364 if (game_status == GAME_MODE_NAMES)
2365 HandleChoosePlayerName(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2366 else if (game_status == GAME_MODE_LEVELS)
2367 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2368 else if (game_status == GAME_MODE_LEVELNR)
2369 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2370 else if (game_status == GAME_MODE_SETUP)
2371 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2372 else if (game_status == GAME_MODE_INFO)
2373 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2374 else if (game_status == GAME_MODE_SCORES)
2375 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2378 case KSYM_Page_Down:
2379 if (game_status == GAME_MODE_NAMES)
2380 HandleChoosePlayerName(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2381 else if (game_status == GAME_MODE_LEVELS)
2382 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2383 else if (game_status == GAME_MODE_LEVELNR)
2384 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2385 else if (game_status == GAME_MODE_SETUP)
2386 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2387 else if (game_status == GAME_MODE_INFO)
2388 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2389 else if (game_status == GAME_MODE_SCORES)
2390 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2398 case GAME_MODE_EDITOR:
2399 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2400 HandleLevelEditorKeyInput(key);
2403 case GAME_MODE_PLAYING:
2408 RequestQuitGame(setup.ask_on_escape);
2418 if (key == KSYM_Escape)
2420 SetGameStatus(GAME_MODE_MAIN);
2429 void HandleNoEvent(void)
2431 HandleMouseCursor();
2433 switch (game_status)
2435 case GAME_MODE_PLAYING:
2436 HandleButtonOrFinger(-1, -1, -1);
2441 void HandleEventActions(void)
2443 // if (button_status && game_status != GAME_MODE_PLAYING)
2444 if (button_status && (game_status != GAME_MODE_PLAYING ||
2446 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2448 HandleButton(0, 0, button_status, -button_status);
2455 if (network.enabled)
2458 switch (game_status)
2460 case GAME_MODE_MAIN:
2461 DrawPreviewLevelAnimation();
2464 case GAME_MODE_EDITOR:
2465 HandleLevelEditorIdle();
2473 static void HandleTileCursor(int dx, int dy, int button)
2476 ClearPlayerMouseAction();
2483 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2484 (dx < 0 ? MB_LEFTBUTTON :
2485 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2487 else if (!tile_cursor.moving)
2489 int old_xpos = tile_cursor.xpos;
2490 int old_ypos = tile_cursor.ypos;
2491 int new_xpos = old_xpos;
2492 int new_ypos = old_ypos;
2494 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2495 new_xpos = old_xpos + dx;
2497 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2498 new_ypos = old_ypos + dy;
2500 SetTileCursorTargetXY(new_xpos, new_ypos);
2504 static int HandleJoystickForAllPlayers(void)
2508 boolean no_joysticks_configured = TRUE;
2509 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2510 static byte joy_action_last[MAX_PLAYERS];
2512 for (i = 0; i < MAX_PLAYERS; i++)
2513 if (setup.input[i].use_joystick)
2514 no_joysticks_configured = FALSE;
2516 // if no joysticks configured, map connected joysticks to players
2517 if (no_joysticks_configured)
2518 use_as_joystick_nr = TRUE;
2520 for (i = 0; i < MAX_PLAYERS; i++)
2522 byte joy_action = 0;
2524 joy_action = JoystickExt(i, use_as_joystick_nr);
2525 result |= joy_action;
2527 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2528 joy_action != joy_action_last[i])
2529 stored_player[i].action = joy_action;
2531 joy_action_last[i] = joy_action;
2537 void HandleJoystick(void)
2539 static unsigned int joytest_delay = 0;
2540 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2541 static int joytest_last = 0;
2542 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2543 int delay_value = GADGET_FRAME_DELAY;
2544 int joystick = HandleJoystickForAllPlayers();
2545 int keyboard = key_joystick_mapping;
2546 int joy = (joystick | keyboard);
2547 int joytest = joystick;
2548 int left = joy & JOY_LEFT;
2549 int right = joy & JOY_RIGHT;
2550 int up = joy & JOY_UP;
2551 int down = joy & JOY_DOWN;
2552 int button = joy & JOY_BUTTON;
2553 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2554 int dx = (left ? -1 : right ? 1 : 0);
2555 int dy = (up ? -1 : down ? 1 : 0);
2556 boolean use_delay_value_first = (joytest != joytest_last);
2558 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2560 // do not handle this button event anymore
2564 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2565 game_status == GAME_MODE_PSEUDO_TYPENAMES ||
2566 anyTextGadgetActive()))
2568 // leave name input in main menu or text input gadget
2569 HandleKey(KSYM_Escape, KEY_PRESSED);
2570 HandleKey(KSYM_Escape, KEY_RELEASED);
2575 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2577 if (game_status == GAME_MODE_PLAYING)
2579 // when playing MM style levels, also use delay for keyboard events
2580 joytest |= keyboard;
2582 // only use first delay value for new events, but not for changed events
2583 use_delay_value_first = (!joytest != !joytest_last);
2585 // only use delay after the initial keyboard event
2589 // for any joystick or keyboard event, enable playfield tile cursor
2590 if (dx || dy || button)
2591 SetTileCursorEnabled(TRUE);
2594 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2596 // delay joystick/keyboard actions if axes/keys continually pressed
2597 newbutton = dx = dy = 0;
2601 // first start with longer delay, then continue with shorter delay
2602 joytest_delay_value =
2603 (use_delay_value_first ? delay_value_first : delay_value);
2606 joytest_last = joytest;
2608 switch (game_status)
2610 case GAME_MODE_TITLE:
2611 case GAME_MODE_MAIN:
2612 case GAME_MODE_NAMES:
2613 case GAME_MODE_LEVELS:
2614 case GAME_MODE_LEVELNR:
2615 case GAME_MODE_SETUP:
2616 case GAME_MODE_INFO:
2617 case GAME_MODE_SCORES:
2619 if (anyTextGadgetActive())
2622 if (game_status == GAME_MODE_TITLE)
2623 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2624 else if (game_status == GAME_MODE_MAIN)
2625 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2626 else if (game_status == GAME_MODE_NAMES)
2627 HandleChoosePlayerName(0,0,dx,dy,newbutton?MB_MENU_CHOICE:MB_MENU_MARK);
2628 else if (game_status == GAME_MODE_LEVELS)
2629 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2630 else if (game_status == GAME_MODE_LEVELNR)
2631 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2632 else if (game_status == GAME_MODE_SETUP)
2633 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2634 else if (game_status == GAME_MODE_INFO)
2635 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2636 else if (game_status == GAME_MODE_SCORES)
2637 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2642 case GAME_MODE_PLAYING:
2644 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2645 if (tape.playing || keyboard)
2646 newbutton = ((joy & JOY_BUTTON) != 0);
2649 if (newbutton && game.all_players_gone)
2656 if (tape.recording && tape.pausing && tape.use_key_actions)
2658 if (tape.single_step)
2660 if (joystick & JOY_ACTION)
2661 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2665 if (joystick & JOY_ACTION)
2666 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2670 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2671 HandleTileCursor(dx, dy, button);
2680 void HandleSpecialGameControllerButtons(Event *event)
2685 switch (event->type)
2687 case SDL_CONTROLLERBUTTONDOWN:
2688 key_status = KEY_PRESSED;
2691 case SDL_CONTROLLERBUTTONUP:
2692 key_status = KEY_RELEASED;
2699 switch (event->cbutton.button)
2701 case SDL_CONTROLLER_BUTTON_START:
2705 case SDL_CONTROLLER_BUTTON_BACK:
2713 HandleKey(key, key_status);
2716 void HandleSpecialGameControllerKeys(Key key, int key_status)
2718 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2719 int button = SDL_CONTROLLER_BUTTON_INVALID;
2721 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2722 if (key == KSYM_Rewind)
2723 button = SDL_CONTROLLER_BUTTON_A;
2724 else if (key == KSYM_FastForward || key == KSYM_Menu)
2725 button = SDL_CONTROLLER_BUTTON_B;
2727 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2731 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2732 SDL_CONTROLLERBUTTONUP);
2734 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2735 event.cbutton.button = button;
2736 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2739 HandleJoystickEvent(&event);
2744 boolean DoKeysymAction(int keysym)
2748 Key key = (Key)(-keysym);
2750 HandleKey(key, KEY_PRESSED);
2751 HandleKey(key, KEY_RELEASED);