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 HandleTypeName(0, KSYM_Return);
1755 case GAME_MODE_LEVELS:
1756 HandleChooseLevelSet(mx, my, 0, 0, button);
1759 case GAME_MODE_LEVELNR:
1760 HandleChooseLevelNr(mx, my, 0, 0, button);
1763 case GAME_MODE_SCORES:
1764 HandleHallOfFame(0, 0, 0, 0, button);
1767 case GAME_MODE_EDITOR:
1768 HandleLevelEditorIdle();
1771 case GAME_MODE_INFO:
1772 HandleInfoScreen(mx, my, 0, 0, button);
1775 case GAME_MODE_SETUP:
1776 HandleSetupScreen(mx, my, 0, 0, button);
1779 case GAME_MODE_PLAYING:
1780 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1781 HandleButtonOrFinger(mx, my, button);
1783 SetPlayerMouseAction(mx, my, button);
1786 if (button == MB_PRESSED && !motion_status && !button_hold &&
1787 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1788 DumpTileFromScreen(mx, my);
1798 #define MAX_CHEAT_INPUT_LEN 32
1800 static void HandleKeysSpecial(Key key)
1802 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1803 char letter = getCharFromKey(key);
1804 int cheat_input_len = strlen(cheat_input);
1810 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1812 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1813 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1815 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1818 cheat_input[cheat_input_len++] = letter;
1819 cheat_input[cheat_input_len] = '\0';
1821 #if DEBUG_EVENTS_KEY
1822 Debug("event:key:special", "'%s' [%d]", cheat_input, cheat_input_len);
1825 if (game_status == GAME_MODE_MAIN)
1827 if (strSuffix(cheat_input, ":insert-solution-tape") ||
1828 strSuffix(cheat_input, ":ist"))
1830 InsertSolutionTape();
1832 else if (strSuffix(cheat_input, ":play-solution-tape") ||
1833 strSuffix(cheat_input, ":pst"))
1837 else if (strSuffix(cheat_input, ":reload-graphics") ||
1838 strSuffix(cheat_input, ":rg"))
1840 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1843 else if (strSuffix(cheat_input, ":reload-sounds") ||
1844 strSuffix(cheat_input, ":rs"))
1846 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1849 else if (strSuffix(cheat_input, ":reload-music") ||
1850 strSuffix(cheat_input, ":rm"))
1852 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1855 else if (strSuffix(cheat_input, ":reload-artwork") ||
1856 strSuffix(cheat_input, ":ra"))
1858 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1859 1 << ARTWORK_TYPE_SOUNDS |
1860 1 << ARTWORK_TYPE_MUSIC);
1863 else if (strSuffix(cheat_input, ":dump-level") ||
1864 strSuffix(cheat_input, ":dl"))
1868 else if (strSuffix(cheat_input, ":dump-tape") ||
1869 strSuffix(cheat_input, ":dt"))
1873 else if (strSuffix(cheat_input, ":undo-tape") ||
1874 strSuffix(cheat_input, ":ut"))
1878 else if (strSuffix(cheat_input, ":fix-tape") ||
1879 strSuffix(cheat_input, ":ft"))
1881 FixTape_ForceSinglePlayer();
1883 else if (strSuffix(cheat_input, ":save-native-level") ||
1884 strSuffix(cheat_input, ":snl"))
1886 SaveNativeLevel(&level);
1888 else if (strSuffix(cheat_input, ":frames-per-second") ||
1889 strSuffix(cheat_input, ":fps"))
1891 global.show_frames_per_second = !global.show_frames_per_second;
1894 else if (game_status == GAME_MODE_PLAYING)
1897 if (strSuffix(cheat_input, ".q"))
1898 DEBUG_SetMaximumDynamite();
1901 else if (game_status == GAME_MODE_EDITOR)
1903 if (strSuffix(cheat_input, ":dump-brush") ||
1904 strSuffix(cheat_input, ":DB"))
1908 else if (strSuffix(cheat_input, ":DDB"))
1913 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1915 if (letter == 'x') // copy brush to clipboard (small size)
1917 CopyBrushToClipboard_Small();
1919 else if (letter == 'c') // copy brush to clipboard (normal size)
1921 CopyBrushToClipboard();
1923 else if (letter == 'v') // paste brush from Clipboard
1925 CopyClipboardToBrush();
1927 else if (letter == 'z') // undo or redo last operation
1929 if (GetKeyModState() & KMOD_Shift)
1930 RedoLevelEditorOperation();
1932 UndoLevelEditorOperation();
1937 // special key shortcuts for all game modes
1938 if (strSuffix(cheat_input, ":dump-event-actions") ||
1939 strSuffix(cheat_input, ":dea") ||
1940 strSuffix(cheat_input, ":DEA"))
1942 DumpGadgetIdentifiers();
1943 DumpScreenIdentifiers();
1947 boolean HandleKeysDebug(Key key, int key_status)
1952 if (key_status != KEY_PRESSED)
1955 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1957 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1959 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1961 if (key == setup.debug.frame_delay_key[i] &&
1962 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1964 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1965 setup.debug.frame_delay[i] : setup.game_frame_delay);
1967 if (!setup.debug.frame_delay_game_only)
1968 MenuFrameDelay = GameFrameDelay;
1970 SetVideoFrameDelay(GameFrameDelay);
1972 if (GameFrameDelay > ONE_SECOND_DELAY)
1973 Debug("event:key:debug", "frame delay == %d ms", GameFrameDelay);
1974 else if (GameFrameDelay != 0)
1975 Debug("event:key:debug", "frame delay == %d ms (max. %d fps / %d %%)",
1976 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1977 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1979 Debug("event:key:debug", "frame delay == 0 ms (maximum speed)");
1986 if (game_status == GAME_MODE_PLAYING)
1990 options.debug = !options.debug;
1992 Debug("event:key:debug", "debug mode %s",
1993 (options.debug ? "enabled" : "disabled"));
1997 else if (key == KSYM_v)
1999 Debug("event:key:debug", "currently using game engine version %d",
2000 game.engine_version);
2010 void HandleKey(Key key, int key_status)
2012 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2013 static boolean ignore_repeated_key = FALSE;
2014 static struct SetupKeyboardInfo ski;
2015 static struct SetupShortcutInfo ssi;
2024 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
2025 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
2026 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
2027 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
2028 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
2029 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
2034 if (HandleKeysDebug(key, key_status))
2035 return; // do not handle already processed keys again
2037 // map special keys (media keys / remote control buttons) to default keys
2038 if (key == KSYM_PlayPause)
2040 else if (key == KSYM_Select)
2043 HandleSpecialGameControllerKeys(key, key_status);
2045 if (game_status == GAME_MODE_PLAYING)
2047 // only needed for single-step tape recording mode
2048 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2051 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2053 byte key_action = 0;
2054 byte key_snap_action = 0;
2056 if (setup.input[pnr].use_joystick)
2059 ski = setup.input[pnr].key;
2061 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2062 if (key == *key_info[i].key_custom)
2063 key_action |= key_info[i].action;
2065 // use combined snap+direction keys for the first player only
2068 ssi = setup.shortcut;
2070 // also remember normal snap key when handling snap+direction keys
2071 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2073 for (i = 0; i < NUM_DIRECTIONS; i++)
2075 if (key == *key_info[i].key_snap)
2077 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2078 key_snap_action |= key_info[i].action;
2083 if (key_status == KEY_PRESSED)
2085 stored_player[pnr].action |= key_action;
2086 stored_player[pnr].snap_action |= key_snap_action;
2090 stored_player[pnr].action &= ~key_action;
2091 stored_player[pnr].snap_action &= ~key_snap_action;
2094 // restore snap action if one of several pressed snap keys was released
2095 if (stored_player[pnr].snap_action)
2096 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2098 if (tape.recording && tape.pausing && tape.use_key_actions)
2100 if (tape.single_step)
2102 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2104 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2106 // if snap key already pressed, keep pause mode when releasing
2107 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2108 has_snapped[pnr] = TRUE;
2110 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2112 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2114 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2115 getRedDiskReleaseFlag_SP() == 0)
2117 // add a single inactive frame before dropping starts
2118 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2119 stored_player[pnr].force_dropping = TRUE;
2122 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2124 // if snap key was pressed without direction, leave pause mode
2125 if (!has_snapped[pnr])
2126 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2128 has_snapped[pnr] = FALSE;
2133 // prevent key release events from un-pausing a paused game
2134 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2135 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2139 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2140 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2146 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2147 if (key == key_info[i].key_default)
2148 joy |= key_info[i].action;
2153 if (key_status == KEY_PRESSED)
2154 key_joystick_mapping |= joy;
2156 key_joystick_mapping &= ~joy;
2161 if (game_status != GAME_MODE_PLAYING)
2162 key_joystick_mapping = 0;
2164 if (key_status == KEY_RELEASED)
2166 // reset flag to ignore repeated "key pressed" events after key release
2167 ignore_repeated_key = FALSE;
2172 if ((key == KSYM_F11 ||
2173 ((key == KSYM_Return ||
2174 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2175 video.fullscreen_available &&
2176 !ignore_repeated_key)
2178 setup.fullscreen = !setup.fullscreen;
2180 ToggleFullscreenIfNeeded();
2182 if (game_status == GAME_MODE_SETUP)
2183 RedrawSetupScreenAfterFullscreenToggle();
2185 UpdateMousePosition();
2187 // set flag to ignore repeated "key pressed" events
2188 ignore_repeated_key = TRUE;
2193 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2194 key == KSYM_minus || key == KSYM_KP_Subtract ||
2195 key == KSYM_plus || key == KSYM_KP_Add ||
2196 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2197 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2198 video.window_scaling_available &&
2199 !video.fullscreen_enabled)
2201 if (key == KSYM_0 || key == KSYM_KP_0)
2202 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2203 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2204 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2206 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2208 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2209 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2210 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2211 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2213 ChangeWindowScalingIfNeeded();
2215 if (game_status == GAME_MODE_SETUP)
2216 RedrawSetupScreenAfterFullscreenToggle();
2218 UpdateMousePosition();
2223 // some key events are handled like clicks for global animations
2224 boolean click = (key == KSYM_space ||
2225 key == KSYM_Return ||
2226 key == KSYM_Escape);
2228 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2230 // do not handle this key event anymore
2231 if (key != KSYM_Escape) // always allow ESC key to be handled
2235 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2236 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2243 if (game_status == GAME_MODE_MAIN &&
2244 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2246 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2251 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2253 if (key == setup.shortcut.save_game)
2255 else if (key == setup.shortcut.load_game)
2257 else if (key == setup.shortcut.toggle_pause)
2258 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2260 HandleTapeButtonKeys(key);
2261 HandleSoundButtonKeys(key);
2264 if (game_status == GAME_MODE_PLAYING && !network_playing)
2266 int centered_player_nr_next = -999;
2268 if (key == setup.shortcut.focus_player_all)
2269 centered_player_nr_next = -1;
2271 for (i = 0; i < MAX_PLAYERS; i++)
2272 if (key == setup.shortcut.focus_player[i])
2273 centered_player_nr_next = i;
2275 if (centered_player_nr_next != -999)
2277 game.centered_player_nr_next = centered_player_nr_next;
2278 game.set_centered_player = TRUE;
2282 tape.centered_player_nr_next = game.centered_player_nr_next;
2283 tape.set_centered_player = TRUE;
2288 HandleKeysSpecial(key);
2290 if (HandleGadgetsKeyInput(key))
2291 return; // do not handle already processed keys again
2293 switch (game_status)
2295 case GAME_MODE_PSEUDO_TYPENAME:
2296 HandleTypeName(0, key);
2299 case GAME_MODE_TITLE:
2300 case GAME_MODE_MAIN:
2301 case GAME_MODE_LEVELS:
2302 case GAME_MODE_LEVELNR:
2303 case GAME_MODE_SETUP:
2304 case GAME_MODE_INFO:
2305 case GAME_MODE_SCORES:
2307 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2314 if (game_status == GAME_MODE_TITLE)
2315 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2316 else if (game_status == GAME_MODE_MAIN)
2317 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2318 else if (game_status == GAME_MODE_LEVELS)
2319 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2320 else if (game_status == GAME_MODE_LEVELNR)
2321 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2322 else if (game_status == GAME_MODE_SETUP)
2323 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2324 else if (game_status == GAME_MODE_INFO)
2325 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2326 else if (game_status == GAME_MODE_SCORES)
2327 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2331 if (game_status != GAME_MODE_MAIN)
2332 FadeSkipNextFadeIn();
2334 if (game_status == GAME_MODE_TITLE)
2335 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2336 else if (game_status == GAME_MODE_LEVELS)
2337 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2338 else if (game_status == GAME_MODE_LEVELNR)
2339 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2340 else if (game_status == GAME_MODE_SETUP)
2341 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2342 else if (game_status == GAME_MODE_INFO)
2343 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2344 else if (game_status == GAME_MODE_SCORES)
2345 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2349 if (game_status == GAME_MODE_LEVELS)
2350 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2351 else if (game_status == GAME_MODE_LEVELNR)
2352 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2353 else if (game_status == GAME_MODE_SETUP)
2354 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2355 else if (game_status == GAME_MODE_INFO)
2356 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2357 else if (game_status == GAME_MODE_SCORES)
2358 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2361 case KSYM_Page_Down:
2362 if (game_status == GAME_MODE_LEVELS)
2363 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2364 else if (game_status == GAME_MODE_LEVELNR)
2365 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2366 else if (game_status == GAME_MODE_SETUP)
2367 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2368 else if (game_status == GAME_MODE_INFO)
2369 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2370 else if (game_status == GAME_MODE_SCORES)
2371 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2379 case GAME_MODE_EDITOR:
2380 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2381 HandleLevelEditorKeyInput(key);
2384 case GAME_MODE_PLAYING:
2389 RequestQuitGame(setup.ask_on_escape);
2399 if (key == KSYM_Escape)
2401 SetGameStatus(GAME_MODE_MAIN);
2410 void HandleNoEvent(void)
2412 HandleMouseCursor();
2414 switch (game_status)
2416 case GAME_MODE_PLAYING:
2417 HandleButtonOrFinger(-1, -1, -1);
2422 void HandleEventActions(void)
2424 // if (button_status && game_status != GAME_MODE_PLAYING)
2425 if (button_status && (game_status != GAME_MODE_PLAYING ||
2427 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2429 HandleButton(0, 0, button_status, -button_status);
2436 if (network.enabled)
2439 switch (game_status)
2441 case GAME_MODE_MAIN:
2442 DrawPreviewLevelAnimation();
2445 case GAME_MODE_EDITOR:
2446 HandleLevelEditorIdle();
2454 static void HandleTileCursor(int dx, int dy, int button)
2457 ClearPlayerMouseAction();
2464 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2465 (dx < 0 ? MB_LEFTBUTTON :
2466 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2468 else if (!tile_cursor.moving)
2470 int old_xpos = tile_cursor.xpos;
2471 int old_ypos = tile_cursor.ypos;
2472 int new_xpos = old_xpos;
2473 int new_ypos = old_ypos;
2475 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2476 new_xpos = old_xpos + dx;
2478 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2479 new_ypos = old_ypos + dy;
2481 SetTileCursorTargetXY(new_xpos, new_ypos);
2485 static int HandleJoystickForAllPlayers(void)
2489 boolean no_joysticks_configured = TRUE;
2490 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2491 static byte joy_action_last[MAX_PLAYERS];
2493 for (i = 0; i < MAX_PLAYERS; i++)
2494 if (setup.input[i].use_joystick)
2495 no_joysticks_configured = FALSE;
2497 // if no joysticks configured, map connected joysticks to players
2498 if (no_joysticks_configured)
2499 use_as_joystick_nr = TRUE;
2501 for (i = 0; i < MAX_PLAYERS; i++)
2503 byte joy_action = 0;
2505 joy_action = JoystickExt(i, use_as_joystick_nr);
2506 result |= joy_action;
2508 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2509 joy_action != joy_action_last[i])
2510 stored_player[i].action = joy_action;
2512 joy_action_last[i] = joy_action;
2518 void HandleJoystick(void)
2520 static unsigned int joytest_delay = 0;
2521 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2522 static int joytest_last = 0;
2523 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2524 int delay_value = GADGET_FRAME_DELAY;
2525 int joystick = HandleJoystickForAllPlayers();
2526 int keyboard = key_joystick_mapping;
2527 int joy = (joystick | keyboard);
2528 int joytest = joystick;
2529 int left = joy & JOY_LEFT;
2530 int right = joy & JOY_RIGHT;
2531 int up = joy & JOY_UP;
2532 int down = joy & JOY_DOWN;
2533 int button = joy & JOY_BUTTON;
2534 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2535 int dx = (left ? -1 : right ? 1 : 0);
2536 int dy = (up ? -1 : down ? 1 : 0);
2537 boolean use_delay_value_first = (joytest != joytest_last);
2539 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2541 // do not handle this button event anymore
2545 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2546 anyTextGadgetActive()))
2548 // leave name input in main menu or text input gadget
2549 HandleKey(KSYM_Escape, KEY_PRESSED);
2550 HandleKey(KSYM_Escape, KEY_RELEASED);
2555 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2557 if (game_status == GAME_MODE_PLAYING)
2559 // when playing MM style levels, also use delay for keyboard events
2560 joytest |= keyboard;
2562 // only use first delay value for new events, but not for changed events
2563 use_delay_value_first = (!joytest != !joytest_last);
2565 // only use delay after the initial keyboard event
2569 // for any joystick or keyboard event, enable playfield tile cursor
2570 if (dx || dy || button)
2571 SetTileCursorEnabled(TRUE);
2574 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2576 // delay joystick/keyboard actions if axes/keys continually pressed
2577 newbutton = dx = dy = 0;
2581 // first start with longer delay, then continue with shorter delay
2582 joytest_delay_value =
2583 (use_delay_value_first ? delay_value_first : delay_value);
2586 joytest_last = joytest;
2588 switch (game_status)
2590 case GAME_MODE_TITLE:
2591 case GAME_MODE_MAIN:
2592 case GAME_MODE_LEVELS:
2593 case GAME_MODE_LEVELNR:
2594 case GAME_MODE_SETUP:
2595 case GAME_MODE_INFO:
2596 case GAME_MODE_SCORES:
2598 if (anyTextGadgetActive())
2601 if (game_status == GAME_MODE_TITLE)
2602 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2603 else if (game_status == GAME_MODE_MAIN)
2604 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2605 else if (game_status == GAME_MODE_LEVELS)
2606 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2607 else if (game_status == GAME_MODE_LEVELNR)
2608 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2609 else if (game_status == GAME_MODE_SETUP)
2610 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2611 else if (game_status == GAME_MODE_INFO)
2612 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2613 else if (game_status == GAME_MODE_SCORES)
2614 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2619 case GAME_MODE_PLAYING:
2621 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2622 if (tape.playing || keyboard)
2623 newbutton = ((joy & JOY_BUTTON) != 0);
2626 if (newbutton && game.all_players_gone)
2633 if (tape.recording && tape.pausing && tape.use_key_actions)
2635 if (tape.single_step)
2637 if (joystick & JOY_ACTION)
2638 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2642 if (joystick & JOY_ACTION)
2643 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2647 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2648 HandleTileCursor(dx, dy, button);
2657 void HandleSpecialGameControllerButtons(Event *event)
2662 switch (event->type)
2664 case SDL_CONTROLLERBUTTONDOWN:
2665 key_status = KEY_PRESSED;
2668 case SDL_CONTROLLERBUTTONUP:
2669 key_status = KEY_RELEASED;
2676 switch (event->cbutton.button)
2678 case SDL_CONTROLLER_BUTTON_START:
2682 case SDL_CONTROLLER_BUTTON_BACK:
2690 HandleKey(key, key_status);
2693 void HandleSpecialGameControllerKeys(Key key, int key_status)
2695 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2696 int button = SDL_CONTROLLER_BUTTON_INVALID;
2698 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2699 if (key == KSYM_Rewind)
2700 button = SDL_CONTROLLER_BUTTON_A;
2701 else if (key == KSYM_FastForward || key == KSYM_Menu)
2702 button = SDL_CONTROLLER_BUTTON_B;
2704 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2708 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2709 SDL_CONTROLLERBUTTONUP);
2711 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2712 event.cbutton.button = button;
2713 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2716 HandleJoystickEvent(&event);
2721 boolean DoKeysymAction(int keysym)
2725 Key key = (Key)(-keysym);
2727 HandleKey(key, KEY_PRESSED);
2728 HandleKey(key, KEY_RELEASED);