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 SDL_CONTROLLERBUTTONDOWN:
237 case SDL_CONTROLLERBUTTONUP:
238 // for any game controller button event, disable overlay buttons
239 SetOverlayEnabled(FALSE);
241 HandleSpecialGameControllerButtons(event);
244 case SDL_CONTROLLERDEVICEADDED:
245 case SDL_CONTROLLERDEVICEREMOVED:
246 case SDL_CONTROLLERAXISMOTION:
247 case SDL_JOYAXISMOTION:
248 case SDL_JOYBUTTONDOWN:
249 case SDL_JOYBUTTONUP:
250 HandleJoystickEvent(event);
254 case SDL_DROPCOMPLETE:
257 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 static int HandleDropFileEvent(char *filename)
1429 Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
1431 // check and extract dropped zip files into correct user data directory
1432 if (!strSuffixLower(filename, ".zip"))
1434 Error(ERR_WARN, "file '%s' not supported", filename);
1436 return TREE_TYPE_UNDEFINED;
1439 TreeInfo *tree_node = NULL;
1440 int tree_type = GetZipFileTreeType(filename);
1441 char *directory = TREE_USERDIR(tree_type);
1443 if (directory == NULL)
1445 Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1447 return TREE_TYPE_UNDEFINED;
1450 if (tree_type == TREE_TYPE_LEVEL_DIR &&
1451 game_status == GAME_MODE_LEVELS &&
1452 leveldir_current->node_parent != NULL)
1454 // extract new level set next to currently selected level set
1455 tree_node = leveldir_current;
1457 // get parent directory of currently selected level set directory
1458 directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1460 // use private level directory instead of top-level package level directory
1461 if (strPrefix(directory, options.level_directory) &&
1462 strEqual(leveldir_current->node_parent->fullpath, "."))
1463 directory = getUserLevelDir(NULL);
1466 // extract level or artwork set from zip file to target directory
1467 char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1469 if (top_dir == NULL)
1471 // error message already issued by "ExtractZipFileIntoDirectory()"
1473 return TREE_TYPE_UNDEFINED;
1476 // add extracted level or artwork set to tree info structure
1477 AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1479 // update menu screen (and possibly change current level set)
1480 DrawScreenAfterAddingSet(top_dir, tree_type);
1485 static void HandleDropTextEvent(char *text)
1487 Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1490 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1491 int num_artwork_sets_succeeded,
1492 int num_files_failed)
1494 // only show request dialog if no other request dialog already active
1495 if (game.request_active)
1498 // this case can happen with drag-and-drop with older SDL versions
1499 if (num_level_sets_succeeded == 0 &&
1500 num_artwork_sets_succeeded == 0 &&
1501 num_files_failed == 0)
1506 if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1508 char message_part1[50];
1510 sprintf(message_part1, "New %s set%s added",
1511 (num_artwork_sets_succeeded == 0 ? "level" :
1512 num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1513 (num_level_sets_succeeded +
1514 num_artwork_sets_succeeded > 1 ? "s" : ""));
1516 if (num_files_failed > 0)
1517 sprintf(message, "%s, but %d dropped file%s failed!",
1518 message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1520 sprintf(message, "%s!", message_part1);
1522 else if (num_files_failed > 0)
1524 sprintf(message, "Failed to process dropped file%s!",
1525 num_files_failed > 1 ? "s" : "");
1528 Request(message, REQ_CONFIRM);
1531 void HandleDropEvent(Event *event)
1533 static boolean confirm_on_drop_complete = FALSE;
1534 static int num_level_sets_succeeded = 0;
1535 static int num_artwork_sets_succeeded = 0;
1536 static int num_files_failed = 0;
1538 switch (event->type)
1542 confirm_on_drop_complete = TRUE;
1543 num_level_sets_succeeded = 0;
1544 num_artwork_sets_succeeded = 0;
1545 num_files_failed = 0;
1552 int tree_type = HandleDropFileEvent(event->drop.file);
1554 if (tree_type == TREE_TYPE_LEVEL_DIR)
1555 num_level_sets_succeeded++;
1556 else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1557 tree_type == TREE_TYPE_SOUNDS_DIR ||
1558 tree_type == TREE_TYPE_MUSIC_DIR)
1559 num_artwork_sets_succeeded++;
1563 // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1564 if (!confirm_on_drop_complete)
1566 // process all remaining events, including further SDL_DROPFILE events
1569 HandleDropCompleteEvent(num_level_sets_succeeded,
1570 num_artwork_sets_succeeded,
1573 num_level_sets_succeeded = 0;
1574 num_artwork_sets_succeeded = 0;
1575 num_files_failed = 0;
1583 HandleDropTextEvent(event->drop.file);
1588 case SDL_DROPCOMPLETE:
1590 HandleDropCompleteEvent(num_level_sets_succeeded,
1591 num_artwork_sets_succeeded,
1598 if (event->drop.file != NULL)
1599 SDL_free(event->drop.file);
1602 void HandleButton(int mx, int my, int button, int button_nr)
1604 static int old_mx = 0, old_my = 0;
1605 boolean button_hold = FALSE;
1606 boolean handle_gadgets = TRUE;
1612 button_nr = -button_nr;
1621 #if defined(PLATFORM_ANDROID)
1622 // when playing, only handle gadgets when using "follow finger" controls
1623 // or when using touch controls in combination with the MM game engine
1624 // or when using gadgets that do not overlap with virtual buttons
1626 (game_status != GAME_MODE_PLAYING ||
1627 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1628 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1629 (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1630 !virtual_button_pressed));
1633 if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1635 // do not handle this button event anymore
1636 return; // force mouse event not to be handled at all
1639 if (handle_gadgets && HandleGadgets(mx, my, button))
1641 // do not handle this button event anymore
1642 mx = my = -32; // force mouse event to be outside screen tiles
1645 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1648 // do not use scroll wheel button events for anything other than gadgets
1649 if (IS_WHEEL_BUTTON(button_nr))
1652 switch (game_status)
1654 case GAME_MODE_TITLE:
1655 HandleTitleScreen(mx, my, 0, 0, button);
1658 case GAME_MODE_MAIN:
1659 HandleMainMenu(mx, my, 0, 0, button);
1662 case GAME_MODE_PSEUDO_TYPENAME:
1663 HandleTypeName(0, KSYM_Return);
1666 case GAME_MODE_LEVELS:
1667 HandleChooseLevelSet(mx, my, 0, 0, button);
1670 case GAME_MODE_LEVELNR:
1671 HandleChooseLevelNr(mx, my, 0, 0, button);
1674 case GAME_MODE_SCORES:
1675 HandleHallOfFame(0, 0, 0, 0, button);
1678 case GAME_MODE_EDITOR:
1679 HandleLevelEditorIdle();
1682 case GAME_MODE_INFO:
1683 HandleInfoScreen(mx, my, 0, 0, button);
1686 case GAME_MODE_SETUP:
1687 HandleSetupScreen(mx, my, 0, 0, button);
1690 case GAME_MODE_PLAYING:
1691 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1692 HandleButtonOrFinger(mx, my, button);
1694 SetPlayerMouseAction(mx, my, button);
1697 if (button == MB_PRESSED && !motion_status && !button_hold &&
1698 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1699 DumpTileFromScreen(mx, my);
1709 static boolean is_string_suffix(char *string, char *suffix)
1711 int string_len = strlen(string);
1712 int suffix_len = strlen(suffix);
1714 if (suffix_len > string_len)
1717 return (strEqual(&string[string_len - suffix_len], suffix));
1720 #define MAX_CHEAT_INPUT_LEN 32
1722 static void HandleKeysSpecial(Key key)
1724 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1725 char letter = getCharFromKey(key);
1726 int cheat_input_len = strlen(cheat_input);
1732 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1734 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1735 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1737 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1740 cheat_input[cheat_input_len++] = letter;
1741 cheat_input[cheat_input_len] = '\0';
1743 #if DEBUG_EVENTS_KEY
1744 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1747 if (game_status == GAME_MODE_MAIN)
1749 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1750 is_string_suffix(cheat_input, ":ist"))
1752 InsertSolutionTape();
1754 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1755 is_string_suffix(cheat_input, ":pst"))
1759 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1760 is_string_suffix(cheat_input, ":rg"))
1762 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1765 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1766 is_string_suffix(cheat_input, ":rs"))
1768 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1771 else if (is_string_suffix(cheat_input, ":reload-music") ||
1772 is_string_suffix(cheat_input, ":rm"))
1774 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1777 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1778 is_string_suffix(cheat_input, ":ra"))
1780 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1781 1 << ARTWORK_TYPE_SOUNDS |
1782 1 << ARTWORK_TYPE_MUSIC);
1785 else if (is_string_suffix(cheat_input, ":dump-level") ||
1786 is_string_suffix(cheat_input, ":dl"))
1790 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1791 is_string_suffix(cheat_input, ":dt"))
1795 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1796 is_string_suffix(cheat_input, ":ft"))
1798 /* fix single-player tapes that contain player input for more than one
1799 player (due to a bug in 3.3.1.2 and earlier versions), which results
1800 in playing levels with more than one player in multi-player mode,
1801 even though the tape was originally recorded in single-player mode */
1803 // remove player input actions for all players but the first one
1804 for (i = 1; i < MAX_PLAYERS; i++)
1805 tape.player_participates[i] = FALSE;
1807 tape.changed = TRUE;
1809 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1810 is_string_suffix(cheat_input, ":snl"))
1812 SaveNativeLevel(&level);
1814 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1815 is_string_suffix(cheat_input, ":fps"))
1817 global.show_frames_per_second = !global.show_frames_per_second;
1820 else if (game_status == GAME_MODE_PLAYING)
1823 if (is_string_suffix(cheat_input, ".q"))
1824 DEBUG_SetMaximumDynamite();
1827 else if (game_status == GAME_MODE_EDITOR)
1829 if (is_string_suffix(cheat_input, ":dump-brush") ||
1830 is_string_suffix(cheat_input, ":DB"))
1834 else if (is_string_suffix(cheat_input, ":DDB"))
1839 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1841 if (letter == 'x') // copy brush to clipboard (small size)
1843 CopyBrushToClipboard_Small();
1845 else if (letter == 'c') // copy brush to clipboard (normal size)
1847 CopyBrushToClipboard();
1849 else if (letter == 'v') // paste brush from Clipboard
1851 CopyClipboardToBrush();
1856 // special key shortcuts for all game modes
1857 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1858 is_string_suffix(cheat_input, ":dea") ||
1859 is_string_suffix(cheat_input, ":DEA"))
1861 DumpGadgetIdentifiers();
1862 DumpScreenIdentifiers();
1866 boolean HandleKeysDebug(Key key, int key_status)
1871 if (key_status != KEY_PRESSED)
1874 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1876 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1878 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1880 if (key == setup.debug.frame_delay_key[i] &&
1881 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1883 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1884 setup.debug.frame_delay[i] : setup.game_frame_delay);
1886 if (!setup.debug.frame_delay_game_only)
1887 MenuFrameDelay = GameFrameDelay;
1889 SetVideoFrameDelay(GameFrameDelay);
1891 if (GameFrameDelay > ONE_SECOND_DELAY)
1892 Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
1893 else if (GameFrameDelay != 0)
1894 Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
1895 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1896 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1898 Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
1905 if (game_status == GAME_MODE_PLAYING)
1909 options.debug = !options.debug;
1911 Error(ERR_INFO, "debug mode %s",
1912 (options.debug ? "enabled" : "disabled"));
1916 else if (key == KSYM_v)
1918 Error(ERR_INFO, "currently using game engine version %d",
1919 game.engine_version);
1929 void HandleKey(Key key, int key_status)
1931 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1932 static boolean ignore_repeated_key = FALSE;
1933 static struct SetupKeyboardInfo ski;
1934 static struct SetupShortcutInfo ssi;
1943 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1944 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1945 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1946 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1947 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1948 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1953 if (HandleKeysDebug(key, key_status))
1954 return; // do not handle already processed keys again
1956 // map special keys (media keys / remote control buttons) to default keys
1957 if (key == KSYM_PlayPause)
1959 else if (key == KSYM_Select)
1962 HandleSpecialGameControllerKeys(key, key_status);
1964 if (game_status == GAME_MODE_PLAYING)
1966 // only needed for single-step tape recording mode
1967 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1970 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1972 byte key_action = 0;
1973 byte key_snap_action = 0;
1975 if (setup.input[pnr].use_joystick)
1978 ski = setup.input[pnr].key;
1980 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1981 if (key == *key_info[i].key_custom)
1982 key_action |= key_info[i].action;
1984 // use combined snap+direction keys for the first player only
1987 ssi = setup.shortcut;
1989 // also remember normal snap key when handling snap+direction keys
1990 key_snap_action |= key_action & JOY_BUTTON_SNAP;
1992 for (i = 0; i < NUM_DIRECTIONS; i++)
1994 if (key == *key_info[i].key_snap)
1996 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1997 key_snap_action |= key_info[i].action;
2002 if (key_status == KEY_PRESSED)
2004 stored_player[pnr].action |= key_action;
2005 stored_player[pnr].snap_action |= key_snap_action;
2009 stored_player[pnr].action &= ~key_action;
2010 stored_player[pnr].snap_action &= ~key_snap_action;
2013 // restore snap action if one of several pressed snap keys was released
2014 if (stored_player[pnr].snap_action)
2015 stored_player[pnr].action |= JOY_BUTTON_SNAP;
2017 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2019 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2021 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2023 // if snap key already pressed, keep pause mode when releasing
2024 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2025 has_snapped[pnr] = TRUE;
2027 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2029 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2031 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2032 getRedDiskReleaseFlag_SP() == 0)
2034 // add a single inactive frame before dropping starts
2035 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2036 stored_player[pnr].force_dropping = TRUE;
2039 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2041 // if snap key was pressed without direction, leave pause mode
2042 if (!has_snapped[pnr])
2043 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2045 has_snapped[pnr] = FALSE;
2048 else if (tape.recording && tape.pausing && !tape.use_mouse)
2050 // prevent key release events from un-pausing a paused game
2051 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2052 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2055 // for MM style levels, handle in-game keyboard input in HandleJoystick()
2056 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2062 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2063 if (key == key_info[i].key_default)
2064 joy |= key_info[i].action;
2069 if (key_status == KEY_PRESSED)
2070 key_joystick_mapping |= joy;
2072 key_joystick_mapping &= ~joy;
2077 if (game_status != GAME_MODE_PLAYING)
2078 key_joystick_mapping = 0;
2080 if (key_status == KEY_RELEASED)
2082 // reset flag to ignore repeated "key pressed" events after key release
2083 ignore_repeated_key = FALSE;
2088 if ((key == KSYM_F11 ||
2089 ((key == KSYM_Return ||
2090 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2091 video.fullscreen_available &&
2092 !ignore_repeated_key)
2094 setup.fullscreen = !setup.fullscreen;
2096 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2098 if (game_status == GAME_MODE_SETUP)
2099 RedrawSetupScreenAfterFullscreenToggle();
2101 // set flag to ignore repeated "key pressed" events
2102 ignore_repeated_key = TRUE;
2107 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2108 key == KSYM_minus || key == KSYM_KP_Subtract ||
2109 key == KSYM_plus || key == KSYM_KP_Add ||
2110 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2111 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2112 video.window_scaling_available &&
2113 !video.fullscreen_enabled)
2115 if (key == KSYM_0 || key == KSYM_KP_0)
2116 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2117 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2118 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2120 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2122 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2123 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2124 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2125 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2127 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2129 if (game_status == GAME_MODE_SETUP)
2130 RedrawSetupScreenAfterFullscreenToggle();
2135 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2136 key == KSYM_Return ||
2137 key == KSYM_Escape), TRUE))
2139 // do not handle this key event anymore
2140 if (key != KSYM_Escape) // always allow ESC key to be handled
2144 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2145 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2152 if (game_status == GAME_MODE_MAIN &&
2153 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2155 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2160 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2162 if (key == setup.shortcut.save_game)
2164 else if (key == setup.shortcut.load_game)
2166 else if (key == setup.shortcut.toggle_pause)
2167 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2169 HandleTapeButtonKeys(key);
2170 HandleSoundButtonKeys(key);
2173 if (game_status == GAME_MODE_PLAYING && !network_playing)
2175 int centered_player_nr_next = -999;
2177 if (key == setup.shortcut.focus_player_all)
2178 centered_player_nr_next = -1;
2180 for (i = 0; i < MAX_PLAYERS; i++)
2181 if (key == setup.shortcut.focus_player[i])
2182 centered_player_nr_next = i;
2184 if (centered_player_nr_next != -999)
2186 game.centered_player_nr_next = centered_player_nr_next;
2187 game.set_centered_player = TRUE;
2191 tape.centered_player_nr_next = game.centered_player_nr_next;
2192 tape.set_centered_player = TRUE;
2197 HandleKeysSpecial(key);
2199 if (HandleGadgetsKeyInput(key))
2200 return; // do not handle already processed keys again
2202 switch (game_status)
2204 case GAME_MODE_PSEUDO_TYPENAME:
2205 HandleTypeName(0, key);
2208 case GAME_MODE_TITLE:
2209 case GAME_MODE_MAIN:
2210 case GAME_MODE_LEVELS:
2211 case GAME_MODE_LEVELNR:
2212 case GAME_MODE_SETUP:
2213 case GAME_MODE_INFO:
2214 case GAME_MODE_SCORES:
2216 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2223 if (game_status == GAME_MODE_TITLE)
2224 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2225 else if (game_status == GAME_MODE_MAIN)
2226 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2227 else if (game_status == GAME_MODE_LEVELS)
2228 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2229 else if (game_status == GAME_MODE_LEVELNR)
2230 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2231 else if (game_status == GAME_MODE_SETUP)
2232 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2233 else if (game_status == GAME_MODE_INFO)
2234 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2235 else if (game_status == GAME_MODE_SCORES)
2236 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2240 if (game_status != GAME_MODE_MAIN)
2241 FadeSkipNextFadeIn();
2243 if (game_status == GAME_MODE_TITLE)
2244 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2245 else if (game_status == GAME_MODE_LEVELS)
2246 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2247 else if (game_status == GAME_MODE_LEVELNR)
2248 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2249 else if (game_status == GAME_MODE_SETUP)
2250 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2251 else if (game_status == GAME_MODE_INFO)
2252 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2253 else if (game_status == GAME_MODE_SCORES)
2254 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2258 if (game_status == GAME_MODE_LEVELS)
2259 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2260 else if (game_status == GAME_MODE_LEVELNR)
2261 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2262 else if (game_status == GAME_MODE_SETUP)
2263 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2264 else if (game_status == GAME_MODE_INFO)
2265 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2266 else if (game_status == GAME_MODE_SCORES)
2267 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2270 case KSYM_Page_Down:
2271 if (game_status == GAME_MODE_LEVELS)
2272 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2273 else if (game_status == GAME_MODE_LEVELNR)
2274 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2275 else if (game_status == GAME_MODE_SETUP)
2276 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2277 else if (game_status == GAME_MODE_INFO)
2278 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2279 else if (game_status == GAME_MODE_SCORES)
2280 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2288 case GAME_MODE_EDITOR:
2289 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2290 HandleLevelEditorKeyInput(key);
2293 case GAME_MODE_PLAYING:
2298 RequestQuitGame(setup.ask_on_escape);
2308 if (key == KSYM_Escape)
2310 SetGameStatus(GAME_MODE_MAIN);
2319 void HandleNoEvent(void)
2321 HandleMouseCursor();
2323 switch (game_status)
2325 case GAME_MODE_PLAYING:
2326 HandleButtonOrFinger(-1, -1, -1);
2331 void HandleEventActions(void)
2333 // if (button_status && game_status != GAME_MODE_PLAYING)
2334 if (button_status && (game_status != GAME_MODE_PLAYING ||
2336 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2338 HandleButton(0, 0, button_status, -button_status);
2345 if (network.enabled)
2348 switch (game_status)
2350 case GAME_MODE_MAIN:
2351 DrawPreviewLevelAnimation();
2354 case GAME_MODE_EDITOR:
2355 HandleLevelEditorIdle();
2363 static void HandleTileCursor(int dx, int dy, int button)
2366 ClearPlayerMouseAction();
2373 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2374 (dx < 0 ? MB_LEFTBUTTON :
2375 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2377 else if (!tile_cursor.moving)
2379 int old_xpos = tile_cursor.xpos;
2380 int old_ypos = tile_cursor.ypos;
2381 int new_xpos = old_xpos;
2382 int new_ypos = old_ypos;
2384 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2385 new_xpos = old_xpos + dx;
2387 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2388 new_ypos = old_ypos + dy;
2390 SetTileCursorTargetXY(new_xpos, new_ypos);
2394 static int HandleJoystickForAllPlayers(void)
2398 boolean no_joysticks_configured = TRUE;
2399 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2400 static byte joy_action_last[MAX_PLAYERS];
2402 for (i = 0; i < MAX_PLAYERS; i++)
2403 if (setup.input[i].use_joystick)
2404 no_joysticks_configured = FALSE;
2406 // if no joysticks configured, map connected joysticks to players
2407 if (no_joysticks_configured)
2408 use_as_joystick_nr = TRUE;
2410 for (i = 0; i < MAX_PLAYERS; i++)
2412 byte joy_action = 0;
2414 joy_action = JoystickExt(i, use_as_joystick_nr);
2415 result |= joy_action;
2417 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2418 joy_action != joy_action_last[i])
2419 stored_player[i].action = joy_action;
2421 joy_action_last[i] = joy_action;
2427 void HandleJoystick(void)
2429 static unsigned int joytest_delay = 0;
2430 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2431 static int joytest_last = 0;
2432 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2433 int delay_value = GADGET_FRAME_DELAY;
2434 int joystick = HandleJoystickForAllPlayers();
2435 int keyboard = key_joystick_mapping;
2436 int joy = (joystick | keyboard);
2437 int joytest = joystick;
2438 int left = joy & JOY_LEFT;
2439 int right = joy & JOY_RIGHT;
2440 int up = joy & JOY_UP;
2441 int down = joy & JOY_DOWN;
2442 int button = joy & JOY_BUTTON;
2443 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2444 int dx = (left ? -1 : right ? 1 : 0);
2445 int dy = (up ? -1 : down ? 1 : 0);
2446 boolean use_delay_value_first = (joytest != joytest_last);
2448 if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2450 // do not handle this button event anymore
2454 if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2455 anyTextGadgetActive()))
2457 // leave name input in main menu or text input gadget
2458 HandleKey(KSYM_Escape, KEY_PRESSED);
2459 HandleKey(KSYM_Escape, KEY_RELEASED);
2464 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2466 if (game_status == GAME_MODE_PLAYING)
2468 // when playing MM style levels, also use delay for keyboard events
2469 joytest |= keyboard;
2471 // only use first delay value for new events, but not for changed events
2472 use_delay_value_first = (!joytest != !joytest_last);
2474 // only use delay after the initial keyboard event
2478 // for any joystick or keyboard event, enable playfield tile cursor
2479 if (dx || dy || button)
2480 SetTileCursorEnabled(TRUE);
2483 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2485 // delay joystick/keyboard actions if axes/keys continually pressed
2486 newbutton = dx = dy = 0;
2490 // first start with longer delay, then continue with shorter delay
2491 joytest_delay_value =
2492 (use_delay_value_first ? delay_value_first : delay_value);
2495 joytest_last = joytest;
2497 switch (game_status)
2499 case GAME_MODE_TITLE:
2500 case GAME_MODE_MAIN:
2501 case GAME_MODE_LEVELS:
2502 case GAME_MODE_LEVELNR:
2503 case GAME_MODE_SETUP:
2504 case GAME_MODE_INFO:
2505 case GAME_MODE_SCORES:
2507 if (anyTextGadgetActive())
2510 if (game_status == GAME_MODE_TITLE)
2511 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2512 else if (game_status == GAME_MODE_MAIN)
2513 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2514 else if (game_status == GAME_MODE_LEVELS)
2515 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2516 else if (game_status == GAME_MODE_LEVELNR)
2517 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2518 else if (game_status == GAME_MODE_SETUP)
2519 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2520 else if (game_status == GAME_MODE_INFO)
2521 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2522 else if (game_status == GAME_MODE_SCORES)
2523 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2528 case GAME_MODE_PLAYING:
2530 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2531 if (tape.playing || keyboard)
2532 newbutton = ((joy & JOY_BUTTON) != 0);
2535 if (newbutton && game.all_players_gone)
2542 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2544 if (joystick & JOY_ACTION)
2545 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2547 else if (tape.recording && tape.pausing && !tape.use_mouse)
2549 if (joystick & JOY_ACTION)
2550 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2553 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2554 HandleTileCursor(dx, dy, button);
2563 void HandleSpecialGameControllerButtons(Event *event)
2568 switch (event->type)
2570 case SDL_CONTROLLERBUTTONDOWN:
2571 key_status = KEY_PRESSED;
2574 case SDL_CONTROLLERBUTTONUP:
2575 key_status = KEY_RELEASED;
2582 switch (event->cbutton.button)
2584 case SDL_CONTROLLER_BUTTON_START:
2588 case SDL_CONTROLLER_BUTTON_BACK:
2596 HandleKey(key, key_status);
2599 void HandleSpecialGameControllerKeys(Key key, int key_status)
2601 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2602 int button = SDL_CONTROLLER_BUTTON_INVALID;
2604 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2605 if (key == KSYM_Rewind)
2606 button = SDL_CONTROLLER_BUTTON_A;
2607 else if (key == KSYM_FastForward || key == KSYM_Menu)
2608 button = SDL_CONTROLLER_BUTTON_B;
2610 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2614 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2615 SDL_CONTROLLERBUTTONUP);
2617 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2618 event.cbutton.button = button;
2619 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2622 HandleJoystickEvent(&event);
2627 boolean DoKeysymAction(int keysym)
2631 Key key = (Key)(-keysym);
2633 HandleKey(key, KEY_PRESSED);
2634 HandleKey(key, KEY_RELEASED);