1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://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 virtual_button_pressed = FALSE;
43 static boolean stop_processing_events = FALSE;
46 // forward declarations for internal use
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 // non-motion events are directly passed to event handler functions
99 if (event->type != EVENT_MOTIONNOTIFY)
102 motion = (MotionEvent *)event;
103 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
104 motion->y >= SY && motion->y < SY + SYSIZE);
106 // set correct mouse x/y position (for pointer class global animations)
107 // (this is required in rare cases where the mouse x/y position calculated
108 // from raw values (to apply logical screen size scaling corrections) does
109 // not match the final mouse event x/y position -- this may happen because
110 // the SDL renderer's viewport position is internally represented as float,
111 // but only accessible as integer, which may lead to rounding errors)
112 gfx.mouse_x = motion->x;
113 gfx.mouse_y = motion->y;
115 // do no reset mouse cursor before all pending events have been processed
116 if (gfx.cursor_mode == cursor_mode_last &&
117 ((game_status == GAME_MODE_TITLE &&
118 gfx.cursor_mode == CURSOR_NONE) ||
119 (game_status == GAME_MODE_PLAYING &&
120 gfx.cursor_mode == CURSOR_PLAYFIELD)))
122 SetMouseCursor(CURSOR_DEFAULT);
124 DelayReached(&special_cursor_delay, 0);
126 cursor_mode_last = CURSOR_DEFAULT;
129 // skip mouse motion events without pressed button outside level editor
130 if (button_status == MB_RELEASED &&
131 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
137 // to prevent delay problems, skip mouse motion events if the very next
138 // event is also a mouse motion event (and therefore effectively only
139 // handling the last of a row of mouse motion events in the event queue)
141 static boolean SkipPressedMouseMotionEvent(const Event *event)
143 // nothing to do if the current event is not a mouse motion event
144 if (event->type != EVENT_MOTIONNOTIFY)
147 // only skip motion events with pressed button outside the game
148 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
155 PeekEvent(&next_event);
157 // if next event is also a mouse motion event, skip the current one
158 if (next_event.type == EVENT_MOTIONNOTIFY)
165 static boolean WaitValidEvent(Event *event)
169 if (!FilterEvents(event))
172 if (SkipPressedMouseMotionEvent(event))
178 /* this is especially needed for event modifications for the Android target:
179 if mouse coordinates should be modified in the event filter function,
180 using a properly installed SDL event filter does not work, because in
181 the event filter, mouse coordinates in the event structure are still
182 physical pixel positions, not logical (scaled) screen positions, so this
183 has to be handled at a later stage in the event processing functions
184 (when device pixel positions are already converted to screen positions) */
186 boolean NextValidEvent(Event *event)
188 while (PendingEvent())
189 if (WaitValidEvent(event))
195 void StopProcessingEvents(void)
197 stop_processing_events = TRUE;
200 static void HandleEvents(void)
203 unsigned int event_frame_delay = 0;
204 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
206 ResetDelayCounter(&event_frame_delay);
208 stop_processing_events = FALSE;
210 while (NextValidEvent(&event))
214 case EVENT_BUTTONPRESS:
215 case EVENT_BUTTONRELEASE:
216 HandleButtonEvent((ButtonEvent *) &event);
219 case EVENT_MOTIONNOTIFY:
220 HandleMotionEvent((MotionEvent *) &event);
223 case EVENT_WHEELMOTION:
224 HandleWheelEvent((WheelEvent *) &event);
227 case SDL_WINDOWEVENT:
228 HandleWindowEvent((WindowEvent *) &event);
231 case EVENT_FINGERPRESS:
232 case EVENT_FINGERRELEASE:
233 case EVENT_FINGERMOTION:
234 HandleFingerEvent((FingerEvent *) &event);
237 case EVENT_TEXTINPUT:
238 HandleTextEvent((TextEvent *) &event);
241 case SDL_APP_WILLENTERBACKGROUND:
242 case SDL_APP_DIDENTERBACKGROUND:
243 case SDL_APP_WILLENTERFOREGROUND:
244 case SDL_APP_DIDENTERFOREGROUND:
245 HandlePauseResumeEvent((PauseResumeEvent *) &event);
249 case EVENT_KEYRELEASE:
250 HandleKeyEvent((KeyEvent *) &event);
254 HandleUserEvent((UserEvent *) &event);
258 HandleOtherEvents(&event);
262 // do not handle events for longer than standard frame delay period
263 if (DelayReached(&event_frame_delay, event_frame_delay_value))
266 // do not handle any further events if triggered by a special flag
267 if (stop_processing_events)
272 void HandleOtherEvents(Event *event)
276 case SDL_CONTROLLERBUTTONDOWN:
277 case SDL_CONTROLLERBUTTONUP:
278 // for any game controller button event, disable overlay buttons
279 SetOverlayEnabled(FALSE);
281 HandleSpecialGameControllerButtons(event);
284 case SDL_CONTROLLERDEVICEADDED:
285 case SDL_CONTROLLERDEVICEREMOVED:
286 case SDL_CONTROLLERAXISMOTION:
287 case SDL_JOYAXISMOTION:
288 case SDL_JOYBUTTONDOWN:
289 case SDL_JOYBUTTONUP:
290 HandleJoystickEvent(event);
294 case SDL_DROPCOMPLETE:
297 HandleDropEvent(event);
309 static void HandleMouseCursor(void)
311 if (game_status == GAME_MODE_TITLE)
313 // when showing title screens, hide mouse pointer (if not moved)
315 if (gfx.cursor_mode != CURSOR_NONE &&
316 DelayReached(&special_cursor_delay, special_cursor_delay_value))
318 SetMouseCursor(CURSOR_NONE);
321 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
324 // when playing, display a special mouse pointer inside the playfield
326 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
327 cursor_inside_playfield &&
328 DelayReached(&special_cursor_delay, special_cursor_delay_value))
330 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
332 SetMouseCursor(CURSOR_PLAYFIELD);
335 else if (gfx.cursor_mode != CURSOR_DEFAULT)
337 SetMouseCursor(CURSOR_DEFAULT);
340 // this is set after all pending events have been processed
341 cursor_mode_last = gfx.cursor_mode;
353 // execute event related actions after pending events have been processed
354 HandleEventActions();
356 // don't use all CPU time when idle; the main loop while playing
357 // has its own synchronization and is CPU friendly, too
359 if (game_status == GAME_MODE_PLAYING)
362 // always copy backbuffer to visible screen for every video frame
365 // reset video frame delay to default (may change again while playing)
366 SetVideoFrameDelay(MenuFrameDelay);
368 if (game_status == GAME_MODE_QUIT)
373 void ClearAutoRepeatKeyEvents(void)
375 while (PendingEvent())
379 PeekEvent(&next_event);
381 // if event is repeated key press event, remove it from event queue
382 if (next_event.type == EVENT_KEYPRESS &&
383 next_event.key.repeat)
384 WaitEvent(&next_event);
390 void ClearEventQueue(void)
394 while (NextValidEvent(&event))
398 case EVENT_BUTTONRELEASE:
399 button_status = MB_RELEASED;
402 case EVENT_KEYRELEASE:
406 case SDL_CONTROLLERBUTTONUP:
407 HandleJoystickEvent(&event);
412 HandleOtherEvents(&event);
418 static void ClearPlayerMouseAction(void)
420 local_player->mouse_action.lx = 0;
421 local_player->mouse_action.ly = 0;
422 local_player->mouse_action.button = 0;
425 void ClearPlayerAction(void)
429 // simulate key release events for still pressed keys
430 key_joystick_mapping = 0;
431 for (i = 0; i < MAX_PLAYERS; i++)
433 stored_player[i].action = 0;
434 stored_player[i].snap_action = 0;
437 ClearJoystickState();
438 ClearPlayerMouseAction();
441 static void SetPlayerMouseAction(int mx, int my, int button)
443 int lx = getLevelFromScreenX(mx);
444 int ly = getLevelFromScreenY(my);
445 int new_button = (!local_player->mouse_action.button && button);
447 if (local_player->mouse_action.button_hint)
448 button = local_player->mouse_action.button_hint;
450 ClearPlayerMouseAction();
452 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
455 local_player->mouse_action.lx = lx;
456 local_player->mouse_action.ly = ly;
457 local_player->mouse_action.button = button;
459 if (tape.recording && tape.pausing && tape.use_mouse)
461 // un-pause a paused game only if mouse button was newly pressed down
463 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
466 SetTileCursorXY(lx, ly);
469 void HandleButtonEvent(ButtonEvent *event)
471 #if DEBUG_EVENTS_BUTTON
472 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
474 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
478 // for any mouse button event, disable playfield tile cursor
479 SetTileCursorEnabled(FALSE);
481 #if defined(HAS_SCREEN_KEYBOARD)
482 if (video.shifted_up)
483 event->y += video.shifted_up_pos;
486 motion_status = FALSE;
488 if (event->type == EVENT_BUTTONPRESS)
489 button_status = event->button;
491 button_status = MB_RELEASED;
493 HandleButton(event->x, event->y, button_status, event->button);
496 void HandleMotionEvent(MotionEvent *event)
498 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
501 motion_status = TRUE;
503 #if DEBUG_EVENTS_MOTION
504 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
505 button_status, event->x, event->y);
508 HandleButton(event->x, event->y, button_status, button_status);
511 void HandleWheelEvent(WheelEvent *event)
515 #if DEBUG_EVENTS_WHEEL
517 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
518 event->which, event->x, event->y);
520 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
521 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
522 event->which, event->x, event->y,
523 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
524 "SDL_MOUSEWHEEL_FLIPPED"));
528 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
529 event->x > 0 ? MB_WHEEL_RIGHT :
530 event->y < 0 ? MB_WHEEL_DOWN :
531 event->y > 0 ? MB_WHEEL_UP : 0);
533 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
534 // accelerated mouse wheel available on Mac and Windows
535 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
537 // no accelerated mouse wheel available on Unix/Linux
538 wheel_steps = DEFAULT_WHEEL_STEPS;
541 motion_status = FALSE;
543 button_status = button_nr;
544 HandleButton(0, 0, button_status, -button_nr);
546 button_status = MB_RELEASED;
547 HandleButton(0, 0, button_status, -button_nr);
550 void HandleWindowEvent(WindowEvent *event)
552 #if DEBUG_EVENTS_WINDOW
553 int subtype = event->event;
556 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
557 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
558 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
559 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
560 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
561 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
562 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
563 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
564 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
565 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
566 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
567 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
568 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
569 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
572 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
573 event_name, event->data1, event->data2);
577 // (not needed, as the screen gets redrawn every 20 ms anyway)
578 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
579 event->event == SDL_WINDOWEVENT_RESIZED ||
580 event->event == SDL_WINDOWEVENT_EXPOSED)
584 if (event->event == SDL_WINDOWEVENT_RESIZED)
586 if (!video.fullscreen_enabled)
588 int new_window_width = event->data1;
589 int new_window_height = event->data2;
591 // if window size has changed after resizing, calculate new scaling factor
592 if (new_window_width != video.window_width ||
593 new_window_height != video.window_height)
595 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
596 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
598 // (extreme window scaling allowed, but cannot be saved permanently)
599 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
600 setup.window_scaling_percent =
601 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
602 MAX_WINDOW_SCALING_PERCENT);
604 video.window_width = new_window_width;
605 video.window_height = new_window_height;
607 if (game_status == GAME_MODE_SETUP)
608 RedrawSetupScreenAfterFullscreenToggle();
610 UpdateMousePosition();
615 #if defined(PLATFORM_ANDROID)
618 int new_display_width = event->data1;
619 int new_display_height = event->data2;
621 // if fullscreen display size has changed, device has been rotated
622 if (new_display_width != video.display_width ||
623 new_display_height != video.display_height)
625 int nr = GRID_ACTIVE_NR(); // previous screen orientation
627 video.display_width = new_display_width;
628 video.display_height = new_display_height;
630 SDLSetScreenProperties();
632 // check if screen orientation has changed (should always be true here)
633 if (nr != GRID_ACTIVE_NR())
637 if (game_status == GAME_MODE_SETUP)
638 RedrawSetupScreenAfterScreenRotation(nr);
640 nr = GRID_ACTIVE_NR();
642 overlay.grid_xsize = setup.touch.grid_xsize[nr];
643 overlay.grid_ysize = setup.touch.grid_ysize[nr];
645 for (x = 0; x < MAX_GRID_XSIZE; x++)
646 for (y = 0; y < MAX_GRID_YSIZE; y++)
647 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
655 #define NUM_TOUCH_FINGERS 3
660 SDL_FingerID finger_id;
664 } touch_info[NUM_TOUCH_FINGERS];
666 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
668 int x = event->x * overlay.grid_xsize;
669 int y = event->y * overlay.grid_ysize;
670 int grid_button = overlay.grid_button[x][y];
671 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
672 Key key = (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
673 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
674 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
675 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
676 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
677 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
679 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
681 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
685 virtual_button_pressed = (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
687 // for any touch input event, enable overlay buttons (if activated)
688 SetOverlayEnabled(TRUE);
690 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
691 getKeyNameFromKey(key), key_status_name, event->fingerId);
693 if (key_status == KEY_PRESSED)
694 overlay.grid_button_action |= grid_button_action;
696 overlay.grid_button_action &= ~grid_button_action;
698 // check if we already know this touch event's finger id
699 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
701 if (touch_info[i].touched &&
702 touch_info[i].finger_id == event->fingerId)
704 // Error(ERR_DEBUG, "MARK 1: %d", i);
710 if (i >= NUM_TOUCH_FINGERS)
712 if (key_status == KEY_PRESSED)
714 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
716 // unknown finger id -- get new, empty slot, if available
717 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
719 if (touch_info[i].counter < oldest_counter)
722 oldest_counter = touch_info[i].counter;
724 // Error(ERR_DEBUG, "MARK 2: %d", i);
727 if (!touch_info[i].touched)
729 // Error(ERR_DEBUG, "MARK 3: %d", i);
735 if (i >= NUM_TOUCH_FINGERS)
737 // all slots allocated -- use oldest slot
740 // Error(ERR_DEBUG, "MARK 4: %d", i);
745 // release of previously unknown key (should not happen)
747 if (key != KSYM_UNDEFINED)
749 HandleKey(key, KEY_RELEASED);
751 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
752 getKeyNameFromKey(key), "KEY_RELEASED", i);
757 if (i < NUM_TOUCH_FINGERS)
759 if (key_status == KEY_PRESSED)
761 if (touch_info[i].key != key)
763 if (touch_info[i].key != KSYM_UNDEFINED)
765 HandleKey(touch_info[i].key, KEY_RELEASED);
767 // undraw previous grid button when moving finger away
768 overlay.grid_button_action &= ~touch_info[i].action;
770 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
771 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
774 if (key != KSYM_UNDEFINED)
776 HandleKey(key, KEY_PRESSED);
778 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
779 getKeyNameFromKey(key), "KEY_PRESSED", i);
783 touch_info[i].touched = TRUE;
784 touch_info[i].finger_id = event->fingerId;
785 touch_info[i].counter = Counter();
786 touch_info[i].key = key;
787 touch_info[i].action = grid_button_action;
791 if (touch_info[i].key != KSYM_UNDEFINED)
793 HandleKey(touch_info[i].key, KEY_RELEASED);
795 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
796 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
799 touch_info[i].touched = FALSE;
800 touch_info[i].finger_id = 0;
801 touch_info[i].counter = 0;
802 touch_info[i].key = 0;
803 touch_info[i].action = JOY_NO_ACTION;
808 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
810 static Key motion_key_x = KSYM_UNDEFINED;
811 static Key motion_key_y = KSYM_UNDEFINED;
812 static Key button_key = KSYM_UNDEFINED;
813 static float motion_x1, motion_y1;
814 static float button_x1, button_y1;
815 static SDL_FingerID motion_id = -1;
816 static SDL_FingerID button_id = -1;
817 int move_trigger_distance_percent = setup.touch.move_distance;
818 int drop_trigger_distance_percent = setup.touch.drop_distance;
819 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
820 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
821 float event_x = event->x;
822 float event_y = event->y;
824 if (event->type == EVENT_FINGERPRESS)
826 if (event_x > 1.0 / 3.0)
830 motion_id = event->fingerId;
835 motion_key_x = KSYM_UNDEFINED;
836 motion_key_y = KSYM_UNDEFINED;
838 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
844 button_id = event->fingerId;
849 button_key = setup.input[0].key.snap;
851 HandleKey(button_key, KEY_PRESSED);
853 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
856 else if (event->type == EVENT_FINGERRELEASE)
858 if (event->fingerId == motion_id)
862 if (motion_key_x != KSYM_UNDEFINED)
863 HandleKey(motion_key_x, KEY_RELEASED);
864 if (motion_key_y != KSYM_UNDEFINED)
865 HandleKey(motion_key_y, KEY_RELEASED);
867 motion_key_x = KSYM_UNDEFINED;
868 motion_key_y = KSYM_UNDEFINED;
870 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
872 else if (event->fingerId == button_id)
876 if (button_key != KSYM_UNDEFINED)
877 HandleKey(button_key, KEY_RELEASED);
879 button_key = KSYM_UNDEFINED;
881 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
884 else if (event->type == EVENT_FINGERMOTION)
886 if (event->fingerId == motion_id)
888 float distance_x = ABS(event_x - motion_x1);
889 float distance_y = ABS(event_y - motion_y1);
890 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
891 event_x > motion_x1 ? setup.input[0].key.right :
893 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
894 event_y > motion_y1 ? setup.input[0].key.down :
897 if (distance_x < move_trigger_distance / 2 ||
898 distance_x < distance_y)
899 new_motion_key_x = KSYM_UNDEFINED;
901 if (distance_y < move_trigger_distance / 2 ||
902 distance_y < distance_x)
903 new_motion_key_y = KSYM_UNDEFINED;
905 if (distance_x > move_trigger_distance ||
906 distance_y > move_trigger_distance)
908 if (new_motion_key_x != motion_key_x)
910 if (motion_key_x != KSYM_UNDEFINED)
911 HandleKey(motion_key_x, KEY_RELEASED);
912 if (new_motion_key_x != KSYM_UNDEFINED)
913 HandleKey(new_motion_key_x, KEY_PRESSED);
916 if (new_motion_key_y != motion_key_y)
918 if (motion_key_y != KSYM_UNDEFINED)
919 HandleKey(motion_key_y, KEY_RELEASED);
920 if (new_motion_key_y != KSYM_UNDEFINED)
921 HandleKey(new_motion_key_y, KEY_PRESSED);
927 motion_key_x = new_motion_key_x;
928 motion_key_y = new_motion_key_y;
930 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
933 else if (event->fingerId == button_id)
935 float distance_x = ABS(event_x - button_x1);
936 float distance_y = ABS(event_y - button_y1);
938 if (distance_x < drop_trigger_distance / 2 &&
939 distance_y > drop_trigger_distance)
941 if (button_key == setup.input[0].key.snap)
942 HandleKey(button_key, KEY_RELEASED);
947 button_key = setup.input[0].key.drop;
949 HandleKey(button_key, KEY_PRESSED);
951 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
957 void HandleFingerEvent(FingerEvent *event)
959 #if DEBUG_EVENTS_FINGER
960 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
961 event->type == EVENT_FINGERPRESS ? "pressed" :
962 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
966 event->dx, event->dy,
970 runtime.uses_touch_device = TRUE;
972 if (game_status != GAME_MODE_PLAYING)
975 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
977 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
978 local_player->mouse_action.button_hint =
979 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
980 event->x < 0.5 ? MB_LEFTBUTTON :
981 event->x > 0.5 ? MB_RIGHTBUTTON :
987 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
988 HandleFingerEvent_VirtualButtons(event);
989 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
990 HandleFingerEvent_WipeGestures(event);
993 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
995 static int old_mx = 0, old_my = 0;
996 static int last_button = MB_LEFTBUTTON;
997 static boolean touched = FALSE;
998 static boolean tapped = FALSE;
1000 // screen tile was tapped (but finger not touching the screen anymore)
1001 // (this point will also be reached without receiving a touch event)
1002 if (tapped && !touched)
1004 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1009 // stop here if this function was not triggered by a touch event
1013 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1015 // finger started touching the screen
1025 ClearPlayerMouseAction();
1027 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1030 else if (button == MB_RELEASED && touched)
1032 // finger stopped touching the screen
1037 SetPlayerMouseAction(old_mx, old_my, last_button);
1039 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1041 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1046 // finger moved while touching the screen
1048 int old_x = getLevelFromScreenX(old_mx);
1049 int old_y = getLevelFromScreenY(old_my);
1050 int new_x = getLevelFromScreenX(mx);
1051 int new_y = getLevelFromScreenY(my);
1053 if (new_x != old_x || new_y != old_y)
1058 // finger moved left or right from (horizontal) starting position
1060 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1062 SetPlayerMouseAction(old_mx, old_my, button_nr);
1064 last_button = button_nr;
1066 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1070 // finger stays at or returned to (horizontal) starting position
1072 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1074 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1079 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1081 static int old_mx = 0, old_my = 0;
1082 static int last_button = MB_LEFTBUTTON;
1083 static boolean touched = FALSE;
1084 static boolean tapped = FALSE;
1086 // screen tile was tapped (but finger not touching the screen anymore)
1087 // (this point will also be reached without receiving a touch event)
1088 if (tapped && !touched)
1090 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1095 // stop here if this function was not triggered by a touch event
1099 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1101 // finger started touching the screen
1111 ClearPlayerMouseAction();
1113 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1116 else if (button == MB_RELEASED && touched)
1118 // finger stopped touching the screen
1123 SetPlayerMouseAction(old_mx, old_my, last_button);
1125 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1127 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1132 // finger moved while touching the screen
1134 int old_x = getLevelFromScreenX(old_mx);
1135 int old_y = getLevelFromScreenY(old_my);
1136 int new_x = getLevelFromScreenX(mx);
1137 int new_y = getLevelFromScreenY(my);
1139 if (new_x != old_x || new_y != old_y)
1141 // finger moved away from starting position
1143 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1145 // quickly alternate between clicking and releasing for maximum speed
1146 if (FrameCounter % 2 == 0)
1147 button_nr = MB_RELEASED;
1149 SetPlayerMouseAction(old_mx, old_my, button_nr);
1152 last_button = button_nr;
1156 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1160 // finger stays at or returned to starting position
1162 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1164 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1169 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1171 static int old_mx = 0, old_my = 0;
1172 static Key motion_key_x = KSYM_UNDEFINED;
1173 static Key motion_key_y = KSYM_UNDEFINED;
1174 static boolean touched = FALSE;
1175 static boolean started_on_player = FALSE;
1176 static boolean player_is_dropping = FALSE;
1177 static int player_drop_count = 0;
1178 static int last_player_x = -1;
1179 static int last_player_y = -1;
1181 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1190 started_on_player = FALSE;
1191 player_is_dropping = FALSE;
1192 player_drop_count = 0;
1196 motion_key_x = KSYM_UNDEFINED;
1197 motion_key_y = KSYM_UNDEFINED;
1199 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1202 else if (button == MB_RELEASED && touched)
1209 if (motion_key_x != KSYM_UNDEFINED)
1210 HandleKey(motion_key_x, KEY_RELEASED);
1211 if (motion_key_y != KSYM_UNDEFINED)
1212 HandleKey(motion_key_y, KEY_RELEASED);
1214 if (started_on_player)
1216 if (player_is_dropping)
1218 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1220 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1224 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1226 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1230 motion_key_x = KSYM_UNDEFINED;
1231 motion_key_y = KSYM_UNDEFINED;
1233 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1238 int src_x = local_player->jx;
1239 int src_y = local_player->jy;
1240 int dst_x = getLevelFromScreenX(old_mx);
1241 int dst_y = getLevelFromScreenY(old_my);
1242 int dx = dst_x - src_x;
1243 int dy = dst_y - src_y;
1244 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1245 dx > 0 ? setup.input[0].key.right :
1247 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1248 dy > 0 ? setup.input[0].key.down :
1251 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1252 (last_player_x != local_player->jx ||
1253 last_player_y != local_player->jy))
1255 // in case of asymmetric diagonal movement, use "preferred" direction
1257 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1259 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1260 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1262 local_player->last_move_dir = last_move_dir;
1264 // (required to prevent accidentally forcing direction for next movement)
1265 last_player_x = local_player->jx;
1266 last_player_y = local_player->jy;
1269 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1271 started_on_player = TRUE;
1272 player_drop_count = getPlayerInventorySize(0);
1273 player_is_dropping = (player_drop_count > 0);
1275 if (player_is_dropping)
1277 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1279 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1283 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1285 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1288 else if (dx != 0 || dy != 0)
1290 if (player_is_dropping &&
1291 player_drop_count == getPlayerInventorySize(0))
1293 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1295 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1296 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1298 player_is_dropping = FALSE;
1302 if (new_motion_key_x != motion_key_x)
1304 Error(ERR_DEBUG, "---------- %s %s ----------",
1305 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1306 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1308 if (motion_key_x != KSYM_UNDEFINED)
1309 HandleKey(motion_key_x, KEY_RELEASED);
1310 if (new_motion_key_x != KSYM_UNDEFINED)
1311 HandleKey(new_motion_key_x, KEY_PRESSED);
1314 if (new_motion_key_y != motion_key_y)
1316 Error(ERR_DEBUG, "---------- %s %s ----------",
1317 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1318 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1320 if (motion_key_y != KSYM_UNDEFINED)
1321 HandleKey(motion_key_y, KEY_RELEASED);
1322 if (new_motion_key_y != KSYM_UNDEFINED)
1323 HandleKey(new_motion_key_y, KEY_PRESSED);
1326 motion_key_x = new_motion_key_x;
1327 motion_key_y = new_motion_key_y;
1331 static void HandleButtonOrFinger(int mx, int my, int button)
1333 if (game_status != GAME_MODE_PLAYING)
1336 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1338 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1339 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1340 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1341 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1342 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1343 SetPlayerMouseAction(mx, my, button); // special case
1347 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1348 HandleButtonOrFinger_FollowFinger(mx, my, button);
1352 static boolean checkTextInputKeyModState(void)
1354 // when playing, only handle raw key events and ignore text input
1355 if (game_status == GAME_MODE_PLAYING)
1358 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1361 void HandleTextEvent(TextEvent *event)
1363 char *text = event->text;
1364 Key key = getKeyFromKeyName(text);
1366 #if DEBUG_EVENTS_TEXT
1367 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1370 text[0], (int)(text[0]),
1372 getKeyNameFromKey(key),
1376 #if !defined(HAS_SCREEN_KEYBOARD)
1377 // non-mobile devices: only handle key input with modifier keys pressed here
1378 // (every other key input is handled directly as physical key input event)
1379 if (!checkTextInputKeyModState())
1383 // process text input as "classic" (with uppercase etc.) key input event
1384 HandleKey(key, KEY_PRESSED);
1385 HandleKey(key, KEY_RELEASED);
1388 void HandlePauseResumeEvent(PauseResumeEvent *event)
1390 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1394 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1400 void HandleKeyEvent(KeyEvent *event)
1402 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1403 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1404 Key key = GetEventKey(event, with_modifiers);
1405 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1407 #if DEBUG_EVENTS_KEY
1408 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1409 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1410 event->keysym.scancode,
1415 getKeyNameFromKey(key));
1418 #if defined(PLATFORM_ANDROID)
1419 if (key == KSYM_Back)
1421 // always map the "back" button to the "escape" key on Android devices
1424 else if (key == KSYM_Menu)
1426 // the "menu" button can be used to toggle displaying virtual buttons
1427 if (key_status == KEY_PRESSED)
1428 SetOverlayEnabled(!GetOverlayEnabled());
1432 // for any other "real" key event, disable virtual buttons
1433 SetOverlayEnabled(FALSE);
1437 HandleKeyModState(keymod, key_status);
1439 // only handle raw key input without text modifier keys pressed
1440 if (!checkTextInputKeyModState())
1441 HandleKey(key, key_status);
1444 static int HandleDropFileEvent(char *filename)
1446 Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
1448 // check and extract dropped zip files into correct user data directory
1449 if (!strSuffixLower(filename, ".zip"))
1451 Error(ERR_WARN, "file '%s' not supported", filename);
1453 return TREE_TYPE_UNDEFINED;
1456 TreeInfo *tree_node = NULL;
1457 int tree_type = GetZipFileTreeType(filename);
1458 char *directory = TREE_USERDIR(tree_type);
1460 if (directory == NULL)
1462 Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1464 return TREE_TYPE_UNDEFINED;
1467 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1468 game_status == GAME_MODE_LEVELS &&
1469 leveldir_current->node_parent != NULL)
1471 // extract new level set next to currently selected level set
1472 tree_node = leveldir_current;
1474 // get parent directory of currently selected level set directory
1475 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1477 // use private level directory instead of top-level package level directory
1478 if (strPrefix(directory, options.level_directory) &&
1479 strEqual(leveldir_current->node_parent->fullpath, "."))
1480 directory = getUserLevelDir(NULL);
1483 // extract level or artwork set from zip file to target directory
1484 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1486 if (top_dir == NULL)
1488 // error message already issued by "ExtractZipFileIntoDirectory()"
1490 return TREE_TYPE_UNDEFINED;
1493 // add extracted level or artwork set to tree info structure
1494 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1496 // update menu screen (and possibly change current level set)
1497 DrawScreenAfterAddingSet(top_dir, tree_type);
1502 static void HandleDropTextEvent(char *text)
1504 Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1507 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1508 int num_artwork_sets_succeeded,
1509 int num_files_failed)
1511 // only show request dialog if no other request dialog already active
1512 if (game.request_active)
1515 // this case can happen with drag-and-drop with older SDL versions
1516 if (num_level_sets_succeeded == 0 &&
1517 num_artwork_sets_succeeded == 0 &&
1518 num_files_failed == 0)
1523 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1525 char message_part1[50];
1527 sprintf(message_part1, "New %s set%s added",
1528 (num_artwork_sets_succeeded == 0 ? "level" :
1529 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1530 (num_level_sets_succeeded +
1531 num_artwork_sets_succeeded > 1 ? "s" : ""));
1533 if (num_files_failed > 0)
1534 sprintf(message, "%s, but %d dropped file%s failed!",
1535 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1537 sprintf(message, "%s!", message_part1);
1539 else if (num_files_failed > 0)
1541 sprintf(message, "Failed to process dropped file%s!",
1542 num_files_failed > 1 ? "s" : "");
1545 Request(message, REQ_CONFIRM);
1548 void HandleDropEvent(Event *event)
1550 static boolean confirm_on_drop_complete = FALSE;
1551 static int num_level_sets_succeeded = 0;
1552 static int num_artwork_sets_succeeded = 0;
1553 static int num_files_failed = 0;
1555 switch (event->type)
1559 confirm_on_drop_complete = TRUE;
1560 num_level_sets_succeeded = 0;
1561 num_artwork_sets_succeeded = 0;
1562 num_files_failed = 0;
1569 int tree_type = HandleDropFileEvent(event->drop.file);
1571 if (tree_type == TREE_TYPE_LEVEL_DIR)
1572 num_level_sets_succeeded++;
1573 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1574 tree_type == TREE_TYPE_SOUNDS_DIR ||
1575 tree_type == TREE_TYPE_MUSIC_DIR)
1576 num_artwork_sets_succeeded++;
1580 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1581 if (!confirm_on_drop_complete)
1583 // process all remaining events, including further SDL_DROPFILE events
1586 HandleDropCompleteEvent(num_level_sets_succeeded,
1587 num_artwork_sets_succeeded,
1590 num_level_sets_succeeded = 0;
1591 num_artwork_sets_succeeded = 0;
1592 num_files_failed = 0;
1600 HandleDropTextEvent(event->drop.file);
1605 case SDL_DROPCOMPLETE:
1607 HandleDropCompleteEvent(num_level_sets_succeeded,
1608 num_artwork_sets_succeeded,
1615 if (event->drop.file != NULL)
1616 SDL_free(event->drop.file);
1619 void HandleUserEvent(UserEvent *event)
1621 switch (event->code)
1623 case USEREVENT_ANIM_DELAY_ACTION:
1624 case USEREVENT_ANIM_EVENT_ACTION:
1625 // execute action functions until matching action was found
1626 if (DoKeysymAction(event->value1) ||
1627 DoGadgetAction(event->value1) ||
1628 DoScreenAction(event->value1))
1637 void HandleButton(int mx, int my, int button, int button_nr)
1639 static int old_mx = 0, old_my = 0;
1640 boolean button_hold = FALSE;
1641 boolean handle_gadgets = TRUE;
1647 button_nr = -button_nr;
1656 #if defined(PLATFORM_ANDROID)
1657 // when playing, only handle gadgets when using "follow finger" controls
1658 // or when using touch controls in combination with the MM game engine
1659 // or when using gadgets that do not overlap with virtual buttons
1661 (game_status != GAME_MODE_PLAYING ||
1662 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1663 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1664 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1665 !virtual_button_pressed));
1668 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1670 // do not handle this button event anymore
1671 return; // force mouse event not to be handled at all
1674 if (handle_gadgets && HandleGadgets(mx, my, button))
1676 // do not handle this button event anymore
1677 mx = my = -32; // force mouse event to be outside screen tiles
1680 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1683 // do not use scroll wheel button events for anything other than gadgets
1684 if (IS_WHEEL_BUTTON(button_nr))
1687 switch (game_status)
1689 case GAME_MODE_TITLE:
1690 HandleTitleScreen(mx, my, 0, 0, button);
1693 case GAME_MODE_MAIN:
1694 HandleMainMenu(mx, my, 0, 0, button);
1697 case GAME_MODE_PSEUDO_TYPENAME:
1698 HandleTypeName(0, KSYM_Return);
1701 case GAME_MODE_LEVELS:
1702 HandleChooseLevelSet(mx, my, 0, 0, button);
1705 case GAME_MODE_LEVELNR:
1706 HandleChooseLevelNr(mx, my, 0, 0, button);
1709 case GAME_MODE_SCORES:
1710 HandleHallOfFame(0, 0, 0, 0, button);
1713 case GAME_MODE_EDITOR:
1714 HandleLevelEditorIdle();
1717 case GAME_MODE_INFO:
1718 HandleInfoScreen(mx, my, 0, 0, button);
1721 case GAME_MODE_SETUP:
1722 HandleSetupScreen(mx, my, 0, 0, button);
1725 case GAME_MODE_PLAYING:
1726 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1727 HandleButtonOrFinger(mx, my, button);
1729 SetPlayerMouseAction(mx, my, button);
1732 if (button == MB_PRESSED && !motion_status && !button_hold &&
1733 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1734 DumpTileFromScreen(mx, my);
1744 static boolean is_string_suffix(char *string, char *suffix)
1746 int string_len = strlen(string);
1747 int suffix_len = strlen(suffix);
1749 if (suffix_len > string_len)
1752 return (strEqual(&string[string_len - suffix_len], suffix));
1755 #define MAX_CHEAT_INPUT_LEN 32
1757 static void HandleKeysSpecial(Key key)
1759 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1760 char letter = getCharFromKey(key);
1761 int cheat_input_len = strlen(cheat_input);
1767 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1769 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1770 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1772 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1775 cheat_input[cheat_input_len++] = letter;
1776 cheat_input[cheat_input_len] = '\0';
1778 #if DEBUG_EVENTS_KEY
1779 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1782 if (game_status == GAME_MODE_MAIN)
1784 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1785 is_string_suffix(cheat_input, ":ist"))
1787 InsertSolutionTape();
1789 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1790 is_string_suffix(cheat_input, ":pst"))
1794 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1795 is_string_suffix(cheat_input, ":rg"))
1797 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1800 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1801 is_string_suffix(cheat_input, ":rs"))
1803 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1806 else if (is_string_suffix(cheat_input, ":reload-music") ||
1807 is_string_suffix(cheat_input, ":rm"))
1809 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1812 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1813 is_string_suffix(cheat_input, ":ra"))
1815 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1816 1 << ARTWORK_TYPE_SOUNDS |
1817 1 << ARTWORK_TYPE_MUSIC);
1820 else if (is_string_suffix(cheat_input, ":dump-level") ||
1821 is_string_suffix(cheat_input, ":dl"))
1825 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1826 is_string_suffix(cheat_input, ":dt"))
1830 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1831 is_string_suffix(cheat_input, ":ft"))
1833 /* fix single-player tapes that contain player input for more than one
1834 player (due to a bug in 3.3.1.2 and earlier versions), which results
1835 in playing levels with more than one player in multi-player mode,
1836 even though the tape was originally recorded in single-player mode */
1838 // remove player input actions for all players but the first one
1839 for (i = 1; i < MAX_PLAYERS; i++)
1840 tape.player_participates[i] = FALSE;
1842 tape.changed = TRUE;
1844 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1845 is_string_suffix(cheat_input, ":snl"))
1847 SaveNativeLevel(&level);
1849 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1850 is_string_suffix(cheat_input, ":fps"))
1852 global.show_frames_per_second = !global.show_frames_per_second;
1855 else if (game_status == GAME_MODE_PLAYING)
1858 if (is_string_suffix(cheat_input, ".q"))
1859 DEBUG_SetMaximumDynamite();
1862 else if (game_status == GAME_MODE_EDITOR)
1864 if (is_string_suffix(cheat_input, ":dump-brush") ||
1865 is_string_suffix(cheat_input, ":DB"))
1869 else if (is_string_suffix(cheat_input, ":DDB"))
1874 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1876 if (letter == 'x') // copy brush to clipboard (small size)
1878 CopyBrushToClipboard_Small();
1880 else if (letter == 'c') // copy brush to clipboard (normal size)
1882 CopyBrushToClipboard();
1884 else if (letter == 'v') // paste brush from Clipboard
1886 CopyClipboardToBrush();
1888 else if (letter == 'z') // undo or redo last operation
1890 if (GetKeyModState() & KMOD_Shift)
1891 RedoLevelEditorOperation();
1893 UndoLevelEditorOperation();
1898 // special key shortcuts for all game modes
1899 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1900 is_string_suffix(cheat_input, ":dea") ||
1901 is_string_suffix(cheat_input, ":DEA"))
1903 DumpGadgetIdentifiers();
1904 DumpScreenIdentifiers();
1908 boolean HandleKeysDebug(Key key, int key_status)
1913 if (key_status != KEY_PRESSED)
1916 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1918 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1920 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1922 if (key == setup.debug.frame_delay_key[i] &&
1923 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1925 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1926 setup.debug.frame_delay[i] : setup.game_frame_delay);
1928 if (!setup.debug.frame_delay_game_only)
1929 MenuFrameDelay = GameFrameDelay;
1931 SetVideoFrameDelay(GameFrameDelay);
1933 if (GameFrameDelay > ONE_SECOND_DELAY)
1934 Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
1935 else if (GameFrameDelay != 0)
1936 Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
1937 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1938 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1940 Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
1947 if (game_status == GAME_MODE_PLAYING)
1951 options.debug = !options.debug;
1953 Error(ERR_INFO, "debug mode %s",
1954 (options.debug ? "enabled" : "disabled"));
1958 else if (key == KSYM_v)
1960 Error(ERR_INFO, "currently using game engine version %d",
1961 game.engine_version);
1971 void HandleKey(Key key, int key_status)
1973 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1974 static boolean ignore_repeated_key = FALSE;
1975 static struct SetupKeyboardInfo ski;
1976 static struct SetupShortcutInfo ssi;
1985 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1986 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1987 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1988 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1989 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1990 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1995 if (HandleKeysDebug(key, key_status))
1996 return; // do not handle already processed keys again
1998 // map special keys (media keys / remote control buttons) to default keys
1999 if (key == KSYM_PlayPause)
2001 else if (key == KSYM_Select)
2004 HandleSpecialGameControllerKeys(key, key_status);
2006 if (game_status == GAME_MODE_PLAYING)
2008 // only needed for single-step tape recording mode
2009 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2012 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2014 byte key_action = 0;
2015 byte key_snap_action = 0;
2017 if (setup.input[pnr].use_joystick)
2020 ski = setup.input[pnr].key;
2022 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2023 if (key == *key_info[i].key_custom)
2024 key_action |= key_info[i].action;
2026 // use combined snap+direction keys for the first player only
2029 ssi = setup.shortcut;
2031 // also remember normal snap key when handling snap+direction keys
2032 key_snap_action |= key_action & JOY_BUTTON_SNAP;
2034 for (i = 0; i < NUM_DIRECTIONS; i++)
2036 if (key == *key_info[i].key_snap)
2038 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2039 key_snap_action |= key_info[i].action;
2044 if (key_status == KEY_PRESSED)
2046 stored_player[pnr].action |= key_action;
2047 stored_player[pnr].snap_action |= key_snap_action;
2051 stored_player[pnr].action &= ~key_action;
2052 stored_player[pnr].snap_action &= ~key_snap_action;
2055 // restore snap action if one of several pressed snap keys was released
2056 if (stored_player[pnr].snap_action)
2057 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2059 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2061 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2063 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2065 // if snap key already pressed, keep pause mode when releasing
2066 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2067 has_snapped[pnr] = TRUE;
2069 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2071 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2073 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2074 getRedDiskReleaseFlag_SP() == 0)
2076 // add a single inactive frame before dropping starts
2077 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2078 stored_player[pnr].force_dropping = TRUE;
2081 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2083 // if snap key was pressed without direction, leave pause mode
2084 if (!has_snapped[pnr])
2085 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2087 has_snapped[pnr] = FALSE;
2090 else if (tape.recording && tape.pausing && !tape.use_mouse)
2092 // prevent key release events from un-pausing a paused game
2093 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2094 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2097 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2098 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2104 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2105 if (key == key_info[i].key_default)
2106 joy |= key_info[i].action;
2111 if (key_status == KEY_PRESSED)
2112 key_joystick_mapping |= joy;
2114 key_joystick_mapping &= ~joy;
2119 if (game_status != GAME_MODE_PLAYING)
2120 key_joystick_mapping = 0;
2122 if (key_status == KEY_RELEASED)
2124 // reset flag to ignore repeated "key pressed" events after key release
2125 ignore_repeated_key = FALSE;
2130 if ((key == KSYM_F11 ||
2131 ((key == KSYM_Return ||
2132 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2133 video.fullscreen_available &&
2134 !ignore_repeated_key)
2136 setup.fullscreen = !setup.fullscreen;
2138 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2140 if (game_status == GAME_MODE_SETUP)
2141 RedrawSetupScreenAfterFullscreenToggle();
2143 UpdateMousePosition();
2145 // set flag to ignore repeated "key pressed" events
2146 ignore_repeated_key = TRUE;
2151 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2152 key == KSYM_minus || key == KSYM_KP_Subtract ||
2153 key == KSYM_plus || key == KSYM_KP_Add ||
2154 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2155 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2156 video.window_scaling_available &&
2157 !video.fullscreen_enabled)
2159 if (key == KSYM_0 || key == KSYM_KP_0)
2160 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2161 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2162 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2164 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2166 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2167 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2168 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2169 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2171 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2173 if (game_status == GAME_MODE_SETUP)
2174 RedrawSetupScreenAfterFullscreenToggle();
2176 UpdateMousePosition();
2181 // some key events are handled like clicks for global animations
2182 boolean click = (key == KSYM_space ||
2183 key == KSYM_Return ||
2184 key == KSYM_Escape);
2186 if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2188 // do not handle this key event anymore
2189 if (key != KSYM_Escape) // always allow ESC key to be handled
2193 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2194 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2201 if (game_status == GAME_MODE_MAIN &&
2202 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2204 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2209 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2211 if (key == setup.shortcut.save_game)
2213 else if (key == setup.shortcut.load_game)
2215 else if (key == setup.shortcut.toggle_pause)
2216 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2218 HandleTapeButtonKeys(key);
2219 HandleSoundButtonKeys(key);
2222 if (game_status == GAME_MODE_PLAYING && !network_playing)
2224 int centered_player_nr_next = -999;
2226 if (key == setup.shortcut.focus_player_all)
2227 centered_player_nr_next = -1;
2229 for (i = 0; i < MAX_PLAYERS; i++)
2230 if (key == setup.shortcut.focus_player[i])
2231 centered_player_nr_next = i;
2233 if (centered_player_nr_next != -999)
2235 game.centered_player_nr_next = centered_player_nr_next;
2236 game.set_centered_player = TRUE;
2240 tape.centered_player_nr_next = game.centered_player_nr_next;
2241 tape.set_centered_player = TRUE;
2246 HandleKeysSpecial(key);
2248 if (HandleGadgetsKeyInput(key))
2249 return; // do not handle already processed keys again
2251 switch (game_status)
2253 case GAME_MODE_PSEUDO_TYPENAME:
2254 HandleTypeName(0, key);
2257 case GAME_MODE_TITLE:
2258 case GAME_MODE_MAIN:
2259 case GAME_MODE_LEVELS:
2260 case GAME_MODE_LEVELNR:
2261 case GAME_MODE_SETUP:
2262 case GAME_MODE_INFO:
2263 case GAME_MODE_SCORES:
2265 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2272 if (game_status == GAME_MODE_TITLE)
2273 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2274 else if (game_status == GAME_MODE_MAIN)
2275 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2276 else if (game_status == GAME_MODE_LEVELS)
2277 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2278 else if (game_status == GAME_MODE_LEVELNR)
2279 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2280 else if (game_status == GAME_MODE_SETUP)
2281 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2282 else if (game_status == GAME_MODE_INFO)
2283 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2284 else if (game_status == GAME_MODE_SCORES)
2285 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2289 if (game_status != GAME_MODE_MAIN)
2290 FadeSkipNextFadeIn();
2292 if (game_status == GAME_MODE_TITLE)
2293 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2294 else if (game_status == GAME_MODE_LEVELS)
2295 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2296 else if (game_status == GAME_MODE_LEVELNR)
2297 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2298 else if (game_status == GAME_MODE_SETUP)
2299 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2300 else if (game_status == GAME_MODE_INFO)
2301 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2302 else if (game_status == GAME_MODE_SCORES)
2303 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2307 if (game_status == GAME_MODE_LEVELS)
2308 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2309 else if (game_status == GAME_MODE_LEVELNR)
2310 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2311 else if (game_status == GAME_MODE_SETUP)
2312 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2313 else if (game_status == GAME_MODE_INFO)
2314 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2315 else if (game_status == GAME_MODE_SCORES)
2316 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2319 case KSYM_Page_Down:
2320 if (game_status == GAME_MODE_LEVELS)
2321 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2322 else if (game_status == GAME_MODE_LEVELNR)
2323 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2324 else if (game_status == GAME_MODE_SETUP)
2325 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2326 else if (game_status == GAME_MODE_INFO)
2327 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2328 else if (game_status == GAME_MODE_SCORES)
2329 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2337 case GAME_MODE_EDITOR:
2338 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2339 HandleLevelEditorKeyInput(key);
2342 case GAME_MODE_PLAYING:
2347 RequestQuitGame(setup.ask_on_escape);
2357 if (key == KSYM_Escape)
2359 SetGameStatus(GAME_MODE_MAIN);
2368 void HandleNoEvent(void)
2370 HandleMouseCursor();
2372 switch (game_status)
2374 case GAME_MODE_PLAYING:
2375 HandleButtonOrFinger(-1, -1, -1);
2380 void HandleEventActions(void)
2382 // if (button_status && game_status != GAME_MODE_PLAYING)
2383 if (button_status && (game_status != GAME_MODE_PLAYING ||
2385 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2387 HandleButton(0, 0, button_status, -button_status);
2394 if (network.enabled)
2397 switch (game_status)
2399 case GAME_MODE_MAIN:
2400 DrawPreviewLevelAnimation();
2403 case GAME_MODE_EDITOR:
2404 HandleLevelEditorIdle();
2412 static void HandleTileCursor(int dx, int dy, int button)
2415 ClearPlayerMouseAction();
2422 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2423 (dx < 0 ? MB_LEFTBUTTON :
2424 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2426 else if (!tile_cursor.moving)
2428 int old_xpos = tile_cursor.xpos;
2429 int old_ypos = tile_cursor.ypos;
2430 int new_xpos = old_xpos;
2431 int new_ypos = old_ypos;
2433 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2434 new_xpos = old_xpos + dx;
2436 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2437 new_ypos = old_ypos + dy;
2439 SetTileCursorTargetXY(new_xpos, new_ypos);
2443 static int HandleJoystickForAllPlayers(void)
2447 boolean no_joysticks_configured = TRUE;
2448 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2449 static byte joy_action_last[MAX_PLAYERS];
2451 for (i = 0; i < MAX_PLAYERS; i++)
2452 if (setup.input[i].use_joystick)
2453 no_joysticks_configured = FALSE;
2455 // if no joysticks configured, map connected joysticks to players
2456 if (no_joysticks_configured)
2457 use_as_joystick_nr = TRUE;
2459 for (i = 0; i < MAX_PLAYERS; i++)
2461 byte joy_action = 0;
2463 joy_action = JoystickExt(i, use_as_joystick_nr);
2464 result |= joy_action;
2466 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2467 joy_action != joy_action_last[i])
2468 stored_player[i].action = joy_action;
2470 joy_action_last[i] = joy_action;
2476 void HandleJoystick(void)
2478 static unsigned int joytest_delay = 0;
2479 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2480 static int joytest_last = 0;
2481 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2482 int delay_value = GADGET_FRAME_DELAY;
2483 int joystick = HandleJoystickForAllPlayers();
2484 int keyboard = key_joystick_mapping;
2485 int joy = (joystick | keyboard);
2486 int joytest = joystick;
2487 int left = joy & JOY_LEFT;
2488 int right = joy & JOY_RIGHT;
2489 int up = joy & JOY_UP;
2490 int down = joy & JOY_DOWN;
2491 int button = joy & JOY_BUTTON;
2492 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2493 int dx = (left ? -1 : right ? 1 : 0);
2494 int dy = (up ? -1 : down ? 1 : 0);
2495 boolean use_delay_value_first = (joytest != joytest_last);
2497 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2499 // do not handle this button event anymore
2503 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2504 anyTextGadgetActive()))
2506 // leave name input in main menu or text input gadget
2507 HandleKey(KSYM_Escape, KEY_PRESSED);
2508 HandleKey(KSYM_Escape, KEY_RELEASED);
2513 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2515 if (game_status == GAME_MODE_PLAYING)
2517 // when playing MM style levels, also use delay for keyboard events
2518 joytest |= keyboard;
2520 // only use first delay value for new events, but not for changed events
2521 use_delay_value_first = (!joytest != !joytest_last);
2523 // only use delay after the initial keyboard event
2527 // for any joystick or keyboard event, enable playfield tile cursor
2528 if (dx || dy || button)
2529 SetTileCursorEnabled(TRUE);
2532 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2534 // delay joystick/keyboard actions if axes/keys continually pressed
2535 newbutton = dx = dy = 0;
2539 // first start with longer delay, then continue with shorter delay
2540 joytest_delay_value =
2541 (use_delay_value_first ? delay_value_first : delay_value);
2544 joytest_last = joytest;
2546 switch (game_status)
2548 case GAME_MODE_TITLE:
2549 case GAME_MODE_MAIN:
2550 case GAME_MODE_LEVELS:
2551 case GAME_MODE_LEVELNR:
2552 case GAME_MODE_SETUP:
2553 case GAME_MODE_INFO:
2554 case GAME_MODE_SCORES:
2556 if (anyTextGadgetActive())
2559 if (game_status == GAME_MODE_TITLE)
2560 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2561 else if (game_status == GAME_MODE_MAIN)
2562 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2563 else if (game_status == GAME_MODE_LEVELS)
2564 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2565 else if (game_status == GAME_MODE_LEVELNR)
2566 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2567 else if (game_status == GAME_MODE_SETUP)
2568 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2569 else if (game_status == GAME_MODE_INFO)
2570 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2571 else if (game_status == GAME_MODE_SCORES)
2572 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2577 case GAME_MODE_PLAYING:
2579 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2580 if (tape.playing || keyboard)
2581 newbutton = ((joy & JOY_BUTTON) != 0);
2584 if (newbutton && game.all_players_gone)
2591 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2593 if (joystick & JOY_ACTION)
2594 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2596 else if (tape.recording && tape.pausing && !tape.use_mouse)
2598 if (joystick & JOY_ACTION)
2599 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2602 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2603 HandleTileCursor(dx, dy, button);
2612 void HandleSpecialGameControllerButtons(Event *event)
2617 switch (event->type)
2619 case SDL_CONTROLLERBUTTONDOWN:
2620 key_status = KEY_PRESSED;
2623 case SDL_CONTROLLERBUTTONUP:
2624 key_status = KEY_RELEASED;
2631 switch (event->cbutton.button)
2633 case SDL_CONTROLLER_BUTTON_START:
2637 case SDL_CONTROLLER_BUTTON_BACK:
2645 HandleKey(key, key_status);
2648 void HandleSpecialGameControllerKeys(Key key, int key_status)
2650 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2651 int button = SDL_CONTROLLER_BUTTON_INVALID;
2653 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2654 if (key == KSYM_Rewind)
2655 button = SDL_CONTROLLER_BUTTON_A;
2656 else if (key == KSYM_FastForward || key == KSYM_Menu)
2657 button = SDL_CONTROLLER_BUTTON_B;
2659 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2663 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2664 SDL_CONTROLLERBUTTONUP);
2666 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2667 event.cbutton.button = button;
2668 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2671 HandleJoystickEvent(&event);
2676 boolean DoKeysymAction(int keysym)
2680 Key key = (Key)(-keysym);
2682 HandleKey(key, KEY_PRESSED);
2683 HandleKey(key, KEY_RELEASED);