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;
45 // forward declarations for internal use
46 static void HandleNoEvent(void);
47 static void HandleEventActions(void);
50 // event filter especially needed for SDL event filtering due to
51 // delay problems with lots of mouse motion events when mouse button
52 // not pressed (X11 can handle this with 'PointerMotionHintMask')
54 // event filter addition for SDL2: as SDL2 does not have a function to enable
55 // or disable keyboard auto-repeat, filter repeated keyboard events instead
57 static int FilterEvents(const Event *event)
61 // skip repeated key press events if keyboard auto-repeat is disabled
62 if (event->type == EVENT_KEYPRESS &&
67 if (event->type == EVENT_BUTTONPRESS ||
68 event->type == EVENT_BUTTONRELEASE)
70 ((ButtonEvent *)event)->x -= video.screen_xoffset;
71 ((ButtonEvent *)event)->y -= video.screen_yoffset;
73 else if (event->type == EVENT_MOTIONNOTIFY)
75 ((MotionEvent *)event)->x -= video.screen_xoffset;
76 ((MotionEvent *)event)->y -= video.screen_yoffset;
78 gfx.mouse_x = ((MotionEvent *)event)->x;
79 gfx.mouse_y = ((MotionEvent *)event)->y;
82 // non-motion events are directly passed to event handler functions
83 if (event->type != EVENT_MOTIONNOTIFY)
86 motion = (MotionEvent *)event;
87 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
88 motion->y >= SY && motion->y < SY + SYSIZE);
90 // do no reset mouse cursor before all pending events have been processed
91 if (gfx.cursor_mode == cursor_mode_last &&
92 ((game_status == GAME_MODE_TITLE &&
93 gfx.cursor_mode == CURSOR_NONE) ||
94 (game_status == GAME_MODE_PLAYING &&
95 gfx.cursor_mode == CURSOR_PLAYFIELD)))
97 SetMouseCursor(CURSOR_DEFAULT);
99 DelayReached(&special_cursor_delay, 0);
101 cursor_mode_last = CURSOR_DEFAULT;
104 // skip mouse motion events without pressed button outside level editor
105 if (button_status == MB_RELEASED &&
106 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
112 // to prevent delay problems, skip mouse motion events if the very next
113 // event is also a mouse motion event (and therefore effectively only
114 // handling the last of a row of mouse motion events in the event queue)
116 static boolean SkipPressedMouseMotionEvent(const Event *event)
118 // nothing to do if the current event is not a mouse motion event
119 if (event->type != EVENT_MOTIONNOTIFY)
122 // only skip motion events with pressed button outside the game
123 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
130 PeekEvent(&next_event);
132 // if next event is also a mouse motion event, skip the current one
133 if (next_event.type == EVENT_MOTIONNOTIFY)
140 static boolean WaitValidEvent(Event *event)
144 if (!FilterEvents(event))
147 if (SkipPressedMouseMotionEvent(event))
153 /* this is especially needed for event modifications for the Android target:
154 if mouse coordinates should be modified in the event filter function,
155 using a properly installed SDL event filter does not work, because in
156 the event filter, mouse coordinates in the event structure are still
157 physical pixel positions, not logical (scaled) screen positions, so this
158 has to be handled at a later stage in the event processing functions
159 (when device pixel positions are already converted to screen positions) */
161 boolean NextValidEvent(Event *event)
163 while (PendingEvent())
164 if (WaitValidEvent(event))
170 static void HandleEvents(void)
173 unsigned int event_frame_delay = 0;
174 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
176 ResetDelayCounter(&event_frame_delay);
178 while (NextValidEvent(&event))
182 case EVENT_BUTTONPRESS:
183 case EVENT_BUTTONRELEASE:
184 HandleButtonEvent((ButtonEvent *) &event);
187 case EVENT_MOTIONNOTIFY:
188 HandleMotionEvent((MotionEvent *) &event);
191 case EVENT_WHEELMOTION:
192 HandleWheelEvent((WheelEvent *) &event);
195 case SDL_WINDOWEVENT:
196 HandleWindowEvent((WindowEvent *) &event);
199 case EVENT_FINGERPRESS:
200 case EVENT_FINGERRELEASE:
201 case EVENT_FINGERMOTION:
202 HandleFingerEvent((FingerEvent *) &event);
205 case EVENT_TEXTINPUT:
206 HandleTextEvent((TextEvent *) &event);
209 case SDL_APP_WILLENTERBACKGROUND:
210 case SDL_APP_DIDENTERBACKGROUND:
211 case SDL_APP_WILLENTERFOREGROUND:
212 case SDL_APP_DIDENTERFOREGROUND:
213 HandlePauseResumeEvent((PauseResumeEvent *) &event);
217 case EVENT_KEYRELEASE:
218 HandleKeyEvent((KeyEvent *) &event);
222 HandleOtherEvents(&event);
226 // do not handle events for longer than standard frame delay period
227 if (DelayReached(&event_frame_delay, event_frame_delay_value))
232 void HandleOtherEvents(Event *event)
236 case EVENT_CLIENTMESSAGE:
237 HandleClientMessageEvent((ClientMessageEvent *) event);
240 case SDL_CONTROLLERBUTTONDOWN:
241 case SDL_CONTROLLERBUTTONUP:
242 // for any game controller button event, disable overlay buttons
243 SetOverlayEnabled(FALSE);
245 HandleSpecialGameControllerButtons(event);
248 case SDL_CONTROLLERDEVICEADDED:
249 case SDL_CONTROLLERDEVICEREMOVED:
250 case SDL_CONTROLLERAXISMOTION:
251 case SDL_JOYAXISMOTION:
252 case SDL_JOYBUTTONDOWN:
253 case SDL_JOYBUTTONUP:
254 HandleJoystickEvent(event);
258 case SDL_DROPCOMPLETE:
261 HandleDropEvent(event);
269 static void HandleMouseCursor(void)
271 if (game_status == GAME_MODE_TITLE)
273 // when showing title screens, hide mouse pointer (if not moved)
275 if (gfx.cursor_mode != CURSOR_NONE &&
276 DelayReached(&special_cursor_delay, special_cursor_delay_value))
278 SetMouseCursor(CURSOR_NONE);
281 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
284 // when playing, display a special mouse pointer inside the playfield
286 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
287 cursor_inside_playfield &&
288 DelayReached(&special_cursor_delay, special_cursor_delay_value))
290 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
292 SetMouseCursor(CURSOR_PLAYFIELD);
295 else if (gfx.cursor_mode != CURSOR_DEFAULT)
297 SetMouseCursor(CURSOR_DEFAULT);
300 // this is set after all pending events have been processed
301 cursor_mode_last = gfx.cursor_mode;
313 // execute event related actions after pending events have been processed
314 HandleEventActions();
316 // don't use all CPU time when idle; the main loop while playing
317 // has its own synchronization and is CPU friendly, too
319 if (game_status == GAME_MODE_PLAYING)
322 // always copy backbuffer to visible screen for every video frame
325 // reset video frame delay to default (may change again while playing)
326 SetVideoFrameDelay(MenuFrameDelay);
328 if (game_status == GAME_MODE_QUIT)
333 void ClearAutoRepeatKeyEvents(void)
335 while (PendingEvent())
339 PeekEvent(&next_event);
341 // if event is repeated key press event, remove it from event queue
342 if (next_event.type == EVENT_KEYPRESS &&
343 next_event.key.repeat)
344 WaitEvent(&next_event);
350 void ClearEventQueue(void)
354 while (NextValidEvent(&event))
358 case EVENT_BUTTONRELEASE:
359 button_status = MB_RELEASED;
362 case EVENT_KEYRELEASE:
366 case SDL_CONTROLLERBUTTONUP:
367 HandleJoystickEvent(&event);
372 HandleOtherEvents(&event);
378 static void ClearPlayerMouseAction(void)
380 local_player->mouse_action.lx = 0;
381 local_player->mouse_action.ly = 0;
382 local_player->mouse_action.button = 0;
385 void ClearPlayerAction(void)
389 // simulate key release events for still pressed keys
390 key_joystick_mapping = 0;
391 for (i = 0; i < MAX_PLAYERS; i++)
393 stored_player[i].action = 0;
394 stored_player[i].snap_action = 0;
397 ClearJoystickState();
398 ClearPlayerMouseAction();
401 static void SetPlayerMouseAction(int mx, int my, int button)
403 int lx = getLevelFromScreenX(mx);
404 int ly = getLevelFromScreenY(my);
405 int new_button = (!local_player->mouse_action.button && button);
407 if (local_player->mouse_action.button_hint)
408 button = local_player->mouse_action.button_hint;
410 ClearPlayerMouseAction();
412 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
415 local_player->mouse_action.lx = lx;
416 local_player->mouse_action.ly = ly;
417 local_player->mouse_action.button = button;
419 if (tape.recording && tape.pausing && tape.use_mouse)
421 // un-pause a paused game only if mouse button was newly pressed down
423 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
426 SetTileCursorXY(lx, ly);
429 void HandleButtonEvent(ButtonEvent *event)
431 #if DEBUG_EVENTS_BUTTON
432 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
434 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
438 // for any mouse button event, disable playfield tile cursor
439 SetTileCursorEnabled(FALSE);
441 #if defined(HAS_SCREEN_KEYBOARD)
442 if (video.shifted_up)
443 event->y += video.shifted_up_pos;
446 motion_status = FALSE;
448 if (event->type == EVENT_BUTTONPRESS)
449 button_status = event->button;
451 button_status = MB_RELEASED;
453 HandleButton(event->x, event->y, button_status, event->button);
456 void HandleMotionEvent(MotionEvent *event)
458 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
461 motion_status = TRUE;
463 #if DEBUG_EVENTS_MOTION
464 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
465 button_status, event->x, event->y);
468 HandleButton(event->x, event->y, button_status, button_status);
471 void HandleWheelEvent(WheelEvent *event)
475 #if DEBUG_EVENTS_WHEEL
477 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
478 event->which, event->x, event->y);
480 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
481 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
482 event->which, event->x, event->y,
483 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
484 "SDL_MOUSEWHEEL_FLIPPED"));
488 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
489 event->x > 0 ? MB_WHEEL_RIGHT :
490 event->y < 0 ? MB_WHEEL_DOWN :
491 event->y > 0 ? MB_WHEEL_UP : 0);
493 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
494 // accelerated mouse wheel available on Mac and Windows
495 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
497 // no accelerated mouse wheel available on Unix/Linux
498 wheel_steps = DEFAULT_WHEEL_STEPS;
501 motion_status = FALSE;
503 button_status = button_nr;
504 HandleButton(0, 0, button_status, -button_nr);
506 button_status = MB_RELEASED;
507 HandleButton(0, 0, button_status, -button_nr);
510 void HandleWindowEvent(WindowEvent *event)
512 #if DEBUG_EVENTS_WINDOW
513 int subtype = event->event;
516 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
517 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
518 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
519 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
520 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
521 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
522 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
523 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
524 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
525 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
526 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
527 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
528 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
529 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
532 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
533 event_name, event->data1, event->data2);
537 // (not needed, as the screen gets redrawn every 20 ms anyway)
538 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
539 event->event == SDL_WINDOWEVENT_RESIZED ||
540 event->event == SDL_WINDOWEVENT_EXPOSED)
544 if (event->event == SDL_WINDOWEVENT_RESIZED)
546 if (!video.fullscreen_enabled)
548 int new_window_width = event->data1;
549 int new_window_height = event->data2;
551 // if window size has changed after resizing, calculate new scaling factor
552 if (new_window_width != video.window_width ||
553 new_window_height != video.window_height)
555 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
556 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
558 // (extreme window scaling allowed, but cannot be saved permanently)
559 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
560 setup.window_scaling_percent =
561 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
562 MAX_WINDOW_SCALING_PERCENT);
564 video.window_width = new_window_width;
565 video.window_height = new_window_height;
567 if (game_status == GAME_MODE_SETUP)
568 RedrawSetupScreenAfterFullscreenToggle();
573 #if defined(PLATFORM_ANDROID)
576 int new_display_width = event->data1;
577 int new_display_height = event->data2;
579 // if fullscreen display size has changed, device has been rotated
580 if (new_display_width != video.display_width ||
581 new_display_height != video.display_height)
583 int nr = GRID_ACTIVE_NR(); // previous screen orientation
585 video.display_width = new_display_width;
586 video.display_height = new_display_height;
588 SDLSetScreenProperties();
590 // check if screen orientation has changed (should always be true here)
591 if (nr != GRID_ACTIVE_NR())
595 if (game_status == GAME_MODE_SETUP)
596 RedrawSetupScreenAfterScreenRotation(nr);
598 nr = GRID_ACTIVE_NR();
600 overlay.grid_xsize = setup.touch.grid_xsize[nr];
601 overlay.grid_ysize = setup.touch.grid_ysize[nr];
603 for (x = 0; x < MAX_GRID_XSIZE; x++)
604 for (y = 0; y < MAX_GRID_YSIZE; y++)
605 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
613 #define NUM_TOUCH_FINGERS 3
618 SDL_FingerID finger_id;
622 } touch_info[NUM_TOUCH_FINGERS];
624 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
627 int x = event->x * overlay.grid_xsize;
628 int y = event->y * overlay.grid_ysize;
629 int grid_button = overlay.grid_button[x][y];
630 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
631 Key key = (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
632 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
633 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
634 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
635 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
636 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
639 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
640 float event_x = (event->x);
641 float event_y = (event->y - ypos) / (1 - ypos);
642 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
643 event_y > 2.0 / 3.0 && event_y < 1 ?
644 setup.input[0].key.snap :
645 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
646 event_y > 2.0 / 3.0 && event_y < 1 ?
647 setup.input[0].key.drop :
648 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
649 event_y > 0 && event_y < 1.0 / 3.0 ?
650 setup.input[0].key.up :
651 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
652 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
653 setup.input[0].key.left :
654 event_x > 8.0 / 9.0 && event_x < 1 &&
655 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
656 setup.input[0].key.right :
657 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
658 event_y > 2.0 / 3.0 && event_y < 1 ?
659 setup.input[0].key.down :
662 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
664 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
668 virtual_button_pressed = (key_status == KEY_PRESSED && key != KSYM_UNDEFINED);
670 // for any touch input event, enable overlay buttons (if activated)
671 SetOverlayEnabled(TRUE);
673 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
674 getKeyNameFromKey(key), key_status_name, event->fingerId);
676 if (key_status == KEY_PRESSED)
677 overlay.grid_button_action |= grid_button_action;
679 overlay.grid_button_action &= ~grid_button_action;
681 // check if we already know this touch event's finger id
682 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
684 if (touch_info[i].touched &&
685 touch_info[i].finger_id == event->fingerId)
687 // Error(ERR_DEBUG, "MARK 1: %d", i);
693 if (i >= NUM_TOUCH_FINGERS)
695 if (key_status == KEY_PRESSED)
697 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
699 // unknown finger id -- get new, empty slot, if available
700 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
702 if (touch_info[i].counter < oldest_counter)
705 oldest_counter = touch_info[i].counter;
707 // Error(ERR_DEBUG, "MARK 2: %d", i);
710 if (!touch_info[i].touched)
712 // Error(ERR_DEBUG, "MARK 3: %d", i);
718 if (i >= NUM_TOUCH_FINGERS)
720 // all slots allocated -- use oldest slot
723 // Error(ERR_DEBUG, "MARK 4: %d", i);
728 // release of previously unknown key (should not happen)
730 if (key != KSYM_UNDEFINED)
732 HandleKey(key, KEY_RELEASED);
734 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
735 getKeyNameFromKey(key), "KEY_RELEASED", i);
740 if (i < NUM_TOUCH_FINGERS)
742 if (key_status == KEY_PRESSED)
744 if (touch_info[i].key != key)
746 if (touch_info[i].key != KSYM_UNDEFINED)
748 HandleKey(touch_info[i].key, KEY_RELEASED);
750 // undraw previous grid button when moving finger away
751 overlay.grid_button_action &= ~touch_info[i].action;
753 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
754 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
757 if (key != KSYM_UNDEFINED)
759 HandleKey(key, KEY_PRESSED);
761 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
762 getKeyNameFromKey(key), "KEY_PRESSED", i);
766 touch_info[i].touched = TRUE;
767 touch_info[i].finger_id = event->fingerId;
768 touch_info[i].counter = Counter();
769 touch_info[i].key = key;
770 touch_info[i].action = grid_button_action;
774 if (touch_info[i].key != KSYM_UNDEFINED)
776 HandleKey(touch_info[i].key, KEY_RELEASED);
778 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
779 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
782 touch_info[i].touched = FALSE;
783 touch_info[i].finger_id = 0;
784 touch_info[i].counter = 0;
785 touch_info[i].key = 0;
786 touch_info[i].action = JOY_NO_ACTION;
791 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
793 static Key motion_key_x = KSYM_UNDEFINED;
794 static Key motion_key_y = KSYM_UNDEFINED;
795 static Key button_key = KSYM_UNDEFINED;
796 static float motion_x1, motion_y1;
797 static float button_x1, button_y1;
798 static SDL_FingerID motion_id = -1;
799 static SDL_FingerID button_id = -1;
800 int move_trigger_distance_percent = setup.touch.move_distance;
801 int drop_trigger_distance_percent = setup.touch.drop_distance;
802 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
803 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
804 float event_x = event->x;
805 float event_y = event->y;
807 if (event->type == EVENT_FINGERPRESS)
809 if (event_x > 1.0 / 3.0)
813 motion_id = event->fingerId;
818 motion_key_x = KSYM_UNDEFINED;
819 motion_key_y = KSYM_UNDEFINED;
821 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
827 button_id = event->fingerId;
832 button_key = setup.input[0].key.snap;
834 HandleKey(button_key, KEY_PRESSED);
836 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
839 else if (event->type == EVENT_FINGERRELEASE)
841 if (event->fingerId == motion_id)
845 if (motion_key_x != KSYM_UNDEFINED)
846 HandleKey(motion_key_x, KEY_RELEASED);
847 if (motion_key_y != KSYM_UNDEFINED)
848 HandleKey(motion_key_y, KEY_RELEASED);
850 motion_key_x = KSYM_UNDEFINED;
851 motion_key_y = KSYM_UNDEFINED;
853 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
855 else if (event->fingerId == button_id)
859 if (button_key != KSYM_UNDEFINED)
860 HandleKey(button_key, KEY_RELEASED);
862 button_key = KSYM_UNDEFINED;
864 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
867 else if (event->type == EVENT_FINGERMOTION)
869 if (event->fingerId == motion_id)
871 float distance_x = ABS(event_x - motion_x1);
872 float distance_y = ABS(event_y - motion_y1);
873 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
874 event_x > motion_x1 ? setup.input[0].key.right :
876 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
877 event_y > motion_y1 ? setup.input[0].key.down :
880 if (distance_x < move_trigger_distance / 2 ||
881 distance_x < distance_y)
882 new_motion_key_x = KSYM_UNDEFINED;
884 if (distance_y < move_trigger_distance / 2 ||
885 distance_y < distance_x)
886 new_motion_key_y = KSYM_UNDEFINED;
888 if (distance_x > move_trigger_distance ||
889 distance_y > move_trigger_distance)
891 if (new_motion_key_x != motion_key_x)
893 if (motion_key_x != KSYM_UNDEFINED)
894 HandleKey(motion_key_x, KEY_RELEASED);
895 if (new_motion_key_x != KSYM_UNDEFINED)
896 HandleKey(new_motion_key_x, KEY_PRESSED);
899 if (new_motion_key_y != motion_key_y)
901 if (motion_key_y != KSYM_UNDEFINED)
902 HandleKey(motion_key_y, KEY_RELEASED);
903 if (new_motion_key_y != KSYM_UNDEFINED)
904 HandleKey(new_motion_key_y, KEY_PRESSED);
910 motion_key_x = new_motion_key_x;
911 motion_key_y = new_motion_key_y;
913 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
916 else if (event->fingerId == button_id)
918 float distance_x = ABS(event_x - button_x1);
919 float distance_y = ABS(event_y - button_y1);
921 if (distance_x < drop_trigger_distance / 2 &&
922 distance_y > drop_trigger_distance)
924 if (button_key == setup.input[0].key.snap)
925 HandleKey(button_key, KEY_RELEASED);
930 button_key = setup.input[0].key.drop;
932 HandleKey(button_key, KEY_PRESSED);
934 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
940 void HandleFingerEvent(FingerEvent *event)
942 #if DEBUG_EVENTS_FINGER
943 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
944 event->type == EVENT_FINGERPRESS ? "pressed" :
945 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
949 event->dx, event->dy,
953 runtime.uses_touch_device = TRUE;
955 if (game_status != GAME_MODE_PLAYING)
958 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
960 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
961 local_player->mouse_action.button_hint =
962 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
963 event->x < 0.5 ? MB_LEFTBUTTON :
964 event->x > 0.5 ? MB_RIGHTBUTTON :
970 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
971 HandleFingerEvent_VirtualButtons(event);
972 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
973 HandleFingerEvent_WipeGestures(event);
976 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
978 static int old_mx = 0, old_my = 0;
979 static int last_button = MB_LEFTBUTTON;
980 static boolean touched = FALSE;
981 static boolean tapped = FALSE;
983 // screen tile was tapped (but finger not touching the screen anymore)
984 // (this point will also be reached without receiving a touch event)
985 if (tapped && !touched)
987 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
992 // stop here if this function was not triggered by a touch event
996 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
998 // finger started touching the screen
1008 ClearPlayerMouseAction();
1010 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1013 else if (button == MB_RELEASED && touched)
1015 // finger stopped touching the screen
1020 SetPlayerMouseAction(old_mx, old_my, last_button);
1022 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1024 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1029 // finger moved while touching the screen
1031 int old_x = getLevelFromScreenX(old_mx);
1032 int old_y = getLevelFromScreenY(old_my);
1033 int new_x = getLevelFromScreenX(mx);
1034 int new_y = getLevelFromScreenY(my);
1036 if (new_x != old_x || new_y != old_y)
1041 // finger moved left or right from (horizontal) starting position
1043 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1045 SetPlayerMouseAction(old_mx, old_my, button_nr);
1047 last_button = button_nr;
1049 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1053 // finger stays at or returned to (horizontal) starting position
1055 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1057 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1062 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1064 static int old_mx = 0, old_my = 0;
1065 static int last_button = MB_LEFTBUTTON;
1066 static boolean touched = FALSE;
1067 static boolean tapped = FALSE;
1069 // screen tile was tapped (but finger not touching the screen anymore)
1070 // (this point will also be reached without receiving a touch event)
1071 if (tapped && !touched)
1073 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1078 // stop here if this function was not triggered by a touch event
1082 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1084 // finger started touching the screen
1094 ClearPlayerMouseAction();
1096 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1099 else if (button == MB_RELEASED && touched)
1101 // finger stopped touching the screen
1106 SetPlayerMouseAction(old_mx, old_my, last_button);
1108 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1110 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1115 // finger moved while touching the screen
1117 int old_x = getLevelFromScreenX(old_mx);
1118 int old_y = getLevelFromScreenY(old_my);
1119 int new_x = getLevelFromScreenX(mx);
1120 int new_y = getLevelFromScreenY(my);
1122 if (new_x != old_x || new_y != old_y)
1124 // finger moved away from starting position
1126 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1128 // quickly alternate between clicking and releasing for maximum speed
1129 if (FrameCounter % 2 == 0)
1130 button_nr = MB_RELEASED;
1132 SetPlayerMouseAction(old_mx, old_my, button_nr);
1135 last_button = button_nr;
1139 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1143 // finger stays at or returned to starting position
1145 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1147 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1152 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1154 static int old_mx = 0, old_my = 0;
1155 static Key motion_key_x = KSYM_UNDEFINED;
1156 static Key motion_key_y = KSYM_UNDEFINED;
1157 static boolean touched = FALSE;
1158 static boolean started_on_player = FALSE;
1159 static boolean player_is_dropping = FALSE;
1160 static int player_drop_count = 0;
1161 static int last_player_x = -1;
1162 static int last_player_y = -1;
1164 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1173 started_on_player = FALSE;
1174 player_is_dropping = FALSE;
1175 player_drop_count = 0;
1179 motion_key_x = KSYM_UNDEFINED;
1180 motion_key_y = KSYM_UNDEFINED;
1182 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1185 else if (button == MB_RELEASED && touched)
1192 if (motion_key_x != KSYM_UNDEFINED)
1193 HandleKey(motion_key_x, KEY_RELEASED);
1194 if (motion_key_y != KSYM_UNDEFINED)
1195 HandleKey(motion_key_y, KEY_RELEASED);
1197 if (started_on_player)
1199 if (player_is_dropping)
1201 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1203 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1207 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1209 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1213 motion_key_x = KSYM_UNDEFINED;
1214 motion_key_y = KSYM_UNDEFINED;
1216 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1221 int src_x = local_player->jx;
1222 int src_y = local_player->jy;
1223 int dst_x = getLevelFromScreenX(old_mx);
1224 int dst_y = getLevelFromScreenY(old_my);
1225 int dx = dst_x - src_x;
1226 int dy = dst_y - src_y;
1227 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1228 dx > 0 ? setup.input[0].key.right :
1230 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1231 dy > 0 ? setup.input[0].key.down :
1234 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1235 (last_player_x != local_player->jx ||
1236 last_player_y != local_player->jy))
1238 // in case of asymmetric diagonal movement, use "preferred" direction
1240 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1242 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1243 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1245 local_player->last_move_dir = last_move_dir;
1247 // (required to prevent accidentally forcing direction for next movement)
1248 last_player_x = local_player->jx;
1249 last_player_y = local_player->jy;
1252 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1254 started_on_player = TRUE;
1255 player_drop_count = getPlayerInventorySize(0);
1256 player_is_dropping = (player_drop_count > 0);
1258 if (player_is_dropping)
1260 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1262 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1266 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1268 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1271 else if (dx != 0 || dy != 0)
1273 if (player_is_dropping &&
1274 player_drop_count == getPlayerInventorySize(0))
1276 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1278 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1279 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1281 player_is_dropping = FALSE;
1285 if (new_motion_key_x != motion_key_x)
1287 Error(ERR_DEBUG, "---------- %s %s ----------",
1288 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1289 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1291 if (motion_key_x != KSYM_UNDEFINED)
1292 HandleKey(motion_key_x, KEY_RELEASED);
1293 if (new_motion_key_x != KSYM_UNDEFINED)
1294 HandleKey(new_motion_key_x, KEY_PRESSED);
1297 if (new_motion_key_y != motion_key_y)
1299 Error(ERR_DEBUG, "---------- %s %s ----------",
1300 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1301 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1303 if (motion_key_y != KSYM_UNDEFINED)
1304 HandleKey(motion_key_y, KEY_RELEASED);
1305 if (new_motion_key_y != KSYM_UNDEFINED)
1306 HandleKey(new_motion_key_y, KEY_PRESSED);
1309 motion_key_x = new_motion_key_x;
1310 motion_key_y = new_motion_key_y;
1314 static void HandleButtonOrFinger(int mx, int my, int button)
1316 if (game_status != GAME_MODE_PLAYING)
1319 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1321 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1322 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1323 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1324 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1325 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1326 SetPlayerMouseAction(mx, my, button); // special case
1330 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1331 HandleButtonOrFinger_FollowFinger(mx, my, button);
1335 static boolean checkTextInputKeyModState(void)
1337 // when playing, only handle raw key events and ignore text input
1338 if (game_status == GAME_MODE_PLAYING)
1341 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1344 void HandleTextEvent(TextEvent *event)
1346 char *text = event->text;
1347 Key key = getKeyFromKeyName(text);
1349 #if DEBUG_EVENTS_TEXT
1350 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1353 text[0], (int)(text[0]),
1355 getKeyNameFromKey(key),
1359 #if !defined(HAS_SCREEN_KEYBOARD)
1360 // non-mobile devices: only handle key input with modifier keys pressed here
1361 // (every other key input is handled directly as physical key input event)
1362 if (!checkTextInputKeyModState())
1366 // process text input as "classic" (with uppercase etc.) key input event
1367 HandleKey(key, KEY_PRESSED);
1368 HandleKey(key, KEY_RELEASED);
1371 void HandlePauseResumeEvent(PauseResumeEvent *event)
1373 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1377 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1383 void HandleKeyEvent(KeyEvent *event)
1385 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1386 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1387 Key key = GetEventKey(event, with_modifiers);
1388 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1390 #if DEBUG_EVENTS_KEY
1391 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1392 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1393 event->keysym.scancode,
1398 getKeyNameFromKey(key));
1401 #if defined(PLATFORM_ANDROID)
1402 if (key == KSYM_Back)
1404 // always map the "back" button to the "escape" key on Android devices
1407 else if (key == KSYM_Menu)
1409 // the "menu" button can be used to toggle displaying virtual buttons
1410 if (key_status == KEY_PRESSED)
1411 SetOverlayEnabled(!GetOverlayEnabled());
1415 // for any other "real" key event, disable virtual buttons
1416 SetOverlayEnabled(FALSE);
1420 HandleKeyModState(keymod, key_status);
1422 // only handle raw key input without text modifier keys pressed
1423 if (!checkTextInputKeyModState())
1424 HandleKey(key, key_status);
1427 void HandleClientMessageEvent(ClientMessageEvent *event)
1429 if (CheckCloseWindowEvent(event))
1433 static int HandleDropFileEvent(char *filename)
1435 Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
1437 // check and extract dropped zip files into correct user data directory
1438 if (!strSuffixLower(filename, ".zip"))
1440 Error(ERR_WARN, "file '%s' not supported", filename);
1442 return TREE_TYPE_UNDEFINED;
1445 TreeInfo *tree_node = NULL;
1446 int tree_type = GetZipFileTreeType(filename);
1447 char *directory = TREE_USERDIR(tree_type);
1449 if (directory == NULL)
1451 Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1453 return TREE_TYPE_UNDEFINED;
1456 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1457 game_status == GAME_MODE_LEVELS &&
1458 leveldir_current->node_parent != NULL)
1460 // extract new level set next to currently selected level set
1461 tree_node = leveldir_current;
1463 // get parent directory of currently selected level set directory
1464 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1466 // use private level directory instead of top-level package level directory
1467 if (strPrefix(directory, options.level_directory) &&
1468 strEqual(leveldir_current->node_parent->fullpath, "."))
1469 directory = getUserLevelDir(NULL);
1472 // extract level or artwork set from zip file to target directory
1473 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1475 if (top_dir == NULL)
1477 // error message already issued by "ExtractZipFileIntoDirectory()"
1479 return TREE_TYPE_UNDEFINED;
1482 // add extracted level or artwork set to tree info structure
1483 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1485 // update menu screen (and possibly change current level set)
1486 DrawScreenAfterAddingSet(top_dir, tree_type);
1491 static void HandleDropTextEvent(char *text)
1493 Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1496 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1497 int num_artwork_sets_succeeded,
1498 int num_files_failed)
1500 // only show request dialog if no other request dialog already active
1501 if (game.request_active)
1504 // this case can happen with drag-and-drop with older SDL versions
1505 if (num_level_sets_succeeded == 0 &&
1506 num_artwork_sets_succeeded == 0 &&
1507 num_files_failed == 0)
1512 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1514 char message_part1[50];
1516 sprintf(message_part1, "New %s set%s added",
1517 (num_artwork_sets_succeeded == 0 ? "level" :
1518 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1519 (num_level_sets_succeeded +
1520 num_artwork_sets_succeeded > 1 ? "s" : ""));
1522 if (num_files_failed > 0)
1523 sprintf(message, "%s, but %d dropped file%s failed!",
1524 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1526 sprintf(message, "%s!", message_part1);
1528 else if (num_files_failed > 0)
1530 sprintf(message, "Failed to process dropped file%s!",
1531 num_files_failed > 1 ? "s" : "");
1534 Request(message, REQ_CONFIRM);
1537 void HandleDropEvent(Event *event)
1539 static boolean confirm_on_drop_complete = FALSE;
1540 static int num_level_sets_succeeded = 0;
1541 static int num_artwork_sets_succeeded = 0;
1542 static int num_files_failed = 0;
1544 switch (event->type)
1548 confirm_on_drop_complete = TRUE;
1549 num_level_sets_succeeded = 0;
1550 num_artwork_sets_succeeded = 0;
1551 num_files_failed = 0;
1558 int tree_type = HandleDropFileEvent(event->drop.file);
1560 if (tree_type == TREE_TYPE_LEVEL_DIR)
1561 num_level_sets_succeeded++;
1562 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1563 tree_type == TREE_TYPE_SOUNDS_DIR ||
1564 tree_type == TREE_TYPE_MUSIC_DIR)
1565 num_artwork_sets_succeeded++;
1569 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1570 if (!confirm_on_drop_complete)
1572 // process all remaining events, including further SDL_DROPFILE events
1575 HandleDropCompleteEvent(num_level_sets_succeeded,
1576 num_artwork_sets_succeeded,
1579 num_level_sets_succeeded = 0;
1580 num_artwork_sets_succeeded = 0;
1581 num_files_failed = 0;
1589 HandleDropTextEvent(event->drop.file);
1594 case SDL_DROPCOMPLETE:
1596 HandleDropCompleteEvent(num_level_sets_succeeded,
1597 num_artwork_sets_succeeded,
1604 if (event->drop.file != NULL)
1605 SDL_free(event->drop.file);
1608 void HandleButton(int mx, int my, int button, int button_nr)
1610 static int old_mx = 0, old_my = 0;
1611 boolean button_hold = FALSE;
1612 boolean handle_gadgets = TRUE;
1618 button_nr = -button_nr;
1627 #if defined(PLATFORM_ANDROID)
1628 // when playing, only handle gadgets when using "follow finger" controls
1629 // or when using touch controls in combination with the MM game engine
1630 // or when using gadgets that do not overlap with virtual buttons
1632 (game_status != GAME_MODE_PLAYING ||
1633 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1634 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1635 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1636 !virtual_button_pressed));
1639 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1641 // do not handle this button event anymore
1642 return; // force mouse event not to be handled at all
1645 if (handle_gadgets && HandleGadgets(mx, my, button))
1647 // do not handle this button event anymore
1648 mx = my = -32; // force mouse event to be outside screen tiles
1651 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1654 // do not use scroll wheel button events for anything other than gadgets
1655 if (IS_WHEEL_BUTTON(button_nr))
1658 switch (game_status)
1660 case GAME_MODE_TITLE:
1661 HandleTitleScreen(mx, my, 0, 0, button);
1664 case GAME_MODE_MAIN:
1665 HandleMainMenu(mx, my, 0, 0, button);
1668 case GAME_MODE_PSEUDO_TYPENAME:
1669 HandleTypeName(0, KSYM_Return);
1672 case GAME_MODE_LEVELS:
1673 HandleChooseLevelSet(mx, my, 0, 0, button);
1676 case GAME_MODE_LEVELNR:
1677 HandleChooseLevelNr(mx, my, 0, 0, button);
1680 case GAME_MODE_SCORES:
1681 HandleHallOfFame(0, 0, 0, 0, button);
1684 case GAME_MODE_EDITOR:
1685 HandleLevelEditorIdle();
1688 case GAME_MODE_INFO:
1689 HandleInfoScreen(mx, my, 0, 0, button);
1692 case GAME_MODE_SETUP:
1693 HandleSetupScreen(mx, my, 0, 0, button);
1696 case GAME_MODE_PLAYING:
1697 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1698 HandleButtonOrFinger(mx, my, button);
1700 SetPlayerMouseAction(mx, my, button);
1703 if (button == MB_PRESSED && !motion_status && !button_hold &&
1704 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1705 DumpTileFromScreen(mx, my);
1715 static boolean is_string_suffix(char *string, char *suffix)
1717 int string_len = strlen(string);
1718 int suffix_len = strlen(suffix);
1720 if (suffix_len > string_len)
1723 return (strEqual(&string[string_len - suffix_len], suffix));
1726 #define MAX_CHEAT_INPUT_LEN 32
1728 static void HandleKeysSpecial(Key key)
1730 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1731 char letter = getCharFromKey(key);
1732 int cheat_input_len = strlen(cheat_input);
1738 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1740 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1741 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1743 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1746 cheat_input[cheat_input_len++] = letter;
1747 cheat_input[cheat_input_len] = '\0';
1749 #if DEBUG_EVENTS_KEY
1750 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1753 if (game_status == GAME_MODE_MAIN)
1755 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1756 is_string_suffix(cheat_input, ":ist"))
1758 InsertSolutionTape();
1760 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1761 is_string_suffix(cheat_input, ":pst"))
1765 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1766 is_string_suffix(cheat_input, ":rg"))
1768 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1771 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1772 is_string_suffix(cheat_input, ":rs"))
1774 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1777 else if (is_string_suffix(cheat_input, ":reload-music") ||
1778 is_string_suffix(cheat_input, ":rm"))
1780 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1783 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1784 is_string_suffix(cheat_input, ":ra"))
1786 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1787 1 << ARTWORK_TYPE_SOUNDS |
1788 1 << ARTWORK_TYPE_MUSIC);
1791 else if (is_string_suffix(cheat_input, ":dump-level") ||
1792 is_string_suffix(cheat_input, ":dl"))
1796 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1797 is_string_suffix(cheat_input, ":dt"))
1801 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1802 is_string_suffix(cheat_input, ":ft"))
1804 /* fix single-player tapes that contain player input for more than one
1805 player (due to a bug in 3.3.1.2 and earlier versions), which results
1806 in playing levels with more than one player in multi-player mode,
1807 even though the tape was originally recorded in single-player mode */
1809 // remove player input actions for all players but the first one
1810 for (i = 1; i < MAX_PLAYERS; i++)
1811 tape.player_participates[i] = FALSE;
1813 tape.changed = TRUE;
1815 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1816 is_string_suffix(cheat_input, ":snl"))
1818 SaveNativeLevel(&level);
1820 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1821 is_string_suffix(cheat_input, ":fps"))
1823 global.show_frames_per_second = !global.show_frames_per_second;
1826 else if (game_status == GAME_MODE_PLAYING)
1829 if (is_string_suffix(cheat_input, ".q"))
1830 DEBUG_SetMaximumDynamite();
1833 else if (game_status == GAME_MODE_EDITOR)
1835 if (is_string_suffix(cheat_input, ":dump-brush") ||
1836 is_string_suffix(cheat_input, ":DB"))
1840 else if (is_string_suffix(cheat_input, ":DDB"))
1845 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1847 if (letter == 'x') // copy brush to clipboard (small size)
1849 CopyBrushToClipboard_Small();
1851 else if (letter == 'c') // copy brush to clipboard (normal size)
1853 CopyBrushToClipboard();
1855 else if (letter == 'v') // paste brush from Clipboard
1857 CopyClipboardToBrush();
1862 // special key shortcuts for all game modes
1863 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1864 is_string_suffix(cheat_input, ":dea") ||
1865 is_string_suffix(cheat_input, ":DEA"))
1867 DumpGadgetIdentifiers();
1868 DumpScreenIdentifiers();
1872 boolean HandleKeysDebug(Key key, int key_status)
1877 if (key_status != KEY_PRESSED)
1880 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1882 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1884 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1886 if (key == setup.debug.frame_delay_key[i] &&
1887 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1889 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1890 setup.debug.frame_delay[i] : setup.game_frame_delay);
1892 if (!setup.debug.frame_delay_game_only)
1893 MenuFrameDelay = GameFrameDelay;
1895 SetVideoFrameDelay(GameFrameDelay);
1897 if (GameFrameDelay > ONE_SECOND_DELAY)
1898 Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
1899 else if (GameFrameDelay != 0)
1900 Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
1901 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1902 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1904 Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
1911 if (game_status == GAME_MODE_PLAYING)
1915 options.debug = !options.debug;
1917 Error(ERR_INFO, "debug mode %s",
1918 (options.debug ? "enabled" : "disabled"));
1922 else if (key == KSYM_v)
1924 Error(ERR_INFO, "currently using game engine version %d",
1925 game.engine_version);
1935 void HandleKey(Key key, int key_status)
1937 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1938 static boolean ignore_repeated_key = FALSE;
1939 static struct SetupKeyboardInfo ski;
1940 static struct SetupShortcutInfo ssi;
1949 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1950 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1951 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1952 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1953 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1954 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1959 if (HandleKeysDebug(key, key_status))
1960 return; // do not handle already processed keys again
1962 // map special keys (media keys / remote control buttons) to default keys
1963 if (key == KSYM_PlayPause)
1965 else if (key == KSYM_Select)
1968 HandleSpecialGameControllerKeys(key, key_status);
1970 if (game_status == GAME_MODE_PLAYING)
1972 // only needed for single-step tape recording mode
1973 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1976 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1978 byte key_action = 0;
1979 byte key_snap_action = 0;
1981 if (setup.input[pnr].use_joystick)
1984 ski = setup.input[pnr].key;
1986 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1987 if (key == *key_info[i].key_custom)
1988 key_action |= key_info[i].action;
1990 // use combined snap+direction keys for the first player only
1993 ssi = setup.shortcut;
1995 // also remember normal snap key when handling snap+direction keys
1996 key_snap_action |= key_action & JOY_BUTTON_SNAP;
1998 for (i = 0; i < NUM_DIRECTIONS; i++)
2000 if (key == *key_info[i].key_snap)
2002 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
2003 key_snap_action |= key_info[i].action;
2008 if (key_status == KEY_PRESSED)
2010 stored_player[pnr].action |= key_action;
2011 stored_player[pnr].snap_action |= key_snap_action;
2015 stored_player[pnr].action &= ~key_action;
2016 stored_player[pnr].snap_action &= ~key_snap_action;
2019 // restore snap action if one of several pressed snap keys was released
2020 if (stored_player[pnr].snap_action)
2021 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2023 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2025 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2027 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2029 // if snap key already pressed, keep pause mode when releasing
2030 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2031 has_snapped[pnr] = TRUE;
2033 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2035 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2037 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2038 getRedDiskReleaseFlag_SP() == 0)
2040 // add a single inactive frame before dropping starts
2041 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2042 stored_player[pnr].force_dropping = TRUE;
2045 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2047 // if snap key was pressed without direction, leave pause mode
2048 if (!has_snapped[pnr])
2049 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2051 has_snapped[pnr] = FALSE;
2054 else if (tape.recording && tape.pausing && !tape.use_mouse)
2056 // prevent key release events from un-pausing a paused game
2057 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2058 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2061 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2062 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2068 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2069 if (key == key_info[i].key_default)
2070 joy |= key_info[i].action;
2075 if (key_status == KEY_PRESSED)
2076 key_joystick_mapping |= joy;
2078 key_joystick_mapping &= ~joy;
2083 if (game_status != GAME_MODE_PLAYING)
2084 key_joystick_mapping = 0;
2086 if (key_status == KEY_RELEASED)
2088 // reset flag to ignore repeated "key pressed" events after key release
2089 ignore_repeated_key = FALSE;
2094 if ((key == KSYM_F11 ||
2095 ((key == KSYM_Return ||
2096 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2097 video.fullscreen_available &&
2098 !ignore_repeated_key)
2100 setup.fullscreen = !setup.fullscreen;
2102 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2104 if (game_status == GAME_MODE_SETUP)
2105 RedrawSetupScreenAfterFullscreenToggle();
2107 // set flag to ignore repeated "key pressed" events
2108 ignore_repeated_key = TRUE;
2113 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2114 key == KSYM_minus || key == KSYM_KP_Subtract ||
2115 key == KSYM_plus || key == KSYM_KP_Add ||
2116 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2117 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2118 video.window_scaling_available &&
2119 !video.fullscreen_enabled)
2121 if (key == KSYM_0 || key == KSYM_KP_0)
2122 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2123 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2124 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2126 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2128 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2129 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2130 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2131 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2133 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2135 if (game_status == GAME_MODE_SETUP)
2136 RedrawSetupScreenAfterFullscreenToggle();
2141 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2142 key == KSYM_Return ||
2143 key == KSYM_Escape), TRUE))
2145 // do not handle this key event anymore
2146 if (key != KSYM_Escape) // always allow ESC key to be handled
2150 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2151 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2158 if (game_status == GAME_MODE_MAIN &&
2159 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2161 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2166 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2168 if (key == setup.shortcut.save_game)
2170 else if (key == setup.shortcut.load_game)
2172 else if (key == setup.shortcut.toggle_pause)
2173 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2175 HandleTapeButtonKeys(key);
2176 HandleSoundButtonKeys(key);
2179 if (game_status == GAME_MODE_PLAYING && !network_playing)
2181 int centered_player_nr_next = -999;
2183 if (key == setup.shortcut.focus_player_all)
2184 centered_player_nr_next = -1;
2186 for (i = 0; i < MAX_PLAYERS; i++)
2187 if (key == setup.shortcut.focus_player[i])
2188 centered_player_nr_next = i;
2190 if (centered_player_nr_next != -999)
2192 game.centered_player_nr_next = centered_player_nr_next;
2193 game.set_centered_player = TRUE;
2197 tape.centered_player_nr_next = game.centered_player_nr_next;
2198 tape.set_centered_player = TRUE;
2203 HandleKeysSpecial(key);
2205 if (HandleGadgetsKeyInput(key))
2206 return; // do not handle already processed keys again
2208 switch (game_status)
2210 case GAME_MODE_PSEUDO_TYPENAME:
2211 HandleTypeName(0, key);
2214 case GAME_MODE_TITLE:
2215 case GAME_MODE_MAIN:
2216 case GAME_MODE_LEVELS:
2217 case GAME_MODE_LEVELNR:
2218 case GAME_MODE_SETUP:
2219 case GAME_MODE_INFO:
2220 case GAME_MODE_SCORES:
2222 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2229 if (game_status == GAME_MODE_TITLE)
2230 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2231 else if (game_status == GAME_MODE_MAIN)
2232 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2233 else if (game_status == GAME_MODE_LEVELS)
2234 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2235 else if (game_status == GAME_MODE_LEVELNR)
2236 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2237 else if (game_status == GAME_MODE_SETUP)
2238 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2239 else if (game_status == GAME_MODE_INFO)
2240 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2241 else if (game_status == GAME_MODE_SCORES)
2242 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2246 if (game_status != GAME_MODE_MAIN)
2247 FadeSkipNextFadeIn();
2249 if (game_status == GAME_MODE_TITLE)
2250 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2251 else if (game_status == GAME_MODE_LEVELS)
2252 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2253 else if (game_status == GAME_MODE_LEVELNR)
2254 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2255 else if (game_status == GAME_MODE_SETUP)
2256 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2257 else if (game_status == GAME_MODE_INFO)
2258 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2259 else if (game_status == GAME_MODE_SCORES)
2260 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2264 if (game_status == GAME_MODE_LEVELS)
2265 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2266 else if (game_status == GAME_MODE_LEVELNR)
2267 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2268 else if (game_status == GAME_MODE_SETUP)
2269 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2270 else if (game_status == GAME_MODE_INFO)
2271 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2272 else if (game_status == GAME_MODE_SCORES)
2273 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2276 case KSYM_Page_Down:
2277 if (game_status == GAME_MODE_LEVELS)
2278 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2279 else if (game_status == GAME_MODE_LEVELNR)
2280 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2281 else if (game_status == GAME_MODE_SETUP)
2282 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2283 else if (game_status == GAME_MODE_INFO)
2284 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2285 else if (game_status == GAME_MODE_SCORES)
2286 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2294 case GAME_MODE_EDITOR:
2295 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2296 HandleLevelEditorKeyInput(key);
2299 case GAME_MODE_PLAYING:
2304 RequestQuitGame(setup.ask_on_escape);
2314 if (key == KSYM_Escape)
2316 SetGameStatus(GAME_MODE_MAIN);
2325 void HandleNoEvent(void)
2327 HandleMouseCursor();
2329 switch (game_status)
2331 case GAME_MODE_PLAYING:
2332 HandleButtonOrFinger(-1, -1, -1);
2337 void HandleEventActions(void)
2339 // if (button_status && game_status != GAME_MODE_PLAYING)
2340 if (button_status && (game_status != GAME_MODE_PLAYING ||
2342 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2344 HandleButton(0, 0, button_status, -button_status);
2351 if (network.enabled)
2354 switch (game_status)
2356 case GAME_MODE_MAIN:
2357 DrawPreviewLevelAnimation();
2360 case GAME_MODE_EDITOR:
2361 HandleLevelEditorIdle();
2369 static void HandleTileCursor(int dx, int dy, int button)
2372 ClearPlayerMouseAction();
2379 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2380 (dx < 0 ? MB_LEFTBUTTON :
2381 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2383 else if (!tile_cursor.moving)
2385 int old_xpos = tile_cursor.xpos;
2386 int old_ypos = tile_cursor.ypos;
2387 int new_xpos = old_xpos;
2388 int new_ypos = old_ypos;
2390 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2391 new_xpos = old_xpos + dx;
2393 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2394 new_ypos = old_ypos + dy;
2396 SetTileCursorTargetXY(new_xpos, new_ypos);
2400 static int HandleJoystickForAllPlayers(void)
2404 boolean no_joysticks_configured = TRUE;
2405 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2406 static byte joy_action_last[MAX_PLAYERS];
2408 for (i = 0; i < MAX_PLAYERS; i++)
2409 if (setup.input[i].use_joystick)
2410 no_joysticks_configured = FALSE;
2412 // if no joysticks configured, map connected joysticks to players
2413 if (no_joysticks_configured)
2414 use_as_joystick_nr = TRUE;
2416 for (i = 0; i < MAX_PLAYERS; i++)
2418 byte joy_action = 0;
2420 joy_action = JoystickExt(i, use_as_joystick_nr);
2421 result |= joy_action;
2423 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2424 joy_action != joy_action_last[i])
2425 stored_player[i].action = joy_action;
2427 joy_action_last[i] = joy_action;
2433 void HandleJoystick(void)
2435 static unsigned int joytest_delay = 0;
2436 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2437 static int joytest_last = 0;
2438 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2439 int delay_value = GADGET_FRAME_DELAY;
2440 int joystick = HandleJoystickForAllPlayers();
2441 int keyboard = key_joystick_mapping;
2442 int joy = (joystick | keyboard);
2443 int joytest = joystick;
2444 int left = joy & JOY_LEFT;
2445 int right = joy & JOY_RIGHT;
2446 int up = joy & JOY_UP;
2447 int down = joy & JOY_DOWN;
2448 int button = joy & JOY_BUTTON;
2449 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2450 int dx = (left ? -1 : right ? 1 : 0);
2451 int dy = (up ? -1 : down ? 1 : 0);
2452 boolean use_delay_value_first = (joytest != joytest_last);
2454 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2456 // do not handle this button event anymore
2460 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2461 anyTextGadgetActive()))
2463 // leave name input in main menu or text input gadget
2464 HandleKey(KSYM_Escape, KEY_PRESSED);
2465 HandleKey(KSYM_Escape, KEY_RELEASED);
2470 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2472 if (game_status == GAME_MODE_PLAYING)
2474 // when playing MM style levels, also use delay for keyboard events
2475 joytest |= keyboard;
2477 // only use first delay value for new events, but not for changed events
2478 use_delay_value_first = (!joytest != !joytest_last);
2480 // only use delay after the initial keyboard event
2484 // for any joystick or keyboard event, enable playfield tile cursor
2485 if (dx || dy || button)
2486 SetTileCursorEnabled(TRUE);
2489 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2491 // delay joystick/keyboard actions if axes/keys continually pressed
2492 newbutton = dx = dy = 0;
2496 // first start with longer delay, then continue with shorter delay
2497 joytest_delay_value =
2498 (use_delay_value_first ? delay_value_first : delay_value);
2501 joytest_last = joytest;
2503 switch (game_status)
2505 case GAME_MODE_TITLE:
2506 case GAME_MODE_MAIN:
2507 case GAME_MODE_LEVELS:
2508 case GAME_MODE_LEVELNR:
2509 case GAME_MODE_SETUP:
2510 case GAME_MODE_INFO:
2511 case GAME_MODE_SCORES:
2513 if (anyTextGadgetActive())
2516 if (game_status == GAME_MODE_TITLE)
2517 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2518 else if (game_status == GAME_MODE_MAIN)
2519 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2520 else if (game_status == GAME_MODE_LEVELS)
2521 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2522 else if (game_status == GAME_MODE_LEVELNR)
2523 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2524 else if (game_status == GAME_MODE_SETUP)
2525 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2526 else if (game_status == GAME_MODE_INFO)
2527 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2528 else if (game_status == GAME_MODE_SCORES)
2529 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2534 case GAME_MODE_PLAYING:
2536 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2537 if (tape.playing || keyboard)
2538 newbutton = ((joy & JOY_BUTTON) != 0);
2541 if (newbutton && game.all_players_gone)
2548 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2550 if (joystick & JOY_ACTION)
2551 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2553 else if (tape.recording && tape.pausing && !tape.use_mouse)
2555 if (joystick & JOY_ACTION)
2556 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2559 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2560 HandleTileCursor(dx, dy, button);
2569 void HandleSpecialGameControllerButtons(Event *event)
2574 switch (event->type)
2576 case SDL_CONTROLLERBUTTONDOWN:
2577 key_status = KEY_PRESSED;
2580 case SDL_CONTROLLERBUTTONUP:
2581 key_status = KEY_RELEASED;
2588 switch (event->cbutton.button)
2590 case SDL_CONTROLLER_BUTTON_START:
2594 case SDL_CONTROLLER_BUTTON_BACK:
2602 HandleKey(key, key_status);
2605 void HandleSpecialGameControllerKeys(Key key, int key_status)
2607 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2608 int button = SDL_CONTROLLER_BUTTON_INVALID;
2610 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2611 if (key == KSYM_Rewind)
2612 button = SDL_CONTROLLER_BUTTON_A;
2613 else if (key == KSYM_FastForward || key == KSYM_Menu)
2614 button = SDL_CONTROLLER_BUTTON_B;
2616 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2620 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2621 SDL_CONTROLLERBUTTONUP);
2623 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2624 event.cbutton.button = button;
2625 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2628 HandleJoystickEvent(&event);
2633 boolean DoKeysymAction(int keysym)
2637 Key key = (Key)(-keysym);
2639 HandleKey(key, KEY_PRESSED);
2640 HandleKey(key, KEY_RELEASED);